Files
life-echo/docs/todo/2026-03-12-app-android-overall-architecture-plan.md

26 KiB
Raw Blame History

App-Android Overall Architecture Refactor Plan

Document Type: Abstract architecture plan

Scope: app-android whole-project refactor

Purpose: Define the target architecture, target file structure, and phased work sequence for the Android client without dropping into code-level implementation details.

Primary References: This plan is based on the Android official guidance set provided for this task, including architecture recommendations, app architecture overview, Android component fundamentals, UI layer, UI events, state holders, state production, View Binding, Data Binding, Lifecycle, Compose integration, ViewModel, saved state, LiveData, coroutines, Paging 3, domain layer, data layer, offline-first, DataStore, App Startup, and modularization.

Architecture Foundations

UI Layer and State

ViewModel and Lifecycle

View Binding and Data Binding

Data, Persistence, and Startup

Paging

Modularization


1. Overall Architecture Goals

The Android project should evolve from a single-module application with mixed package responsibilities into a feature-oriented architecture with clear layer boundaries, explicit state ownership, and stable app entry points.

The target end state is defined by the following goals:

1.1 App Shell Stays Thin

app should only own:

  • Android entry points
  • root navigation
  • app startup wiring
  • dependency assembly
  • app-wide configuration boundaries

Activity, Application, and manifest-declared components should no longer carry feature business rules, session orchestration, or screen-specific state decisions.

1.2 Features Become the Main Organizational Unit

The codebase should stop being primarily organized by technical type such as ui/screens, ui/viewmodel, data/repository, and network.

The main product capabilities should become first-class feature boundaries:

  • auth
  • conversation
  • memoir
  • payment
  • profile

Each feature should own its own presentation, domain, data, and navigation concerns inside its boundary.

1.3 State Ownership Becomes Explicit

The project should use one consistent UI architecture model:

  • screen UI is driven from state
  • state flows downward
  • user actions flow upward
  • screen-level state is owned by screen-level state holders
  • transient UI element state stays local unless it must survive process recreation

Global singletons should no longer be used as ad hoc UI-observable state stores.

1.4 Data Layer Becomes a Real Boundary

Repositories should become the only boundary that coordinates:

  • local persistence
  • remote transport
  • sync policy
  • domain-facing data access

Core product data should move toward a single clear source of truth per data set. For data that must support caching or offline continuity, local persistence should become the canonical read source.

1.5 Domain Logic Is Used Selectively

The project should not introduce a full domain layer everywhere.

Domain use cases should be introduced only where business workflows are long-lived, stateful, or operationally complex, especially:

  • session recovery
  • realtime conversation
  • recording and segment upload flows
  • payment order lifecycle
  • authentication refresh and account state recovery

Simple CRUD-like flows should remain repository-driven.

1.6 Startup and Lifecycle Handling Become Intentional

Startup responsibilities should be explicit and minimal. App-wide initialization should be separated from business state loading. Lifecycle-aware state collection, effect handling, and saved-state restoration should become standard conventions instead of screen-by-screen exceptions.

1.7 Modularization Happens After Boundaries Stabilize

The project should not begin with aggressive Gradle modularization. It should first stabilize package boundaries inside the current module, then split mature boundaries into Gradle modules with low coupling and high cohesion.

1.8 Compose-First UI Policy Remains Intact

The project should remain Compose-first.

View Binding and Data Binding should be treated as compatibility tools, not as the primary UI architecture:

  • new feature screens should not be designed around XML binding
  • future View-based interoperability should prefer View Binding
  • Data Binding should only be used for retained XML-based surfaces if there is a concrete reason to keep them

1.9 List and Data Scaling Is Deferred Until Foundations Are Stable

Paging 3 should be introduced only after:

  • repository boundaries are stable
  • list ownership is feature-scoped
  • local and remote responsibilities are explicit

It should be treated as a list-scaling layer, not as the starting point of the refactor.


2. Current Structural Issues

The current codebase already contains useful refactor direction, especially the emerging ports and adapters structure in conversation, memoir, payment, and profile. However, the project still has project-wide structural problems that prevent the architecture from being consistent.

