26 KiB
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.
Official Reference Links
Architecture Foundations
- https://developer.android.com/topic/architecture/recommendations
- https://developer.android.com/topic/architecture
- https://developer.android.com/guide/components/fundamentals
- https://developer.android.com/topic/architecture/domain-layer
- https://developer.android.com/topic/architecture/data-layer
- https://developer.android.com/topic/architecture/data-layer/offline-first
UI Layer and State
- https://developer.android.com/topic/architecture/ui-layer
- https://developer.android.com/topic/architecture/ui-layer/events
- https://developer.android.com/topic/architecture/ui-layer/stateholders
- https://developer.android.com/topic/architecture/ui-layer/state-production
- https://developer.android.com/topic/libraries/architecture/compose
- https://developer.android.com/topic/libraries/architecture/saving-states
ViewModel and Lifecycle
- https://developer.android.com/topic/libraries/architecture/lifecycle
- https://developer.android.com/topic/libraries/architecture/viewmodel
- https://developer.android.com/topic/libraries/architecture/viewmodel/viewmodel-factories
- https://developer.android.com/topic/libraries/architecture/viewmodel/viewmodel-apis
- https://developer.android.com/topic/libraries/architecture/viewmodel/viewmodel-savedstate
- https://developer.android.com/topic/libraries/architecture/viewmodel/viewmodel-cheatsheet
- https://developer.android.com/topic/libraries/architecture/livedata
- https://developer.android.com/topic/libraries/architecture/coroutines
View Binding and Data Binding
- https://developer.android.com/topic/libraries/view-binding
- https://developer.android.com/topic/libraries/view-binding/migration
- https://developer.android.com/topic/libraries/data-binding
- https://developer.android.com/topic/libraries/data-binding/start
- https://developer.android.com/topic/libraries/data-binding/expressions
- https://developer.android.com/topic/libraries/data-binding/observability
- https://developer.android.com/topic/libraries/data-binding/generated-binding
- https://developer.android.com/topic/libraries/data-binding/binding-adapters
- https://developer.android.com/topic/libraries/data-binding/architecture
- https://developer.android.com/topic/libraries/data-binding/two-way
Data, Persistence, and Startup
- https://developer.android.com/topic/libraries/architecture/datastore
- https://developer.android.com/topic/libraries/app-startup
Paging
- https://developer.android.com/topic/libraries/architecture/paging/v3-overview
- https://developer.android.com/topic/libraries/architecture/paging/v3-paged-data
- https://developer.android.com/topic/libraries/architecture/paging/v3-network-db
- https://developer.android.com/topic/libraries/architecture/paging/v3-transform
- https://developer.android.com/topic/libraries/architecture/paging/load-state
- https://developer.android.com/topic/libraries/architecture/paging/test
- https://developer.android.com/topic/libraries/architecture/paging/v3-migration
Modularization
- https://developer.android.com/topic/modularization
- https://developer.android.com/topic/modularization/patterns
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:
authconversationmemoirpaymentprofile
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:
:appis 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/, andfeature/ - 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, andAppConfig - 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.ktapp-android/app/src/main/java/com/huaga/life_echo/app/LifeEchoApp.ktapp-android/app/src/main/java/com/huaga/life_echo/app/AppContainer.ktapp-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
:appmodule - 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/andapp/di/navigation/->app/navigation/and feature-scopednavigation/ui/screens/->feature/*/presentation/ui/viewmodel/->feature/*/presentation/data/repository/->feature/*/data/orcore/*depending on scopedata/database/->core/database/data/preferences/and settings/session storage ->core/datastore/data/auth/->core/datastore/orfeature/auth/data/depending on responsibilitynetwork/->core/network/payment/->feature/payment/- shared reusable Compose UI ->
core/ui/ - generic helpers in
utils/-> redistributed intocore/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.ktsapp-android/app/build.gradle.ktsapp-android/app/src/main/AndroidManifest.xmlapp-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/, andfeature/ - one agreed migration rule for what stays in
:appfor 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/screensandui/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.ktapp-android/app/src/main/java/com/huaga/life_echo/app/LifeEchoApp.ktapp-android/app/src/main/java/com/huaga/life_echo/app/AppContainer.ktapp-android/app/src/main/java/com/huaga/life_echo/navigation/app-android/app/src/main/java/com/huaga/life_echo/config/AppConfig.ktapp-android/app/src/main/AndroidManifest.xmlapp-android/app/src/main/java/com/huaga/life_echo/wxapi/
Target Structural Result
app/
entry/
navigation/
startup/
di/
Architecture Guidance
MainActivitybecomes a host for root Compose content and system UI coordination onlyLifeEchoAppbecomes 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/networkowns transport primitives, adapters, runtime providers, and network policycore/databaseowns Room entities, DAOs, and database assemblycore/datastoreowns preference-like and session-like persisted app datacore/uiowns reusable design-system-level Compose primitives, not feature-specific widgetsutils/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/adaptersdirection 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.ktapp-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
SavedStateHandleis used for screen-restorable business state when neededrememberSaveableis 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
collectAsStateWithLifecyclebecomes the standard collection pattern for screen stateLiveDatashould 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.ktapp-android/app/src/main/java/com/huaga/life_echo/data/auth/TokenManager.ktapp-android/app/src/main/java/com/huaga/life_echo/data/preferences/TokenPreferences.ktapp-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.ktapp-android/app/src/main/java/com/huaga/life_echo/ui/viewmodel/AuthViewModel.ktapp-android/app/src/main/java/com/huaga/life_echo/ui/viewmodel/PaymentViewModel.ktapp-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 project’s 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.ktsapp-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.ktsapp-android/build.gradle.ktsapp-android/app/build.gradle.kts- all stabilized
app/,core/, andfeature/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
coremodules 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.ktapp-android/app/src/main/java/com/huaga/life_echo/ui/screens/MyMemoirScreen.ktapp-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:
- establish architecture contract
- thin the app shell
- normalize shared core capabilities
- reorganize features in place
- standardize state and saved-state rules
- rebuild session and settings boundaries
- make repositories uniform
- extract complex domain workflows
- stabilize View interop policy
- split stable packages into modules
- add paging where justified
- 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
coreandfeaturepackages 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.