Skip to main content
RapidDev - Software Development Agency
flutterflow-tutorials

How to Create an Advanced Photo Gallery with Editing Features in FlutterFlow

Build an advanced photo gallery in FlutterFlow by storing images in Firebase Storage and metadata in Firestore. Use a GridView for thumbnail display with Firebase Extension-generated thumbnails, a PageView with InteractiveViewer for full-screen zoom, Custom Widgets wrapping image_cropper and photofilters for editing, and long-press gesture detection for multi-select mode.

What you'll learn

  • Display photo thumbnails efficiently in a GridView using Firebase Extension-generated resized images
  • Implement full-screen photo viewing with pinch-to-zoom using InteractiveViewer
  • Add crop and filter editing capabilities via Custom Widget packages
  • Enable multi-select mode with long-press gesture detection
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner9 min read50-70 minFlutterFlow Pro+ (code export required for custom package integration)March 2026RapidDev Engineering Team
TL;DR

Build an advanced photo gallery in FlutterFlow by storing images in Firebase Storage and metadata in Firestore. Use a GridView for thumbnail display with Firebase Extension-generated thumbnails, a PageView with InteractiveViewer for full-screen zoom, Custom Widgets wrapping image_cropper and photofilters for editing, and long-press gesture detection for multi-select mode.

Full-Featured Photo Gallery with In-App Editing

This tutorial builds a complete photo gallery experience in FlutterFlow. The gallery screen uses a GridView displaying thumbnail images generated by the Firebase Resize Images Extension — small versions of each photo that load quickly in a grid. Tapping a photo opens a full-screen PageView where users can swipe between photos and pinch to zoom using Flutter's built-in InteractiveViewer widget. An Edit button opens a bottom sheet with two Custom Widgets: one wrapping the image_cropper package for crop and rotate, and one wrapping the photofilters package for applying Instagram-style filters. Long-pressing a photo in the grid activates multi-select mode, showing checkboxes and a toolbar for bulk delete or share actions.

Prerequisites

  • FlutterFlow Pro account with Firebase Storage and Firestore enabled
  • Firebase Resize Images Extension installed in Firebase Extensions
  • Basic familiarity with FlutterFlow Custom Widgets
  • A Firestore collection named photos with fields: imageUrl, thumbnailUrl, userId, createdAt

Step-by-step guide

1

Install Firebase Resize Images Extension and Set Up Firestore Schema

In the Firebase Console, go to Extensions, search for Resize Images, and install it. Configure it to generate 200x200 thumbnails and store them with a _200x200 suffix in the same Storage path. When a user uploads photos/abc123.jpg, the extension automatically creates photos/abc123_200x200.jpg. In Firestore, create a photos collection with fields: imageUrl (String — full resolution URL), thumbnailUrl (String — the 200x200 URL), width (Integer), height (Integer), userId (String), createdAt (Timestamp), and isSelected (Boolean, default false — used for UI multi-select state). In FlutterFlow's Firestore panel, import the schema and create the photos Document Type.

Expected result: Firebase Extension is installed. Uploading a test image creates both a full-size and a thumbnail version in Firebase Storage.

2

Build the Thumbnail GridView Gallery Screen

