Pratical Guide: Flutter + Firebase + Flutterfire CLI + CI (Codemagic)

Pratical Guide: Flutter + Firebase + Flutterfire CLI + CI (Codemagic)

This article was initially posted on codemagic blog

This article will highlight three technologies that give you a super-app when combined: Flutter, Firebase, and Codemagic. A demo starter's code will be provided, our task would be to configure firebase for all platforms supported by the flutter framework, utilize firebase remote-config to alter the appearance of our app without making these changes manually, then proceed to set up CI/CD using codemagic to distribute our app via firebase app distribution. Below is a live demo of the final result.

Demo application gif

Before we delve into setting up firebase with flutterfire CLI, let's discuss these technologies we'll be using.

What is Flutter Framework

By attempting to read this article, I believe you already have some kind of idea of what Flutter is. Flutter is a UI toolkit developed by Google. It is used to develop cross-platform applications for Android, IOS, Linux, macOS, and Windows with a single codebase.

What is Firebase

Firebase is a Backend-as-a-Service (Baas), which has several tools and services to help ease up the process of building products, tracking the growth of the product, and scaling it up. Some of the tools offered by firebase are:

You can skip this part if you already know the tools provided by Firebase

  • Real-time database: It is a no-SQL database provided by firebase to store and sync data between your users in real-time. It is also optimized for offline use.

  • Cloud Firestore: Is an upgrade of the real-time database, It provides a new and more intuitive data model, introducing the concept of collections and document. It also features richer, faster queries and scales further than the real-time database, both in performance and pricing.

  • Authentication: Firebase also provides an easier way to perform authentication with different services or platforms, including but not limited to; email-password, phone number, Google, Facebook, Twitter, Github authentication.

  • Cloud Storage: A storage service provided by Firebase. It is cost-effective, powerful, and easy to adopt.

  • Remote Config: This is a cloud service that offers you flexibility in changing your app’s behavior or appearance without requiring users to download an update.

  • Dynamic Links: This service allows you to create a URL that when clicked on can send users to different parts of the app depending on how it is configured.

  • App Distribution: This helps you get your app build to testers quickly without any hassle.

    For more services provided by Firebase visit the Firebase docs

What is Codemagic

Codemagic is a Continuous Integration/Delivery tool that can be set up easily and works very well with flutter. It provides the service of building your app, running tests, or any prerequisite tasks.

It doesn't stop there, depending on your configuration, allows you to automatically publish the app to various services like Google Play Store, App Store, Firebase App Distribution, all these are done while you relax sipping your coffee without the anguish of waiting for a long build time to manually publish your app.

What is FlutterFire CLI

Have you engaged in setting up firebase manually on flutter before this? You've probably noticed there is a lot of hassle involved, you have to follow different setup configurations to enable firebase on the different platforms supported by flutter, including frustrating errors that can come up due to doing something wrong during setup.

The Flutterfire CLI (Command Line Interface) is a useful tool that provides commands to help ease the installation process of firebase across all flutter supported platforms.

A side story about my experience with the command line: When I started, I used to be scared of command-line codes and preferred using Graphical User Interface (GUI) for configurations, I had the opinion that command-line codes were for geeks. Well, over time I realized just how easy, and convenient using command line code is, and NO you don't have to cram all the codes, you can always google or have your command-line codes cheat sheet. If you fall into this category, be rest assured, I got you.

Why use FlutterFire CLI

Using the FlutterFire CLI is faster and more efficient. It saves you time and energy trying to figure out a persistent error during manual firebase configuration.

What to Expect from this Article

This article will be broken into sections five sections.

  • Section one: The first section, which we've just concluded, enlightened us on the different technologies we'd be using. >
  • Section two: The second section walks us through the installation of Flutterfire CLI. >
  • Section three: This section will walk us through configuring firebase for all platforms supported by flutter using flutterfire CLI. >
  • Section four: We'll start implementation of firebase remote-config to alter the looks of the app remotely. >
  • Section five: Setting up codemagic to build our application and upload it to firebase app distribution is what we will tackle in section five.

