Firebase Picture Uploader

A few months ago I was in the middle of creating the Dating App Tiver with Flutter. Therefore, I had to develop a firebase picture uploader that users of Tiver can upload profile pictures. Sadly, I couldn’t find a library which solves this issue for me. So I had to create it by my own. Recently, I published my code for the community, see firebase_picture_uploader.

With this article I want to explain you the features of firebase_picture_uploader. In addition, I want to guide you through the installation as well as configuration.

Firebase Picture Uploader Features

  • Main Features
    • Upload 1…x images via upload buttons to a specified directory in your firebase storage
    • Additionally, you can delete the uploaded images with the UI
  • Customizable UI
    • Customize PictureUploadWidget with multiple customization settings to match your UI/UX (see PictureUploadButtonStyle), e.g.
      • fontSize, backgroundColor, color, etc.
  • Image Manipulation before upload (via ImageManipulationSettings)
    • Set image compression level
    • Set image crop aspect ratio and dimension
  • Option for custom upload & delete functions
    • The library comes with a default upload and delete function for your firebase storage
    • Anyhow, if you want to do a pre-/post-processing or just want to use your own upload function, these can be passed to PictureUploadWidget via PictureUploadSettings.

Getting Started – Short Version

If you cannot await using this library, here’s the summary what you shall do in order to use it:

  1. First, add firebase_picture_uploader to your pubspec.yaml
  2. Second, add Firebase to your project, e.g. by following this tutorial: Flutter Firebase Tutorial
  3. Make sure that you have write permissions to the upload directory which you want to use in firebase storage
  4. Afterwards, configure image_picker for iOS, see Image Picker Library
  5. Finally, include PictureUploadWidget into your UI and add the callback functions:
new PictureUploadWidget(
  onPicturesChange: <profilePictureCallback>,
  initialImages: <_profilePictures>,
  settings: PictureUploadSettings(onErrorFunction: <onErrorCallback>, uploadDirectory: '/Uploads/'),
  buttonStyle: const PictureUploadButtonStyle(),
  buttonText: 'Upload Picture',
  enabled: true,
)

All in all, that’s it 🙂

Getting Started – Long Version

1. Add firebase_picture_uploader to your pubspec.yaml

This is a simple one, just add the following line to your pubspec.yaml:

dependencies:
  firebase_picture_uploader 1.0.0+3

2. Add Firebase to your project, e.g. by following this tutorial: Flutter Firebase Tutorial

Luckily, there is a tutorial for this, so just follow the tutorial linked ;-).

3. Make sure that you have write permissions to the upload directory which you want to use in firebase storage

Therefore, in firebase console go to => Storage (left menu) -> Rules (tab) and e.g. add the following rule:

match /Uploads/{allPaths=**} {
  allow read: if false; 
  allow write: if true; // don't for get to modify this, e.g. to if request.auth.uid != null;
}

4. Configure image_picker for iOS, see Image Picker Library

For this one, just follow the tutorial linked ;-).

5. Include PictureUploadWidget into your UI and add the callback functions

Example:

import 'package:firebase_picture_uploader/firebase_picture_uploader.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';

void main() => runApp(ExampleApp());

class ExampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: MyHome(),
    );
  }
}

class MyHome extends StatefulWidget {
  @override
  _MyHomeState createState() => new _MyHomeState();
}

class _MyHomeState extends State<MyHome> {
  List<UploadJob> _profilePictures = [];

  @override
  Widget build(BuildContext context) {
    final profilePictureTile = new Material(
      color: Colors.transparent,
      child: new Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          const Text('Profile Picture',
              style: TextStyle(
                color: CupertinoColors.systemBlue,
                fontSize: 15.0,
              )),
          const Padding(
            padding: EdgeInsets.only(bottom: 5.0),
          ),
          new PictureUploadWidget(
            onPicturesChange: profilePictureCallback,
            initialImages: _profilePictures,
            settings: PictureUploadSettings(onErrorFunction: onErrorCallback),
            buttonStyle: const PictureUploadButtonStyle(),
            buttonText: 'Upload Picture',
            enabled: true,
          ),
        ],
      ),
    );

    return new Scaffold(
      body: Padding(
          padding: const EdgeInsets.fromLTRB(20, 100, 20, 50),
          child: Column(children: <Widget>[profilePictureTile])),
    );
  }

  void onErrorCallback(error, stackTrace) {
    print(error);
    print(stackTrace);
  }

  void profilePictureCallback(
      {List<UploadJob> uploadJobs, bool pictureUploadProcessing}) {
    _profilePictures = uploadJobs;
  }
}

Configuration of Firebase Picture Uploader

As noted in the features section, firebase_picture_uploader has multiple configuration options. Therefore, in this chapter I want to explain which options you have:

class PictureUploadWidget {
  /// function is called after an image is uploaded, the the UploadJob as parameter
  final Function onPicturesChange; 
  /// the images which shall be displayed initiall
  final List<UploadJob> initialImages; 
  /// the text displayed within the upload button
  final String buttonText;
  /// if false, the widget won't react if clicked
  final bool enabled;

  /// all configuration settings for the upload
  final PictureUploadSettings settings;
  /// all ui customization settings for the upload button
  final PictureUploadButtonStyle buttonStyle;
}


class PictureUploadSettings {
  /// the directory where you want to upload to
  final String uploadDirectory;
  /// the function which shall be called to upload the image, if you don't want to use the default one
  final Function customUploadFunction;
  /// the function which shall be called to delete the image, if you don't want to use the default one
  final Function customDeleteFunction;  
  /// the function which shall be called if an error occurs
  final Function onErrorFunction;
  /// the minimum images which shall be uploaded (controls the delete button)
  final int minImageCount;
  /// the maximum images which can be uploaded
  final int maxImageCount;

  /// the settings how the image shall be modified before upload
  final ImageManipulationSettings imageManipulationSettings;
}

class ImageManipulationSettings {
  /// the requested aspect ratio for the image
  final CropAspectRatio aspectRatio;
  /// the requested maxWidth of the image
  final int maxWidth;
  /// the requested maxHeight of the image
  final int maxHeight;
  /// the requested compressQuality of the image [0..100]
  final int compressQuality;
}

class PictureUploadButtonStyle {
  /// the icon which shall be displayed within the upload button
  final IconData iconData;
  /// the icon size of the icon
  final double iconSize;
  /// the background color of the upload button
  final Color backgroundColor;
  /// the font color of the text within the upload button
  final Color fontColor;
  /// the font size of the text within the upload button
  final double fontSize;
}

Custom Firebase Storage Upload / Delete Functions

Therefore, you can use the following example custom functions as template:

Future<StorageReference> uploadProfilePicture(File image, int id) async {
  StorageReference imgRef = FirebaseStorage.instance.ref().child('/Uploads/' +
      'Directory +
      '/' +
     'custom1' +
      '_' +
      id.toString() +
      '_800.jpg');

  // start upload
  StorageUploadTask uploadTask =
      imgRef.putFile(image, new StorageMetadata(contentType: 'image/jpg'));

  // wait until upload is complete
  await uploadTask.onComplete;
  
  return imgRef;
}

Future<void> deleteProfilePicture(StorageReference oldUpload) async {
  // ask backend to transform images
  await oldUpload.delete();
}

Finally, I hope that this tutorial saves you some time :-).

Best,

Christoph