The main issues are:

  • :app is the only Gradle module, so all concerns are compiled and coupled together
  • app-wide entry points still coordinate business state directly
  • feature code is split across horizontal folders such as ui/, data/, network/, payment/, and feature/
  • state is stored across ViewModels, local Compose state, singleton objects, and startup code without one shared ownership model
  • session, settings, and configuration responsibilities are spread across MainActivity, LifeEchoApp, TokenManager, AppSettings, and AppConfig
  • repositories are present, but local/remote/source-of-truth rules are not yet uniformly enforced
  • tests are not yet a strong enough architecture guardrail for a project-wide refactor

Representative current files and directories:

  • app-android/app/src/main/java/com/huaga/life_echo/MainActivity.kt
  • app-android/app/src/main/java/com/huaga/life_echo/app/LifeEchoApp.kt
  • app-android/app/src/main/java/com/huaga/life_echo/app/AppContainer.kt
  • app-android/app/src/main/java/com/huaga/life_echo/navigation/
  • app-android/app/src/main/java/com/huaga/life_echo/ui/screens/
  • app-android/app/src/main/java/com/huaga/life_echo/ui/viewmodel/
  • app-android/app/src/main/java/com/huaga/life_echo/data/
  • app-android/app/src/main/java/com/huaga/life_echo/network/
  • app-android/app/src/main/java/com/huaga/life_echo/payment/
  • app-android/app/src/main/java/com/huaga/life_echo/feature/

3. Target Structure

This plan uses a two-stage target structure:

  • first stabilize feature boundaries inside the existing :app module
  • then split stable boundaries into Gradle modules

3.1 Intermediate Target Structure Inside the Current :app Module

app-android/app/src/main/java/com/huaga/life_echo/
  app/
    entry/
    navigation/
    startup/
    di/
  core/
    common/
    model/
    ui/
    datastore/
    database/
    network/
    testing/
  feature/
    auth/
      presentation/
      domain/
      data/
      navigation/
    conversation/
      presentation/
      domain/
      data/
      navigation/
    memoir/
      presentation/
      domain/
      data/
      navigation/
    payment/
      presentation/
      domain/
      data/
      navigation/
    profile/
      presentation/
      domain/
      data/
      navigation/

3.2 Final Target Gradle Module Structure

app-android/
  app/
  core/
    common/
    model/
    ui/
    network/
    database/
    datastore/
    testing/
  feature/
    auth/
    conversation/
    memoir/
    payment/
    profile/

3.3 File and Directory Migration Intent

