bosc_flutter_file_manager (1.0.0)

Published 2026-02-23 06:20:17 +00:00 by mansi.kansara

Installation

dart pub add bosc_flutter_file_manager:1.0.0 --hosted-url=

About this package

A production-grade, reusable file upload client for Flutter with AWS S3 support, pre-signed URLs, retry logic, and offline queue management.

Bosc Flutter File Manager 📤

A production-grade Flutter package for upload, download, and view file operations with multi-cloud storage support (AWS S3, Firebase Storage, Supabase Storage). Supports Android and iOS (and other platforms). Includes pre-signed URLs, automatic retry logic, offline queue management, and structured logging.

pub package
License: MIT

📱 Supported Platforms

  • Android
  • iOS
  • Web, macOS, Windows, Linux (where applicable)

☁️ Supported Cloud Storage

  • AWS S3 - Amazon's object storage (via pre-signed URLs)
  • Firebase Storage - Google's Firebase cloud storage
  • Supabase Storage - Open-source Firebase alternative

Features

  • Multi-Cloud Support - Switch between AWS, Firebase, and Supabase easily
  • AWS S3 Pre-signed URL Support - Upload directly to S3 using pre-signed URLs
  • Firebase Storage Integration - Native Firebase Storage support
  • Supabase Storage Integration - Native Supabase Storage support
  • Multiple Upload Methods - Pre-signed URL, API proxy, and direct uploads
  • Automatic Retry Logic - Exponential backoff with configurable retry attempts
  • Offline Queue Management - Queue uploads when offline, auto-resume when online
  • Real-time Progress Tracking - Track upload progress with detailed callbacks
  • File Validation - Validate file size, type, extension before upload
  • Batch Upload Support - Upload multiple files with parallel/sequential options
  • Cancellation Support - Cancel uploads at any time
  • Checksum Validation - MD5/SHA256 checksum support
  • Comprehensive Error Handling - Domain-specific exceptions with detailed error info
  • Network Detection - Automatic connectivity monitoring
  • Download & View - Download files from storage URLs and open them with the native app (Android/iOS)
  • Structured Logging - dart:developer log integration; optional custom logger and enableLogging on clients
  • Production Ready - Enterprise-grade code quality and architecture

📦 Installation

Add this to your package's pubspec.yaml file:

dependencies:
  bosc_flutter_file_manager: ^1.0.0

Then run:

flutter pub get

🚀 Quick Start

Choose Your Storage Provider

AWS S3

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

final client = UniversalFileUploadClient(
  config: UniversalUploadClientConfig(
    storageConfig: AwsStorageConfig(
      presignedUrlEndpoint: 'https://api.example.com/upload/presigned-url',
      // Optional: auth headers for your backend pre-signed URL endpoint
      presignedUrlHeaders: {
        'Authorization': 'Bearer <token>',
      },
    ),
  ),
);

Firebase Storage

final client = UniversalFileUploadClient(
  config: UniversalUploadClientConfig(
    storageConfig: FirebaseStorageConfig(
      bucketName: 'your-bucket.appspot.com', // Optional
    ),
  ),
);

Supabase Storage

final client = UniversalFileUploadClient(
  config: UniversalUploadClientConfig(
    storageConfig: SupabaseStorageConfig(
      supabaseUrl: 'https://your-project.supabase.co',
      supabaseKey: 'your-anon-key',
      bucketName: 'uploads',
    ),
  ),
);

Note: the current Supabase provider uses uploadBinary, which reads the full file into memory. For very large files, consider AWS pre-signed PUT uploads or a custom streaming implementation.

Upload a File

// Works the same for all storage providers!
try {
  final response = await client.upload(
    file: File('/path/to/file.pdf'),
    path: 'documents/file.pdf',  // Storage path
    onProgress: (progress) {
      print('Upload progress: ${(progress.percentage * 100).toStringAsFixed(1)}%');
    },
  );

  if (response.success) {
    print('✓ File uploaded: ${response.fileUrl}');
    print('  ETag: ${response.eTag}');
    print('  File Key: ${response.fileKey}');
  }
} on ValidationException catch (e) {
  print('✗ Validation failed: ${e.message}');
} on NoInternetException catch (e) {
  print('✗ No internet connection');
} on UploadException catch (e) {
  print('✗ Upload failed: ${e.message}');
}

Download a File

Download a file from a URL (e.g. from response.fileUrl) and optionally open it with the device’s default app (works on Android and iOS):

