Skip to content

Commit bb60fed

Browse files
authored
Merge pull request #80 from android/mlykotom/improve-di-container
Improve retrieving ViewModels
2 parents 8c3aae5 + 667571b commit bb60fed

File tree

17 files changed

+88
-122
lines changed

17 files changed

+88
-122
lines changed

.github/workflows/Fruitties.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ on:
99
paths:
1010
- 'Fruitties/**'
1111
- '.github/workflows/Fruitties.yml'
12-
pull_request:
13-
paths:
14-
- 'Fruitties/**'
15-
- '.github/workflows/Fruitties.yml'
12+
pull_request:
13+
paths:
14+
- 'Fruitties/**'
15+
- '.github/workflows/Fruitties.yml'
1616

1717
concurrency:
1818
group: build-${{ github.ref }}

Fruitties/androidApp/src/main/AndroidManifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
android:dataExtractionRules="@xml/data_extraction_rules"
2323
android:supportsRtl="true"
2424
android:theme="@style/AppTheme"
25-
android:name=".di.App">
25+
android:name=".FruittiesAndroidApp">
2626
<activity
2727
android:name=".MainActivity"
2828
android:exported="true">

Fruitties/androidApp/src/main/java/com/example/fruitties/android/di/App.kt renamed to Fruitties/androidApp/src/main/java/com/example/fruitties/android/FruittiesAndroidApp.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@
1414
* limitations under the License.
1515
*/
1616

17-
package com.example.fruitties.android.di
17+
package com.example.fruitties.android
1818

1919
import android.app.Application
20+
import androidx.compose.runtime.staticCompositionLocalOf
2021
import com.example.fruitties.di.AppContainer
2122
import com.example.fruitties.di.Factory
2223