Set up

To follow along, download the starter's code by running the command below. It's a very simple application that has a single screen, that shows a list of rentals.

git clone -b starters-code git@github.com:jasperessien2/rental_app.git

You can see the folder structure below,

lib/
|- domain
|  |_ repository.dart
|
|- data
|  |- model
|  |  |_ property.dart
|  |_ repository_impl.dart
|
|- presentation
|  |- widgets
|  |  |_ item_property.dart
|  |
|  |_ data_controller.dart
|  |_ home_screen.dart
|  |_ repository_provider.dart
|
|_ main.dart

The domain layer houses the repository contract, the data layer contains our model class and the implementation of the repository. The presentation layer holds widget classes, data_controller and repository_provider that is responsible for injecting the repository down the widget tree. The next section will dwell on setting up flutterfire CLI.

Installing Flutterfire CLI

Run the command line code below to install the Firebase CLI tool on your computer. The reason for this installation is that Flutterfire CLI depends on Firebase CLI.

npm install -g firebase-tools

To run this command you need to have Node.js installed on your computer. If you don't have it installed, visit https://nodejs.org/en/download/ and download a Node.js installer for your OS.

Install the Flutterfire CLI tool by running the command below.

dart pub global activate flutterfire_cli

If your terminal does not recognize this command, make sure that the dart SDK is added to PATH

You should see these messages below to signify that the command above was successful.

Building package executables... (3.6s)
Built flutterfire_cli:flutterfire.
Installed executable flutterfire.
Activated flutterfire_cli 0.1.3.

Configuring Firebase

As a prerequisite for this section, create a Firebase account here, if you don't have one. Also, make sure that the firebase_core dependency is added to the project by running flutter pub add firebase_core.

Next, run the command below to begin flutterfire configuration.

flutterfire configure

Make sure this command is run at the root of the flutter project.

After running the above command, you'll see a list of existing firebase projects and a create a new project option. In our case, we will create a new project since we haven't created one specific to our app. Use your arrow key to control selection.

Type in the name to use for the firebase project, if you run into an error that says the name has been taken already, try with a different project name.

The next step is to select platforms flutterfire should configure for (use arrow keys for control and space key to select or deselect). After a successful configuration, a firebase_options.dart file is generated along with some .json files. The success should look a lot more like the image below.

Image of a successful flutterfire configuration

flutterfire_configure_success.png You can also check your firebase console to confirm that a project was created and configurations were done for all platforms you specified.

Image of a successful firebase console project creation

If you've ever configured firebase for different platforms, you'll appreciate the introduction of flutterfire CLI, as it makes this process very seamless and easy. It's time to make use of firebase services, we'll see that in the next section.

Implementing Firebase remote config

The goal of this section is to use remote config to change the look of our app remotely, specifically the colors. Run flutter pub add firebase_remote_config to add the firebase plugin for remote config.

Normally, before using firebase in a flutter app, you'll need to initialize firebase by calling Firebase.initialiseApp(). So let's do that, but this time we will pass in our generated firebase option as the option argument.

Go to the main.dart file and update the main() method with the code below.


 void main() async {
   WidgetsFlutterBinding.ensureInitialized();

     /// Here we initialise firebase and pass in our generated
     /// firebase option [DefaultFirebaseOptions.currentPlatform]
    await Firebase.initializeApp(
        options: DefaultFirebaseOptions.currentPlatform,
    );

    runApp(const MyApp());
}

The reason for passing DefaultFirebaseOptions.currentPlatform is so flutter uses configurations automatically for the current platform running.

Do not forget to import the generated firebase_options.dart file by adding the code below, at the top-most level of the main.dart file.

import 'firebase_options.dart';

Remote config provides options to save and restore simple values of type String, Int, Boolean, and Double.