import 'package:bosc_flutter_file_manager/bosc_flutter_file_manager.dart';

// Download and open (e.g. PDF, images)
final result = await FileDownloadService.downloadAndOpen(
  url: fileUrl,           // from upload response or getFileUrl()
  fileName: 'document.pdf',
  openAfterDownload: true,
  onProgress: (progress) {
    print('Download: ${(progress * 100).toStringAsFixed(0)}%');
  },
);
if (!result.success) print(result.message);

Download only (no open):

final localPath = await FileDownloadService.download(
  url: fileUrl,
  fileName: 'document.pdf',
  onProgress: (progress) { /* ... */ },
);

View (Open) a File

Open a local file with the system default app (e.g. PDF viewer, image viewer) on Android and iOS:

import 'package:bosc_flutter_file_manager/bosc_flutter_file_manager.dart';

final result = await FileOpenerService.open('/path/to/local/file.pdf');
if (result.success) {
  // File opened in external app
} else {
  print(result.message); // e.g. "No app found to open this file type."
}

Get file URL from storage (for download/view):

final fileUrl = await client.getFileUrl('documents/file.pdf');
// Use fileUrl with FileDownloadService.downloadAndOpen or download

Enable Logging

Use structured logging (e.g. in Flutter DevTools) or a custom logger:

final client = UniversalFileUploadClient(
  config: UniversalUploadClientConfig(
    storageConfig: yourStorageConfig,
    enableLogging: true,
    // Optional: custom logger
    // logger: (msg) => myLogger.info(msg),
  ),
);

Upload with File Validation

// Configure validation for images only
final client = AwsFileUploadClient(
  config: UploadClientConfig(
    validationConfig: FileValidationConfig.images(maxSizeMB: 10),
  ),
);

// Upload will automatically validate before uploading
final response = await client.upload(
  file: File('/path/to/image.jpg'),
  onProgress: (progress) {
    print('${progress.progressString}');
  },
);

Batch Upload

final files = [
  File('/path/to/file1.pdf'),
  File('/path/to/file2.pdf'),
  File('/path/to/file3.pdf'),
];

final requests = files.map((file) => UploadRequest(
  file: file,
  folder: 'documents',
  presignedUrlEndpoint: 'https://api.example.com/upload/presigned-url',
)).toList();

final batchResponse = await client.uploadBatch(
  requests,
  parallel: true,
  maxConcurrent: 3,
  onProgress: (index, progress) {
    print('File $index: ${(progress.percentage * 100).toStringAsFixed(1)}%');
  },
);

print('✓ Uploaded ${batchResponse.successCount} files');
print('✗ Failed ${batchResponse.failureCount} files');

📚 Storage Provider Guide

See MULTI_CLOUD_GUIDE.md for detailed documentation on each storage provider.

AWS S3 (Pre-signed URL Flow)

The most common pattern for AWS S3 uploads. Your backend generates a pre-signed URL, and the client uploads directly to S3.

final client = UniversalFileUploadClient(
  config: UniversalUploadClientConfig(
    storageConfig: AwsStorageConfig(
      presignedUrlEndpoint: 'https://api.example.com/upload/presigned-url',
    ),
  ),
);

final response = await client.upload(
  file: File('/path/to/file.pdf'),
  path: 'uploads/document.pdf',
);

Backend Contract Example:

// POST https://api.example.com/upload/presigned-url
// Request:
{
  "fileName": "document.pdf",
  "folder": "uploads",
  "contentType": "application/pdf"
}

// Response:
{
  "uploadUrl": "https://bucket.s3.amazonaws.com/uploads/document.pdf?X-Amz-Algorithm=...",
  "method": "PUT",
  "headers": {
    "Content-Type": "application/pdf"
  },
  "fileKey": "uploads/document.pdf"
}

2. API Proxy Upload

Upload file to your backend API, which handles the S3 upload internally.

final response = await client.upload(
  file: File('/path/to/file.pdf'),
  method: UploadMethod.apiProxy,
  presignedUrlEndpoint: 'https://api.example.com/upload',
);

3. Direct Upload (with Pre-signed URL)

If you already have a pre-signed URL, use it directly:

final response = await client.upload(
  file: File('/path/to/file.pdf'),
  directUploadUrl: 'https://bucket.s3.amazonaws.com/...',
  httpMethod: UploadHttpMethod.put,
);

