From eb721f82951a77d26af6d94b6a7e922a912bca15 Mon Sep 17 00:00:00 2001 From: Don Turner Date: Thu, 20 Oct 2022 12:39:41 +0100 Subject: [PATCH] Add MainActivity view model --- app/build.gradle.kts | 1 + .../samples/apps/nowinandroid/MainActivity.kt | 52 ++++++++----------- .../nowinandroid/MainActivityViewModel.kt | 48 +++++++++++++++++ 3 files changed, 72 insertions(+), 29 deletions(-) create mode 100644 app/src/main/java/com/google/samples/apps/nowinandroid/MainActivityViewModel.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f7d561dbc..6d3a8e6b5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -106,6 +106,7 @@ dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.splashscreen) implementation(libs.androidx.compose.runtime) + implementation(libs.androidx.lifecycle.runtimeCompose) implementation(libs.androidx.compose.runtime.tracing) implementation(libs.androidx.compose.material3.windowSizeClass) implementation(libs.androidx.hilt.navigation.compose) diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/MainActivity.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/MainActivity.kt index 3f9af2a4d..09ea0f2ff 100644 --- a/app/src/main/java/com/google/samples/apps/nowinandroid/MainActivity.kt +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/MainActivity.kt @@ -19,6 +19,7 @@ package com.google.samples.apps.nowinandroid import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.activity.viewModels import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass @@ -34,13 +35,11 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.metrics.performance.JankStats import com.google.accompanist.systemuicontroller.rememberSystemUiController -import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository +import com.google.samples.apps.nowinandroid.MainActivityUiState.Loading +import com.google.samples.apps.nowinandroid.MainActivityUiState.Success import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand -import com.google.samples.apps.nowinandroid.core.model.data.UserData -import com.google.samples.apps.nowinandroid.core.result.Result -import com.google.samples.apps.nowinandroid.core.result.asResult import com.google.samples.apps.nowinandroid.ui.NiaApp import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @@ -58,25 +57,20 @@ class MainActivity : ComponentActivity() { @Inject lateinit var lazyStats: dagger.Lazy - @Inject - lateinit var userDataRepository: UserDataRepository + val viewModel: MainActivityViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { val splashScreen = installSplashScreen() super.onCreate(savedInstanceState) - /** - * The current user data, updated here to drive the UI theme - */ - var userDataResult: Result by mutableStateOf(Result.Loading) + var uiState: MainActivityUiState by mutableStateOf(Loading) - // Update the user data + // Update the uiState lifecycleScope.launch { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { - userDataRepository.userDataStream - .asResult() + viewModel.uiState .onEach { - userDataResult = it + uiState = it } .collect() } @@ -84,9 +78,9 @@ class MainActivity : ComponentActivity() { // Keep the splash screen on-screen until the user data is loaded splashScreen.setKeepOnScreenCondition { - when (userDataResult) { - Result.Loading -> true - is Result.Success, is Result.Error -> false + when (uiState) { + Loading -> true + is Success -> false } } @@ -96,7 +90,7 @@ class MainActivity : ComponentActivity() { setContent { val systemUiController = rememberSystemUiController() - val darkTheme = shouldUseDarkTheme(userDataResult) + val darkTheme = shouldUseDarkTheme(uiState) // Update the dark content of the system bars to match the theme DisposableEffect(systemUiController, darkTheme) { @@ -106,7 +100,7 @@ class MainActivity : ComponentActivity() { NiaTheme( darkTheme = darkTheme, - androidTheme = shouldUseAndroidTheme(userDataResult) + androidTheme = shouldUseAndroidTheme(uiState) ) { NiaApp( windowSizeClass = calculateWindowSizeClass(this), @@ -127,29 +121,29 @@ class MainActivity : ComponentActivity() { } /** - * Returns `true` if the Android theme should be used, as a function of the [userDataResult]. + * Returns `true` if the Android theme should be used, as a function of the [uiState]. */ @Composable fun shouldUseAndroidTheme( - userDataResult: Result, -): Boolean = when (userDataResult) { - Result.Loading, is Result.Error -> false - is Result.Success -> when (userDataResult.data.themeBrand) { + uiState: MainActivityUiState, +): Boolean = when (uiState) { + Loading -> false + is Success -> when (uiState.userData.themeBrand) { ThemeBrand.DEFAULT -> false ThemeBrand.ANDROID -> true } } /** - * Returns `true` if dark theme should be used, as a function of the [userDataResult] and the + * Returns `true` if dark theme should be used, as a function of the [uiState] and the * current system context. */ @Composable fun shouldUseDarkTheme( - userDataResult: Result, -): Boolean = when (userDataResult) { - Result.Loading, is Result.Error -> isSystemInDarkTheme() - is Result.Success -> when (userDataResult.data.darkThemeConfig) { + uiState: MainActivityUiState, +): Boolean = when (uiState) { + Loading -> isSystemInDarkTheme() + is Success -> when (uiState.userData.darkThemeConfig) { DarkThemeConfig.FOLLOW_SYSTEM -> isSystemInDarkTheme() DarkThemeConfig.LIGHT -> false DarkThemeConfig.DARK -> true diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/MainActivityViewModel.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/MainActivityViewModel.kt new file mode 100644 index 000000000..885fb6c3d --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/MainActivityViewModel.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.google.samples.apps.nowinandroid.MainActivityUiState.Loading +import com.google.samples.apps.nowinandroid.MainActivityUiState.Success +import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository +import com.google.samples.apps.nowinandroid.core.model.data.UserData +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +@HiltViewModel +class MainActivityViewModel @Inject constructor( + userDataRepository: UserDataRepository +) : ViewModel() { + val uiState: StateFlow = userDataRepository.userDataStream.map { + Success(it) + }.stateIn( + scope = viewModelScope, + initialValue = Loading, + started = SharingStarted.WhileSubscribed(5_000) + ) +} + +sealed interface MainActivityUiState { + object Loading : MainActivityUiState + data class Success(val userData: UserData) : MainActivityUiState +}