Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

A growing mobile product needs clear boundaries more than it needs a fashionable pattern. For an Android app, start with a UI layer and a data layer, put repositories between the UI and every data source, and add a domain layer only when business logic is reused or genuinely difficult to manage in a ViewModel. Keep durable state in models owned outside lifecycle-bound screens, expose one authoritative owner for each kind of data, and let events flow back to that owner. As the team and device matrix expand, split modules around cohesive features or domains rather than creating a maze of tiny projects.

This approach gives a product room to add features, survive process death and intermittent connectivity, support tablets and foldables, and let teams work independently without imposing structure that the app does not yet need.

The practical baseline: UI, data and an optional domain layer

Android’s official architecture guidance establishes a small baseline that works for both a new app and a product that is beginning to grow. The UI layer displays application data and handles user interaction. The data layer contains business logic and exposes application data. Repositories sit at the boundary, centralizing changes to data, hiding whether information came from a network, database or another source, and resolving conflicts when more than one source exists.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A domain layer can sit between UI and data, but it is not a mandatory tier. Introduce it when the same operation is used by several ViewModels or when presentation-facing business logic has become too complex to understand and test in one ViewModel. A small feature with one straightforward screen may be clearer without it.

Layer Primary responsibility What it should not do
UI Render state, collect user input, and send events Open databases, call network clients directly, or decide where data is stored
Domain (optional) Represent reusable use cases and complex business rules Own screen widgets or duplicate repository storage logic
Data Implement repositories, data sources, mapping and business data operations Know about a particular Activity, screen layout or navigation destination

The most important boundary is between UI components and data sources. Even when an app has only one source, a repository keeps the source replaceable and gives the rest of the application one consistent API. Screens ask for application data; they do not need to know whether the answer came from a local store, a remote service or a combination of both.

How responsibilities and data flow fit together

Use a one-way path for normal data movement and a deliberate return path for user intent:

  1. The UI observes an immutable state model and renders it.
  2. A user action becomes an event, such as Retry, SaveAddress or Refresh.
  3. The ViewModel or another presentation coordinator handles the event and calls a repository or, where justified, a domain use case.
  4. The repository reads or changes the underlying sources, applies synchronization or conflict rules, and updates its owned model.
  5. The resulting state flows back through the ViewModel to the UI.

This arrangement makes the owner of a change visible. It also prevents two screens from quietly keeping incompatible copies of the same account, cart or settings data. A repository should expose data to callers in an immutable form; only the owner is allowed to mutate it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

When a domain layer earns its place

Do not create a use-case class for every function by default. Add the layer when at least one of these conditions is true:

  • The same business operation is needed by multiple ViewModels or features.
  • A ViewModel is coordinating several repositories, branching through many business rules, or becoming difficult to read.
  • The operation has a clear business name that is more meaningful than a collection of low-level data calls.

If none applies, keeping the operation in the ViewModel or repository can reduce indirection. The goal is understandable responsibility, not a prescribed number of folders.

Design state for lifecycle loss and unreliable connections

Mobile operating systems control component lifecycles. Android can stop an app process when resources are needed, launch components individually or out of order, and destroy and recreate screens during configuration changes. An Activity or other lifecycle-bound entry point therefore cannot be the only place that important application data lives.

Keep durable state in models that outlive a screen. A process restart should reconstruct the UI from those models rather than leaving the app with an empty or contradictory screen. Persistent models are especially important for data that must remain available during an intermittent connection or after the user temporarily leaves the app.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choose one owner for each data type

For every important data type, name a single source of truth: one component that can mutate the value and exposes it to the rest of the app in an immutable form. Examples include a repository owning the signed-in account, a local store owning a saved draft, or a synchronizing repository owning the latest known order.

Data Possible owner UI interaction
Signed-in account Account repository backed by the app’s account sources Observe account state; send sign-out or refresh events
Checkout draft Persistent draft model managed by a checkout repository Render fields; send edits; restore after recreation
Temporary screen presentation ViewModel state for that screen Render loading, error and content states

Two screens may display the same value, but they should not each become an independent writer. Central ownership avoids race conditions and makes refresh, logout and offline recovery predictable.

Use unidirectional data flow

Unidirectional data flow means state travels toward the UI and events travel back toward the state owner. Android’s current guidance pairs this model with lifecycle-aware state collection, ViewModels where appropriate, and coroutines and flows for communication between layers. These are Android recommendations; other mobile stacks may implement the same ideas with different primitives.

