In this blog post, we offer a foundational understanding of micro-features. The focus is on providing straightforward examples to facilitate the grasp of the concept. Our intention is not to present an ideal micro-feature architecture implementation but to offer practical insights for easier understanding.
This article introduces the Micro-Feature architecture, developed for the purpose of constructing scalable mobile applications. The process of its development commenced in the early part of Q1 2023 within the Logistics Deliveries Tribe and is still ongoing. Currently, it plays a central role in the development of the map-centric Android Rider Application. Notably, this architectural approach is not limited to any specific platform but can be seamlessly applied across a diverse array of platforms.
Mobile devices have limited screen size for showing content when compared to desktop apps. The most common approach to overcome this constraint is to introduce several tabs (or some other means of navigation components) so that users can switch between different tabs to see different content.
The other approach is to have a single context-aware screen that shows the most relevant content to the user but moves the secondary content to other places that can be reachable with a single click from that screen. The solutions for this approach are more complex because of the dynamic nature of the screen.
Nowadays, it is common for mobile apps to have several features and these features are being developed by different teams. With the first approach mentioned above, it could be straightforward to split teams and every team owns a dedicated tab and teams could continue developing features independently. However, with the second approach, it could be a bit tricky to enable teams to develop their features independently. This is because they will be developing for the same screen.
Micro-feature architecture is designed to provide a solution for the second approach. This architecture has two main objectives:
- To provide a foundation for showing highly dynamic content on a single screen in a context-aware manner
- To enable feature teams to work independently while highly collaborating on a single-screen
What is a Micro-Feature?
Micro-features are the logical building blocks of a screen. They are:
- Self-contained: Populates its state itself depending on data sources
- Host-agnostic: Designed to work on any host (if host is providing bare minimums)
- Easy to add or remove: The footprint for integration to host is minimal
- Meaningful as a unit: From a product perspective it should be a meaningful unit and should have only a single responsibility
Micro-features need hosts to start functioning. Hosts are designated places on the screen where you can inject your micro-feature components. Hosts can have their own layout rules for arranging the components. Here is an example of home screen hosts for the Rider application:
Implementation
Implementing a Micro-Feature
A micro-feature should be implemented using Unidirectional Data Flow (UDF). It mainly consists of a view, a UI model and a repository. A UI model is responsible for managing the UI state. Mainly it receives user events from the view and reacts to it by publishing a new UI state.
A micro-feature also collects its own data from relevant data sources. It is highly recommended that it employs observable repositories while doing this collection. This will help to create a reactive chain up to view. An example of data flow can be viewed from the diagram above.
A host is not interested in the implementation details of a micro-feature. It is only interested in how it can integrate a micro-feature. Here is a blueprint outlining how this integration can be achieved using the well-known api-implementation modules approach:
Integration is done through factory APIs. Factory APIs are responsible for providing UI models and view instances when requested from a host. As depicted in the diagram, all elements in an api module are interfaces. This ensures that no implementation details are leaked to other modules. Host will be consuming only api module interfaces to inject a micro-feature into its container.
Implementing a Host
A host manages the layout of a part of the screen. There could be multiple hosts on a screen at a time. A host serves as a placeholder for multiple micro-features. Hosts are mostly agnostic about what is being shown but still may enforce some layout rules for micro-features. This enforcement can be done via host item interfaces. Any micro-feature UI model that wants to be shown in a particular host can adapt itself to that host by implementing this interface.
Micro-features are registered to hosts using factory APIs. Host creates an instance of view and UI model using factory APIs when needed. Host is also responsible for binding UI models to their respective views.
Note that a host should only register the micro-feature but it should not control its visibility. This is because micro-features should be self-contained and they need to decide whether they need to be visible in the current context themselves. This clear distinction is important to define a contract between a host and a micro-feature in terms of responsibility perspective.
Dynamic Host Configurations
A host might register multiple micro-features. As the number of registered micro-features grows in a host then it can bring some memory pressure. In order to avoid this, host configurations can be introduced. A host configuration represents a subset of registered micro-features. A host might have multiple configurations. Depending on the context, a host can switch between configurations. This ensures that only context-relevant micro-features are registered at a particular time.
It is important to note that host configurations might not be needed all the time. If the number of micro-features is growing and keeping all of them active is becoming costly then it might be time to consider introducing host configurations. Otherwise, it is better to keep host implementation simple without any configuration complexity.
From Concept to Code: Implementing and Integrating Foo Micro-Feature
Note that this example is for the Android platform. Examples are written in Kotlin and views are implemented using Jetpack Compose. Even if the example is for Android, it can give an idea about the implementation of micro-feature architecture for any platform.
Here is the big picture of what we are going to build in terms of UI model and composable hierarchy.
The name of the micro-feature that we are going to focus on is Foo. Foo is a feature that has a title, description and a CTA that navigates to the bar feature. In this example, first, we are going to implement the micro-feature itself following the architecture defined in the previous section. Then this micro-feature will be integrated into a bottom sheet host. In addition to this, we will go one level up on the hierarchy and integrate this host into the home screen.
Implementation of Foo Micro-Feature
Let’s create the UI Model interface along with its state and action data classes following the recommended solution from the class diagram.
Foo API module
Foo Implementation module
Integrating Foo Micro-Feature into Bottom Sheet Host
Similar to a micro-feature, we are going to have api and implementation modules for the bottom sheet host. Note that the bottom sheet will be hosting 3 micro-features, namely foo, bar and baz.
Bottom sheet API module
Bottom sheet Implementation module
One More Level Up in the Hierarchy: Integrating Hosts to Home Screen
Let’s consider that we have a home screen with several hosts. One of these hosts is the bottom sheet host that is already implemented. Let’s assume that we have some more hosts, namely map and floating layer. Map is the host for map content and floating layer is the host for floating buttons and some other floating content over the map.
Note that here we will provide only the ViewModel implementation and skip the view part for the sake of brevity. However, it is similar to what we did for the bottom sheet host.
Conclusion
Micro-feature architecture aims to produce self-contained, portable UI components that can function in any host with minimal integration effort. This enables us to compose highly dynamic screens depending on the user’s context with micro-feature components. Since integration footprint into hosts is minimal, teams can still collaborate on a single screen while delivering their own features.
As this architecture continues to evolve, it holds the promise of transforming how we create mobile apps, making them more dynamic, context-aware, and user-friendly.
If you like what you’ve read and you’re someone who wants to work on open, interesting projects in a caring environment, check out our full list of open roles here – from Backend to Frontend and everything in between. We’d love to have you on board for an amazing journey ahead.