Этот блок инициализации находится в моем ViewModel
:
init {
viewModelScope.launch {
userRepository.login()
userRepository.user.collect {
_uiState.value = UiState.Success(it)
}
}
}
Это очень похоже на то, что на самом деле написано в приложении, но даже этот простой пример не работает. После userRepository.login()
user
, который является SharedFlow
, создает новое пользовательское состояние. Это последнее значение ДЕЙСТВИТЕЛЬНО собирается в этой функции сбора, показанной выше, но при создании нового uiState, содержащего результат, представление не получает такого обновления.
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
Сделать это почему-то не получается. Я подозреваю, что проблема связана с жизненным циклом модели представления, потому что, когда я рассматриваю модель представления как синглтон, этого не происходит. Это происходит только тогда, когда модель представления уничтожается, а затем создается второй (или более) раз.
Я пытаюсь добиться того, чтобы экран, содержащий модель представления, знал о состоянии пользователя. Это означает, что когда я перехожу к экрану, я хочу, чтобы он собирал последнее состояние пользователя, а затем решал, какой контент показывать.
Я также понимаю, что это не лучший образец, скорее всего. В настоящее время я изучаю решение, которое удерживает пользователя как часть состояния приложения и собирает данные для каждого экрана (учитывая, что оно в основном меняет все или многие экраны и функции), поэтому, если у вас есть какие-либо ресурсы для примера такой реализации, я бы быть благодарным. Но я не могу понять, почему эта текущая реализация не работает, поэтому любой свет, пролитый на ситуацию, очень ценится.
РЕДАКТИРОВАТЬ Это то, что я имею в виду для репозитория
private val _user = MutableSharedFlow<User>()
override val user: Flow<User> = _user
override suspend fun login() {
delay(2000)
_user.emit(LoggedUser.aLoggedUser())
}
override suspend fun logout() {
delay(2000)
_user.emit(GuestUser)
}
Для вашего случая лучше использовать этот шаблон:
Класс ViewModel:
sealed interface UserUiState {
object NotLoggedIn : UserUiState
object Error : UserUiState
data class LoggedIn(val user: User) : UserUiState
}
class MyViewModel @Inject constructor(
userRepository: UserRepository
) : ViewModel() {
val userUiState = userRepository.login()
.map { user ->
if (user != null)
UserUiState.LoggedIn(user)
else
UserUiState.Error
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = UserUiState.NotLoggedIn
)
}
Класс репозитория:
class UserRepository {
fun login(): Flow<User?> = flow {
val user = TODO("Your code to get user")
if (isSuccess) {
emit(user)
} else {
emit(null)
}
}
}
Ваш экран
@Composable
fun Screen() {
val userUiState by viewModel.userUiState.collectAsStateWithLifecycle()
when (userUiState) {
is UserUiState.LoggedIn -> { TODO("Success code") }
UserUiState.NotLoggedIn -> { TODO("Waiting for login code") }
UserUiState.Error -> { TODO("Error display code") }
}
}
Как это работает: login() в репозитории возвращает авторизованный пользовательский поток, который можно использовать в ViewModel. Я использую закрытый класс UserUiState для обработки возможных состояний пользователя. Затем я конвертирую значение пользователя на карте {} в UserUiState, чтобы отобразить его на уровне пользовательского интерфейса. Затем поток UserUiState нужно преобразовать в StateFlow, чтобы получить его из функции Composable, поэтому я сделал stateIn. И, конечно же, это решит вашу проблему.
Сообщите мне в комментариях, если я что-то напутал или код не соответствует вашим ожиданиям.
Примечание. SharedFlow и StateFlow не используются на уровне данных, как вы.
Обновлено: Вы можете испускать поток следующим образом, если вы работаете с сетью:
val user = flow of {
while (true) {
// network call to get user
delay(2000)
}
}
Если вы используете Room, вы можете сделать это в своем дао.
@Query(TODO("get actual user query"))
fun getUser(): Flow<User>
Это лучший способ, рекомендованный разработчиками Android на канале YouTube.
Вы можете найти то, о чем я говорю, в разделе редактирования поста.
Хотя это выглядит очень похоже на то, что я думаю с точки зрения LiveData
Холодный поток @Lheonair нельзя изменить. Так что да, вам нужно использовать LiveData для этого
@Lheonair отредактировал мой ответ, возможно, это решение подойдет вам. Это рекомендовано каналом Android Developers на YouTube.
Большое спасибо за Вашу помощь! Это определенно полезно, но я пытаюсь изменить это, чтобы оно соответствовало моим идеям. Я предполагаю, что я пытаюсь получить Flow<User>, который излучает текущее состояние. Это означает, что при входе в систему он создает пользователя, а при выходе из системы он создает гостевого пользователя (или null). Таким образом, например, экран в NavHost может выполнять login(), а элемент в нижней панели навигации может собирать новое состояние через репозиторий Flow<User>. Имеет ли это смысл?