23-
class App : Application() {
24+
class FruittiesAndroidApp : Application() {
2425
/** AppContainer instance used by the rest of classes to obtain dependencies */
2526
lateinit var container: AppContainer
2627

@@ -29,3 +30,11 @@ class App : Application() {
2930
container = AppContainer(Factory(this))
3031
}
3132
}
33+
34+
/**
35+
* Allows retrieving the AppContainer, which represents a DI graph everywhere from a composable.
36+
* Because the [AppContainer] is effectively a singleton, we can use static composition local,
37+
* because it won't change during the app execution.
38+
*/
39+
val LocalAppContainer =
40+
staticCompositionLocalOf<AppContainer> { error("No AppContainer provided!") }

Fruitties/androidApp/src/main/java/com/example/fruitties/android/MainActivity.kt

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import androidx.compose.foundation.layout.fillMaxSize
2424
import androidx.compose.material3.MaterialTheme
2525
import androidx.compose.material3.Surface
2626
import androidx.compose.runtime.Composable
27+
import androidx.compose.runtime.CompositionLocalProvider
2728
import androidx.compose.ui.Modifier
2829
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
2930
import androidx.navigation3.runtime.NavKey
@@ -35,6 +36,7 @@ import androidx.navigation3.ui.NavDisplay
3536
import androidx.navigation3.ui.rememberSceneSetupNavEntryDecorator
3637
import com.example.fruitties.android.ui.CartScreen
3738
import com.example.fruitties.android.ui.FruittieScreen
39+
import com.example.fruitties.android.ui.FruittiesTheme
3840
import com.example.fruitties.android.ui.ListScreen
3941
import kotlinx.serialization.Serializable
4042

@@ -54,12 +56,16 @@ class MainActivity : ComponentActivity() {
5456
super.onCreate(savedInstanceState)
5557
enableEdgeToEdge()
5658
setContent {
57-
MyApplicationTheme {
58-
Surface(
59-
modifier = Modifier.fillMaxSize(),
60-
color = MaterialTheme.colorScheme.background,
61-
) {
62-
NavApp()
59+
CompositionLocalProvider(
60+
LocalAppContainer provides (this.applicationContext as FruittiesAndroidApp).container,
61+
) {
62+
FruittiesTheme {
63+
Surface(
64+
modifier = Modifier.fillMaxSize(),
65+
color = MaterialTheme.colorScheme.background,
66+
) {
67+
NavApp()
68+
}
6369
}
6470
}
6571
}

Fruitties/androidApp/src/main/java/com/example/fruitties/android/ui/CartScreen.kt

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -50,35 +50,23 @@ import androidx.compose.runtime.getValue
5050
import androidx.compose.ui.Alignment
5151
import androidx.compose.ui.Modifier
5252
import androidx.compose.ui.graphics.Color
53-
import androidx.compose.ui.platform.LocalContext
5453
import androidx.compose.ui.res.stringResource
5554
import androidx.compose.ui.text.style.TextAlign
5655
import androidx.compose.ui.tooling.preview.Preview
5756
import androidx.compose.ui.unit.dp
5857
import androidx.lifecycle.viewmodel.compose.viewModel
59-
import com.example.fruitties.android.MyApplicationTheme
58+
import com.example.fruitties.android.LocalAppContainer
6059
import com.example.fruitties.android.R
61-
import com.example.fruitties.android.di.App
6260
import com.example.fruitties.model.CartItemDetails
6361
import com.example.fruitties.model.Fruittie
6462
import com.example.fruitties.viewmodel.CartUiState
6563
import com.example.fruitties.viewmodel.CartViewModel
66-
import com.example.fruitties.viewmodel.creationExtras
6764

6865
@Composable
69-
fun CartScreen(onNavBarBack: () -> Unit) {
70-
// Instantiate a ViewModel with a dependency on the AppContainer.
71-
// To make ViewModel compatible with KMP, the ViewModel factory must
72-
// create an instance without referencing the Android Application.
73-
// Here we put the KMP-compatible AppContainer into the extras
74-
// so it can be passed to the ViewModel factory.
75-
val app = LocalContext.current.applicationContext as App
76-
77-
val viewModel: CartViewModel = viewModel(
78-
factory = CartViewModel.Factory,
79-
extras = creationExtras(app.container),
80-
)
81-
66+
fun CartScreen(
67+
onNavBarBack: () -> Unit,
68+
viewModel: CartViewModel = viewModel(factory = LocalAppContainer.current.cartViewModelFactory),
69+
) {
8270
val cartState by viewModel.cartUiState.collectAsState()
8371

8472
CartScreen(
@@ -200,7 +188,7 @@ fun CartItem(
200188
@Preview
201189
@Composable
202190
private fun CartScreenPreview() {
203-
MyApplicationTheme {
191+
FruittiesTheme {
204192
CartScreen(
205193
onNavBarBack = {},
206194
cartState = CartUiState(
@@ -240,7 +228,7 @@ private fun CartScreenPreview() {
240228
@Preview
241229
@Composable
242230
private fun CartItemPreview() {
243-
MyApplicationTheme {
231+
FruittiesTheme {
244232
CartItem(
245233
cartItem = CartItemDetails(
246234
fruittie = Fruittie(

Fruitties/androidApp/src/main/java/com/example/fruitties/android/ui/FruittieScreen.kt

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,11 @@ import androidx.compose.runtime.Composable
2222
import androidx.compose.runtime.collectAsState
2323
import androidx.compose.ui.Alignment
2424
import androidx.compose.ui.Modifier
25-
import androidx.compose.ui.platform.LocalContext
2625
import androidx.compose.ui.res.stringResource
2726
import androidx.compose.ui.unit.dp
2827
import androidx.lifecycle.viewmodel.compose.viewModel
28+
import com.example.fruitties.android.LocalAppContainer
2929
import com.example.fruitties.android.R
30-
import com.example.fruitties.android.di.App
3130
import com.example.fruitties.model.Fruittie
3231
import com.example.fruitties.viewmodel.FruittieViewModel
3332
import com.example.fruitties.viewmodel.FruittieViewModel.Companion.FRUITTIE_ID_KEY
@@ -38,16 +37,14 @@ import com.example.fruitties.viewmodel.creationExtras
3837
fun FruittieScreen(
3938
fruittieId: Long,
4039
onNavBarBack: () -> Unit,
41-
) {
42-
val app = LocalContext.current.applicationContext as App
43-
44-
val viewModel: FruittieViewModel = viewModel(
45-
factory = FruittieViewModel.Factory,
46-
extras = creationExtras(app.container) {
40+
viewModel: FruittieViewModel = viewModel(
41+
key = "fruittie_$fruittieId",
42+
factory = LocalAppContainer.current.fruittieViewModelFactory,
43+
extras = creationExtras {
4744
set(FRUITTIE_ID_KEY, fruittieId)
4845
},
49-
)
50-
46+
),
47+
) {
5148
val state = viewModel.state.collectAsState().value
5249

5350
FruittieScreen(

Fruitties/androidApp/src/main/java/com/example/fruitties/android/MyApplicationTheme.kt renamed to Fruitties/androidApp/src/main/java/com/example/fruitties/android/ui/FruittiesTheme.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package com.example.fruitties.android
16+
package com.example.fruitties.android.ui
1717

1818
import androidx.compose.foundation.isSystemInDarkTheme
1919
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -31,7 +31,7 @@ import androidx.compose.ui.unit.dp
3131
import androidx.compose.ui.unit.sp
3232

3333
@Composable
34-
fun MyApplicationTheme(
34+
fun FruittiesTheme(
3535
darkTheme: Boolean = isSystemInDarkTheme(),
3636
content: @Composable () -> Unit,
3737
) {

Fruitties/androidApp/src/main/java/com/example/fruitties/android/ui/ListScreen.kt

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -45,35 +45,25 @@ import androidx.compose.runtime.getValue
4545
import androidx.compose.ui.Alignment
4646
import androidx.compose.ui.Modifier
4747
import androidx.compose.ui.input.nestedscroll.nestedScroll
48-
import androidx.compose.ui.platform.LocalContext
4948
import androidx.compose.ui.res.stringResource
5049
import androidx.compose.ui.text.style.TextOverflow
5150
import androidx.compose.ui.tooling.preview.Preview
5251
import androidx.compose.ui.unit.dp
5352
import androidx.lifecycle.viewmodel.compose.viewModel
53+
import com.example.fruitties.android.LocalAppContainer
5454
import com.example.fruitties.android.R
55-
import com.example.fruitties.android.di.App
5655
import com.example.fruitties.model.Fruittie
5756
import com.example.fruitties.viewmodel.MainViewModel
58-
import com.example.fruitties.viewmodel.creationExtras
5957

6058
@OptIn(ExperimentalMaterial3Api::class)
6159
@Composable
6260
fun ListScreen(
6361
onClickViewCart: () -> Unit,
6462
onFruittieClick: (Fruittie) -> Unit,
63+
viewModel: MainViewModel = viewModel(
64+
factory = LocalAppContainer.current.mainViewModelFactory,
65+
),
6566
) {
66-
// Instantiate a ViewModel with a dependency on the AppContainer.
67-
// To make ViewModel compatible with KMP, the ViewModel factory must
68-
// create an instance without referencing the Android Application.
69-
// Here we put the KMP-compatible AppContainer into the extras
70-
// so it can be passed to the ViewModel factory.
71-
val app = LocalContext.current.applicationContext as App
72-
val viewModel: MainViewModel = viewModel(
73-
factory = MainViewModel.Factory,
74-
extras = creationExtras(app.container),
75-
)
76-
7767
val uiState by viewModel.homeUiState.collectAsState()
7868
val topAppBarScrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
7969
Scaffold(

Fruitties/gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ composeBom = "2025.06.01"
2626
dataStore = "1.1.7"
2727
kotlin = "2.2.0"
2828
kotlinx-coroutines = "1.10.2"
29-
kotlinxDatetime = "0.7.0-0.6.x-compat"
29+
kotlinxDatetime = "0.7.0"
3030
ksp = "2.2.0-2.0.2"
3131
ktorVersion = "3.2.1"
3232
pagingComposeAndroid = "3.3.6"

Fruitties/iosApp/iosApp/ui/CartView.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@ struct CartView: View {
3030
/// The `CartViewModel.Factory` and `creationExtras` are provided to enable dependency injection
3131
/// and proper initialization of the ViewModel with its required `AppContainer`.
3232
let cartViewModel: CartViewModel = viewModelStoreOwner.viewModel(
33-
factory: CartViewModel.companion.Factory,
34-
extras: creationExtras(appContainer: appContainer.value)
33+
factory: appContainer.value.cartViewModelFactory
3534
)
3635

3736
/// Observes the `cartUiState` `StateFlow` from the `CartViewModel` using SKIE's `Observing` utility.

0 commit comments

Comments
 (0)