In our case though, we want the flexibility of changing certain colors of our app, so we need a way to convert a Color object to a String and vice-versa. The code snippet below does just that. Create a file named color_ext.dart and include the code below.

import 'package:flutter/material.dart';

extension ColorHex on Color {
  /// String is in the format "aabbcc" or "ffaabbcc" with an optional leading "#".
  static Color fromHex(String hexString) {
    final buffer = StringBuffer();
    if (hexString.length == 6 || hexString.length == 7) buffer.write('ff');
    buffer.write(hexString.replaceFirst('#', ''));
    return Color(int.parse(buffer.toString(), radix: 16));
  }

  /// Prefixes a hash sign if [leadingHashSign] is set to `true` (default is `true`).
  String toHex({bool leadingHashSign = true}) => '${leadingHashSign ? '#' : ''}'
      '${alpha.toRadixString(16).padLeft(2, '0')}'
      '${red.toRadixString(16).padLeft(2, '0')}'
      '${green.toRadixString(16).padLeft(2, '0')}'
      '${blue.toRadixString(16).padLeft(2, '0')}';
}

This code was gotten from an answer on stackoverflow

In main.dart file under the main() method, add a method named _setUpRemoteConfig() that will be responsible for initialising FirebaseRemoteConfig.


Future<FirebaseRemoteConfig> _setUpRemoteConfig() async {
    /// Gets an instance of [FirebaseRemoteConfig]
    final remoteConfig = FirebaseRemoteConfig.instance;

    /// This gives the config some settings
    await remoteConfig.setConfigSettings(
        RemoteConfigSettings(
            /// By giving a timeout of 10 seconds, we tell firebase to try fetching
            /// configurations and wait for 10 seconds max.
            fetchTimeout: const Duration(seconds: 10),

            /// Since remote config caches configuration, setting this param
            /// let firebase know when to consider cached configuration data as obsolete
             minimumFetchInterval: Duration.zero,
        ),
    );

    /// For our configurations, we are giving it default values to fall to
    /// in cases where fetching config fails or isn't found
    await remoteConfig.setDefaults(
        {
        'scaffold_color': Colors.white.toHex(),
        'app_bar_title_color': const Color(0xff333333).toHex(),
        'text_field_color': Colors.grey[100]!.toHex(),
        'shadow_color': Colors.grey[300]!.toHex(),
        },
    );

    return remoteConfig;
}

Call the method above in our main() method.

 await _setUpRemoteConfig();

Considering that the state of our MyApp widget would change, migrate MyApp from a StatelessWidget to a StatefulWidget.

You can find this widget in the lib/main.dart file.

Initialize a global variable of type FirebaseRemoteConfig.

final remoteConfig = FirebaseRemoteConfig.instance;

Override initState() and listen to config changes.

 @override
  void initState() {
    /// Listen to changes and rebuild app, when there's change in config
    remoteConfig.addListener(() {
      setState(() {});
    });

    super.initState();
  }

Replace the build() with the code snippet below.

 @override
  Widget build(BuildContext context) {

    /// Get's the color values saved in remote config
    final scaffoldColor = remoteConfig.getString('scaffold_color');
    final titleColor = remoteConfig.getString('app_bar_title_color');
    final textFieldColor = remoteConfig.getString('text_field_color');

    return RepositoryProvider(
      repository: DummyRepositoryImpl(),
      child: MaterialApp(
        title: 'Rental App',
        theme: ThemeData(
            /// Convert them to Color object and use them
          scaffoldBackgroundColor: ColorHex.fromHex(scaffoldColor),
          backgroundColor: ColorHex.fromHex(scaffoldColor),
          inputDecorationTheme: InputDecorationTheme(
            fillColor: ColorHex.fromHex(textFieldColor),
            filled: true,
            border: OutlineInputBorder(
              borderSide: BorderSide.none,
              borderRadius: BorderRadius.circular(16),
            ),
          ),
          appBarTheme: AppBarTheme(
            backgroundColor: ColorHex.fromHex(scaffoldColor),
            elevation: 0,
            centerTitle: false,
            toolbarTextStyle: _titleTextStyle.copyWith(
              color: ColorHex.fromHex(titleColor),
            ),
            titleTextStyle: _titleTextStyle.copyWith(
              color: ColorHex.fromHex(titleColor),
            ),
          ),
        ),
        home: const MyHomePage(),
      ),
    );
  }