Represent the result of an event in state rather than relying on a fragile one-off signal. For example, a failed save can become an explicit SaveState.Error that remains available until the user edits the form or dismisses the message. The UI renders the state it receives, while the ViewModel coordinates events and delegates data changes.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A step-by-step build plan for a growing Android product

1. Map product responsibilities before choosing packages

List the user-visible capabilities and the data each one needs. Mark which information is durable, which is shared by several features, and which is only temporary presentation state. This map reveals natural boundaries before code structure makes them expensive to change.

2. Define models that can survive recreation

Create application-facing models for important concepts rather than exposing raw responses from a network client or database. Decide which models must be persisted and how they are rebuilt after process death. Keep screen state separate from durable business data so a loading indicator does not overwrite an order or draft.

3. Put every source behind a repository

Define repository interfaces around application needs, not around a vendor SDK. A repository can combine local and remote data, select the freshest value, queue a change, or report a conflict. Screens should depend on that interface and never reach around it to access a database or network client.

4. Add domain operations only for real reuse or complexity

If two ViewModels need the same eligibility rule, pricing calculation or synchronization operation, give that operation a domain-level home. If a single screen merely forwards a button tap to one repository method, an extra abstraction may make the code harder to follow.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

5. Expose immutable state from the presentation layer

Let a ViewModel collect repository flows, process events and publish a state model that the UI can observe with lifecycle awareness. Keep mutable implementation details private. When an event completes, update state and let the UI react to the new state instead of expecting the UI to manage a callback sequence.

6. Model loading, content and failure explicitly

For each screen, decide what the UI should display while data is loading, when cached data is available but a refresh fails, and when no data exists. A repository that can return a durable cached model gives the UI something useful to render during a network outage; an error state can then explain what action is available.

7. Make configuration changes ordinary, not exceptional

Rotation, window resizing and folding can recreate or rearrange UI components. Keep navigation decisions and important state outside the screen instance. The recreated UI should reconnect to the same state owner and render the current model without asking the user to repeat completed work.

8. Exercise process and connectivity scenarios

Walk through the cases that expose hidden ownership: leave a form, rotate the device, return after a process restart, disable the connection, retry a failed operation and open two screens that display the same record. If the result depends on a particular Activity instance or a second hidden copy of the data, move ownership to the appropriate model or repository.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Modularize when boundaries become useful

Android’s modularization guidance does not prescribe one project layout. The durable rule is high cohesion and low coupling: keep code that changes together together, and keep modules from knowing unnecessary details about one another. Two modules that depend heavily on each other may be a single boundary pretending to be two. Conversely, a module whose parts rarely interact may be ready to split.

Module role Useful contents Good reason to create it
Data module Repository implementations, data sources, mapping and domain data logic A domain’s data needs should be encapsulated and consumed through a stable repository API
Feature module Sign-up, checkout or another cohesive capability, including related UI and state handling The feature has a clear boundary or needs to evolve with limited knowledge of other features
App module Application entry point and commonly root navigation The product needs one place to assemble features and start the app
Common module Shared UI, analytics or networking used by multiple areas The code is genuinely reused and the shared dependency will not become a dumping ground

Signals that a split is justified

  • A feature can be described and changed without understanding unrelated features.
  • Several developers need to work in the area while minimizing cross-feature knowledge.
  • A dependency is reused by multiple features and has a stable contract.
  • Parts of the current module rarely interact and can communicate through a small interface.

Signals that a split is premature

  • The proposed modules call each other for most operations.
  • Moving a class would require exposing implementation details to several other modules.
  • The split creates wrappers and interfaces without reducing conceptual coupling.
  • The team cannot explain what independent responsibility each module owns.

When refactoring, extract one meaningful boundary at a time. First identify the owner and public contract, then move implementation behind that contract, and finally remove callers’ access to the old internals. A network of tiny modules can add build and coordination overhead without improving isolation.

Design for phones, tablets and changing windows

A product that grows in users may also grow across form factors. Android’s architecture guidance calls out tablets, foldables, ChromeOS, car displays and XR as possible targets. A fixed portrait phone layout is therefore an architectural constraint, not merely a visual choice.

Change the app must handle Architectural implication
Rotation or window resizing Keep state outside the recreated UI and render from the current model
Fold and unfold Allow navigation and layout decisions to respond to available space
Large screens Use adaptive layouts that can show related content together when space permits
Car or XR surfaces Keep presentation concerns separate from data and business rules so a different UI can consume the same state

Do not let a particular screen width determine where data is stored or how a transaction works. The UI can choose a list, two-pane layout or alternate navigation arrangement while repositories and domain operations continue to expose the same application state.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A decision framework for evaluating an architecture

