ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Follow publication

Generated by Ideogram.ai

Building for multi-form factor devices in Android: The optimal architecture

Gérard Paligot
ProAndroidDev
Published in
6 min readNov 12, 2024

This article is inspired by a talk I gave with David Ta in 2024, entitled “Why is Adaptive Layout a Nightmare?”. During that presentation, we wanted to explain how complex it was to migrate an existing Android application to be compatible with adaptive layouts and the unexpected obstacles which you face across different screen sizes and device configurations. The journey of creating adaptive layouts can indeed feel like a nightmare if you don’t have the proper architectural foundation.

In this post, I’ll present a summarized version of our talk on component modular architecture. This architectural pattern provides a structured solution to achieve designs that are adaptable and reusable in a layered manner. With its multiple layers, a developer can more easily handle navigation, the content at screen levels, and the UI components with ease, making adaptive layouts practical and less challenging to maintain.

The component architecture: Modular design to support additional form factors

As Android applications get more complex and need to handle new form factors like phones, tablets, and foldable devices, traditional architectures can be rigid to handle with the unique demands on each device type. Component architecture addresses these challenges by breaking the presentation layer into modular levels of granularity that are specifically designed for adaptability and scalability across diverse screen configurations.

Overview of the component architecture

The component architecture organizes the presentation layer into three hierarchical levels:

Gradle modules categorized by component levels
  1. App-Level (navigation layer): The app level deals with the global navigation of the application, structures, and flows across different screens. This level makes components handle centralized control over navigation while adapting to various device sizes. In such architecture, the use of navigation components is targeted to toggle between bottom navigation, navigation rails, or drawers concerning device configuration so that it can respond dynamically to changes in screen dimensions.
  2. Screen-Level (adaptive layouts and panes): Screen-level components are responsible for managing layout and structure within a given screen, such that on different screen sizes and orientations, it would adapt to them. Components that may change their layout as the window size changes. This level uses scaffolds like SupportingPaneScaffold or ListDetailPaneScaffold that enable adaptive layouts and make sure content is always presented in the most optimal way for each of the form factors. For example, a screen can display only one pane on a small device, but automatically switch to a dual-pane layout on larger screens.
  3. UI Component Level (reusable components): The UI component level consists of smaller reusable components across various screens. These components are isolated and focus on individual UI elements, which do not change in device configuration but are foundational to screen-level compositions. Examples include buttons, icons, or text fields. By modularizing these components, the architecture allows for reuse and rapid testing across a wide number of screens and layouts.

Key characteristics

  1. Modularity and reusability: Instead of having a single monolithic app strategy, the component approach really encourages modular components to make use of such things as navigation, displays of content, and specific UI elements, which can be reused across various devices and screen configurations. It becomes an ideal paradigm for multi-form factor compatibility whereby each component may independently be designed to adapt to various form factors without affecting the whole application.
  2. Adaptive UI components: Components are designed with adaptability in mind in top of WindowSizeClass add and adaptive scaffolds like NavigationSuiteScaffold, SupportingPaneScaffold, and ListDetailPaneScaffold. These scaffolds automatically change according to different screen dimensions, adapting screen real estate and screen orientation into proper layout.
  3. Separation of Concerns: Compared to other architectures, component architecture extends the principle of separation of concerns, decoupling UI logic from business logic and device-specific configurations. Each layer is going to stay independent, but the UI components will adjust dynamically due to screen size, whereas business logic with core functionalities would remain the same. Due to this approach, code will not be complex; testing is easy, and allows developers to avoid spreading changes in one layer across an entire codebase.

Component modular architecture: Structured for adaptability

In the component modular architecture, components are organized in distinct modules of the presentation layer for better adaptability and maintainability on different screen sizes and device configurations. The modular structure may have three major levels: an application-level, screen-level and a UI component-level. Each of these levels corresponds to a separate module so that the structure of an application is able to cope with multi-form factor devices without noticeable efforts.

The modular architecture in the presentation layer is divided into three hierarchical levels:

