Firebase Real-time and Storage for Flutter Apps – A Practical Guide

This guide shall serve as the continuation of the very previous blog post titled Mastering Firebase Services – Authentication and Crashlytics in a Flutter App. In this post, we shall be deeply exploring implementing Flutter Realtime Database followed by Cloud Storage vis-à-vis adding new features to our existing/ongoing Student Grading Application, and much more. So, without further ado, let’s jump straight to the topic.


Table of Contents

Project’s new Stuff

There will be two additional screens which will contain the entire list of graded students, followed by a grade screen in student_grading_listview and add_student_grading_view.dart respectively.

Moreover, we will attach these screens to sign up and log in as well.

Let’s now move on to the next one.

Firebase Realtime Database

A cloud-hosted NOSQL database built to produce scalable and flexible storage solutions. Serving as a part of Firebase services for various platforms.

Furthermore, certain features need to be addressed:

Real-time Synchronization:

Firebase Database has support for multiple clients (web and mobile), to provide real-time changes.

When data gets changed, it is immediately notified all across.

JSON Data Model:

The database is structured as a JSON tree, where data is organized into hierarchical nodes, containing key-value pairs. Furthermore, these objects can be nested to create complex structures as well.

Offline Capabilities:

Firebase Database provides built-in support for offline data. Clients can read and write data when they are not even connected to the internet. Once the connection is restored, the data gets synchronized automatically.

Security Rules:

Firebase allows you to define security rules for databases to control who can read and write data. Additionally, the rules are defined in declarative language, to ensure that relevant authority gets access.

REST-API:

Besides various SDKs (Android, iOS, JavaScript), Firebase Realtime Database also provides a RESTFUL API, making it accessible to any Programming Language to parse HTTP requests.

Scalability:

Firebase manages the scalability and infrastructure of the database, ensuring that it can handle numerous concurrent users without the need to manage servers or load.

Implementation

In the Firebase Console, followed by the correct Gmail account, head over to the Build section -> Firebase Database.

Realtime DB picture
RT DB rules

Since we’re going to do it in test mode, enable the second one, and you will be good to go.

In addition to this, under the rules tab, you will find:

{
  "rules": {
    ".read": false,
    ".write": false
  }
}

We need to modify these rules, to ensure a successful read and write.

Here is the replaced version of it:

{
  "rules": {
    ".read": true,
    ".write": true
  }
}

Under the existing Flutter project, add the plugin:

firebase_database

With the use of this command:

flutter pub add firebase_database

Firstly, let’s do it to add_student_grading.dart.

add_student_grading.dart

Create an instance of DatabaseReference and provide a valid path to store the object.

final DatabaseReference _databaseReference = FirebaseDatabase.instance.ref(SgConstants.reference);

Note: For the sake of simplicity, I have created a constant class and attached the string. For example → Grade

Secondly, on the callback event itself, make an object and function likewise:

  uploadMarks(User currentUser) async {
    isLoading = true;

    await _databaseReference.child(currentUser.uid).push().set({
      'id': Random().nextInt(100000).toString(),
      'name': _studentC.text,
      'phy': _phyC.text,
      'chem': _chemC.text,
      'math': _mathC.text,
    }).onError((error, stackTrace) {
      setState(() {
        isLoading = false;
      });
      SgUtils().showSnackBar(error.toString());
    });

    setState(() {
      isLoading = false;

      _studentC.clear();
      _phyC.clear();
      _chemC.clear();
      _mathC.clear();
    });
    SgUtils().showSnackBar('Uploaded successfully');
  }


isLoading ? const Center(child: CircularProgressIndicator()) : Padding(
            padding: const EdgeInsets.all(8),
            child: ElevatedButton(
              onPressed: () {
                User? user = FirebaseAuth.instance.currentUser;

                setState(() {});
                uploadMarks(user!);
              },
              style: ElevatedButton.styleFrom(
                minimumSize: Size(MediaQuery.of(context).size.width, 50),
              ),
              child: const Text("Upload"),
            ),
          ),

In the above code snippet, we are storing student data based on current user_id in the form of a list, to distinguish the records concerning each user.

Let’s now do it for student_grading_listview.dart.

First, create two instances; Firebase Database and User (Authentication) respectively.

  final DatabaseReference _databaseReference 
  FirebaseDatabase.instance.ref(SgConstants.reference);
  
final User? currentUser = FirebaseAuth.instance.currentUser;

We are going to fetch the entire snapshot in the form of streams, to ensure a seamless flow throughout.

Here’s the code snippet:

StreamBuilder<DatabaseEvent>(
        stream: _databaseReference.child(currentUser!.uid).onValue,
        builder: (context, s) {

          if(s.hasData) {

            return s.data!.snapshot.children.isEmpty ? const Center(child: Text("No Results"),) : ListView.separated(
              shrinkWrap: true,
              padding: const EdgeInsets.symmetric(horizontal: 3, vertical: 2),
              itemCount: s.data!.snapshot.children.length,
              itemBuilder: (context, i) {

                return Column(
                children: [
                  Text(s.data!.snapshot.value.toString())
                ],
              );

              },
              separatorBuilder: (context, i) => const SizedBox(height: 10),
            );
          }

          else if(s.data == null) {
            return const Center(
              child: CircularProgressIndicator(),
            );
          }

          else if(s.hasError) {
            return Text(s.error.toString());
          }

          return const SizedBox.shrink();
        },
      )

As in the above snippet, I have just printed the entire snapshot’s value, which results in:

Firebase Real-time and Storage for Flutter Apps - A Practical Guide - Student Object

Note: it’s a bit tricky to fetch the values and parse accordingly.