The following high-level migrations define the desired end state:

  • MainActivity.kt -> app/entry/
  • LifeEchoApp.kt, AppContainer.kt -> app/startup/ and app/di/
  • navigation/ -> app/navigation/ and feature-scoped navigation/
  • ui/screens/ -> feature/*/presentation/
  • ui/viewmodel/ -> feature/*/presentation/
  • data/repository/ -> feature/*/data/ or core/* depending on scope
  • data/database/ -> core/database/
  • data/preferences/ and settings/session storage -> core/datastore/
  • data/auth/ -> core/datastore/ or feature/auth/data/ depending on responsibility
  • network/ -> core/network/
  • payment/ -> feature/payment/
  • shared reusable Compose UI -> core/ui/
  • generic helpers in utils/ -> redistributed into core/common/, core/ui/, or feature-local packages

4. Refactor Steps

The steps below are intentionally abstract. Each step defines the work boundary, the affected files or directories, the target structure after the step, and the architectural outcome that should be achieved before moving on.

Step 1: Establish the Project-Wide Architecture Contract

Objective

Create one explicit architecture baseline for app-android so that subsequent refactors are judged against a stable target instead of local conventions.

Primary Current Files and Directories

  • app-android/settings.gradle.kts
  • app-android/app/build.gradle.kts
  • app-android/app/src/main/AndroidManifest.xml
  • app-android/app/src/main/java/com/huaga/life_echo/
  • docs/plans/

Target Structural Result

  • one documented architecture contract in docs/plans/
  • one agreed package naming policy for app/, core/, and feature/
  • one agreed migration rule for what stays in :app for now and what becomes module-ready later

Architecture Guidance

  • define feature boundaries before module boundaries
  • define layer responsibilities before moving files
  • stop adding new files into horizontal buckets such as ui/screens and ui/viewmodel
  • treat this step as the governance layer for all following steps

Step 2: Thin the App Shell and Entry Points

Objective

Reduce Application, Activity, and app-level navigation to shell responsibilities only.

Primary Current Files and Directories

  • app-android/app/src/main/java/com/huaga/life_echo/MainActivity.kt
  • app-android/app/src/main/java/com/huaga/life_echo/app/LifeEchoApp.kt
  • app-android/app/src/main/java/com/huaga/life_echo/app/AppContainer.kt
  • app-android/app/src/main/java/com/huaga/life_echo/navigation/
  • app-android/app/src/main/java/com/huaga/life_echo/config/AppConfig.kt
  • app-android/app/src/main/AndroidManifest.xml
  • app-android/app/src/main/java/com/huaga/life_echo/wxapi/

Target Structural Result

app/
  entry/
  navigation/
  startup/
  di/

Architecture Guidance

  • MainActivity becomes a host for root Compose content and system UI coordination only
  • LifeEchoApp becomes startup assembly, not business bootstrap
  • app navigation owns route composition, not feature decision logic
  • manifest-declared components are treated as system entry points with clear ownership
  • App Startup may be used only for truly app-wide initialization dependencies that need explicit ordering or library-style setup

Step 3: Normalize Shared Core Capabilities

Objective

Convert the current cross-cutting infrastructure into explicit core capabilities so features depend on stable shared boundaries rather than miscellaneous utility folders.

Primary Current Files and Directories

  • app-android/app/src/main/java/com/huaga/life_echo/network/
  • app-android/app/src/main/java/com/huaga/life_echo/network/runtime/
  • app-android/app/src/main/java/com/huaga/life_echo/data/database/
  • app-android/app/src/main/java/com/huaga/life_echo/data/preferences/
  • app-android/app/src/main/java/com/huaga/life_echo/data/auth/
  • app-android/app/src/main/java/com/huaga/life_echo/ui/theme/
  • app-android/app/src/main/java/com/huaga/life_echo/ui/components/common/
  • app-android/app/src/main/java/com/huaga/life_echo/utils/

Target Structural Result

core/
  common/
  model/
  ui/
  datastore/
  database/
  network/
  testing/

Architecture Guidance

  • core/network owns transport primitives, adapters, runtime providers, and network policy
  • core/database owns Room entities, DAOs, and database assembly
  • core/datastore owns preference-like and session-like persisted app data
  • core/ui owns reusable design-system-level Compose primitives, not feature-specific widgets
  • utils/ should not continue to grow as a miscellaneous bucket

Step 4: Reorganize Features In Place Before Splitting Modules

Objective

Move product behavior out of horizontal technical folders into coherent feature boundaries while staying inside the current :app module.

Primary Current Files and Directories

  • app-android/app/src/main/java/com/huaga/life_echo/ui/screens/
  • app-android/app/src/main/java/com/huaga/life_echo/ui/viewmodel/
  • app-android/app/src/main/java/com/huaga/life_echo/feature/
  • app-android/app/src/main/java/com/huaga/life_echo/payment/
  • app-android/app/src/main/java/com/huaga/life_echo/data/repository/

Target Structural Result

feature/
  auth/
    presentation/
    domain/
    data/
    navigation/
  conversation/
    presentation/
    domain/
    data/
    navigation/
  memoir/
    presentation/
    domain/
    data/
    navigation/
  payment/
    presentation/
    domain/
    data/
    navigation/
  profile/
    presentation/
    domain/
    data/
    navigation/

Architecture Guidance

  • each feature owns its screen contracts, screen state holders, feature repositories, and feature navigation mapping
  • feature-local reusable UI stays inside the feature until it proves to be truly cross-feature
  • cross-feature reuse should be promoted to core, not copied into multiple features
  • the existing ports/adapters direction should be preserved and completed, not replaced

Step 5: Standardize UI State, Events, and Saved State Rules

Objective

Replace the current mixed state model with one consistent policy for screen state, local UI state, saved state, and navigation handling.

Primary Current Files and Directories

  • app-android/app/src/main/java/com/huaga/life_echo/ui/viewmodel/
  • app-android/app/src/main/java/com/huaga/life_echo/ui/screens/
  • app-android/app/src/main/java/com/huaga/life_echo/navigation/
  • app-android/app/src/main/java/com/huaga/life_echo/data/auth/TokenManager.kt
  • app-android/app/src/main/java/com/huaga/life_echo/ui/settings/AppSettings.kt

Target Structural Result

  • feature presentation packages expose explicit screen state contracts
  • screen-level state ownership is moved out of app-wide singleton objects
  • route and navigation decisions are driven by UI state and route state, not ad hoc callbacks from global singletons

Architecture Guidance

  • screen state belongs to screen-level state holders
  • short-lived UI element state stays local to Compose unless survival across recreation is required
  • SavedStateHandle is used for screen-restorable business state when needed
  • rememberSaveable is used only for UI-local restorable state
  • navigation is handled as a UI concern, not as a one-off event bus from ViewModel to UI
  • collectAsStateWithLifecycle becomes the standard collection pattern for screen state
  • LiveData should not become the primary state model in this refactor

Step 6: Rebuild Session, Settings, and App Configuration Boundaries

Objective

Separate app configuration, user session state, and user preferences into explicit persisted boundaries instead of process-wide singleton state surfaces.

Primary Current Files and Directories

  • app-android/app/src/main/java/com/huaga/life_echo/config/AppConfig.kt
  • app-android/app/src/main/java/com/huaga/life_echo/data/auth/TokenManager.kt
  • app-android/app/src/main/java/com/huaga/life_echo/data/preferences/TokenPreferences.kt
  • app-android/app/src/main/java/com/huaga/life_echo/ui/settings/AppSettings.kt

Target Structural Result

core/
  datastore/
    session/
    settings/
app/
  startup/
  di/

Architecture Guidance

  • persistent preferences move behind DataStore-backed boundaries
  • session state becomes a data concern, not a UI-observable global object
  • environment and server configuration become startup/configuration concerns, not feature-level global reads
  • one ownership model should exist for dark mode, typography preferences, session restoration, and app startup session checks

Step 7: Make the Data Layer Uniform Across Features

Objective

Move all features toward the same repository and data-source model so data access stops varying by feature.

Primary Current Files and Directories

  • app-android/app/src/main/java/com/huaga/life_echo/data/repository/
  • app-android/app/src/main/java/com/huaga/life_echo/data/database/
  • app-android/app/src/main/java/com/huaga/life_echo/network/models/
  • app-android/app/src/main/java/com/huaga/life_echo/feature/*/ports/
  • app-android/app/src/main/java/com/huaga/life_echo/feature/*/adapters/

Target Structural Result

feature/*/
  data/
    local/
    remote/
    repository/
    mapper/