App-Level: App-level components, like AppNavigation, are placed in a centralized module, typically named main or navigation. This layer controls primary navigation elements across the app, managing the flow and layout of screens in response to different device configurations.

@Composable
fun AppNavigation(
navActions: Immutable<NavAction>,
routeSelected: String,
modifier: Modifier = Modifier,
navController: NavHostController = rememberNavController()
,
) {
NavigationSuiteScaffold(
modifier = modifier,
navigationSuiteItems = {
navActions.forEach { action ->
val selected = action.route == routeSelected
item(
selected = selected,
icon = {
Icon(
imageVector = if (selected) action.iconSelected else action.icon,
contentDescription = action.contentDescription
)
},
onClick = {
// use navController to navigate to your screen
}
)
}
}
) {
NavHost(
navController = navController,
startDestination = "first-screen",
builder = {
// composables
}
)
}
}

Screen-Level: Screen-level components, including those that manage adaptive layouts and link to ViewModels, are organized into a module called presentation. This module includes adaptive components like MyScreenAdaptive, which handles layout adjustments based on the device’s WindowSizeClass. Additionally, screen-level components specific to different panes are grouped in a module named panes.

@Composable
fun MyScreenAdaptive(
showFilterIcon: Boolean,
modifier: Modifier = Modifier,
)
{
SupportingPaneScaffold(
modifier = modifier,
mainPane = {
MyFirstPaneVM()
},
supportingPane = {
MySecondPaneVM(showFilterIcon = showFilterIcon)
}
)
}

UI Component-Level (reusable components): Contain small UI parts, such as buttons and icons. Those are organized in the ui module of each specific feature. For more general components, UI components are developed at the internal design system level for common use across different screens and features.

By assigning components to the main, presentation, panes, and ui modules, the architecture ensures a structured approach in both flexibility and high scalability. This modular hierarchy enables each layer independently to be tested, maintained, and updated while reducing dependency, thus best suited for multi-form factor design.

Why Modular, component-based architecture is ideal for multi-form factors compatibility

Embracing this modern architecture will greatly enhance the flexibility and adaptability of your app across devices. Let me just outline why this approach will fit perfectly for you:

Enhanced adaptability with less code

  • Automatic UI adjustment: Components such as NavigationSuiteScaffold will automatically handle different device configurations without requiring you to adjust the layout manually.
  • Less redundancy: Due to the reusable elements, it is no longer a need for creating separate layouts or components for each device type. ListDetailPaneScaffold and SupportingPaneScaffold self-adjust according to the size of the screen, reducing redundancy of code per screen level.

Improved scalability and maintainability

  • Modular codebase: Separation of navigation, panes and UI components will allow you to develop screens for any device without worrying about the device configuration.
  • Lower maintenance costs: It reduces the total number of views or layouts to maintain, with fewer errors and easier updates. This modularity is extremely valuable when handling compatibility problems as new device types emerge.

Simplification of consistent UI

This architecture lends itself to naturally forcing consistency of UI. Adaptive scaffolds, such as NavigationSuiteScaffold and SupportingPaneScaffold, keep design patterns consistent across devices for cohesiveness in user experience no matter the form factor.

Conclusion

The notion of building multi-form factor compatible applications for Android devices automatically requires a rethink in architecture. Conventional Android architectures are very efficient in building simple apps, probably single-screen apps, but start to limit the developers as screen variations grow. Google’s new component-based and modular architecture empowers developers to design adaptive UIs that can dynamically change with screen size and configuration with minimal redundancy in code and less load of maintenance. This is the architecture that would enable Android developers to create applications, keeping users in mind by providing a seamless experience on all devices while remaining efficient and highly scalable over time.

This new approach makes it even easier to deliver high-quality applications, giving one a consistent and adaptive experience across the diverse Android ecosystem.

Thanks for reading this blog post. If you like it, please clap this article and follow me on Bluesky, Mastodon, GitHub and Medium to be notified about my next blog posts!

Thanks to my reviewer David Ta for this article.

Published in ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Written by Gérard Paligot

Senior Staff Engineer at Decathlon Digital

Responses (1)

Write a response