DEV Community

Vadym Pinchuk
Vadym Pinchuk

Posted on • Edited on

Exploring the Latest State of Flutter’s Experimental Feature for Developing Multiple-Entry Android Applications

Image description

In the rapidly evolving world of mobile application development, developers need to keep themselves updated with the latest tools and frameworks. Flutter is one such framework that has gained immense popularity among developers due to its ease of use and flexibility. In a previous article and its updated version, I shared a sample application that I created to showcase the “Experimental feature on Flutter,” which enables the creation of multi-entry Android application using the Flutter framework. However, with the continuous evolution of Flutter, the framework has undergone significant changes since the initial version. In the current stable version 3.7.9, my sample app’s code looks significantly different. In this article, we will revisit the sample app, incorporating the fresher look and updated codebase, and provide an overview of the changes in the latest version of Flutter.

Problem statement

During my work on another Flutter project, I encountered an intriguing and somewhat unusual task. The project was required to function as a POS terminal on Android OS and other mobile devices but with a simplified version of the app that served as a regular application with a single-entry point on standard iOS and Android devices, without payments and bill printing. On the other hand, the extended fully functional POS-terminal version required a custom launcher, and the application was built and installed in such a way that it had multiple entry points leading to separated chunks of functionality, with no access from one point to another (for example, from catalogs to reports).

While this is a specific case that currently only applies to Android, I found the Flutter GitHub Repository’s wiki to be helpful at the beginning of my work. However, for some reason, it did not work correctly and caused problems with my project.

Although it is an experimental feature and an edge case in development, I decided to create a test application to make any future attempts easier. In the following pages, I will explain this test application and how it can be used to simplify the process of building multi-entry Android applications with Flutter.

Test application

The test application that I created to simplify building multi-entry Android applications with Flutter consists of three pages, each with its own unique colour and title, and buttons for navigation. These pages are named Amber, Blue, and Purple, and can be found on the AppBar. The application has three separate entry points while maintaining the same package name.

  • The “1 Amber” entry point leads to the limited functionality with the Amber page only.
  • The “1 Blue” entry point leads to the limited functionality with the Blue page only.
  • The “1 Purple” entry point leads to the Purple page only.
  • The “All 3 colors” is a fully functional application with 3 screens and interaction in between

The View

Image description
Single entry app (on the left) and multiple entry app (on the right)

Steps to reproduce

Creating a simple Flutter application with multiple entries is a straightforward process that can be accomplished by following a few simple steps. To get started, you can follow the guide provided in this article. Alternatively, you can clone my sample application, which is referenced in the footer of this article, to see an example of how it can be done.

Lets dive into Android code a bit, to define flavours we build:

  • To implement multi-entry functionality in our Flutter application, we need to define two or more build flavours in the app/build.gradle file. For the purposes of this article, I have created two build flavours: “single” and “multiple”:
productFlavors {

    multiple {
        dimension "app"
        versionCode 1
        versionName "1.0.0"
    }

    single {
        dimension "app"
        versionCode 1
        versionName "1.0.0"
    }
}
Enter fullscreen mode Exit fullscreen mode
  • For each build flavour we need to create a dedicated directory within the project structure. The directory structure will look something like this:

Image description

  • The “main” directory in Android codebase will contain general resources and a simple entry point, but it will not be used for the build process.
  • On the other hand, the “multiple” directory will contain multiple implementations of the BaseActivity, which is responsible for defining the entry point name for the Flutter application:
abstract class BaseActivity : FlutterActivity() {

    abstract var entryPoint: String

    override fun getDartEntrypointFunctionName() = entryPoint

    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) =
            GeneratedPluginRegistrant.registerWith(flutterEngine)
}
Enter fullscreen mode Exit fullscreen mode
  • In the “single” directory there is only one AllColorsActivity, which is a fairly generic implementation:
class AllColorsActivity : FlutterActivity() {}
Enter fullscreen mode Exit fullscreen mode
  • The final step on the Android side of our multi-entry Flutter application is to declare the activities in the AndroidManifest.xml file, with a separate title for each entry point:
<activity
    android:name=".AllColorsActivity"
    android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
    android:exported="true"
    android:hardwareAccelerated="true"
    android:label="All 3 colors"
    android:launchMode="singleTop"
    android:theme="@style/LaunchTheme"
    android:windowSoftInputMode="adjustResize">
    <meta-data
        android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
        android:resource="@drawable/launch_background" />

    <meta-data
        android:name="io.flutter.embedding.android.NormalTheme"
        android:resource="@style/NormalTheme" />
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
Enter fullscreen mode Exit fullscreen mode
  • As you develop your application in Android Studio, it’s crucial to create distinct build configurations for situations involving single and multiple entries. Remember, the last step in this process is just as vital as the first:

Image description

Flutter part is simple and very intuitive, while some code is just created for better feature showcasing.

  • In the main.dart file, create the main entry point along with a few alternative entry points, as demonstrated in the code. To prevent TreeShaking in release mode, make sure to mark the alternative entry points with the @pragma(‘vm:entry-point’) annotation.
void main() => runApp(
      MultiEntryApp(
        initialRoute: defaultRoute,
        primaryColor: Colors.blueGrey,
      ),
    );

@pragma('vm:entry-point')
void amber() => runApp(
  MultiEntryApp(
    initialRoute: amberRoute,
    primaryColor: Colors.pink,
  ),
);
Enter fullscreen mode Exit fullscreen mode
  • In our multi-entry Flutter application, each entry point starts with its own route name, making route generation much simpler. The key to this functionality lies in the routes generation, which limits accessibility to some screens for each specific entry point. By defining routes in this way, we can ensure that users are only able to access the screens that are relevant to their entry point, providing a clear and intuitive user experience:
const String defaultRoute = "/";
const String amberRoute = "/amber";
const String blueRoute = "/blue";
const String purpleRoute = "/purple";

Enter fullscreen mode Exit fullscreen mode
  • The code for route generation may not be the most elegant or sophisticated, but it is simple and easy to understand:
Route<dynamic> generateRoute(RouteSettings settings, String initial) {
  switch (initial) {
    case blueRoute:
      return CupertinoPageRoute(builder: (context) => BluePage(false));
    case purpleRoute:
      return CupertinoPageRoute(builder: (context) => PurplePage(false));
    case amberRoute:
      return CupertinoPageRoute(builder: (context) => AmberPage(false));
    default:
      return _allRoutes(settings);
  }
}
Enter fullscreen mode Exit fullscreen mode

And with that, we have covered all of the key components of building a multi-entry Flutter application.

Important notes

  • To prevent entry points in our Flutter application from being removed during the tree-shaking process, it is important to annotate them with the @pragma(‘vm:entry-point’) annotation. This tells the compiler to keep the entry points in the final executable, ensuring that they are available for use by the application.
  • To ensure that our multi-entry Flutter application routes correctly and avoid any confusion, it is recommended to use initialRoute: “/”’ in the MaterialApp.
  • In earlier versions of Flutter, using Experimental feature could potentially cause issues with the SplashScreen. However, starting from Flutter v1.12 and future versions, this is no longer a problem, as the Splash screen is now defined in the activity meta-data by using ‘io.flutter.app.android.SplashScreenUntilFirstFrame’.

Summary

In summary, while the techniques and approaches discussed in this article may not work for every situation or project, they provide valuable insights into the process of building a multi-entry Flutter application. It is important for developers to remain flexible and adapt to the needs of the project and the users. By sharing our experiences and insights, we can encourage others to explore the possibilities of Flutter and push the boundaries of mobile application development.

GitHub - VadymPinchuk/multiple_entry

Top comments (0)