Setting Flutter on Fire with Firebase
I started working with Flutter over a year ago, but have yet to work with it in combination with a back-end as a service. Firebase is Google’s back-end as a service so it seems to make sense to see how these technologies work together, with a focus on the database, messaging, and associated REST APIs, and supporting libraries/packages. Can Firebase set my Flutter app world on fire? I’m going to find out…
Basic Flutter and Firebase Setup and Configuration
I’ll be using my typical setup, which you can read about here. I’ll focus on Android Studio as my IDE. Dart, Flutter (v2.0.5), and Android Studio are up to date as of writing (Oct 2021). My MBP is on MacOS 11.6 (Big Sur). Setting up Firebase itself is pretty simple and the documentation from Google is pretty comprehensive. Apologies for my typing, my MBP keyboard is going belly up, lots of extra spaces and characters getting into my text, however, I’ll try to get that all cleaned up prior to publishing.
At the time of writing I am using Flutter 2.6.0-12.0.pre.205 on macOS 11.6.20G165. Dart 2.15.0-177.0.dev goes along with that. You can read in my previous posts if you need help setting up Flutter – but Flutter’s main site would be my recommendation.
Working with Flutter and Firebase
My Use Case
I’m looking to build a private chat app for my family. We’re using Slack right now, but we have just simple needs, don’t want to pay anything, and really don’t want to worry about people looking at our chats. So that’s the use case I’m going to explore.
Firebase Setup and Configuration
For the most part the Google documentation is excellent and it is straight forward to get things configured rather quickly. However, I did find a few tricks and quirks in the configuration process. I setup myself with the “Spark Plan”, that is free, but it does require a $300 credit card authorization. For development, unless you do something silly, you will not get charged as most of the charge thresholds are quite generous. I would definitely recommend checking the Firebase pricing here.
For development purposes, I created one Firebase project and I keep reusing it for various things. I added an Android and iOS app in the project so that I can test all the Flutter and Firebase features as I go along developing.
The next thing to do is to add an Authentication type, for the basics I just enabled email/password. You can always add more later on.
Then I enabled Firestore and the Realtime Database. Once you do that I would definitely recommend adding some basic rules so that only authenticated users can read/write/create/delete data. Super important since if a malicious person stumbled upon your project, that is one way to run up costs quickly. Firesbase will prompt you about this right away with either service. If you enable storage, that will include a default rule that has this already taken care of, but the databases do not.
Protecting Firebase Keys
One thing to be careful about is your keys. I do not like putting keys into source code because it is really easy for that to make its way into github.com or bitbucket.org. I have two things I have been doing. The first is adding a gitignore for files like google-services.json and info.plist files. This keeps them from getting added to your repo fairly well. Another trick is to use a Flutter package like dotenv to put keys into a .env file that is gitignore’d. You then use a future in your main() in main.dart to load this up and the keys will then be programmatically available to you with a call like:
final messageCollectionId = dotenv.get('FIRESTORE_COLLECTION_ID');
That is super handy and IMHO the best way to protect sensitive keys.
Cloud Messaging
Cloud Messaging is initially fairly simple to setup in the Firebase Cloud Messaging console. What’s really cool is that if you go to your app project, scroll down the menu to Engage, locate Messaging, you can pull up tools to define your notifications and even test them. However if you have an app already installed in your emulator or device, don’t forget that to actually see the notifications you will need to go to the emulator’s settings and enable Play Services. Then you will need to go to the app’s settings on the device and enable notifications otherwise they will not show up on the emulator.
Firebase Cloud Messaging quickly becomes the most complicated service to setup and use. With Android things are simple, but there are a lot of configuration steps for iOS.
In addition, there are a lot of variations to deal with in terms of how to handle foreground, background, and app terminated, scenarios when messages arrive. I found the FlutterFire Messaging Example especially useful. In addition, you’ll need Flutter Local Notifications if you want to handle messages while the app is opened and visible.
With that package I was able to get notifications working with the app open, in the background, and terminated.
Cloud Messaging Initializers
Background and terminated messages were simple to handle. However, the tricky part was getting the icon for messages while the app was open to work. You can look at my entire app here, but the key part for the icon to display correctly is that you need to initialize some parameters on the FlutterLocationNotificationsPlugin object. To do that you can use the initializer for each platform, here’s mine for android:
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('@mipmap/ic_launcher');
The icon has to be in your android/app/src/main/res folder bundle. Then you can pass all the initializers you want (I only did android, you can them for for all the platforms Flutter supports) to this method:
final InitializationSettings initializationSettings = InitializationSettings(
android: initializationSettingsAndroid,
);
Then call the plugin’s initialize method passing it your initializationSettings object:
await _flutterLocalNotificationsPlugin.initialize(initializationSettings,
onSelectNotification: (String? payload) async {
if (payload != null) {
debugPrint('notification payload: $payload');
}
selectedNotificationPayload = payload;
selectNotificationSubject.add(payload);
});
You can check my code or go and dig through the Flutter Cloud Messaging and Flutter Local Notifications documentation and example code. The latter is probably a better approach since this is really my first time working with these packages. You can see my icon, the default Flutter icon, in the screen grab on the right.
Firebase Data Storage
Realtime Database
This is the “original” database that Firebase offered. Its great for storing simple JSON datasets, with smaller volumes of data, and works quite nicely with the the REST API. Get your “maps” on because working with data requires good understanding of maps and I suppose Flutter builder objects too.
Basically if your needs are simple and you don’t want to use the SDK packages, then this is the best solution.
Cloud Firestore Database
For this app I did use the Firestore Database. I wanted to store chat messages, but also additional information about users. So I had a few collections that I wanted and I was also expecting more significant traffic/data volumes. Once you understand the UI of administrative interface, it is fairly easy to setup and use documents and the collections that are attached to them.
The SDK allows you to query for data and returns things in Flutter’s StreamBuilder object, which maintains a connection and continually updates the data as it changes. So for an app like this, it is amazing functionality, with a small amount of effort. As messages are posted to the database, they automatically appear in the stream and are rendered in real time. I do this by assigning the StreamBuilder’s snapashot of the data to a ListView.builder. As new messages appear the StreamBuilder updates the snapshot and the ListView automatically updates. Here’s the code required to do this:
return StreamBuilder(
stream: FirebaseFirestore.instance
.collection('caints/$messageCollectionId/messages')
.orderBy(
'createdAt',
descending: true,
)
.snapshots(),
builder: (ctx, AsyncSnapshot<QuerySnapshot> caintsSnapshot) {
if (caintsSnapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
final caints = caintsSnapshot.data!.docs;
return ListView.builder(
reverse: true,
itemCount: caints.length,
itemBuilder: (ctx, index) => MessageBubble(
caints[index]['text'],
caints[index]['userId'] == FirebaseAuth.instance.currentUser?.uid,
caints[index]['username'],
caints[index]['userimage'],
key: ValueKey(caints[index].id),
),
);
},
);
There’s obviously more code behind the scenes. I’ve got a widget called MessageBubble and stuff, but with this little amount of code, the level of functionality between Firebase and Flutter is pretty amazing.
Useful Firebase Libraries and Supporting Packages
- Flutter REST API documentation
- FlutterFire – extended library for Firebase
- Firebase REST API
- Extended Flutter IO-Client
- Official Dart Firebase Package
- flutter_dotenv
- Flutter Local Notifications
Conclusions on Firebase and Flutter
Its is an amazingly powerful combination. I built a functionally complete app (definitely not production grade yet!) with a robust back-end authentication service, a database, a binary storage capability, which I did not go into in this post. I have an app that is actually pretty nice as a private messaging and chat app. I’ve been using these kinds of “BaaS” platforms for a long time, all the back to Kinvey.com. I have to say that Firebase worked flawlessly as did the Flutter SDKs and integrations with it.
Flutter itself is improving dramatically. I find programming in Flutter to be quite enjoyable, altough Null Safety takes some getting used to. All the code I have written/leveraged is compliant with Flutter’s Null Safety standards, which is not true of most sample code out there.
You definitely need to read the Firebase docs as well as the FutterFire and Flutter documents/code and study the code in github.com to make your work efficient. There is no denying Firebase+Flutter is something to really consider as a viable combination for apps.
I do admit there are still annoying things in Flutter that need to get stabilized/cleaned up. Here’s a good example:
https://stackoverflow.com/questions/50423645/flutter-unable-to-catch-platformexception-while-attempting-firebase-auth
The Firebase PlatformException does not work right in debug mode, which is the only way to effectively run something in an emulator. Annoying. I spent quite a bit of time trying to figure out why authentication errors were not getting caught there, but falling down into a more general exception handler. PITA, but at least SO has tons of activity with Flutter so an answer is usually out there. Lesson –> 10 mins looking to fix something and then go to SO.