Create a new page called Gallery. Add a Backend Query at the page level streaming the photos collection filtered by userId equals currentUser.uid, ordered by createdAt descending. Add a GridView widget set to 3 columns with 2px spacing. Inside the GridView, add an Image widget. Bind the image source to the thumbnailUrl field of each photo document — this loads the small 200x200 version, not the 5MB original. Add a GestureDetector wrapping each grid item to handle two gestures: On Tap (navigate to detail view, passing the photo document reference) and On Long Press (enter multi-select mode by updating an App State variable isMultiSelectMode to true and toggling the current photo's isSelected field). Add a Conditional Visibility overlay on each grid item showing a blue checkmark icon when isMultiSelectMode is true and the photo's isSelected is true.

Expected result: The gallery shows a smooth 3-column grid of thumbnails. Long-pressing activates multi-select with visible checkboxes.

3

Build the Full-Screen Viewer with Pinch-to-Zoom

Create a new page called PhotoDetail. Pass the photo document reference as a page parameter. At the page level, query the photos collection to get the full list for the current user (so users can swipe between photos). Add a PageView widget and set its initial page to the index of the passed photo in the list. Inside the PageView, add an InteractiveViewer widget — this is a built-in Flutter widget available in FlutterFlow under the Widgets panel. Set its minScale to 0.5 and maxScale to 4.0. Inside InteractiveViewer, add an Image widget bound to the 800x800 medium-resolution URL. Add a back arrow IconButton in the top-left and an Edit button IconButton in the top-right. The Edit button opens a Bottom Sheet containing the editing options.

Expected result: Tapping a photo opens a full-screen viewer. Users can pinch to zoom up to 4x and swipe to navigate between photos.

4

Add Crop and Filter Editing via Custom Widgets

In FlutterFlow, go to Custom Widgets and create two widgets. The first widget is named ImageCropWidget. In the pubspec dependencies, add image_cropper: ^5.0.0 and image_picker: ^1.0.4. The widget accepts a parameter imageUrl (String) and an onCropComplete callback. It uses ImageCropper().cropImage() to display the system crop UI, then returns the cropped file path via the callback. The second widget is named ImageFilterWidget. Add photofilters: ^3.0.0 as a dependency. This widget displays a horizontal scrollable list of filter previews (Clarendon, Gingham, Moon, etc.). When a filter is selected, it applies the filter to the image bytes and returns the modified bytes via an onFilterApplied callback. In the Bottom Sheet opened by the Edit button, show two tabs — Crop and Filters — that display these Custom Widgets.

image_crop_widget.dart
1// Custom Widget: ImageCropWidget (pubspec: image_cropper: ^5.0.0)
2import 'package:image_cropper/image_cropper.dart';
3
4class ImageCropWidget extends StatefulWidget {
5 final String imageUrl;
6 final Future<dynamic> Function(String croppedPath) onCropComplete;
7 const ImageCropWidget({Key? key, required this.imageUrl, required this.onCropComplete}) : super(key: key);
8
9 @override
10 State<ImageCropWidget> createState() => _ImageCropWidgetState();
11}
12
13class _ImageCropWidgetState extends State<ImageCropWidget> {
14 @override
15 void initState() {
16 super.initState();
17 _startCrop();
18 }
19
20 Future<void> _startCrop() async {
21 final cropped = await ImageCropper().cropImage(
22 sourcePath: widget.imageUrl,
23 uiSettings: [
24 AndroidUiSettings(toolbarTitle: 'Crop Photo', lockAspectRatio: false),
25 IOSUiSettings(title: 'Crop Photo'),
26 ],
27 );
28 if (cropped != null) {
29 widget.onCropComplete(cropped.path);
30 }
31 }
32
33 @override
34 Widget build(BuildContext context) {
35 return const Center(child: CircularProgressIndicator());
36 }
37}

Expected result: The Edit bottom sheet opens with Crop and Filter tabs. Cropping opens the system crop UI. Filters show a horizontal scrollable preview strip.

5

Implement Multi-Select Mode with Bulk Actions

Add two App State variables: isMultiSelectMode (Boolean, default false) and selectedPhotoIds (List of Strings, default empty). On long press of any grid item, set isMultiSelectMode to true and add the tapped photo's document ID to selectedPhotoIds. When isMultiSelectMode is true, show a selection toolbar at the bottom of the Gallery page using Conditional Visibility. The toolbar contains three IconButtons: Share (uses the share_plus package to share selected images), Delete (calls a Cloud Function that batch-deletes selected Firestore documents and Storage files), and Cancel (resets isMultiSelectMode to false and clears selectedPhotoIds). Add a SelectAll button in the app bar that adds all visible photo IDs to selectedPhotoIds when tapped.

Expected result: Long-pressing activates multi-select. The toolbar appears with Share, Delete, and Cancel options. Tapping Cancel returns to normal gallery mode.

Complete working example

image_crop_widget.dart
1// Full Custom Widget: ImageCropWidget for FlutterFlow
2// pubspec.yaml dependencies:
3// image_cropper: ^5.0.0
4// image_picker: ^1.0.4
5
6import 'package:flutter/material.dart';
7import 'package:image_cropper/image_cropper.dart';
8import 'package:image_picker/image_picker.dart';
9
10class ImageCropWidget extends StatefulWidget {
11 final String imageUrl;
12 final double width;
13 final double height;
14 final Future<dynamic> Function(String croppedPath) onCropComplete;
15 final Future<dynamic> Function() onCancel;
16
17 const ImageCropWidget({
18 Key? key,
19 required this.imageUrl,
20 required this.width,
21 required this.height,
22 required this.onCropComplete,
23 required this.onCancel,
24 }) : super(key: key);
25
26 @override
27 State<ImageCropWidget> createState() => _ImageCropWidgetState();
28}
29
30class _ImageCropWidgetState extends State<ImageCropWidget> {
31 bool _loading = true;
32
33 @override
34 void initState() {
35 super.initState();
36 WidgetsBinding.instance.addPostFrameCallback((_) => _startCrop());
37 }
38
39 Future<void> _startCrop() async {
40 try {
41 final cropped = await ImageCropper().cropImage(
42 sourcePath: widget.imageUrl,
43 compressQuality: 90,
44 uiSettings: [
45 AndroidUiSettings(
46 toolbarTitle: 'Crop Photo',
47 toolbarColor: Colors.black,
48 toolbarWidgetColor: Colors.white,
49 lockAspectRatio: false,
50 hideBottomControls: false,
51 ),
52 IOSUiSettings(
53 title: 'Crop Photo',
54 cancelButtonTitle: 'Cancel',
55 doneButtonTitle: 'Done',
56 ),
57 ],
58 );
59 if (cropped != null) {
60 await widget.onCropComplete(cropped.path);
61 } else {
62 await widget.onCancel();
63 }
64 } catch (e) {
65 await widget.onCancel();
66 } finally {
67 if (mounted) setState(() => _loading = false);
68 }
69 }
70
71 @override
72 Widget build(BuildContext context) {
73 return SizedBox(
74 width: widget.width,
75 height: widget.height,
76 child: _loading
77 ? const Center(
78 child: CircularProgressIndicator(color: Colors.white),
79 )
80 : const SizedBox.shrink(),
81 );
82 }
83}

Common mistakes when creating an Advanced Photo Gallery with Editing Features in FlutterFlow

Why it's a problem: Displaying full-resolution images (4000x3000, 5MB each) in GridView thumbnails

How to avoid: Use the Firebase Resize Images Extension to auto-generate 200x200 thumbnails. Bind the GridView Image widget to the thumbnailUrl field, not imageUrl. Only load the full-resolution image when the user opens the detail view.

Why it's a problem: Saving the edited image over the original in Firebase Storage

How to avoid: Upload edited images as new files with an _edited or timestamp suffix. Update the Firestore document's editedUrl field. Keep the original imageUrl intact so users can revert.

Why it's a problem: Running crop and filter operations synchronously on the main UI thread

How to avoid: Wrap image processing in an async Future and show a CircularProgressIndicator overlay while it runs. The image_cropper package already handles this, but any custom filter processing should use compute() to run on an isolate.

Best practices

  • Always use thumbnails in grid views and only load full resolution on demand — this is the single biggest performance win in a photo gallery app.
  • Store both imageUrl and thumbnailUrl in Firestore so you can switch between them based on context without additional Storage queries.
  • Keep original photos intact — save edits as new files and track them in a separate editedUrl field.
  • Limit the Backend Query for the gallery to the most recent 200 photos and implement infinite scroll for older content.
  • Use Hero animations between grid thumbnails and the detail PageView for a polished transition that feels native.
  • Add image upload progress tracking using Firebase Storage's upload task progress stream — show a loading indicator on the grid cell while the upload completes.
  • Request Camera and Photo Library permissions gracefully — explain why the app needs them before the system permission dialog appears.

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I'm building a photo gallery in FlutterFlow with Firebase Storage. My GridView is very slow because I'm loading full-resolution images. I've installed the Firebase Resize Images Extension which generates _200x200 thumbnails. How do I update my Firestore document structure and FlutterFlow GridView binding to use the thumbnail URL instead of the full image URL?

FlutterFlow Prompt

Create a FlutterFlow Custom Widget that wraps the image_cropper package. The widget should accept a String imageUrl parameter and a callback function onCropComplete that receives the cropped file path as a String. Show a CircularProgressIndicator while the crop UI is loading.

Frequently asked questions

Which image editing packages work best with FlutterFlow Custom Widgets?

image_cropper (v5+) for crop/rotate, photofilters for filter effects, and flutter_colorpicker for color adjustments are the most reliable options. They have pre-built UI, are actively maintained, and work on both iOS and Android. Add them in the Custom Widget's pubspec dependencies section.

How do I handle video files alongside photos in the gallery?

Add a mediaType field (String) to your Firestore schema with values 'image' or 'video'. For videos, use the video_player package in a Custom Widget for thumbnail and playback. The GridView can conditionally show a play icon overlay on video items based on the mediaType field.

Can users share photos from within the FlutterFlow app?

Yes. Use the share_plus package in a Custom Action. Pass the Firebase Storage download URL to Share.shareUri() for sharing a link, or download the image bytes first and use Share.shareXFiles() to share the actual image file.

How do I implement photo albums or folders?

Add an albumId (String) field to your photos Firestore collection. Create a separate albums collection. Filter the gallery GridView Backend Query by albumId using a page parameter passed from an album list screen.

The pinch-to-zoom resets every time I swipe to the next photo — how do I fix this?

This is expected behavior — each PageView child creates a new InteractiveViewer with default scale. Add a TransformationController to each InteractiveViewer and reset it in the PageView's onPageChanged callback. This gives smooth zoom reset between photos.

How many photos can I store per user before performance degrades?

Firestore has no practical limit on documents per collection. The performance constraint is in the GridView — loading more than 200 thumbnails in a single query will slow page load. Use FlutterFlow's Infinite Scroll on the GridView to load 50 photos at a time and fetch more as the user scrolls down.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.