refactor(ui): Replace waitForState and shareLatest with lifecycle-aware state collection

Add collectAsStateWithLifecycle for all Compose state collection (except Glance widgets). Add asLiveState() extension to ViewModel2 as a simpler replacement for shareLatest(scope = vmScope). Remove custom waitForState helper and shareLatest utility.
This commit is contained in:
darken
2026-03-03 17:21:55 +00:00
committed by Matthias Urhahn
parent e13331dfcf
commit cd6488a713
28 changed files with 63 additions and 94 deletions
-2
View File
@@ -24,8 +24,6 @@ PR titles appear in auto-generated changelogs and are read by users. Use **ELI5,
| **General** | Dashboard, settings, notifications, themes, onboarding, support, app-wide UI |
| **Fix** | Bug fixes spanning multiple areas |
For non-user-facing PRs (refactors, tests, CI, dependency bumps, translations), apply the `changelog-ignore` GitHub label. These don't need user-facing titles.
### Title Examples
- `Widget: Add color themes and transparency slider`
@@ -1,13 +0,0 @@
package eu.darken.capod.common.compose
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.produceState
import kotlinx.coroutines.flow.Flow
@Composable
fun <T> waitForState(flow: Flow<T>): State<T?> {
return produceState(initialValue = null) {
flow.collect { value = it }
}
}
@@ -9,13 +9,12 @@ import androidx.activity.viewModels
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.graphics.toArgb
import androidx.core.view.WindowCompat
import dagger.hilt.android.AndroidEntryPoint
import eu.darken.capod.common.compose.waitForState
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.common.theming.CapodTheme
import eu.darken.capod.common.uix.Activity2
@@ -41,7 +40,7 @@ class RecorderActivity : Activity2() {
}
setContent {
val themeState by generalSettings.themeState.collectAsState(initial = generalSettings.currentThemeState)
val themeState by generalSettings.themeState.collectAsStateWithLifecycle(initialValue = generalSettings.currentThemeState)
CapodTheme(state = themeState) {
val backgroundColor = MaterialTheme.colorScheme.background
val useDarkIcons = backgroundColor.luminance() > 0.5f
@@ -61,7 +60,7 @@ class RecorderActivity : Activity2() {
}
}
val state by waitForState(vm.state)
val state by vm.state.collectAsStateWithLifecycle(initialValue = null)
state?.let {
RecorderScreen(
state = it,
@@ -14,41 +14,17 @@ import kotlinx.coroutines.flow.WhileSubscribed
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.scan
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.flow.transformWhile
import kotlin.time.Duration
/**
* Create a stateful flow, with the initial value of null, but never emits a null value.
* Helper method to create a new flow without suspending and without initial value
* The flow collector will just wait for the first value
*/
fun <T : Any> Flow<T>.shareLatest(
tag: String? = null,
scope: CoroutineScope,
started: SharingStarted = SharingStarted.WhileSubscribed(replayExpirationMillis = 0)
) = this
.onStart { if (tag != null) log(tag) { "shareLatest(...) start" } }
.onEach { if (tag != null) log(tag) { "shareLatest(...) emission: $it" } }
.onCompletion { if (tag != null) log(tag) { "shareLatest(...) completed." } }
.catch {
if (tag != null) log(tag) { "shareLatest(...) catch(): ${it.asLog()}" }
throw it
}
.stateIn(
scope = scope,
started = started,
initialValue = null
)
.filterNotNull()
fun <T : Any?> Flow<T>.replayingShare(scope: CoroutineScope) = this.shareIn(
scope = scope,
@@ -13,7 +13,11 @@ import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.WhileSubscribed
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
import kotlin.coroutines.CoroutineContext
@@ -64,4 +68,12 @@ abstract class ViewModel2(
open fun <T> Flow<T>.launchInViewModel() = this.launchIn(vmScope)
fun <T : Any> Flow<T>.asLiveState(
started: SharingStarted = SharingStarted.WhileSubscribed(5_000),
): Flow<T> = this.stateIn(
scope = vmScope,
started = started,
initialValue = null,
).filterNotNull()
}
@@ -6,8 +6,8 @@ import androidx.activity.enableEdgeToEdge
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.graphics.toArgb
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
@@ -49,7 +49,7 @@ class MainActivity : Activity2() {
}
setContent {
val themeState by generalSettings.themeState.collectAsState(initial = generalSettings.currentThemeState)
val themeState by generalSettings.themeState.collectAsStateWithLifecycle(initialValue = generalSettings.currentThemeState)
val backStack = rememberNavBackStack(startDestination)
navCtrl.setup(backStack)
@@ -26,7 +26,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
import eu.darken.capod.R
import eu.darken.capod.common.compose.Preview2
import eu.darken.capod.common.compose.PreviewWrapper
import eu.darken.capod.common.compose.waitForState
import eu.darken.capod.common.error.ErrorEventHandler
import eu.darken.capod.common.navigation.NavigationEventHandler
@@ -43,7 +43,7 @@ import eu.darken.capod.R
import eu.darken.capod.common.compose.Preview2
import eu.darken.capod.common.compose.PreviewWrapper
import eu.darken.capod.common.compose.preview.MockPodDataProvider
import eu.darken.capod.common.compose.waitForState
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import eu.darken.capod.common.error.ErrorEventHandler
import eu.darken.capod.common.navigation.NavigationEventHandler
import eu.darken.capod.common.permissions.Permission
@@ -129,11 +129,11 @@ fun OverviewScreenHost(vm: OverviewViewModel = hiltViewModel()) {
}
}
val stateHolder = waitForState(vm.state)
val state = stateHolder.value ?: return
val state by vm.state.collectAsStateWithLifecycle(initialValue = null)
val currentState = state ?: return
OverviewScreen(
state = state,
state = currentState,
onRequestPermission = { vm.requestPermission(it) },
onManageDevices = { vm.goToDeviceManager() },
onSettings = { vm.goToSettings() },
@@ -9,7 +9,7 @@ import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.common.flow.SingleEventFlow
import eu.darken.capod.common.flow.combine
import eu.darken.capod.common.flow.shareLatest
import eu.darken.capod.common.flow.throttleLatest
import eu.darken.capod.common.navigation.Nav
import eu.darken.capod.common.permissions.Permission
@@ -74,7 +74,7 @@ class OverviewViewModel @Inject constructor(
monitorControl.startMonitor()
}
}
.shareLatest(scope = vmScope)
.asLiveState()
private val updateTicker = channelFlow<Unit> {
while (isActive) {
@@ -113,7 +113,7 @@ class OverviewViewModel @Inject constructor(
upgradeInfo = upgradeInfo,
showUnmatchedDevices = showUnmatched,
)
}.shareLatest(scope = vmScope)
}.asLiveState()
data class State(
val now: Instant,
@@ -29,7 +29,7 @@ import eu.darken.capod.common.compose.Preview2
import eu.darken.capod.common.compose.PreviewWrapper
import eu.darken.capod.common.BuildConfigWrap
import eu.darken.capod.common.PrivacyPolicy
import eu.darken.capod.common.compose.waitForState
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import eu.darken.capod.common.error.ErrorEventHandler
import eu.darken.capod.common.navigation.Nav
import eu.darken.capod.common.navigation.NavigationEventHandler
@@ -41,7 +41,7 @@ fun SettingsScreenHost(vm: SettingsViewModel = hiltViewModel()) {
ErrorEventHandler(vm)
NavigationEventHandler(vm)
val state by waitForState(vm.state)
val state by vm.state.collectAsStateWithLifecycle(initialValue = null)
state?.let {
SettingsScreen(
state = it,
@@ -4,7 +4,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import eu.darken.capod.common.WebpageTool
import eu.darken.capod.common.coroutine.DispatcherProvider
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.common.flow.shareLatest
import eu.darken.capod.common.uix.ViewModel4
import eu.darken.capod.common.upgrade.UpgradeRepo
import kotlinx.coroutines.flow.map
@@ -23,7 +23,7 @@ class SettingsViewModel @Inject constructor(
val state = upgradeRepo.upgradeInfo
.map { State(sponsorUrl = upgradeRepo.getSponsorUrl()) }
.shareLatest(scope = vmScope)
.asLiveState()
fun openUrl(url: String) {
webpageTool.open(url)
@@ -51,7 +51,7 @@ import eu.darken.capod.R
import eu.darken.capod.common.compose.Preview2
import eu.darken.capod.common.compose.PreviewWrapper
import eu.darken.capod.common.bluetooth.ScannerMode
import eu.darken.capod.common.compose.waitForState
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import eu.darken.capod.common.error.ErrorEventHandler
import eu.darken.capod.common.navigation.NavigationEventHandler
import eu.darken.capod.common.settings.SettingsBaseItem
@@ -74,7 +74,7 @@ fun GeneralSettingsScreenHost(vm: GeneralSettingsViewModel = hiltViewModel()) {
vm.launchUpgradeFlow.collect { action -> activity?.let { action(it) } }
}
val state by waitForState(vm.state)
val state by vm.state.collectAsStateWithLifecycle(initialValue = null)
state?.let {
GeneralSettingsScreen(
state = it,
@@ -6,7 +6,6 @@ import eu.darken.capod.common.bluetooth.ScannerMode
import eu.darken.capod.common.coroutine.DispatcherProvider
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.common.flow.SingleEventFlow
import eu.darken.capod.common.flow.shareLatest
import eu.darken.capod.common.navigation.Nav
import eu.darken.capod.common.theming.ThemeColor
import eu.darken.capod.common.theming.ThemeMode
@@ -41,7 +40,7 @@ class GeneralSettingsViewModel @Inject constructor(
val themeState: ThemeState,
)
private val isPro = upgradeRepo.upgradeInfo.map { it.isPro }.shareLatest(scope = vmScope)
private val isPro = upgradeRepo.upgradeInfo.map { it.isPro }.asLiveState()
val launchUpgradeFlow = SingleEventFlow<(Activity) -> Unit>()
@@ -77,7 +76,7 @@ class GeneralSettingsViewModel @Inject constructor(
useIndirectScanResultCallback = compat[2] as Boolean,
themeState = themeState,
)
}.shareLatest(scope = vmScope)
}.asLiveState()
fun setMonitorMode(mode: MonitorMode) {
generalSettings.monitorMode.value = mode
@@ -23,7 +23,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
import eu.darken.capod.R
import eu.darken.capod.common.compose.Preview2
import eu.darken.capod.common.compose.PreviewWrapper
import eu.darken.capod.common.compose.waitForState
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import eu.darken.capod.common.error.ErrorEventHandler
import eu.darken.capod.common.navigation.NavigationEventHandler
import eu.darken.capod.common.settings.SettingsBaseItem
@@ -33,7 +33,7 @@ fun DebugSettingsScreenHost(vm: DebugSettingsViewModel = hiltViewModel()) {
ErrorEventHandler(vm)
NavigationEventHandler(vm)
val state by waitForState(vm.state)
val state by vm.state.collectAsStateWithLifecycle(initialValue = null)
state?.let {
DebugSettingsScreen(
state = it,
@@ -4,7 +4,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import eu.darken.capod.common.coroutine.DispatcherProvider
import eu.darken.capod.common.debug.DebugSettings
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.common.flow.shareLatest
import eu.darken.capod.common.uix.ViewModel4
import kotlinx.coroutines.flow.combine
import javax.inject.Inject
@@ -31,7 +31,7 @@ class DebugSettingsViewModel @Inject constructor(
showFakeData = fakeData,
showUnfiltered = unfiltered,
)
}.shareLatest(scope = vmScope)
}.asLiveState()
fun setDebugModeEnabled(enabled: Boolean) {
debugSettings.isDebugModeEnabled.value = enabled
@@ -33,7 +33,7 @@ import eu.darken.capod.common.PrivacyPolicy
import eu.darken.capod.common.WebpageTool
import eu.darken.capod.common.compose.Preview2
import eu.darken.capod.common.compose.PreviewWrapper
import eu.darken.capod.common.compose.waitForState
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import eu.darken.capod.common.debug.recording.ui.RecorderConsentDialog
import eu.darken.capod.common.error.ErrorEventHandler
import eu.darken.capod.common.navigation.NavigationEventHandler
@@ -50,7 +50,7 @@ fun SupportScreenHost(vm: SupportViewModel = hiltViewModel()) {
val context = LocalContext.current
val state by waitForState(vm.state)
val state by vm.state.collectAsStateWithLifecycle(initialValue = null)
var showShortRecordingWarning by remember { mutableStateOf(false) }
@@ -57,7 +57,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.darken.capod.R
import eu.darken.capod.common.WebpageTool
import eu.darken.capod.common.compose.waitForState
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import eu.darken.capod.common.debug.recording.ui.RecorderConsentDialog
import eu.darken.capod.common.error.ErrorEventHandler
import eu.darken.capod.common.navigation.NavigationEventHandler
@@ -120,7 +120,7 @@ fun ContactFormScreenHost(vm: ContactFormViewModel = hiltViewModel()) {
}
}
val state by waitForState(vm.state)
val state by vm.state.collectAsStateWithLifecycle(initialValue = null)
state?.let {
ContactFormScreen(
state = it,
@@ -9,8 +9,8 @@ import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.graphics.toArgb
import androidx.core.view.WindowCompat
@@ -18,7 +18,6 @@ import androidx.glance.appwidget.GlanceAppWidgetManager
import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.qualifiers.ApplicationContext
import eu.darken.capod.common.compose.waitForState
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.common.theming.CapodTheme
@@ -61,7 +60,7 @@ class WidgetConfigurationActivity : Activity2() {
}
setContent {
val themeState by generalSettings.themeState.collectAsState(initial = generalSettings.currentThemeState)
val themeState by generalSettings.themeState.collectAsStateWithLifecycle(initialValue = generalSettings.currentThemeState)
CapodTheme(state = themeState) {
val backgroundColor = MaterialTheme.colorScheme.background
val useDarkIcons = backgroundColor.luminance() > 0.5f
@@ -72,7 +71,7 @@ class WidgetConfigurationActivity : Activity2() {
insetsController.isAppearanceLightNavigationBars = useDarkIcons
}
val state by waitForState(vm.state)
val state by vm.state.collectAsStateWithLifecycle(initialValue = null)
state?.let { currentState ->
WidgetConfigurationScreen(
state = currentState,
@@ -9,7 +9,6 @@ import eu.darken.capod.common.coroutine.DispatcherProvider
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.common.flow.combine
import eu.darken.capod.common.flow.shareLatest
import eu.darken.capod.common.uix.ViewModel2
import eu.darken.capod.common.upgrade.UpgradeRepo
import eu.darken.capod.profiles.core.DeviceProfile
@@ -70,7 +69,7 @@ class WidgetConfigurationViewModel @Inject constructor(
activePreset = activePreset,
isCustomMode = activePreset == null,
)
}.shareLatest(scope = vmScope)
}.asLiveState()
data class State(
val profiles: List<DeviceProfile> = emptyList(),
@@ -39,7 +39,7 @@ import eu.darken.capod.R
import eu.darken.capod.common.compose.Preview2
import eu.darken.capod.common.compose.PreviewWrapper
import eu.darken.capod.common.compose.preview.MockPodDataProvider
import eu.darken.capod.common.compose.waitForState
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import eu.darken.capod.common.error.ErrorEventHandler
import eu.darken.capod.common.navigation.NavigationEventHandler
import eu.darken.capod.pods.core.PodDevice
@@ -50,7 +50,7 @@ fun DeviceManagerScreenHost(vm: DeviceManagerViewModel = hiltViewModel()) {
ErrorEventHandler(vm)
NavigationEventHandler(vm)
val state by waitForState(vm.state)
val state by vm.state.collectAsStateWithLifecycle(initialValue = null)
state?.let {
DeviceManagerScreen(
state = it,
@@ -4,7 +4,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import eu.darken.capod.common.coroutine.DispatcherProvider
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.common.flow.shareLatest
import eu.darken.capod.common.navigation.Nav
import eu.darken.capod.common.uix.ViewModel4
import eu.darken.capod.profiles.core.DeviceProfile
@@ -20,7 +20,7 @@ class DeviceManagerViewModel @Inject constructor(
val state = deviceProfilesRepo.profiles.map { profiles ->
State(profiles = profiles)
}.shareLatest(scope = vmScope)
}.asLiveState()
data class State(
val profiles: List<DeviceProfile>,
@@ -61,7 +61,7 @@ import eu.darken.capod.R
import eu.darken.capod.common.bluetooth.BluetoothDevice2
import eu.darken.capod.common.compose.Preview2
import eu.darken.capod.common.compose.PreviewWrapper
import eu.darken.capod.common.compose.waitForState
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import eu.darken.capod.common.error.ErrorEventHandler
import eu.darken.capod.common.navigation.NavigationEventHandler
import eu.darken.capod.common.toHex
@@ -77,7 +77,7 @@ fun DeviceProfileCreationScreenHost(
ErrorEventHandler(vm)
NavigationEventHandler(vm)
val state by waitForState(vm.state)
val state by vm.state.collectAsStateWithLifecycle(initialValue = null)
var showUnsavedChangesDialog by rememberSaveable { mutableStateOf(false) }
var showDeleteDialog by rememberSaveable { mutableStateOf(false) }
@@ -11,7 +11,6 @@ import eu.darken.capod.common.coroutine.DispatcherProvider
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.common.flow.SingleEventFlow
import eu.darken.capod.common.flow.shareLatest
import eu.darken.capod.common.fromHex
import eu.darken.capod.common.toHex
import eu.darken.capod.common.uix.ViewModel4
@@ -118,7 +117,7 @@ class DeviceProfileCreationViewModel @Inject constructor(
minimumSignalQuality = editorState.minimumSignalQuality,
canSave = formValid && hasChanges,
)
}.shareLatest(scope = vmScope)
}.asLiveState()
data class State(
val isEditMode: Boolean,
@@ -43,7 +43,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
import eu.darken.capod.R
import eu.darken.capod.common.compose.Preview2
import eu.darken.capod.common.compose.PreviewWrapper
import eu.darken.capod.common.compose.waitForState
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import eu.darken.capod.common.error.ErrorEventHandler
import eu.darken.capod.common.navigation.NavigationEventHandler
import eu.darken.capod.common.settings.SettingsBaseItem
@@ -55,7 +55,7 @@ fun ReactionSettingsScreenHost(vm: ReactionSettingsViewModel = hiltViewModel())
ErrorEventHandler(vm)
NavigationEventHandler(vm)
val state by waitForState(vm.state)
val state by vm.state.collectAsStateWithLifecycle(initialValue = null)
val activity = LocalContext.current as Activity
state?.let {
@@ -4,7 +4,7 @@ import android.app.Activity
import dagger.hilt.android.lifecycle.HiltViewModel
import eu.darken.capod.common.coroutine.DispatcherProvider
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.common.flow.shareLatest
import eu.darken.capod.common.uix.ViewModel4
import eu.darken.capod.common.upgrade.UpgradeRepo
import eu.darken.capod.main.core.GeneralSettings
@@ -35,7 +35,7 @@ class ReactionSettingsViewModel @Inject constructor(
val showPopUpOnConnection: Boolean,
)
private val isPro = upgradeRepo.upgradeInfo.map { it.isPro }.shareLatest(scope = vmScope)
private val isPro = upgradeRepo.upgradeInfo.map { it.isPro }.asLiveState()
val state = combine(
isPro,
@@ -57,7 +57,7 @@ class ReactionSettingsViewModel @Inject constructor(
showPopUpOnCaseOpen = values[6] as Boolean,
showPopUpOnConnection = values[7] as Boolean,
)
}.shareLatest(scope = vmScope)
}.asLiveState()
fun setOnePodMode(enabled: Boolean) {
reactionSettings.onePodMode.value = enabled
@@ -32,7 +32,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
import eu.darken.capod.R
import eu.darken.capod.common.compose.Preview2
import eu.darken.capod.common.compose.PreviewWrapper
import eu.darken.capod.common.compose.waitForState
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import eu.darken.capod.common.error.ErrorEventHandler
import eu.darken.capod.common.navigation.NavigationEventHandler
@@ -41,7 +41,7 @@ fun TroubleShooterScreenHost(vm: TroubleShooterViewModel = hiltViewModel()) {
ErrorEventHandler(vm)
NavigationEventHandler(vm)
val state by waitForState(vm.state)
val state by vm.state.collectAsStateWithLifecycle(initialValue = null)
state?.let {
TroubleShooterScreen(
state = it,
@@ -10,7 +10,7 @@ import eu.darken.capod.common.debug.DebugSettings
import eu.darken.capod.common.debug.logging.Logging.Priority.INFO
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.common.flow.shareLatest
import eu.darken.capod.common.uix.ViewModel4
import eu.darken.capod.main.core.GeneralSettings
import eu.darken.capod.monitor.core.PodMonitor
@@ -45,7 +45,7 @@ class TroubleShooterViewModel @Inject constructor(
data class State(val bleState: BleState)
val state = _bleState.map { State(it) }.shareLatest(scope = vmScope)
val state = _bleState.map { State(it) }.asLiveState()
init {
_bleState
+1
View File
@@ -112,6 +112,7 @@ fun DependencyHandlerScope.addCompose() {
implementation("androidx.activity:activity-compose:1.12.1")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.5")
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.5")
implementation("androidx.hilt:hilt-navigation-compose:1.3.0-alpha01")
}