Handling File Uploads and Downloads in Flutter

In modern mobile applications, handling file uploads and downloads is a common requirement. Whether it’s uploading user profile pictures, downloading reports, or sharing documents, Flutter provides developers with the necessary tools and libraries to implement these features effectively. This blog post will guide you through the process of handling file uploads and downloads in Flutter, covering various aspects such as choosing the right dependencies, implementing the UI, and handling background tasks.

Why File Uploads and Downloads Are Important

  • User Interaction: Enhances user engagement by allowing them to share and access files.
  • Data Management: Enables efficient data storage and retrieval in cloud services.
  • Functionality: Supports features like profile customization, document sharing, and content updates.

Choosing the Right Dependencies

Flutter offers several packages to handle file uploads and downloads. Here are some of the most popular and useful ones:

  • http: A composable, Future-based API for making HTTP requests. It’s commonly used for both uploads and downloads.
  • dio: A powerful HTTP client for Dart, which supports interceptors, global configuration, FormData, file downloads, timeouts, and more.
  • file_picker: Allows users to select files from their device’s storage.
  • path_provider: Provides access to commonly used locations on the filesystem.

Handling File Uploads

To upload files, you’ll typically need to perform these steps:

  1. Allow the user to select a file.
  2. Prepare the file data.
  3. Make an HTTP request to upload the file to a server.

Step 1: Add Dependencies

Add the necessary dependencies to your pubspec.yaml file:

dependencies:
  http: ^0.13.5
  file_picker: ^5.3.2
  path_provider: ^2.0.15

Then run flutter pub get to install the packages.

Step 2: Select a File Using file_picker


import 'package:file_picker/file_picker.dart';

Future pickFile() async {
  FilePickerResult? result = await FilePicker.platform.pickFiles();

  if (result != null) {
    return result;
  } else {
    // User canceled the picker
    return null;
  }
}

Step 3: Upload the File Using http

After picking the file, you can upload it to a server using the http package:


import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:http_parser/http_parser.dart';

Future uploadFile(File file) async {
  var request = http.MultipartRequest(
      'POST', Uri.parse('YOUR_UPLOAD_ENDPOINT'));
  request.files.add(await http.MultipartFile.fromPath(
      'file',
      file.path,
      contentType: MediaType('application', 'octet-stream') // Adjust Content-Type as needed
  ));

  try {
    var streamedResponse = await request.send();
    var response = await http.Response.fromStream(streamedResponse);
    if (response.statusCode == 200) {
      print('Uploaded!');
    } else {
      print('Upload failed');
    }
  } catch (e) {
    print('Error: $e');
  }
}

Usage Example:


ElevatedButton(
  onPressed: () async {
    FilePickerResult? result = await pickFile();
    if (result != null) {
      File file = File(result.files.single.path!);
      await uploadFile(file);
    } else {
      // User canceled the picker
    }
  },
  child: Text('Select and Upload File'),
)

Handling File Downloads

To download files, you’ll typically need to:

  1. Determine the URL of the file to download.
  2. Make an HTTP request to download the file.
  3. Save the file to the device’s storage.

Step 1: Add Dependencies

If not already present from the Upload example above, add the http and path_provider dependencies in your pubspec.yaml file.

dependencies:
  http: ^0.13.5
  path_provider: ^2.0.15

Run flutter pub get to install the packages.

Step 2: Download the File Using http and Save it


import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';

Future downloadFile(String url, String filename) async {
  try {
    final directory = await getApplicationDocumentsDirectory();
    final filePath = '${directory.path}/$filename';
    final file = File(filePath);

    final response = await http.get(Uri.parse(url));

    if (response.statusCode == 200) {
      await file.writeAsBytes(response.bodyBytes);
      print('Downloaded to: $filePath');
    } else {
      print('Download failed: ${response.statusCode}');
    }
  } catch (e) {
    print('Error: $e');
  }
}

Usage Example:


ElevatedButton(
  onPressed: () async {
    await downloadFile('YOUR_DOWNLOAD_URL/example.pdf', 'example.pdf');
  },
  child: Text('Download File'),
)

Using dio for Advanced Handling

The dio package offers more advanced features like interceptors, progress tracking, and request cancellation.

Add Dependency

dependencies:
  dio: ^5.3.3

Downloading with dio and Tracking Progress


import 'package:dio/dio.dart';
import 'package:path_provider/path_provider.dart';

Future downloadFileWithProgress(String url, String filename) async {
  try {
    final directory = await getApplicationDocumentsDirectory();
    final filePath = '${directory.path}/$filename';

    Dio dio = Dio();
    await dio.download(
      url,
      filePath,
      onReceiveProgress: (received, total) {
        if (total != -1) {
          print('${(received / total * 100).toStringAsFixed(0)}%');
          // Update progress bar
        }
      },
    );
    print('Downloaded to: $filePath');
  } catch (e) {
    print('Error: $e');
  }
}

Usage:


ElevatedButton(
  onPressed: () async {
    await downloadFileWithProgress('YOUR_DOWNLOAD_URL/example.pdf', 'example.pdf');
  },
  child: Text('Download File with Progress'),
)

Uploading with dio


import 'dart:io';
import 'package:dio/dio.dart';

Future uploadFileWithDio(File file) async {
  String fileName = file.path.split('/').last;
  FormData formData = FormData.fromMap({
    "file": await MultipartFile.fromFile(
      file.path,
      filename: fileName,
    ),
  });

  Dio dio = Dio();

  try {
    var response = await dio.post('YOUR_UPLOAD_ENDPOINT', data: formData,
    onSendProgress: (int sent, int total) {
      print('$sent $total');
    },);
    print("Response: ${response.statusCode}");
  } catch (e) {
    print("Error: $e");
  }
}

Best Practices and Considerations

  • Error Handling: Implement proper error handling to gracefully manage failures due to network issues, server errors, or file system errors.
  • Background Tasks: For large files, consider using background tasks to avoid blocking the UI thread.
  • Permissions: Ensure that you have the necessary permissions to read and write files on the user’s device.
  • User Feedback: Provide visual feedback to the user, such as progress indicators, to improve the user experience.
  • Security: Validate file uploads on the server-side to prevent malicious attacks.
  • Storage Limits: Consider storage limits on the device and inform the user accordingly.

Conclusion

Handling file uploads and downloads in Flutter is straightforward with the help of libraries like http, dio, file_picker, and path_provider. By following the steps outlined in this guide, you can efficiently implement these features in your Flutter applications, providing users with seamless experiences for sharing and accessing files. Remember to consider error handling, background tasks, and security best practices to create robust and reliable file management functionality.