🔄 Offline Queue Management

Handle uploads when the device is offline:

// NOTE: Offline queue currently supports AwsFileUploadClient flows (AWS pre-signed URL / API proxy).
final awsClient = AwsFileUploadClient(
  config: UploadClientConfig(
    defaultPresignedUrlEndpoint: 'https://api.example.com/upload/presigned-url',
  ),
);

// Initialize queue manager
final queueManager = UploadQueueManager(
  client: awsClient,
  config: QueueConfig(
    maxConcurrent: 3,
    autoStart: true,
    autoRetry: true,
  ),
);

// Enqueue uploads
final uploadId = queueManager.enqueue(
  request: UploadRequest(
    file: File('/path/to/file.pdf'),
    presignedUrlEndpoint: 'https://api.example.com/upload/presigned-url',
  ),
  priority: 10, // Higher priority uploads first
);

// Listen to queue updates
queueManager.onQueueUpdate.listen((uploads) {
  final status = queueManager.getStatus();
  print('Queue: ${status.pending} pending, ${status.uploading} uploading');
});

// Listen to individual upload status
queueManager.onStatusChange.listen((upload) {
  print('Upload ${upload.id}: ${upload.status}');
  if (upload.progress != null) {
    print('Progress: ${upload.progress!.progressString}');
  }
});

// Control the queue
queueManager.pause(); // Pause uploads
queueManager.start(); // Resume uploads
queueManager.retry(uploadId); // Retry failed upload
queueManager.cancel(uploadId); // Cancel upload

// Cleanup
queueManager.dispose();

⚙️ Advanced Configuration

Custom Validation

final validator = FileValidator(
  FileValidationConfig(
    maxFileSize: 50 * 1024 * 1024, // 50 MB
    allowedMimeTypes: ['application/pdf', 'image/*'],
    allowedExtensions: ['.pdf', '.jpg', '.png'],
    allowEmptyFiles: false,
    minFileSize: 1024, // 1 KB minimum
    fileNamePattern: RegExp(r'^[a-zA-Z0-9_-]+\.[a-z]+$'),
  ),
);

// Validate before upload
await validator.validate(file);

Pre-configured Validators

// Images only (10 MB max)
FileValidationConfig.images(maxSizeMB: 10)

// Documents only (25 MB max)
FileValidationConfig.documents(maxSizeMB: 25)

// Videos only (500 MB max)
FileValidationConfig.videos(maxSizeMB: 500)

Retry Configuration

final client = AwsFileUploadClient(
  config: UploadClientConfig(
    retryConfig: RetryConfig(
      maxAttempts: 5,
      initialDelay: Duration(seconds: 1),
      maxDelay: Duration(seconds: 30),
      backoffMultiplier: 2.0,
      enableJitter: true,
      retryOnTimeout: true,
      retryOn5xx: true,
      retryOnNetworkError: true,
    ),
  ),
);

Upload with Custom Headers & Metadata

final response = await client.upload(
  file: File('/path/to/file.pdf'),
  customHeaders: {
    'Authorization': 'Bearer $token',
    'X-Custom-Header': 'value',
  },
  metadata: {
    'userId': '12345',
    'category': 'documents',
  },
  includeMd5Checksum: true,
);

Cancel Upload

final cancelToken = CancelToken();

// Start upload
final uploadFuture = client.upload(
  file: File('/path/to/large-file.mp4'),
  cancelToken: cancelToken,
  onProgress: (progress) {
    if (shouldCancel) {
      cancelToken.cancel('User cancelled');
    }
  },
);

try {
  await uploadFuture;
} on UploadCancelledException {
  print('Upload was cancelled');
}

🎯 Error Handling

The library provides comprehensive error handling with specific exception types:

try {
  await client.upload(file: file);
} on FileNotFoundException catch (e) {
  // File doesn't exist
  print('File not found: ${e.message}');
} on PermissionException catch (e) {
  // Can't read file
  print('Permission denied: ${e.message}');
} on ValidationException catch (e) {
  // File validation failed
  print('Validation error: ${e.message}');
  print('Error type: ${e.errorType}');
} on NoInternetException catch (e) {
  // No internet connection
  print('No internet: ${e.message}');
} on UploadTimeoutException catch (e) {
  // Upload timed out
  print('Timeout after ${e.timeout.inSeconds}s');
} on AwsRejectedException catch (e) {
  // AWS/S3 rejected the upload
  print('AWS error: ${e.message}');
  print('Status code: ${e.statusCode}');
} on PresignedUrlException catch (e) {
  // Failed to get pre-signed URL
  print('Pre-signed URL error: ${e.message}');
} on RetryExhaustedException catch (e) {
  // All retries failed
  print('Failed after ${e.attempts} attempts');
} on NetworkException catch (e) {
  // Generic network error
  print('Network error: ${e.message}');
} on UploadException catch (e) {
  // Base exception (catch-all)
  print('Upload error: ${e.message}');
}

