Flutter Firebase Storage and Cloud Firestore Practical Example
In this guide, we’ll demonstrate how to integrate Firebase Storage and Cloud Firestore in Flutter using a practical example of a photo-sharing app. This setup is designed to help you see Firebase services in real-world use, focusing on uploading images and storing metadata in Firestore.
What We’ll Cover
- Uploading and Storing Images in Firebase Storage.
- Saving Metadata (like descriptions) in Cloud Firestore.
- Retrieving and Displaying Data from Firestore in real-time.
Step 1: Project Setup
1. Create a Flutter Project
flutter create photo_firestore_app
cd photo_firestore_app
2. Add Dependencies
In pubspec.yaml
, add:
dependencies:
firebase_core: latest_version
firebase_storage: latest_version
cloud_firestore: latest_version
image_picker: latest_version
permission_handler: latest_version
Run flutter pub get
to install.
3. Set Up Firebase
- Firebase Configured Watch Here if you don’t know how
Android
- Add permissions in
AndroidManifest.xml
:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.CAMERA"/>
2. Ensure the compileSdkVersion
in android/app/build.gradle
is set to 33 or higher.
iOS
In ios/Runner/Info.plist
, add:
<key>NSCameraUsageDescription</key>
<string>We need access to your camera to upload photos</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>We need access to your photo library to upload photos</string>
Step 3: Firebase Integration in Flutter
Main App Setup (main.dart
)
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'screens/home_screen.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Photo Sharing App',
theme: ThemeData(primarySwatch: Colors.blue),
home: HomeScreen(),
);
}
}
Step 4: Core App Functionality
Pick Image (upload_screen.dart
)
Future<void> _pickImage() async {
// Request permissions if needed
if (await Permission.photos.request().isGranted) {
final picker = ImagePicker();
final pickedFile = await picker.pickImage(source: ImageSource.gallery);
if (pickedFile != null) {
setState(() {
_image = File(pickedFile.path);
});
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Permission denied')),
);
}
}
Upload Image (upload_screen.dart)
Future<void> _uploadImage() async {
if (_image == null || _descriptionController.text.isEmpty) return;
setState(() {
_isLoading = true;
});
try {
final storageRef =
FirebaseStorage.instance.ref().child('photos/${DateTime.now()}.png');
await storageRef.putFile(_image!);
final downloadUrl = await storageRef.getDownloadURL();
FirebaseFirestore.instance.collection('photos').add({
'url': downloadUrl,
'description': _descriptionController.text,
'createdAt': Timestamp.now(),
});
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('Upload successful')));
Navigator.pop(context);
} catch (e) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('Upload failed: $e')));
} finally {
setState(() {
_isLoading = false;
});
}
}
Upload Description (photo_details_screen.dart)
Future<void> _updateDescription(String newDescription) async {
await FirebaseFirestore.instance
.collection('photos')
.doc(widget.photoId)
.update({'description': newDescription});
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('Description updated')));
}
Delete Image (photo_details_screen.dart)
Future<void> _deletePhoto(String photoUrl) async {
await FirebaseFirestore.instance
.collection('photos')
.doc(widget.photoId)
.delete();
await FirebaseStorage.instance.refFromURL(photoUrl).delete();
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('Photo deleted')));
Navigator.pop(context);
}
Home Screen (home_screen.dart
)
The home screen lists uploaded photos by fetching them from Firestore in real-time.
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'upload_screen.dart';
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Photo Gallery')),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context, MaterialPageRoute(builder: (context) => UploadScreen()));
},
child: Icon(Icons.add),
),
body: StreamBuilder(
stream: FirebaseFirestore.instance.collection('photos').orderBy('createdAt', descending: true).snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) return Center(child: CircularProgressIndicator());
return ListView(
children: snapshot.data!.docs.map((doc) {
return ListTile(
leading: Image.network(doc['url'], width: 100),
title: Text(doc['description']),
);
}).toList(),
);
},
),
);
}
}
Step 5: Running the App
- Upload a Photo: Go to the “Upload Photo” screen, select a photo, add a description, and tap “Upload.” The image and description will be uploaded to Firebase Storage and Cloud Firestore.
- View the Gallery: Once uploaded, the photo appears in the gallery on the Home Screen, with data fetched from Firestore in real time.
Conclusion
This example showcases how Firebase Storage and Cloud Firestore work together in a Flutter app to handle real-time data and file uploads. With this setup, you can build more complex functionality, like user profiles, more metadata, or advanced query filtering. Firebase Storage and Firestore provide powerful capabilities for practical, scalable Flutter applications.