文档: 添加 app-android 架构重构计划
This commit is contained in:
739
docs/todo/2026-03-12-app-android-overall-architecture-plan.md
Normal file
739
docs/todo/2026-03-12-app-android-overall-architecture-plan.md
Normal file
@@ -0,0 +1,739 @@
|
||||
# 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:
|
||||
|
||||
- `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
|
||||
|
||||
```text
|
||||
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
|
||||
|
||||
```text
|
||||
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**
|
||||
|
||||
```text
|
||||
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**
|
||||
|
||||
```text
|
||||
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**
|
||||
|
||||
```text
|
||||
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**
|
||||
|
||||
```text
|
||||
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**
|
||||
|
||||
```text
|
||||
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**
|
||||
|
||||
```text
|
||||
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.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**
|
||||
|
||||
```text
|
||||
: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.
|
||||
@@ -1,60 +0,0 @@
|
||||
# TODO: 语音实时分段上传的取消逻辑
|
||||
|
||||
## 背景
|
||||
|
||||
当前 `cancelRecordingVoice()` 只做了:停止录音、删除当前临时文件、重置会话状态,**没有**处理:
|
||||
|
||||
- 已入队的待上传分段(`pendingVoiceSegmentStore`)
|
||||
- 重试任务(`pendingSegmentRetryJob`)
|
||||
- 正在执行的 `dispatchPendingVoiceSegment` 协程
|
||||
|
||||
用户取消后,已切好的分段仍可能被发送或重试。
|
||||
|
||||
## 目标
|
||||
|
||||
用户点击「取消」后:当前语音会话的所有分段不再上传、不再重试,并释放相关资源。
|
||||
|
||||
---
|
||||
|
||||
## TODO 列表
|
||||
|
||||
### 1. ViewModel 增加「已取消会话」集合
|
||||
|
||||
- [ ] 在 `CreateMemoryViewModel` 中增加:`cancelledVoiceSessionIds: MutableSet<String>`
|
||||
- [ ] 在开始新一轮录音时(如 `startRecordingVoice()` 或 `ensureVoiceSessionStarted()`)清理该集合,避免无限增长(可选:只保留最近 N 个或按会话生命周期清理)
|
||||
|
||||
### 2. 完善 `cancelRecordingVoice()`
|
||||
|
||||
- [ ] 在调用 `resetVoiceSessionState()` **之前** 保存 `currentVoiceSessionId` 到局部变量 `sessionId`
|
||||
- [ ] 若 `sessionId != null`:
|
||||
- [ ] `cancelledVoiceSessionIds.add(sessionId)`
|
||||
- [ ] `pendingSegmentRetryJob?.cancel()`
|
||||
- [ ] 列出该 session 的所有待发分段并逐个从 store 删除:
|
||||
- 若 Store 无按 `voiceSessionId` 查询:用 `pendingVoiceSegmentStore.listAll().filter { it.voiceSessionId == sessionId }`,对每个调用 `pendingVoiceSegmentStore.remove(segment.clientSegmentId)`
|
||||
- [ ] 再调用 `resetVoiceSessionState()`(保持现有逻辑)
|
||||
|
||||
### 3. `dispatchPendingVoiceSegment` 开头做取消判断
|
||||
|
||||
- [ ] 在 `tryAcquirePendingDispatch` 之后、`ensureConversationReadyForSegmentUpload()` 之前增加:
|
||||
- 若 `pendingSegment.voiceSessionId in cancelledVoiceSessionIds`:
|
||||
- `pendingVoiceSegmentStore.remove(pendingSegment.clientSegmentId)`
|
||||
- `releasePendingDispatch(pendingSegment.clientSegmentId)`
|
||||
- `return`
|
||||
- [ ] 确保 `finally` 中 `releasePendingDispatch` 仍对正常路径执行(现有逻辑保持不变)
|
||||
|
||||
### 4. (可选)Store 按 session 列举
|
||||
|
||||
- [ ] 在 `PendingVoiceSegmentStore` 增加 `listByVoiceSession(voiceSessionId: String): List<PendingVoiceSegment>`(例如基于 `listAll().filter { it.voiceSessionId == voiceSessionId }`)
|
||||
- [ ] 在 `cancelRecordingVoice()` 中用 `listByVoiceSession(sessionId)` 替代 `listAll().filter { ... }`,减少全量列举
|
||||
|
||||
---
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `app-android/app/src/main/java/com/huaga/life_echo/ui/viewmodel/CreateMemoryViewModel.kt`
|
||||
- `app-android/app/src/main/java/com/huaga/life_echo/feature/voice/PendingVoiceSegmentStore.kt`(仅当做第 4 项时)
|
||||
|
||||
## 验收
|
||||
|
||||
- 长按录音并触发至少一次自动切片(如 30s)后,点击取消:本地待发分段被清除,不再上传,重试任务不执行。
|
||||
- 取消后再次录音:新会话正常录音与分段上传,无旧 session 分段被发送。
|
||||
Reference in New Issue
Block a user