@@ -45,6 +45,7 @@ GEO=false E2E=true ./gradlew assembleDevRelease
4545## Architecture Overview
4646
4747### Tech Stack
48+
4849- ** Language** : Kotlin
4950- ** UI Framework** : Jetpack Compose with Material3
5051- ** Architecture** : MVVM with Hilt dependency injection
@@ -57,6 +58,7 @@ GEO=false E2E=true ./gradlew assembleDevRelease
5758- ** Storage** : DataStore with json files
5859
5960### Project Structure
61+
6062- ** app/src/main/java/to/bitkit/**
6163 - ** App.kt** : Application class with Hilt setup
6264 - ** ui/** : All UI components
@@ -75,34 +77,42 @@ GEO=false E2E=true ./gradlew assembleDevRelease
7577 - ** usecases/** : Domain layer: use cases
7678
7779### Key Architecture Patterns
80+
78811 . ** Single Activity Architecture** : MainActivity hosts all screens via Compose Navigation
79822 . ** Repository Pattern** : Repositories abstract data sources from ViewModels
80833 . ** Service Layer** : Core business logic in services (LightningService, WalletService)
81844 . ** Reactive State Management** : ViewModels expose UI state via StateFlow
82855 . ** Coroutine-based Async** : All async operations use Kotlin coroutines
8386
8487### Build Variants
88+
8589- ** dev** : Regtest network for development
8690- ** tnet** : Testnet network
8791- ** mainnet** : Production (currently commented out)
8892
8993## Common Pitfalls
9094
9195### ❌ DON'T
96+
9297``` kotlin
9398GlobalScope .launch { } // Use viewModelScope
9499val result = nullable!! .doSomething() // Use safe calls
95100Text (" Send Payment" ) // Use string resources
96101class Service (@Inject val vm : ViewModel ) // Never inject VMs
102+
97103suspend fun getData () = runBlocking { } // Use withContext
98104```
99105
100106### ✅ DO
107+
101108``` kotlin
102109viewModelScope.launch { }
103110val result = nullable?.doSomething() ? : default
104111Text (stringResource(R .string.send_payment))
105- class Service { fun process (data : Data ) }
112+ class Service {
113+ fun process (data : Data )
114+ }
115+
106116suspend fun getData () = withContext(Dispatchers .IO ) { }
107117```
108118
@@ -117,29 +127,32 @@ suspend fun getData() = withContext(Dispatchers.IO) { }
117127## Common Patterns
118128
119129### ViewModel State
130+
120131``` kotlin
121132private val _uiState = MutableStateFlow (InitialState )
122133val uiState: StateFlow <UiState > = _uiState .asStateFlow()
123134
124135fun updateState (action : Action ) {
125- viewModelScope.launch {
126- _uiState .update { it.copy(/* fields */ ) }
127- }
136+ viewModelScope.launch {
137+ _uiState .update { it.copy(/* fields */ ) }
138+ }
128139}
129140```
130141
131142### Repository
143+
132144``` kotlin
133145suspend fun getData (): Result <Data > = withContext(Dispatchers .IO ) {
134- runCatching {
135- Result .success(apiService.fetchData())
136- }.onFailure { e ->
137- Logger .error(" Failed" , e = e, context = TAG )
138- }
146+ runCatching {
147+ Result .success(apiService.fetchData())
148+ }.onFailure { e ->
149+ Logger .error(" Failed" , e = e, context = TAG )
150+ }
139151}
140152```
141153
142154### Rules
155+
143156- USE coding rules from ` .cursor/default.rules.mdc `
144157- ALWAYS run ` ./gradlew compileDevDebugKotlin ` after code changes to verify code compiles
145158- ALWAYS run ` ./gradlew testDevDebugUnitTest ` after code changes to verify tests succeed and fix accordingly
@@ -150,7 +163,7 @@ suspend fun getData(): Result<Data> = withContext(Dispatchers.IO) {
150163- USE ` git diff HEAD sourceFilePath ` to diff an uncommitted file against the last commit
151164- ALWAYS check existing code patterns before implementing new features
152165- USE existing extensions and utilities rather than creating new ones
153- - ALWAYS consider applying YAGNI (You Aren ' t Gonna Need It) principle for new code
166+ - ALWAYS consider applying YAGNI (You Aren't Gonna Need It) principle for new code
154167- ALWAYS reuse existing constants
155168- ALWAYS ensure a method exist before calling it
156169- ALWAYS remove unused code after refactors
@@ -184,11 +197,14 @@ suspend fun getData(): Result<Data> = withContext(Dispatchers.IO) {
184197- ALWAYS add business logic to Repository layer via methods returning ` Result<T> ` and use it in ViewModels
185198- ALWAYS use services to wrap RUST code exposed via bindings
186199- ALWAYS order upstream architectural data flow this way: ` UI -> ViewModel -> Repository -> RUST ` and vice-versa for downstream
187- - ALWAYS add new string string resources in alphabetical order in `strings.xml`
200+ - ALWAYS add new localizable string string resources in alphabetical order in ` strings.xml `
201+ - NEVER add string resources for strings used only in dev settings screens and previews and never localize acronyms
188202- ALWAYS use template in ` .github/pull_request_template.md ` for PR descriptions
189203- ALWAYS wrap ` ULong ` numbers with ` USat ` in arithmetic operations, to guard against overflows
204+ - PREFER to use one-liners with ` run {} ` when applicable, e.g. ` override fun someCall(value: String) = run { this.value = value} `
190205
191206### Architecture Guidelines
207+
192208- Use ` LightningNodeService ` to manage background notifications while the node is running
193209- Use ` LightningService ` to wrap node's RUST APIs and manage the inner lifecycle of the node
194210- Use ` LightningRepo ` to defining the business logic for the node operations, usually delegating to ` LightningService `
0 commit comments