When comparing a proposed structure with the current one, score it against the constraints that matter to the product rather than counting layers or patterns.

Question Healthy signal Warning signal
Are responsibilities clear? UI, data access and business rules can change independently Screen classes contain storage, networking and business decisions together
Who owns each state? One mutable owner exposes immutable data and survives recreation Several screens maintain competing copies
Is a domain layer needed? Logic is reused or too complex for a ViewModel Every trivial function has an extra wrapper
Are modules cohesive? Each module represents a meaningful feature or domain Modules communicate through many internal details
Does it cover the device scope? Layouts and navigation adapt to resizing and folding Important state is tied to one portrait screen
Does it fit the team? Boundaries let people work with limited cross-module knowledge Build, dependency and coordination cost exceeds the isolation gained

This framework also exposes trade-offs. A highly abstract design may improve reuse but slow a small team. A single module may be efficient early and become difficult later. Choose the smallest structure that makes the next expected change safe, then revisit the decision as evidence accumulates.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Common failure modes and how to correct them

Screen classes own durable data

Symptom: a rotation or process restart clears a draft, account or in-progress task. Correction: move the durable model to its repository or persistent store and have the recreated UI observe it.

UI calls a database or network client directly

Symptom: every screen implements its own loading, caching and error rules. Correction: place the source behind a repository and expose an application-level contract.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Two owners mutate the same record

Symptom: one screen shows a newer value while another silently overwrites it. Correction: select a single source of truth and make other consumers read immutable state.

One-off UI events carry business results

Symptom: a message, navigation action or success result disappears when the UI is recreated. Correction: represent the meaningful result in state and let the lifecycle-aware UI collect it.

A domain layer is added everywhere

Symptom: developers navigate through several classes to find a simple repository call. Correction: retain the layer only where logic is reused or genuinely complex.

Modules mirror folders instead of responsibilities

Symptom: modules depend on one another for nearly every change. Correction: merge tightly coupled pieces or redraw the boundary around a cohesive feature or domain.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Phone assumptions leak into architecture

Symptom: a fold, resize or large display loses context or forces a restart of a workflow. Correction: keep state and navigation independent of a fixed window size and use adaptive layouts.

Worked example: a checkout capability

Consider a product adding checkout to an existing app. The feature module can own checkout screens and their ViewModels. A checkout repository can expose the cart, address draft, available payment options and order status while hiding whether each value is local, remote or combined. A domain operation is justified if eligibility or order submission is reused by several screens or requires substantial rules.

The screen renders a state model such as editing, submitting, submitted or error. Tapping the submit button sends an event to the ViewModel. The ViewModel invokes the repository or domain operation; the repository becomes the sole mutator of the order state. If the process is killed during address entry, the persistent draft is restored when the feature starts again. If the window widens, the same state can drive a two-pane address-and-summary layout without changing checkout rules.

The app module assembles the feature and owns root navigation. Shared analytics or UI components belong in a common module only if multiple features genuinely consume them. Other features do not reach into checkout’s database tables or internal classes; they use the published contract.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Platform scope and what not to assume

The recommendations in this guide are grounded primarily in Android’s official architecture and modularization guidance. They describe Android layers, ViewModels, lifecycle-aware collection, coroutines and flows, and Android’s range of form factors. The available Apple material does not establish enough current implementation detail for a parallel iOS prescription, and the evidence does not support choosing a cross-platform framework. Teams targeting another platform should translate the principles—clear ownership, durable state, controlled dependencies and adaptive presentation—against that platform’s own guidance rather than assuming identical APIs or project layouts.

Architecture readiness checklist

  • Every screen reads application state through a presentation layer rather than calling a data source directly.
  • Each important data type has one documented mutable owner.
  • Repositories exist even when a domain currently has one source.
  • Durable models can reconstruct the UI after process death or a configuration change.
  • Events update state through a traceable path instead of relying on hidden screen callbacks.
  • A domain layer exists only where reuse or complexity warrants it.
  • Modules represent cohesive features, data domains or truly shared capabilities.
  • Module dependencies are intentionally one-way or otherwise easy to explain.
  • Layouts and navigation respond to rotation, resizing and folding.
  • The design has been checked against the team’s build, ownership and delivery constraints.

A durable default for growth

Start with the smallest architecture that protects the product’s future: UI over repositories and data sources, persistent models for important state, one owner per data type, and a one-way flow of state and events. Add domain and feature modules when reuse, complexity or team boundaries make them valuable. Keep the UI adaptive and the data contracts independent of a particular screen or device. This gives a growing Android product a structure that can absorb new features and form factors without turning every change into a rewrite.

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.