Wait for switched user's home settings before fetching data (#1981)
## Description There is a race condition when switching users between `HomeViewModel.init` and `HomeSetttingsService.loadCurrentSettings`. This happens more often if the new user doesn't have a customized home page. The result is that the previous user's home page settings are used for the initial load of the home page. This PR fixes this by collecting the flows for current user & home settings, waiting until both are valid and refer to the same user, then fetch the data. ### Related issues Fixes #1976 Maybe related (and fixes) #1980, but need to investigate more ### Testing Emulator. It's easy to reproduce by adding a manual `delay(2.seconds)` at the start of `HomeSetttingsService.loadCurrentSettings`. ## Screenshots N/A ## AI or LLM usage None
This commit is contained in:
parent
ffdebd43c3
commit
32a56e8387
5 changed files with 208 additions and 143 deletions
|
|
@ -245,7 +245,7 @@ class HomeSettingsService
|
||||||
settings.rows.mapIndexed { index, config ->
|
settings.rows.mapIndexed { index, config ->
|
||||||
resolve(index, config)
|
resolve(index, config)
|
||||||
}
|
}
|
||||||
HomePageResolvedSettings(resolvedRows)
|
HomePageResolvedSettings(userId, resolvedRows)
|
||||||
} else {
|
} else {
|
||||||
createDefault(userId)
|
createDefault(userId)
|
||||||
}
|
}
|
||||||
|
|
@ -256,12 +256,15 @@ class HomeSettingsService
|
||||||
/**
|
/**
|
||||||
* Resolve the settings and set them to be the current settings
|
* Resolve the settings and set them to be the current settings
|
||||||
*/
|
*/
|
||||||
suspend fun updateCurrent(settings: HomePageSettings) {
|
suspend fun updateCurrent(
|
||||||
|
userId: UUID,
|
||||||
|
settings: HomePageSettings,
|
||||||
|
) {
|
||||||
val resolvedRows =
|
val resolvedRows =
|
||||||
settings.rows.mapIndexed { index, config ->
|
settings.rows.mapIndexed { index, config ->
|
||||||
resolve(index, config)
|
resolve(index, config)
|
||||||
}
|
}
|
||||||
val resolvedSettings = HomePageResolvedSettings(resolvedRows)
|
val resolvedSettings = HomePageResolvedSettings(userId, resolvedRows)
|
||||||
currentSettings.update { resolvedSettings }
|
currentSettings.update { resolvedSettings }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -312,7 +315,7 @@ class HomeSettingsService
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
val rowConfig = continueWatchingRow + includedIds
|
val rowConfig = continueWatchingRow + includedIds
|
||||||
return HomePageResolvedSettings(rowConfig)
|
return HomePageResolvedSettings(userId, rowConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -428,7 +431,7 @@ class HomeSettingsService
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}.flatten()
|
}.flatten()
|
||||||
HomePageResolvedSettings(rowConfigs)
|
HomePageResolvedSettings(userId, rowConfigs)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
@ -1224,10 +1227,11 @@ data class HomeRowConfigDisplay(
|
||||||
* @see HomePageSettings
|
* @see HomePageSettings
|
||||||
*/
|
*/
|
||||||
data class HomePageResolvedSettings(
|
data class HomePageResolvedSettings(
|
||||||
|
val userId: UUID,
|
||||||
val rows: List<HomeRowConfigDisplay>,
|
val rows: List<HomeRowConfigDisplay>,
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
val EMPTY = HomePageResolvedSettings(listOf())
|
val EMPTY = HomePageResolvedSettings(UUID.randomUUID(), emptyList())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,6 @@ class SetupNavigationManager
|
||||||
fun navigateTo(destination: SetupDestination) {
|
fun navigateTo(destination: SetupDestination) {
|
||||||
backStack[0] = destination
|
backStack[0] = destination
|
||||||
log()
|
log()
|
||||||
if (destination !is SetupDestination.AppContent) {
|
|
||||||
navigationManager.reloadHome()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun log() {
|
private fun log() {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ import com.github.damontecres.wholphin.services.ServerReportService
|
||||||
import com.github.damontecres.wholphin.services.UserPreferencesService
|
import com.github.damontecres.wholphin.services.UserPreferencesService
|
||||||
import com.github.damontecres.wholphin.services.deleteItem
|
import com.github.damontecres.wholphin.services.deleteItem
|
||||||
import com.github.damontecres.wholphin.services.tvAccess
|
import com.github.damontecres.wholphin.services.tvAccess
|
||||||
|
import com.github.damontecres.wholphin.ui.collectLatestIn
|
||||||
|
import com.github.damontecres.wholphin.ui.combinePair
|
||||||
import com.github.damontecres.wholphin.ui.data.RowColumn
|
import com.github.damontecres.wholphin.ui.data.RowColumn
|
||||||
import com.github.damontecres.wholphin.ui.launchDefault
|
import com.github.damontecres.wholphin.ui.launchDefault
|
||||||
import com.github.damontecres.wholphin.ui.launchIO
|
import com.github.damontecres.wholphin.ui.launchIO
|
||||||
|
|
@ -31,6 +33,8 @@ import com.github.damontecres.wholphin.util.LoadingState
|
||||||
import com.github.damontecres.wholphin.util.WholphinDispatchers
|
import com.github.damontecres.wholphin.util.WholphinDispatchers
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
@ -44,6 +48,7 @@ import kotlinx.coroutines.sync.withPermit
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.jellyfin.sdk.api.client.exception.InvalidStatusException
|
import org.jellyfin.sdk.api.client.exception.InvalidStatusException
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
|
import org.jellyfin.sdk.model.api.UserDto
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
@ -68,23 +73,77 @@ class HomeViewModel
|
||||||
private val _state = MutableStateFlow(HomeState.EMPTY)
|
private val _state = MutableStateFlow(HomeState.EMPTY)
|
||||||
val state: StateFlow<HomeState> = _state
|
val state: StateFlow<HomeState> = _state
|
||||||
|
|
||||||
|
private var dataLoadingJob: Job? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
datePlayedService.invalidateAll()
|
datePlayedService.invalidateAll()
|
||||||
// init()
|
serverRepository.currentUserDtoFlow
|
||||||
|
.combinePair(homeSettingsService.currentSettings)
|
||||||
|
.collectLatestIn(viewModelScope) { (userDto, settings) ->
|
||||||
|
Timber.v(
|
||||||
|
"Got new userDto & settings, userId=%s, settings?=%s",
|
||||||
|
userDto?.id,
|
||||||
|
settings != HomePageResolvedSettings.EMPTY,
|
||||||
|
)
|
||||||
|
_state.update { HomeState.EMPTY }
|
||||||
|
dataLoadingJob?.cancel()
|
||||||
|
if (userDto == null) {
|
||||||
|
Timber.d("UserDto is null")
|
||||||
|
return@collectLatestIn
|
||||||
|
}
|
||||||
|
if (settings == HomePageResolvedSettings.EMPTY) {
|
||||||
|
Timber.d("Home settings are empty")
|
||||||
|
return@collectLatestIn
|
||||||
|
}
|
||||||
|
if (userDto.id != settings.userId) {
|
||||||
|
Timber.d("User IDs don't match: %s vs %s", userDto?.id, settings.userId)
|
||||||
|
return@collectLatestIn
|
||||||
|
}
|
||||||
|
dataLoadingJob =
|
||||||
|
viewModelScope.launchIO {
|
||||||
|
loadHomeRows(userDto, settings)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun init() {
|
fun init() {
|
||||||
viewModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
Timber.d("init HomeViewModel")
|
Timber.d("init HomeViewModel")
|
||||||
|
serverRepository.currentUserDto?.let { userDto ->
|
||||||
|
if (dataLoadingJob?.isActive == false) {
|
||||||
|
val settings =
|
||||||
|
homeSettingsService.currentSettings.first { it != HomePageResolvedSettings.EMPTY }
|
||||||
|
if (userDto.id == settings.userId) {
|
||||||
|
dataLoadingJob =
|
||||||
|
viewModelScope.launchIO {
|
||||||
|
loadHomeRows(userDto, settings)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Timber.d(
|
||||||
|
"Init user IDs don't match: %s vs %s",
|
||||||
|
userDto.id,
|
||||||
|
settings.userId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Timber.v("Data loading job is active")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadHomeRows(
|
||||||
|
userDto: UserDto,
|
||||||
|
settings: HomePageResolvedSettings,
|
||||||
|
) {
|
||||||
|
Timber.i("Starting loadHomeRows")
|
||||||
try {
|
try {
|
||||||
val preferences = userPreferencesService.getCurrent()
|
val preferences = userPreferencesService.getCurrent()
|
||||||
val prefs = preferences.appPreferences.homePagePreferences
|
val prefs = preferences.appPreferences.homePagePreferences
|
||||||
|
|
||||||
serverRepository.currentUserDto?.let { userDto ->
|
|
||||||
val libraries =
|
val libraries =
|
||||||
navDrawerService.getAllUserLibraries(userDto.id, userDto.tvAccess)
|
navDrawerService.getAllUserLibraries(userDto.id, userDto.tvAccess)
|
||||||
val settings =
|
|
||||||
homeSettingsService.currentSettings.first { it != HomePageResolvedSettings.EMPTY }
|
|
||||||
val state = state.value
|
val state = state.value
|
||||||
|
|
||||||
// Refreshing if a load has already occurred and the rows haven't significantly changed
|
// Refreshing if a load has already occurred and the rows haven't significantly changed
|
||||||
|
|
@ -202,7 +261,8 @@ class HomeViewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Timber.d("Home page load complete")
|
Timber.d("Home page load complete")
|
||||||
}
|
} catch (ex: CancellationException) {
|
||||||
|
throw ex
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
Timber.e(ex, "Exception during home page loading")
|
Timber.e(ex, "Exception during home page loading")
|
||||||
if (state.value.loadingState == LoadingState.Success) {
|
if (state.value.loadingState == LoadingState.Success) {
|
||||||
|
|
@ -215,7 +275,6 @@ class HomeViewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun setWatched(
|
fun setWatched(
|
||||||
itemId: UUID,
|
itemId: UUID,
|
||||||
|
|
@ -298,7 +357,7 @@ data class HomeState(
|
||||||
HomeState(
|
HomeState(
|
||||||
LoadingState.Pending,
|
LoadingState.Pending,
|
||||||
LoadingState.Pending,
|
LoadingState.Pending,
|
||||||
listOf(),
|
emptyList(),
|
||||||
HomePageResolvedSettings.EMPTY,
|
HomePageResolvedSettings.EMPTY,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -595,7 +595,7 @@ class HomeSettingsViewModel
|
||||||
}
|
}
|
||||||
if (shouldSave) {
|
if (shouldSave) {
|
||||||
homeSettingsService.saveToLocal(user.id, settings)
|
homeSettingsService.saveToLocal(user.id, settings)
|
||||||
homeSettingsService.updateCurrent(settings)
|
homeSettingsService.updateCurrent(user.id, settings)
|
||||||
showSaveToast()
|
showSaveToast()
|
||||||
} else {
|
} else {
|
||||||
Timber.d("No changes")
|
Timber.d("No changes")
|
||||||
|
|
@ -616,7 +616,11 @@ class HomeSettingsViewModel
|
||||||
_state.update {
|
_state.update {
|
||||||
update.invoke(it)
|
update.invoke(it)
|
||||||
}
|
}
|
||||||
homeSettingsService.currentSettings.update { HomePageResolvedSettings(state.value.rows) }
|
serverRepository.currentUser?.id?.let { userId ->
|
||||||
|
homeSettingsService.currentSettings.update {
|
||||||
|
HomePageResolvedSettings(userId, state.value.rows)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resizeCards(relative: Int) {
|
fun resizeCards(relative: Int) {
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,7 @@ class SwitchUserViewModel
|
||||||
viewModelScope.async(WholphinDispatchers.IO) {
|
viewModelScope.async(WholphinDispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val current = serverRepository.changeUser(server, user)
|
val current = serverRepository.changeUser(server, user)
|
||||||
|
navigationManager.reloadHome()
|
||||||
setupNavigationManager.navigateTo(SetupDestination.AppContent(current))
|
setupNavigationManager.navigateTo(SetupDestination.AppContent(current))
|
||||||
SwitchUserResult.Success
|
SwitchUserResult.Success
|
||||||
} catch (ex: InvalidStatusException) {
|
} catch (ex: InvalidStatusException) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue