Keep clock widget parts alive across lifecycle stops
`partProviders` was a `stateIn(WhileSubscribed)` flow, so its upstream map ran again every time the clock widget was subscribed after the launcher had been in the background - and built new part providers. Providers cache what their ranking is based on, so the new agenda part started at ranking 0, vanished while the launcher was gone and only came back once its query had run again. Build the provider list in `viewModelScope` into a `MutableStateFlow` instead, so the instances (and their cached state) survive for as long as the ViewModel does. Their ranking flows are still only collected while the widget is subscribed, so observers are still registered and unregistered with the widget's lifecycle.
This commit is contained in:
@@ -17,33 +17,47 @@ import de.mm20.launcher2.ui.launcher.widgets.clock.parts.MusicPartProvider
|
||||
import de.mm20.launcher2.ui.launcher.widgets.clock.parts.PartProvider
|
||||
import de.mm20.launcher2.ui.launcher.widgets.clock.parts.SmartspacerPartProvider
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
class ClockWidgetVM : ViewModel(), KoinComponent {
|
||||
private val settings: ClockWidgetSettings by inject()
|
||||
|
||||
private val partProviders = settings.parts.combine(settings.useSmartspacer) { p, s ->
|
||||
p to s
|
||||
}.map { (parts, smartspacer) ->
|
||||
if (smartspacer && isAtLeastApiLevel(29)) {
|
||||
return@map listOf(SmartspacerPartProvider())
|
||||
}
|
||||
/**
|
||||
* Kept for the lifetime of the ViewModel instead of being rebuilt whenever the clock widget
|
||||
* is subscribed again - which happens on every return to the home screen, because the
|
||||
* launcher stops while it is in the background. Providers cache the data behind their
|
||||
* ranking, so a fresh instance starts at ranking 0 and makes its part disappear until its
|
||||
* query returns; the agenda part would blink on every visit that way.
|
||||
*/
|
||||
private val partProviders = MutableStateFlow<List<PartProvider>>(emptyList())
|
||||
|
||||
val providers = mutableListOf<PartProvider>()
|
||||
if (parts.date) providers += DatePartProvider()
|
||||
if (parts.calendar) providers += CalendarPartProvider()
|
||||
if (parts.music) providers += MusicPartProvider()
|
||||
providers += BatteryPartProvider(parts.battery)
|
||||
if (parts.alarm) providers += AlarmPartProvider()
|
||||
providers
|
||||
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
settings.parts.combine(settings.useSmartspacer) { p, s -> p to s }
|
||||
.collect { (parts, smartspacer) ->
|
||||
partProviders.value = if (smartspacer && isAtLeastApiLevel(29)) {
|
||||
listOf(SmartspacerPartProvider())
|
||||
} else {
|
||||
buildList {
|
||||
if (parts.date) add(DatePartProvider())
|
||||
if (parts.calendar) add(CalendarPartProvider())
|
||||
if (parts.music) add(MusicPartProvider())
|
||||
add(BatteryPartProvider(parts.battery))
|
||||
if (parts.alarm) add(AlarmPartProvider())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getActiveParts(context: Context): Flow<List<PartProvider>> = channelFlow {
|
||||
partProviders.collectLatest { providers ->
|
||||
|
||||
Reference in New Issue
Block a user