Architecture Guidance

  • features read from repositories, not directly from transport adapters
  • local and remote responsibilities are separated
  • mapping responsibility is explicit instead of leaking DTOs through presentation
  • source-of-truth rules are documented per data set
  • offline-first behavior is applied where it adds product value and operational safety

Step 8: Introduce Domain Workflows Only Where Complexity Demands It

Objective

Extract only the business workflows that are too complex to live directly in screen state holders or repositories.

Primary Current Files and Directories

  • app-android/app/src/main/java/com/huaga/life_echo/ui/viewmodel/CreateMemoryViewModel.kt
  • app-android/app/src/main/java/com/huaga/life_echo/ui/viewmodel/AuthViewModel.kt
  • app-android/app/src/main/java/com/huaga/life_echo/ui/viewmodel/PaymentViewModel.kt
  • app-android/app/src/main/java/com/huaga/life_echo/feature/voice/
  • app-android/app/src/main/java/com/huaga/life_echo/network/WebSocketClient.kt

Target Structural Result

feature/*/
  domain/
    usecase/
    coordinator/
    policy/

Architecture Guidance

  • use cases should be introduced only for multi-step workflows
  • orchestration logic should move out of oversized ViewModels
  • realtime coordination, recording coordination, payment lifecycle handling, and session recovery are the first candidates
  • avoid creating a ceremonial domain layer for simple screens

Step 9: Stabilize View Interop Policy

Objective

Make the projects View Binding and Data Binding policy explicit so future Android UI work does not reintroduce architectural ambiguity.

Primary Current Files and Directories

  • app-android/app/build.gradle.kts
  • app-android/app/src/main/res/
  • any future View-based integration surfaces

Target Structural Result

  • Compose remains the default presentation technology
  • XML-based views, if required, are isolated and clearly owned

Architecture Guidance

  • use View Binding for retained or third-party-interoperability View surfaces
  • do not introduce Data Binding as a broad architectural pattern for new screens
  • if Data Binding is ever retained for a legacy surface, keep it isolated to that surface and do not spread it into feature-wide state architecture

Step 10: Split Stable Boundaries into Gradle Modules

Objective

Only after package boundaries are stable, move mature core and feature areas into Gradle modules.

Primary Current Files and Directories

  • app-android/settings.gradle.kts
  • app-android/build.gradle.kts
  • app-android/app/build.gradle.kts
  • all stabilized app/, core/, and feature/ directories

Target Structural Result

:app
:core:common
:core:model
:core:ui
:core:network
:core:database
:core:datastore
:core:testing
:feature:auth
:feature:conversation
:feature:memoir
:feature:payment
:feature:profile

Architecture Guidance

  • split modules only after ownership is already clear in packages
  • start with core modules that have the least feature churn
  • move feature modules after their presentation/data/domain contracts are stable
  • keep dependency direction one-way: app -> feature -> core

Step 11: Add List-Scale Infrastructure and Paging Where Needed

Objective

Introduce Paging 3 only after feature repositories and local/remote ownership are stable.

Primary Current Files and Directories

  • app-android/app/src/main/java/com/huaga/life_echo/ui/screens/ConversationListScreen.kt
  • app-android/app/src/main/java/com/huaga/life_echo/ui/screens/MyMemoirScreen.kt
  • app-android/app/src/main/java/com/huaga/life_echo/ui/screens/MyOrdersScreen.kt
  • corresponding future feature repositories and local data sources

Target Structural Result

  • list-heavy features gain an explicit paging layer inside feature data and presentation

Architecture Guidance

  • use Paging only where data volume or refresh cost justifies it
  • where local storage is the primary read surface, adopt the network-plus-database paging model
  • load state, transform, migration, and test strategy should be planned together rather than introduced piecemeal

Step 12: Finish with Architecture Cleanup and Convergence

Objective

Remove transitional structures and ensure the project no longer mixes old and new architecture styles.

Primary Current Files and Directories

  • app-android/app/src/main/java/com/huaga/life_echo/ui/
  • app-android/app/src/main/java/com/huaga/life_echo/data/
  • app-android/app/src/main/java/com/huaga/life_echo/network/
  • app-android/app/src/main/java/com/huaga/life_echo/utils/
  • any transitional compatibility wrappers created during the migration

Target Structural Result

  • obsolete horizontal folders are removed or reduced to clearly scoped core packages
  • feature ownership is visible from the top-level directory structure
  • app-wide conventions are uniform

Architecture Guidance

  • do not leave the codebase half horizontal and half feature-based
  • remove transitional adapters once all callers move to the new boundaries
  • update architecture documentation whenever the actual package or module graph changes

5. Step Ordering and Dependency Logic

The steps above should be executed in this order:

  1. establish architecture contract
  2. thin the app shell
  3. normalize shared core capabilities
  4. reorganize features in place
  5. standardize state and saved-state rules
  6. rebuild session and settings boundaries
  7. make repositories uniform
  8. extract complex domain workflows
  9. stabilize View interop policy
  10. split stable packages into modules
  11. add paging where justified
  12. remove transitional structure

This sequence matters because the project should first define ownership, then stabilize boundaries, then modularize, and only then add scaling infrastructure.


6. Explicit Non-Goals

The following are not goals of this plan:

  • a full rewrite of the Android client
  • a blanket migration to Data Binding
  • immediate introduction of Hilt or another DI framework as the primary refactor goal
  • immediate Paging adoption across all lists
  • preserving the current horizontal package structure
  • treating every feature as if it needs a full domain layer

7. Expected End State

When this plan is complete, the Android project should have:

  • a thin app shell
  • feature-oriented ownership
  • explicit state-holder rules
  • repository-centered data boundaries
  • Compose-first presentation
  • DataStore-backed app preferences and session boundaries
  • selective domain orchestration for complex workflows
  • stable core and feature packages ready for modularization
  • Gradle modules aligned with product boundaries instead of implementation detail folders

At that point, future Android work should default to adding behavior inside existing feature boundaries rather than creating new top-level horizontal folders.