Implementing Flutter Base - Part 6: Extensions

Firebase

I. Tasks

Through the previous sections, we've built quite a complete base for a Flutter application. However, when working with mobile applications, we often encounter additional tasks such as handling notifications, analytics, security, and more. In this section, I will implement some important tasks, including:

II. Detailed processing

1. Configuring Firebase for different environments

When connecting to the backend and integrating Firebase into the application, it's crucial to differentiate between different environments, such as production (prod) and development (dev). Here, I'll implement this for two types of builds:

To automate this process, it needs to be implemented on both Android and iOS with native code.

For Android:

To switch between Firebase configurations for prod/dev environments, the approach is to copy the corresponding google_service.json file into the android/app/src directory just before building the project. Here's how you can do it:


For iOS:
Following the Android approach, we create a Firebase directory containing GoogleService-Info.plist files for dev and prod, and then copy the appropriate file for the environment.

In Build Phases, I create a Firebase setup run script and move it to run first.

The detailed content:

if [ "${CONFIGURATION}" == "Release" ]; then
cp -r "${PROJECT_DIR}/Runner/Firebase/GoogleService-Info-PROD1.plist" "${PROJECT_DIR}/Runner/GoogleService-Info.plist"
echo "Production GoogleService-Info.plist copied"
elif [ "${CONFIGURATION}" == "Debug" ]; then
cp -r "${PROJECT_DIR}/Runner/Firebase/GoogleService-Info-IDA.plist" "${PROJECT_DIR}/Runner/GoogleService-Info.plist"
echo "IDA GoogleService-Info.plist copied”
fi

2. Managing the global app state and implementing Dependency Injection (DI).

Dependency Injection (DI) is a software development and architectural principle that helps manage dependencies between components in an application easily. In the base structure, we use it to handle dependencies related to:

We use DI to configure these dependencies and access them when needed for use.

What is the global app state? You've probably encountered situations like this: User personal information after logging in, such as name, date of birth, avatar, etc. This information is displayed in many places in the application, on the home page showing the avatar and name, on the profile page displaying all the details, etc. And we have a button to update this personal information. Of course, all the places displaying this data need to be updated, right? For data like this, we often refer to it as the global state. To store user information data, you can have an AuthCubit with a corresponding AuthState. You can use DI to create a singleton for it, and user interface components that need to use this user information can add BlocProvider with the value being the singleton AuthCubit.

3. Some security issues

The issue of security is quite extensive, but when developing an app, many people store information insecurely, such as storing tokens and refresh tokens in SharedPreferences. We can do better by storing sensitive information in encrypted storage (keystore/keychain).

4. Integrating Sentry for easy error detection.

When you deploy your app to production and make it accessible to thousands of users, there can be numerous potential bugs that you might have overlooked during development. Integrating Sentry to capture these errors is very convenient. It's also essential to distinguish between Sentry environments for production and development.

5. Following code linting rules for cleaner code.

In a project with multiple contributors, adding many lint rules to ensure everyone follows a consistent style is a good practice

III. Summary

Through the four main sections, we've built a relatively comprehensive base. I'm delighted to have you accompany me this far. I'm also new to working with Flutter, so there may still be some shortcomings. I hope to receive feedback from everyone

Thank you!