File Operations
Complete examples of implementing file operations with Fasq. Learn how to handle file uploads, downloads, image processing, and file management.
File Upload
Section titled “File Upload”Basic File Upload
Section titled “Basic File Upload”class FileUploadService { static Future<String> uploadFile(File file, String fileName) async { final request = http.MultipartRequest( 'POST', Uri.parse('https://api.example.com/upload'), );
request.files.add( await http.MultipartFile.fromPath('file', file.path), );
request.fields['fileName'] = fileName;
final response = await request.send();
if (response.statusCode == 200) { final responseBody = await response.stream.bytesToString(); final data = jsonDecode(responseBody); return data['fileUrl']; } else { throw Exception('File upload failed'); } }
static Future<List<String>> uploadMultipleFiles(List<File> files) async { final futures = files.map((file) => uploadFile(file, file.path.split('/').last)); return await Future.wait(futures); }}
class FileUploadScreen extends StatefulWidget { @override State<FileUploadScreen> createState() => _FileUploadScreenState();}
class _FileUploadScreenState extends State<FileUploadScreen> { File? _selectedFile; String? _uploadedFileUrl;
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('File Upload')), body: MutationBuilder<String, File>( mutationFn: (file) => FileUploadService.uploadFile(file, file.path.split('/').last), options: MutationOptions( onSuccess: (fileUrl) { setState(() { _uploadedFileUrl = fileUrl; }); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('File uploaded successfully')), ); }, onError: (error) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Upload failed: $error')), ); }, ), builder: (context, state) { return Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ // File selection ElevatedButton( onPressed: () => _selectFile(), child: Text('Select File'), ), SizedBox(height: 16),
// Show selected file if (_selectedFile != null) ...[ Text('Selected: ${_selectedFile!.path.split('/').last}'), SizedBox(height: 16),
// Upload button ElevatedButton( onPressed: state.isLoading ? null : () { state.mutate(_selectedFile!); }, child: state.isLoading ? CircularProgressIndicator() : Text('Upload File'), ), ],
// Show uploaded file URL if (_uploadedFileUrl != null) ...[ SizedBox(height: 16), Text('Uploaded URL: $_uploadedFileUrl'), ], ], ), ); }, ), ); }
Future<void> _selectFile() async { final result = await FilePicker.platform.pickFiles(); if (result != null) { setState(() { _selectedFile = File(result.files.single.path!); }); } }}Image Upload with Preview
Section titled “Image Upload with Preview”class ImageUploadScreen extends StatefulWidget { @override State<ImageUploadScreen> createState() => _ImageUploadScreenState();}
class _ImageUploadScreenState extends State<ImageUploadScreen> { File? _selectedImage; String? _uploadedImageUrl;
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Image Upload')), body: MutationBuilder<String, File>( mutationFn: (image) => FileUploadService.uploadFile(image, 'image.jpg'), options: MutationOptions( onSuccess: (imageUrl) { setState(() { _uploadedImageUrl = imageUrl; }); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Image uploaded successfully')), ); }, onError: (error) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Upload failed: $error')), ); }, ), builder: (context, state) { return Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ // Image selection ElevatedButton( onPressed: () => _selectImage(), child: Text('Select Image'), ), SizedBox(height: 16),
// Show selected image if (_selectedImage != null) ...[ Container( height: 200, width: double.infinity, decoration: BoxDecoration( border: Border.all(color: Colors.grey), borderRadius: BorderRadius.circular(8), ), child: Image.file( _selectedImage!, fit: BoxFit.cover, ), ), SizedBox(height: 16),
// Upload button ElevatedButton( onPressed: state.isLoading ? null : () { state.mutate(_selectedImage!); }, child: state.isLoading ? CircularProgressIndicator() : Text('Upload Image'), ), ],
// Show uploaded image if (_uploadedImageUrl != null) ...[ SizedBox(height: 16), Container( height: 200, width: double.infinity, decoration: BoxDecoration( border: Border.all(color: Colors.grey), borderRadius: BorderRadius.circular(8), ), child: Image.network( _uploadedImageUrl!, fit: BoxFit.cover, ), ), ], ], ), ); }, ), ); }
Future<void> _selectImage() async { final picker = ImagePicker(); final pickedFile = await picker.pickImage(source: ImageSource.gallery);
if (pickedFile != null) { setState(() { _selectedImage = File(pickedFile.path); }); } }}File Download
Section titled “File Download”File Download Service
Section titled “File Download Service”class FileDownloadService { static Future<File> downloadFile(String url, String fileName) async { final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) { final directory = await getApplicationDocumentsDirectory(); final file = File('${directory.path}/$fileName');
await file.writeAsBytes(response.bodyBytes); return file; } else { throw Exception('File download failed'); } }
static Future<List<File>> downloadMultipleFiles(List<String> urls) async { final futures = urls.asMap().entries.map((entry) { final index = entry.key; final url = entry.value; final fileName = 'file_$index'; return downloadFile(url, fileName); });
return await Future.wait(futures); }}
class FileDownloadScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('File Download')), body: QueryBuilder<List<FileInfo>>( queryKey: 'files', queryFn: () => _fetchFiles(), builder: (context, state) { return state.when( loading: () => Center(child: CircularProgressIndicator()), error: (error, stack) => Center(child: Text('Error: $error')), data: (files) => ListView.builder( itemCount: files.length, itemBuilder: (context, index) { final file = files[index]; return FileDownloadTile(file: file); }, ), ); }, ), ); }
Future<List<FileInfo>> _fetchFiles() async { final response = await http.get(Uri.parse('https://api.example.com/files')); if (response.statusCode == 200) { final data = jsonDecode(response.body); return (data['files'] as List) .map((json) => FileInfo.fromJson(json)) .toList(); } else { throw Exception('Failed to fetch files'); } }}
class FileInfo { final String id; final String name; final String url; final int size; final String type;
FileInfo({ required this.id, required this.name, required this.url, required this.size, required this.type, });
factory FileInfo.fromJson(Map<String, dynamic> json) { return FileInfo( id: json['id'], name: json['name'], url: json['url'], size: json['size'], type: json['type'], ); }}
class FileDownloadTile extends StatelessWidget { final FileInfo file;
const FileDownloadTile({required this.file});
@override Widget build(BuildContext context) { return MutationBuilder<File, String>( mutationFn: (url) => FileDownloadService.downloadFile(url, file.name), options: MutationOptions( onSuccess: (downloadedFile) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('File downloaded: ${downloadedFile.path}')), ); }, onError: (error) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Download failed: $error')), ); }, ), builder: (context, state) { return Card( child: ListTile( leading: Icon(_getFileIcon(file.type)), title: Text(file.name), subtitle: Text('${_formatFileSize(file.size)} - ${file.type}'), trailing: ElevatedButton( onPressed: state.isLoading ? null : () { state.mutate(file.url); }, child: state.isLoading ? CircularProgressIndicator() : Text('Download'), ), ), ); }, ); }
IconData _getFileIcon(String type) { switch (type.toLowerCase()) { case 'pdf': return Icons.picture_as_pdf; case 'image': return Icons.image; case 'video': return Icons.video_file; case 'audio': return Icons.audio_file; default: return Icons.insert_drive_file; } }
String _formatFileSize(int bytes) { if (bytes < 1024) return '$bytes B'; if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB'; if (bytes < 1024 * 1024 * 1024) return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB'; return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)} GB'; }}Image Processing
Section titled “Image Processing”Image Processing Service
Section titled “Image Processing Service”class ImageProcessingService { static Future<File> resizeImage(File imageFile, int width, int height) async { final image = await decodeImageFromList(await imageFile.readAsBytes()); final resizedImage = await resize(image, width: width, height: height);
final directory = await getApplicationDocumentsDirectory(); final resizedFile = File('${directory.path}/resized_${imageFile.path.split('/').last}');
await resizedFile.writeAsBytes(await encodeJpg(resizedImage)); return resizedFile; }
static Future<File> compressImage(File imageFile, int quality) async { final image = await decodeImageFromList(await imageFile.readAsBytes()); final compressedImage = await encodeJpg(image, quality: quality);
final directory = await getApplicationDocumentsDirectory(); final compressedFile = File('${directory.path}/compressed_${imageFile.path.split('/').last}');
await compressedFile.writeAsBytes(compressedImage); return compressedFile; }
static Future<File> cropImage(File imageFile, Rect cropRect) async { final image = await decodeImageFromList(await imageFile.readAsBytes()); final croppedImage = await copyCrop(image, cropRect);
final directory = await getApplicationDocumentsDirectory(); final croppedFile = File('${directory.path}/cropped_${imageFile.path.split('/').last}');
await croppedFile.writeAsBytes(await encodeJpg(croppedImage)); return croppedFile; }}
class ImageProcessingScreen extends StatefulWidget { @override State<ImageProcessingScreen> createState() => _ImageProcessingScreenState();}
class _ImageProcessingScreenState extends State<ImageProcessingScreen> { File? _selectedImage; File? _processedImage;
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Image Processing')), body: Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ // Image selection ElevatedButton( onPressed: () => _selectImage(), child: Text('Select Image'), ), SizedBox(height: 16),
// Show selected image if (_selectedImage != null) ...[ Container( height: 200, width: double.infinity, decoration: BoxDecoration( border: Border.all(color: Colors.grey), borderRadius: BorderRadius.circular(8), ), child: Image.file( _selectedImage!, fit: BoxFit.cover, ), ), SizedBox(height: 16),
// Processing buttons Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ ElevatedButton( onPressed: () => _resizeImage(), child: Text('Resize'), ), ElevatedButton( onPressed: () => _compressImage(), child: Text('Compress'), ), ElevatedButton( onPressed: () => _cropImage(), child: Text('Crop'), ), ], ), ],
// Show processed image if (_processedImage != null) ...[ SizedBox(height: 16), Text('Processed Image:'), Container( height: 200, width: double.infinity, decoration: BoxDecoration( border: Border.all(color: Colors.grey), borderRadius: BorderRadius.circular(8), ), child: Image.file( _processedImage!, fit: BoxFit.cover, ), ), ], ], ), ), ); }
Future<void> _selectImage() async { final picker = ImagePicker(); final pickedFile = await picker.pickImage(source: ImageSource.gallery);
if (pickedFile != null) { setState(() { _selectedImage = File(pickedFile.path); _processedImage = null; }); } }
Future<void> _resizeImage() async { if (_selectedImage != null) { final resizedImage = await ImageProcessingService.resizeImage(_selectedImage!, 300, 300); setState(() { _processedImage = resizedImage; }); } }
Future<void> _compressImage() async { if (_selectedImage != null) { final compressedImage = await ImageProcessingService.compressImage(_selectedImage!, 80); setState(() { _processedImage = compressedImage; }); } }
Future<void> _cropImage() async { if (_selectedImage != null) { final cropRect = Rect.fromLTWH(50, 50, 200, 200); final croppedImage = await ImageProcessingService.cropImage(_selectedImage!, cropRect); setState(() { _processedImage = croppedImage; }); } }}File Management
Section titled “File Management”File Manager Screen
Section titled “File Manager Screen”class FileManagerScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('File Manager')), body: QueryBuilder<List<FileInfo>>( queryKey: 'files', queryFn: () => _fetchFiles(), builder: (context, state) { return state.when( loading: () => Center(child: CircularProgressIndicator()), error: (error, stack) => Center(child: Text('Error: $error')), data: (files) => ListView.builder( itemCount: files.length, itemBuilder: (context, index) { final file = files[index]; return FileManagerTile(file: file); }, ), ); }, ), floatingActionButton: FloatingActionButton( onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => FileUploadScreen(), ), ); }, child: Icon(Icons.add), ), ); }
Future<List<FileInfo>> _fetchFiles() async { final response = await http.get(Uri.parse('https://api.example.com/files')); if (response.statusCode == 200) { final data = jsonDecode(response.body); return (data['files'] as List) .map((json) => FileInfo.fromJson(json)) .toList(); } else { throw Exception('Failed to fetch files'); } }}
class FileManagerTile extends StatelessWidget { final FileInfo file;
const FileManagerTile({required this.file});
@override Widget build(BuildContext context) { return Card( child: ListTile( leading: Icon(_getFileIcon(file.type)), title: Text(file.name), subtitle: Text('${_formatFileSize(file.size)} - ${file.type}'), trailing: PopupMenuButton( itemBuilder: (context) => [ PopupMenuItem( value: 'download', child: Row( children: [ Icon(Icons.download), SizedBox(width: 8), Text('Download'), ], ), ), PopupMenuItem( value: 'share', child: Row( children: [ Icon(Icons.share), SizedBox(width: 8), Text('Share'), ], ), ), PopupMenuItem( value: 'delete', child: Row( children: [ Icon(Icons.delete, color: Colors.red), SizedBox(width: 8), Text('Delete', style: TextStyle(color: Colors.red)), ], ), ), ], onSelected: (value) { switch (value) { case 'download': _downloadFile(context); break; case 'share': _shareFile(context); break; case 'delete': _showDeleteDialog(context); break; } }, ), ), ); }
void _downloadFile(BuildContext context) { MutationBuilder<File, String>( mutationFn: (url) => FileDownloadService.downloadFile(url, file.name), options: MutationOptions( onSuccess: (downloadedFile) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('File downloaded: ${downloadedFile.path}')), ); }, onError: (error) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Download failed: $error')), ); }, ), builder: (context, state) { return state.mutate(file.url); }, ); }
void _shareFile(BuildContext context) { Share.share(file.url); }
void _showDeleteDialog(BuildContext context) { showDialog( context: context, builder: (context) => AlertDialog( title: Text('Delete File'), content: Text('Are you sure you want to delete "${file.name}"?'), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: Text('Cancel'), ), TextButton( onPressed: () { Navigator.pop(context); _deleteFile(context); }, child: Text('Delete', style: TextStyle(color: Colors.red)), ), ], ), ); }
void _deleteFile(BuildContext context) { MutationBuilder<void, String>( mutationFn: (id) => _deleteFileFromServer(id), options: MutationOptions( onSuccess: (_, id) { QueryClient().invalidateQuery('files'); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('File deleted successfully')), ); }, onError: (error) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Failed to delete file: $error')), ); }, ), builder: (context, state) { return state.mutate(file.id); }, ); }
Future<void> _deleteFileFromServer(String id) async { final response = await http.delete(Uri.parse('https://api.example.com/files/$id')); if (response.statusCode != 204) { throw Exception('Failed to delete file'); } }
IconData _getFileIcon(String type) { switch (type.toLowerCase()) { case 'pdf': return Icons.picture_as_pdf; case 'image': return Icons.image; case 'video': return Icons.video_file; case 'audio': return Icons.audio_file; default: return Icons.insert_drive_file; } }
String _formatFileSize(int bytes) { if (bytes < 1024) return '$bytes B'; if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB'; if (bytes < 1024 * 1024 * 1024) return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB'; return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)} GB'; }}Best Practices
Section titled “Best Practices”- Validate file types - Check file extensions and MIME types
- Handle large files - Implement progress indicators and chunked uploads
- Compress images - Reduce file sizes for better performance
- Cache downloaded files - Store files locally for offline access
- Handle errors gracefully - Provide meaningful error messages
- Implement file sharing - Allow users to share files
- Secure file operations - Validate permissions and sanitize inputs
Next Steps
Section titled “Next Steps”- Database Queries - Learn about database patterns
- Authentication - Learn about authentication patterns
- CRUD Operations - Learn about CRUD patterns