🏗️ Architecture

The library follows clean architecture principles with clear separation of concerns:

lib/
├── src/
│   ├── client/
│   │   └── aws_file_upload_client.dart    # Main client
│   ├── services/
│   │   ├── upload_service.dart            # Core upload logic
│   │   └── retry_handler.dart             # Retry mechanism
│   ├── models/
│   │   ├── upload_request.dart            # Request model
│   │   ├── upload_response.dart           # Response model
│   │   ├── upload_progress.dart           # Progress tracking
│   │   └── batch_upload_result.dart       # Batch results
│   ├── validators/
│   │   └── file_validator.dart            # File validation
│   ├── exceptions/
│   │   └── upload_exception.dart          # Custom exceptions
│   ├── queue/
│   │   └── upload_queue_manager.dart      # Queue management
│   └── utils/
│       └── network_info.dart              # Network detection
└── bosc_flutter_file_manager.dart       # Public exports

🔒 Security Best Practices

  1. Never log sensitive data - The library sanitizes logs by default
  2. Use HTTPS only - Always use secure endpoints
  3. Validate on both client and server - Client validation is for UX, server validation is for security
  4. Use short-lived pre-signed URLs - Set appropriate expiration times
  5. Implement rate limiting - Prevent abuse of your upload endpoints
  6. Verify checksums - Use MD5/SHA256 checksums to ensure data integrity

⚠️ Common Pitfalls with AWS S3

1. CORS Configuration

Ensure your S3 bucket has proper CORS configuration:

[
  {
    "AllowedHeaders": ["*"],
    "AllowedMethods": ["PUT", "POST"],
    "AllowedOrigins": ["*"],
    "ExposeHeaders": ["ETag"]
  }
]

2. Content-Type Mismatch

The Content-Type in the pre-signed URL must match the upload:

// Backend should return the same Content-Type
await client.upload(
  file: file,
  contentType: 'application/pdf', // Must match backend
);

3. Pre-signed URL Expiration

Handle expired URLs gracefully:

try {
  await client.upload(file: file);
} on AwsRejectedException catch (e) {
  if (e.statusCode == 403) {
    // URL might be expired, retry with new URL
  }
}

4. File Size Limits

AWS S3 has a 5GB limit for single PUT operations. For larger files, use multipart upload:

await client.upload(
  file: largeFile,
  enableChunkedTransfer: true,
  chunkSize: 5 * 1024 * 1024, // 5 MB chunks
);

🧪 Testing

Run the test suite:

flutter test

The package includes comprehensive unit tests for all components.

📝 Example Backend Implementation

Node.js + Express + AWS SDK

const express = require('express');
const AWS = require('aws-sdk');
const { v4: uuidv4 } = require('uuid');

const s3 = new AWS.S3();
const app = express();

app.post('/upload/presigned-url', async (req, res) => {
  const { fileName, folder, contentType } = req.body;
  
  const fileKey = `${folder}/${uuidv4()}-${fileName}`;
  
  const params = {
    Bucket: 'your-bucket-name',
    Key: fileKey,
    ContentType: contentType,
    Expires: 300, // 5 minutes
  };
  
  try {
    const uploadUrl = await s3.getSignedUrlPromise('putObject', params);
    
    res.json({
      uploadUrl,
      method: 'PUT',
      headers: {
        'Content-Type': contentType,
      },
      fileKey,
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.listen(3000);

🤝 Contributing

Contributions are welcome! Please read our contributing guidelines before submitting PRs.

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🙏 Acknowledgments

  • Built with Dio for HTTP requests
  • Uses connectivity_plus for network detection
  • Inspired by AWS SDK patterns

📞 Support


Made with ❤️ by the Flutter community

Details
Pub
2026-02-23 06:20:17 +00:00
6
20 MiB
Assets (1)
1.0.0.tar.gz 20 MiB
Versions (1) View all
1.0.0 2026-02-23