Creating an Android App Launcher
A concrete example of Kotlin + MVVM + Data Binding + AAC
I was recently tasked to research how to enable customers running an app in kiosk mode to still be able to leverage other secondary apps on their devices. The goal was not to integrate these secondary apps with the primary app via an SDK or inter-process communication, but rather just to simply be able to launch these secondary apps from the primary app.
It is probably worthwhile to first explain what I mean by “kiosk mode”. When an app is run in kiosk mode, the user is unable to exit the app. The screen is always on. The app is always in the foreground. The notification bar is usually hidden or disabled and almost always the Home button is disabled. This effectively makes it so that the user of the app must always remain within just that one app. So with that understanding you can probably now see the need for a means to launch approved secondary apps from the “primary” app.
Launching other apps on Android from your app is not in itself terribly challenging. There are plenty of code snippets online for how this can be accomplished. However, what is not so easy to find is a concrete example of how to implement a feature such as this while also adhering to the principles that Google is recommending for new Android development such as writing the code in Kotlin, using the Model View ViewModel (MVVM) architecture pattern, using data binding, and leveraging the Android Architecture Components (AAC) such as ViewModel and Navigation whenever possible.
So without further ado below is a simplified example of a high-level design of an App Launcher feature that I came up with followed by descriptions of each of the numbered components in the diagram. Several helper classes such as a Logger utility are omitted for the sake of brevity, but please feel free to check out the complete sample project on GitHub here.
The purpose of each of the numbered components in the diagram above is described below (note: each component name listed below is also a link to the code for that component).
- (1) AppLauncherDialog
The AppLauncherDialog encapsulates the dialog that will be displayed to the user and it will present the collection of apps that can be launched. This class utilizes data binding and observes the AppLauncherViewModel so that changes to the data are immediately reflected on the UI. This dialog class is very crisp as all of the actions (e.g. launching an app) is handled by the view model. Lastly, in the sample project, this dialog is opened from a skeletal Dashboard (think Home screen) for the purpose of demonstrating navigating from a fragment to a dialog with the Navigation architecture component.
- (2) AppLauncherViewModel
The AppLauncherViewModel class contains the data that will be presented within our view (AppLauncherDialog). The data presented is obtained by having the AppLauncherViewModel observe our data model (AppLauncherModel). Prior to presentation the data is transformed and made both actionable and presentable. In our case this involves extracting the launch info for each of the app packages in the data model, deduplicating the packages, dropping any packages that are not present on the device, and finally sorting the apps alphabetically by name. The snippet below shows how extracting the app info by package name is done.
- (3) AppLauncherModel
The AppLauncherModel contains the pre-transformed or raw data that is observed by the AppLauncherViewModel and eventually presented in the AppLauncherDialog. This data is essentially just a collection of app package names that are supported by the launcher. There is also a flag which determines whether the app should be launched normally or if we should try and launch it in “free form” or “floating” mode which is only supported on devices running Android Nougat and above (and even then , unfortunately, not on all devices running Android Nougat or above).
- (4) AppLauncherFileMonitor
The App Launcher feature needs to know what apps to display to the user. This information could come from any number of places. It all depends on how you want to control the feature. Should it be controlled by you or should it be controlled by your user? If controlled by you then the data could exist, for example, in a cloud database like Firebase Cloud Store. If controlled by your user, then it could exist, for example, in a local database like SQLite (leveraging Room) or even in the app’s Shared Preferences and the user could then be allowed to add new apps to the feature at runtime. For our somewhat basic example here we have chosen to store the configuration in a JSON formatted file (an example of which is shown below) in the app’s data directory.
The AppLauncherFileMonitor service class is used to monitor the configuration file noted above for changes and when changes occur an event is raised using EventBus. The AppLauncherModel class subscribes to these events and updates the data model as needed which then cascades all the way down to the view (the AppLauncherDialog). The gist below shows the file monitoring and event raising logic that we are using for this sample.
Putting all of this together we now have the makings of a basic App Launcher feature. The image below depicts the sample App Launcher Demo running.
That is basically it. Please check out the sample project and feel free to let me know if you have any questions.
Thanks for your interest!