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
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
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.
The test application that I created to simplify building multi-entry Android applications with Flutter consists of three pages, each with its own unique color, 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
This structure allows for a clear separation of functionality, with each entry point leading to a different set of features.
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 the Android code to define the flavors we build:
To implement multi-entry functionality in our Flutter application, we need to define two or more build flavors in the app/build.gradle file. For the purposes of this article, I have created two build flavors: “single” and “multiple”:
productFlavors {
multiple {
dimension "app"
versionCode 1
versionName "1.0.0"
}
single {
dimension "app"
versionCode 1
versionName "1.0.0"
}
}
For each build flavour, we need to create a dedicated directory within the project structure. The directory structure will look something like this:
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)
}
In the “single” directory there is only one AllColorsActivity, which is a fairly generic implementation:
class AllColorsActivity : FlutterActivity() {}
The final step on the Android side of our multi-entry Flutter application is to declare the activities in the AndroidManifest 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>
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:
The 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,
),
);
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";
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);
}
}
And with that, we have covered all of the key components of building a multi-entry Flutter application.
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.
Also published here