For an Android product that is expected to gain features, users, data sources, and device types, start with two layers: a UI layer and a data layer. Put repositories between the UI and databases, network clients, or other platform sources. Add a domain layer only when business logic is reused or a ViewModel becomes too complex. Keep durable state in persistent models rather than lifecycle-bound components, define one owner for each kind of data, and introduce modules only when a meaningful boundary improves cohesion, coupling, or team delivery.
This approach is deliberately practical. It gives a small product enough structure to survive process death and changing requirements without forcing every project into a large, rigid architecture.
As an Amazon Associate I earn from qualifying purchases.
What a growing mobile product needs from its architecture
Architecture is the set of boundaries that determines where responsibilities live, how state moves, and how changes travel through a codebase. Growth exposes weak boundaries in several ways at once:
Recommended Free Tools
- A screen starts fetching data directly, then must reconcile a cache, a network response, and user edits.
- Important state disappears when the operating system stops the app process.
- Two screens implement the same business rule differently.
- A new feature touches unrelated files because no domain or feature boundary exists.
- A layout that worked on a portrait phone breaks when the window is resized, folded, or displayed on a larger device.
A useful architecture makes those changes local and traceable. It does not mean adding every possible layer on day one. The right amount of structure depends on the product, the platform, the devices you support, and the way your team ships.
#1 Best Overall
The Android baseline: UI plus data
Android’s official architecture guidance treats a UI layer and a data layer as the minimum useful separation. The UI layer renders application data and accepts user interaction. The data layer owns business logic and exposes application data to the rest of the app.
| Layer | Primary responsibility | What should not happen there |
|---|---|---|
| UI | Display state, collect user input, and turn interaction into events for the state owner. | Direct access to a database, network client, or other platform data source. |
| Data | Apply business rules, coordinate data sources, and expose app data through repositories. | Knowing the visual details of a screen or widget. |
| Domain (optional) | Hold reusable use cases or especially complex business logic between UI and data. | Existing merely because the project has more than one screen. |
The UI and data layers are the baseline; the domain layer is conditional. A small app can remain clear with two layers when each responsibility is easy to locate.
Why repositories belong in the boundary
A repository is the interface through which the rest of the app obtains and changes data. It centralizes data-related decisions, hides whether information came from one source or several, and resolves conflicts when sources disagree. Android recommends repositories even when an app currently has only one data source, because the boundary prevents that source’s details from spreading into UI code.
For example, a screen should ask for the current account state through a repository rather than deciding whether to read a local store, call a service, or merge the two. If the source changes later, the change stays behind the repository. The UI continues to consume application data instead of learning how that data is stored or fetched.
When a domain layer earns its place
Insert a domain layer when at least one of these conditions is true:
- The same business operation is needed by multiple ViewModels or other presentation owners.
- A ViewModel’s business logic has become difficult to understand, test, or change without affecting presentation concerns.
- A sequence of rules needs a named use case so callers can depend on a stable operation rather than internal data details.
Do not add a domain package simply to make a diagram look complete. An extra layer creates another boundary, more types, and more navigation for maintainers. If it does not remove duplication or complexity, keep the logic in the simplest layer that owns it.
Keep state alive through lifecycle changes
Android components have operating-system-controlled lifecycles. A process can be stopped when resources are needed, components can be launched individually or out of order, and components can be destroyed and recreated. An Activity is therefore a poor place to keep the only copy of important application state.
Store durable state in persistent models and let the UI render from those models. After process recreation, the app should be able to rebuild the visible state by reading the model again rather than relying on fields that existed only in a destroyed component. The same design reduces the damage caused by an intermittent network connection: the app has an owned representation of what it knows instead of treating a transient response as the sole source of truth.
Define one source of truth for each data type
For every important kind of data, name one owner that is allowed to mutate it. That owner exposes an immutable representation to consumers. Other layers may request a change, but they do not silently maintain competing copies.
This rule applies to more than a database record. It can govern an authenticated user, a shopping basket, a download queue, or the state of a feature screen. When two components can independently write the same value, the app has to guess which version is current. A single source of truth makes ownership explicit and gives you one place to enforce validation and conflict handling.
Use unidirectional data flow
Unidirectional data flow gives state a predictable path:
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →- An owner exposes current state to the UI in a read-only form.
- The user or another input produces an event.
- The owner processes that event and applies the relevant rule.
- The owner publishes a new state, which the UI renders.
The UI does not mutate its own copy and hope that another layer catches up. Because changes move through one owner, you can trace why a value changed and keep multiple UI elements consistent.
ViewModels, flows, and event handling on Android
Android’s current recommendations include ViewModels where they fit, lifecycle-aware collection of UI state, and coroutines and flows for communication between layers. A ViewModel can act as the presentation owner that receives events and exposes state while surviving ordinary configuration changes.
Prefer representing the result of an event in state rather than sending a one-off command directly to the UI. For example, instead of asking a screen to imperatively “show the loaded order” through a transient callback, process the load event and publish a state that describes loading, success, or failure. The UI observes that state and renders the appropriate result. This is Android-specific guidance; other mobile stacks may use different lifecycle and state primitives.
Rank #3
Design boundaries that can change independently
Once the baseline works, examine each boundary by asking whether a change can remain local. A good boundary has high cohesion: the code inside it belongs together. It also has low coupling: the code outside it does not need to know its internal details.
Feature boundaries
A feature boundary groups the UI and state handling for a user-facing capability such as sign-up or checkout. It is useful when that capability changes as a unit or when a team needs to work on it without understanding unrelated screens. The feature should call data through repositories or domain operations rather than reaching into storage classes directly.
Data boundaries
A data module can encapsulate the data and business logic for a domain and expose a repository. This is helpful when several features use the same account, catalog, or payment data, because those features depend on one owner instead of each building its own access path.
Application boundary
The app module is the entry point. It commonly owns root navigation and assembles the feature and data pieces into a runnable application. Keep orchestration here; avoid turning the app module into a dumping ground for unrelated business rules.
Common boundaries
Shared UI, analytics, or networking can live in common modules when they are genuinely reused. A common module is not automatically an improvement: if only one feature uses it, placing the code with that feature may be clearer.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Clear out junk files and repair common Windows errors3Fix the driver behind crashes, sound loss and screen glitches| Module role | Typical contents | Introduce it when |
|---|---|---|
| Data | Domain data models, data sources, repositories, and related business rules. | Multiple features need the same data ownership or the data boundary is becoming hard to protect. |
| Feature | Feature UI, presentation state, and feature-specific coordination. | A capability has a coherent lifecycle, separate ownership, or a clear change boundary. |
| App | Application entry point, composition, and root navigation. | Always; it is the shell that assembles the product. |
| Common | Shared UI, analytics, or networking utilities. | Several modules use the same capability and a shared contract reduces duplication. |
When not to split a module
Android’s modularization guidance does not prescribe one layout for every project. Splitting code creates dependency edges, build configuration, ownership questions, and coordination work. If two proposed modules rely heavily on each other, they may represent one cohesive unit and should remain together. If parts of one module rarely interact, separation may help.
Use a module when it clarifies a boundary or supports independent work, not when a folder has reached an arbitrary file count. A network of tiny modules can be harder to navigate than a well-organized larger module because every change crosses more contracts.
Account for devices and changing windows
A mobile product should not assume a fixed portrait phone. Android’s architecture guidance names tablets, foldables, ChromeOS, car displays, and XR among the possible form factors. The architecture must keep state and navigation valid while the available space changes.
Separate layout adaptation from data ownership
Adaptive UI can choose a list, a two-pane layout, or another arrangement based on the current window, but it should render the same owned application state. Do not make a second data owner for a tablet layout or a foldable posture. Keep presentation decisions near the UI while repositories and state owners remain stable.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Handle configuration changes deliberately
Rotation, window resizing, and folding can recreate or rearrange UI components. A screen should be able to obtain its state again and make a valid navigation decision for the new size. Navigation that assumes a single fixed back stack or a permanently visible panel is fragile when the window changes.
Design the state model so it describes facts and user intent rather than pixel positions. The UI can then map those facts to the current form factor without losing the user’s work.
A decision framework for choosing the right amount of architecture
Before adding a layer or module, evaluate the decision on six axes. The answers should describe your product, not an idealized sample app.
| Axis | Questions to answer | Warning sign |
|---|---|---|
| Responsibility boundaries | Can you identify where UI behavior, data access, and business rules live? Can each change independently? | A screen imports storage or networking classes directly. |
| State ownership | Is there one mutable owner for each important data type? Can the app reconstruct state after process recreation or a temporary connection loss? | Several ViewModels or screens write competing copies. |
| Reuse and complexity | Is logic duplicated across presentation owners, or is one ViewModel carrying too many rules? | A domain layer exists without reuse or complexity to solve. |
| Module cohesion and coupling | Do proposed modules represent meaningful domains or features? How many dependencies must change together? | Two modules know most of each other’s internals. |
| Platform and form-factor scope | Which operating systems, device types, window sizes, and adaptive layouts are required? | Navigation or state assumes one portrait phone. |
| Team and delivery constraints | Will the boundary let people work with less coordination, and is the added build and runtime complexity justified? | The architecture adds ceremony without reducing conflicts or change risk. |
A practical implementation sequence
The following sequence keeps the first version small while leaving room to grow.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchPC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11- List the product’s durable data. Identify what must survive a component recreation, process restart, or intermittent connectivity. These items need an owned model rather than Activity fields.
- Assign one owner to each data type. Decide which repository or state owner can mutate the value and expose an immutable form to consumers.
- Place repositories between UI and sources. Route database, network, and platform access through repositories, even if there is only one source today.
- Define UI state and events. Let the UI render state and send events back to its owner. Keep the mutation path explicit so a change can be traced.
- Choose presentation tools that fit Android’s lifecycle. Where appropriate, use ViewModels, lifecycle-aware state collection, and coroutines or flows to communicate between layers.
- Measure domain complexity before adding a domain layer. Add reusable operations when several ViewModels need the same logic or one ViewModel has become difficult to maintain.
- Group the first feature coherently. Keep closely related UI and state code together. Extract a feature or data module when the boundary is clear, not because a template demands it.
- Map the required form factors. Check how state, navigation, and layout behave on phones, larger screens, foldables, ChromeOS, car displays, or XR if those are in scope.
- Recheck coupling after each extraction. If a new module requires frequent reciprocal changes, merge the boundary or redesign the contract.
How the architecture behaves in common growth scenarios
A small, single-source app
Start with UI and data layers. The repository can wrap the one local or remote source, and a ViewModel can expose screen state. Do not create a domain layer or multiple feature modules until duplicated rules or a real ownership boundary appears.
An app adding a second data source
Keep source-specific coordination inside the repository. The repository can decide how to combine or reconcile sources while the UI consumes one application-level representation. This prevents every screen from implementing its own cache-versus-network policy.
A product with repeated business operations
When several ViewModels perform the same operation, move that operation into a domain layer or another clearly owned domain component. The callers then share one rule instead of drifting into subtly different behavior.
A team working on separate capabilities
Use feature modules where capabilities have coherent ownership and limited interaction. Keep shared data in data modules when several features need the same source of truth. The app module assembles them and owns root navigation.
Free tools Windows power users keep installed
One-click scans. No signup required.
A product expanding to larger or foldable displays
Keep the state owner and repository contracts stable while the UI adapts to window size and posture. Test recreation, resizing, rotation, and folding as state transitions, not only as visual changes.
Failure modes and concrete corrections
| Symptom | Likely architectural cause | Correction |
|---|---|---|
| Data vanishes after returning to the app. | The only copy lived in a lifecycle-bound component. | Move durable information into a persistent model owned by the data or state layer, then rebuild UI state from it. |
| Two screens show different values for the same entity. | There is no single source of truth. | Choose one mutable owner and expose an immutable representation to both screens. |
| Every screen has its own cache and refresh rules. | UI code bypasses the repository boundary. | Centralize source coordination and conflict handling in the repository. |
| A ViewModel is hard to change safely. | Presentation code contains reused or overly complex business logic. | Extract the repeated or complex operation into a domain layer with a clear contract. |
| A new module requires synchronized edits in several other modules. | The split created high coupling or divided one cohesive responsibility. | Reduce the public surface, change the contract, or combine the modules. |
| The tablet or foldable layout loses context. | Navigation and state assume a fixed window arrangement. | Keep state independent of layout and make navigation decisions valid for the current available space. |
| One-time UI commands are missed or duplicated. | Important outcomes are sent as transient events instead of represented in state. | Process the event in the owner and publish the resulting state for lifecycle-aware collection. |
What this guidance does—and does not—cover
The recommendations here are grounded in Android’s official architecture and modularization guidance. They describe Android layers, repositories, lifecycle-aware state, ViewModels, coroutines, flows, and modular boundaries.
They are not a current implementation prescription for iOS, and they do not establish a comparison of cross-platform frameworks. Those platforms may have different lifecycle models, state tools, and module systems. If your product targets more than Android, evaluate each platform’s native constraints before selecting a shared architecture.
A concise operating rule
Begin with the smallest architecture that makes ownership explicit: UI renders state, repositories mediate data, and persistent models protect important information from lifecycle loss. Add a domain layer for reuse or genuine complexity, split modules where cohesion and team boundaries justify it, and make every state and navigation decision resilient to the device windows your product actually supports.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

