Flutter Firebase Storage and Cloud Firestore Practical Example

Amanullah Bahram
3 min readOct 30, 2024

--

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

  1. Uploading and Storing Images in Firebase Storage.
  2. Saving Metadata (like descriptions) in Cloud Firestore.
  3. 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

  1. 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

  1. 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.
  2. 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.

--

--