In the code above, notice that rather than hardcoding our color values, we fetch them from FirebaseRemoteConfig and then use them in our ThemeData.

The Floating Action Button in the HomeScreen widget will be responsible for triggering the fetching of configurations from the remote server. In the onPressed callback param, pass in the code below.

FirebaseRemoteConfig.instance.fetchAndActivate()

You can find this file in lib/presentation/home_screen.dart

Go to lib/presentation/widgets/item_property.dart file and update the ItemProperty widget to use the shadow color from remote config.

final remoteConfig = FirebaseRemoteConfig.instance;
final shadowColor =
    ColorHex.fromHex(remoteConfig.getString('shadow_color'));

We are done with our demo app, it's time for us to share it with the rest of the team for testing. We will do that by uploading our app to the Firebase App Distribution service.

But wait, it’s time for a coffee break, the thought of waiting for the app to be built and then uploading to firebase manually is infuriating. Oh well, codemagic comes to the rescue, in the next section we will be easily setting up codemagic to handle building and uploading our application.

Firebase set up on Codemagic

Head over to https://codemagic.io/ to log in or create an account if you don't have one already.

On the codemagic dashboard, click on the Add application button. Then select the git provider for your project.

Connect your project to codemagic

Click on Next: Select repository button, then select the repository which is rental_app, and the project type which is Flutter App.

Select repository and project type

Completing the above process will lead you to the screen below. Select Android and IOS in the Build for platforms section.

Codemagic app home

Build Triggers set up

This section deals with setting up git actions that will trigger codemagic to start building your application. You can trigger to start a build when code is pushed or has a pull request update or tag creation. You can attach these triggers to a specific branch target.

In this case, we want a build to occur when a push or pull request happens on the master branch.

Codemagic build triggers set up

Build section set up

In this section, select APK as the Android build format. Select release as the build mode.

Codemagic build section

Firebase App Distribution set up

Codemagic configure firebase app distribution

Next, head over to the Distribution section, under Firebase App Distribution:

  • Make sure Firebase token is selected.
  • Generate a firebase token by running firebase login: ci on your terminal. You'd be directed to your browser to login to the firebase console, after a successful login, go back to your terminal, copy the token and paste in the field labeled Firebase token.
  • Head over to the generated firebase_options.dart file, which is in the lib/ folder by default.
    • Copy the appId for android configurations and input it in the field labeled Android Firebase App ID on codemagic setup.
    • Copy the appId for ios configurations and input it in the field labeled IOS Firebase App ID on codemagic setup.
  • Add a tester group name for both android and ios. Then head on to Firebase Console to add emails of testers. These testers will receive an invite to download the app for testing.

    Firebase console where you add app distribution testers

    App Distribution -> Add Group -> Add Testers

  • Select APK as the Android artifact type.
  • Click on the Save Button to save changes.

After following the setup above and saving, click on the Start new build button. It will take few minutes and boom, your testers get an invite link sent to their email.

Conclusion

We covered a lot in this article. An understanding of what flutterfire CLI is, why you should use it, and a simple demonstration of how to set it up. We also saw how to utilize remote config which is one of the services offered by firebase.

Finally, we delved into setting up codemagic, which has proven to be a seamless, cost-effective, and efficient continuous integration/delivery tool. We've seen it is very easy to set up and works tremendously well with flutter. We were able to set codemagic up to perform a successful build, upload the application build to firebase app distribution, and invite testers to test the app on our behalf.