So, for that, create a model class as GradingModel and pass on this code snippet:

class GradingModel {
  String? id, name, phy, chem, math;

  GradingModel(
      {required this.id,
      required this.name,
      required this.chem,
      required this.math,
      required this.phy});

  factory GradingModel.fromJson(Map<dynamic, dynamic> json) {
    return GradingModel(
      id: json['id'],
      name: json['name'],
      phy: json['phy'],
      chem: json['chem'],
      math: json['math'],
    );
  }
}

Secondly, right after itemBuilder:

  • Create a List
  • Receive values from the snapshot (in the form of) Map
  • Parse the response to the model
StreamBuilder<DatabaseEvent>(
      stream: _databaseReference.child(currentUser!.uid).onValue,
      builder: (context, s) {

        if(s.hasData) {

          return s.data!.snapshot.children.isEmpty ? const Center(child: Text("No Results"),) : ListView.separated(
            shrinkWrap: true,
            padding: const EdgeInsets.symmetric(horizontal: 3, vertical: 2),
            itemCount: s.data!.snapshot.children.length,
            itemBuilder: (context, i) {

              Map<dynamic, dynamic> snapshot = s.data!.snapshot.value as 
              dynamic;

              List<GradingModel> itemList = [];
              snapshot.forEach((key, value) {

                var res = GradingModel.fromJson(value);
                itemList.add(res);
              });

              return GradingCustomWidget(
                model: itemList[i],
              );
            },
            separatorBuilder: (context, i) => const SizedBox(height: 10),
          );
        }

        else if(s.data == null) {
          return const Center(
            child: CircularProgressIndicator(),
          );
        }

        else if(s.hasError) {
          return Text(s.error.toString());
        }

        return const SizedBox.shrink();
      },
    ),

Video Preview

Note: An interesting fact is that when you change any value inside the Db, it gets reflected instantly.

Let’s now look at Cloud Storage.

Cloud Storage

Firebase Cloud Storage is designed to store and serve user-generated content such as images, videos, and any sort of docs for web and mobile applications. It has several other benefits as well:

Client Side and Server Side SDKs:

Firebase provides Client Side SDKs for various platforms, including JavaScript, Android, and iOS, as this helps in uploading, downloading, and managing your files from the codebase.

Additionally, it provides Server-Side SDKs to interact with the storage service from the server or backend.

Url-based Access:

Each file uploaded to the Cloud Storage server gets a unique URL. This makes it easy to present the metadata to users without any load on the application server itself.

Offline Support:

Firebase Cloud Storage even works seamlessly when the internet connection is not established. However, data gets synchronized upon arrival.

Implementation

In the Firebase Dashboard → Build section → Storage.

Choose test mode and create the appropriate bucket.

Once you’re done, move to the rules tab and modify the code as;

Previous rules:

rules_version = '2';

service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write: if request.time < timestamp.date(2023, 11, 9);
    }
  }
}

Modified rules:

rules_version = '2';

service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write;
    }
  }
}

Now, in the Flutter project, add this plugin to pubspec.yaml:

firebase_storage, image_picker_plus

In the add_student_grading.dart, we will attach a file image with our existing form fields, and then convert this image to a URL and add it to our existing object.

Create this method for getting images from pictures and storing them as well.

  Future getImage(BuildContext context) async {
    ImagePickerPlus picker = ImagePickerPlus(context);

    SelectedImagesDetails? pf = await picker.pickImage(source: 
    ImageSource.gallery);

    if (pf != null) {
      _fileStream.sink.add(File(pf.selectedFiles.first.selectedFile.path));

      /* will save each image unique w.r.t Datetime */ 
      Reference storageRef = 
FirebaseStorage.instance.ref().child('images/img_${DateTime.now().microsecondsSinceEpoch}');

      /* store the file image to storage */ 
      UploadTask uploadTask = storageRef.putFile(pf.selectedFiles.first.selectedFile);
      TaskSnapshot snapshot = await uploadTask;

      /* get the url after upload gets done */ 
      String url = await storageRef.getDownloadURL();
      SgUtils().showSnackBar('Image uploaded successfully');
      
      imageUrl = url;
      setState(() {});
    }
  }

Note: The above method uses Stream to get the image, ensuring a smooth process.

Secondly, make changes to the code that adds the student object to Realtime.

uploadMarks(User currentUser) async {
    isLoading = true;

    await _databaseReference.child(currentUser.uid).push().set({
      'id': Random().nextInt(100000).toString(),
      'name': _studentC.text,
      'phy': _phyC.text,
      'chem': _chemC.text,
      'math': _mathC.text,
      'url': imageUrl,
    }).onError((error, stackTrace) {
      setState(() {
        isLoading = false;
      });
      SgUtils().showSnackBar(error.toString());
    });

    setState(() {
      isLoading = false;

      _studentC.clear();
      _phyC.clear();
      _chemC.clear();
      _mathC.clear();
    });
    SgUtils().showSnackBar('Uploaded successfully');
  }

Preview

Firebase Real-time and Storage for Flutter Apps - A Practical Guide - Student Snapshot with picture

It’s time to wind up this blog post.

Hope you enjoyed reading it!


Wrapping Up

In concluding this, Firebase Real-time and Storage for Flutter Apps – A Practical Guide, we saw two other important Firebase features that are widely used in mid-size, as well as complex level production applications. Additionally, this blog post was filled with useful snippets, snapshots, and video previews.

However, if you find any information incomplete or not up to the mark, please jot it down in the comments below. I would love to hear from you.

Link to the Code Repository

Check out my YouTube channel for Flutter and GitHub tutorials

Thanks much for your precious time!

Flutter Directory – Firebase Series

Leave a Comment