Multiple-Entry Android Application with Flutter, or Alternative Ways to Restrict Access to Application

Vadym Pinchuk
4 min readNov 25, 2019

One interesting and rather uncommon task happened to me when I worked on a Flutter project. Basically, you need a regular application with a single-entry point on ordinary iOS and Android devices. But on specific devices we need to split the application into many entries leading to defined points. Such entry points, however, cannot be defined on a logical level. These should be actual separate entry points.

So, this is a specific case which is currently applicable to Android only. I found this tutorial truly useful at the beginning of my work, but it didn’t work out for some reason, causing issues to my project.

To make your attempts easier, I`ve created a test application which I am going to explain in the next few pages.

Test application

My test application consists of three pages, each of their own color and title, as well as of some buttons to navigate. Those pages are Amber, Blue, and Purple, which you can find on AppBar.

The application has three separate entry points while still being the same app with the same package name.

  • ‘3 colors’ entry point leads to full functionality with navigation between three pages.
  • ‘2 colors’ entry point cuts off the first page and works only on Blue and Purple pages.
  • ‘1 color’ is an entry to Purple only.

How it will look like?

Practically speaking, one application with three different points of entry

Three entry points sample

Steps to get this

  • Create a simple Flutter application and follow the next steps to make it on your own. Or clone my sample application in the footer of this article.
  • Create a few entry points in your main.dart file, as shown in the code below:
=void main() => runApp(
MultiEntryApp(
initialRoute: amberRoute,
primaryColor: Colors.blueGrey,
),
);

@pragma('vm:entry-point')
void blue() => runApp(
MultiEntryApp(
initialRoute: blueRoute,
primaryColor: Colors.teal,
),
);

@pragma('vm:entry-point')
void purple() => runApp(
MultiEntryApp(
initialRoute: purpleRoute,
primaryColor: Colors.red,
),
);
  • Each entry point should be accessed by a separate Activity. For this reason, I have created BaseActivity.kt to contain main logic:
abstract class BaseActivity : FlutterActivity() {

abstract var entryPoint: String

override fun createFlutterView(context: Context?): FlutterView {
val flutterView = FlutterView(this)
flutterView.layoutParams = WindowManager.LayoutParams(-1, -1)
flutterView.dartExecutor.executeDartEntrypoint(DartExecutor.DartEntrypoint(FlutterMain.findAppBundlePath(), entryPoint))
val frameLayout = findViewById<ViewGroup>(android.R.id.content)
frameLayout.addView(flutterView)
return flutterView
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegistrant.registerWith(this)
}
}

This class is responsible for the creation of FlutterView, pointing it to abstract entryPoint with and adding it to root Activity layout.

Any ancestor Activity class contains only an entry point name. It is simple:

class BlueActivity : BaseActivity() {

override var entryPoint = "blue"

}

To serve as an access point, each activity must declare an intent filter with a LAUNCHER category and MAIN action, as demonstrated in the code snippet below, which should be included in the AndroidManifest.xml file.

<activity
android:name=".BlueActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:label="2 colors"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:value="true" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

Other steps are quite usual.

  • Create an application class like this one:
class MultiEntryApp extends StatefulWidget {
final String initialRoute;
final Color primaryColor;

MultiEntryApp({
Key key,
@required this.initialRoute,
@required this.primaryColor,
}) : super(key: key);

@override
_MultiEntryAppState createState() => _MultiEntryAppState();
}

class _MultiEntryAppState extends State<MultiEntryApp> {
@override
Widget build(BuildContext context) => MaterialApp(
theme: ThemeData(
primarySwatch: widget.primaryColor,
),
initialRoute: '/',
onGenerateRoute: (RouteSettings settings) => generateRoute(settings, widget.initialRoute),
);
}

This application accepts the initialRoute and primaryColor parameters, so each entry point has a restricted number of routes it can navigate between and is customized with a primarySwatch color for fun.

onGenerateRoute should accept only the routes that are required for a relevant entry point, but you can also define your logic there. To define routes, I am using generateRoute method which selects proper routes by received from entry point initialRoute:

Route<dynamic> generateRoute(RouteSettings settings, String initial) {
switch (initial) {
case amberRoute:
return _amberRoutes(settings);
case blueRoute:
return _blueRoutes(settings);
case purpleRoute:
default:
return _purpleRoutes(settings);
}
}

So, it just redirects to a proper route selector. As in MaterialApp, we have initialRoute: ‘/’, so by default we should show a route defined in constructor:

@pragma('vm:entry-point')
void blue() => runApp(
MultiEntryApp(
initialRoute: blueRoute,
primaryColor: Colors.teal,
),
);

In this case, blueRoute will open by default. And the theme color will be teal.

  • Create pages for your application and other logic you need.
  • Compile, run and enjoy )

Important notes

  • To preserve entry points from ‘tree-shaking’, annotate them with @pragma(‘vm:entry-point’)
  • To avoid confusion in routing, use initialRoute: ‘/’, in your MaterialApp.
  • This logic will break your SplashScreen. This issue needs to be studied further and, maybe, elaborated in the next article. Should you have time for the study, do not hesitate to advise solutions.
  • I am not pretending to be a single source of the truth, but you are encouraged to share my experience ;) Hope you liked this article and it was helpful.

Used links

Special thanks to

for close collaboration while investigating this experimental Flutter feature.

--

--

Vadym Pinchuk

Android and Flutter developer, public writer, with rich expertise in mobile development