Как работает LaunchedEffect

Я не совсем понимаю, как работает LauchedEffect, можете мне объяснить?

Вот контекст: я вызываю account.get(), чтобы узнать, существует ли пользовательский сеанс в моем приложении или нет, в зависимости от случая я возвращаю userExist = true или = false

Но во всех случаях он возвращает мне return = true, чтобы знать, что я должен запустить свой LaunchedEffect в этот момент, вот мой код

ВсплескУистате:

data class SplashUiState(
  val result: Boolean = false,
  val userExist: Boolean = false,
  val error: List<Pair<String, String>> = emptyList()
)

Модель SplashView:

private val _state = MutableStateFlow(SplashUiState())
val state = _state.asStateFlow()

...

init {
  delay(2000)
  checkSessionExist()
}

...

private suspend fun checkSessionExist() {
    when(val account = repository.getAccount()) {
        is ResponseResult.Error -> {
            Log.d("SplashViewModel", "${account.code}" + " ${account.message}")
            _state.update { it.copy(result = true, userExist = false,
                userError = listOf(
                    "code" to "${account.code}",
                    "message" to "${account.message}"
                )
            ) }
        }

        is ResponseResult.Success -> {
            Log.d("SplashViewModel", "${account.data}")
            _state.update { it.copy(result = true, userExist = true) }
        }
    }
}

Заставка:

val state by viewModel.state.collectAsState()

LaunchedEffect(key1 = state.result) {
  if (state.userExist) {
    Log.d("LaunchedEffect", "user exist")
    navController.navigate(AppRoutes.OVIDE_SCREEN) {
      popUpTo(AppRoutes.SPLASH_SCREEN) {
        inclusive = true
      }
    }
  } else {
    Log.d("LaunchedEffect", "user not exist")
    navController.navigate(AppRoutes.WELCOME_SCREEN) {
      popUpTo(AppRoutes.SPLASH_SCREEN) {
        inclusive = true
      }
    }
  }
}

Мой код кажется мне хорошим, хорошо структурированным, но когда пользователь существует userExist = true, он запускает мой код в else, а не в моем if.

Как это делается?

@BenjyTec Конечно :)

Kako 03.06.2024 13:44
4
1
148
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Строка кода

LaunchedEffect(key1 = state.result) {}

означает, что код внутри LaunchedEffect будет выполнен

  • один раз изначально
  • а затем всякий раз, когда значение state.result меняется.

Это не означает, что LaunchedEffect будет выполнено только тогда, когда значение state.result станет true. Таким образом, ваше приложение немедленно перейдет к WELCOME_SCREEN еще до того, как оно завершит проверку существования сеанса.

Пожалуйста, попробуйте изменить свой код следующим образом:

LaunchedEffect(key1 = state.result) {
    if (state.result) {
        if (state.userExist) {
            Log.d("LaunchedEffect", "user exist")
            navController.navigate(AppRoutes.OVIDE_SCREEN) {
                popUpTo(AppRoutes.SPLASH_SCREEN) { inclusive = true }
            }
        } else {
            Log.d("LaunchedEffect", "user not exist")
            navController.navigate(AppRoutes.WELCOME_SCREEN) {
                popUpTo(AppRoutes.SPLASH_SCREEN) { inclusive = true }
            }
        }
    }
}

Это обеспечит навигацию после завершения загрузки.

«Это не означает, что LaunchedEffect будет выполнен только тогда, когда значение state.result станет истинным». Да, именно так я это и видел, спасибо!

Kako 03.06.2024 14:05

Просмотрите предварительный просмотр в KDoc из LaunchedEffect:

Когда LaunchedEffect входит в композицию, он запускает блок в CoroutineContext композиции. Сопрограмма будет отменена и перезапущена, когда LaunchedEffect будет перекомпонован с другим ключом1.

Ваш checkSessionExist() всегда создает state.result == true. И это может быть причиной того, что ваш LaunchedEffect никогда не переходит в другое состояние.

Попробуйте следующий вызов LaunchedEffect(state) {} А также вставьте уже добавленное дополнительное предложение if @BenjyTec.

Кроме того, должно работать следующее:

LaunchedEffect(state) {
    if (state.result) {
        if (state.userExist) {
            Log.d("LaunchedEffect", "user exist")
            navController.navigate(AppRoutes.OVIDE_SCREEN) {
                popUpTo(AppRoutes.SPLASH_SCREEN) { inclusive = true }
            }
        } else {
            Log.d("LaunchedEffect", "user not exist")
            navController.navigate(AppRoutes.WELCOME_SCREEN) {
                popUpTo(AppRoutes.SPLASH_SCREEN) { inclusive = true }
            }
        }
    }
}

LaunchedEffect remember используется для запоминания и забывания лямбда-блока с RememberObserver и coroutineScope под капотом.

@Composable
@NonRestartableComposable
@OptIn(InternalComposeApi::class)
fun LaunchedEffect(
    key1: Any?,
    block: suspend CoroutineScope.() -> Unit
) {
    val applyContext = currentComposer.applyCoroutineContext
    remember(key1) { LaunchedEffectImpl(applyContext, block) }
}

RememberObserver интерфейс используется в LaunchedEffect для проверки того, когда запоминается блок кода. Это происходит, когда запоминание блока входит в композицию, и забывается, когда Composable или блок кода выходят из композиции.

@Suppress("CallbackName")
interface RememberObserver {
    /**
     * Called when this object is successfully remembered by a composition. This method is called on
     * the composition's **apply thread.**
     */
    fun onRemembered()

    /**
     * Called when this object is forgotten by a composition. This method is called on the
     * composition's **apply thread.**
     */
    fun onForgotten()

    /**
     * Called when this object is returned by the callback to `remember` but is not successfully
     * remembered by a composition.
     */
    fun onAbandoned()
}

Вы можете реализовать этот интерфейс самостоятельно для создания состояния или Composable для очистки или самостоятельно вызвать обратный вызов, например DisposableEffect. Я видел некоторые способы размещения растровых изображений в некоторых кодах.

И реализация LaunchedEffect использует его для отмены задания, которое оно создает как

internal class LaunchedEffectImpl(
    parentCoroutineContext: CoroutineContext,
    private val task: suspend CoroutineScope.() -> Unit
) : RememberObserver {
    private val scope = CoroutineScope(parentCoroutineContext)
    private var job: Job? = null

    override fun onRemembered() {
        // This should never happen but is left here for safety
        job?.cancel("Old job was still running!")
        job = scope.launch(block = task)
    }

    override fun onForgotten() {
        job?.cancel(LeftCompositionCancellationException())
        job = null
    }

    override fun onAbandoned() {
        job?.cancel(LeftCompositionCancellationException())
        job = null
    }
}

По сути, LaunchedEffect проверяет жизненный цикл композиции, реализуя RememberObserver, и вызывает функции приостановки с помощью CoroutineScope.

Поскольку это запоминаемая функция, она сбрасывает свою лямбду или создает новое замыкание при изменении ключей.

DisposableEffects также проверяет onForgotten, что отличается от LaunchedEffect в жизненном цикле компоновки.

private class DisposableEffectImpl(
    private val effect: DisposableEffectScope.() -> DisposableEffectResult
) : RememberObserver {
    private var onDispose: DisposableEffectResult? = null

    override fun onRemembered() {
        onDispose = InternalDisposableEffectScope.effect()
    }

    override fun onForgotten() {
        onDispose?.dispose()
        onDispose = null
    }

    override fun onAbandoned() {
        // Nothing to do as [onRemembered] was not called.
    }
}

Это действительно очень интересно и познавательно, большое спасибо, я собираюсь более подробно рассмотреть то, чем вы поделились.

Kako 03.06.2024 23:10

Другие вопросы по теме

Похожие вопросы

Hilt: kotlin.UninitializedPropertyAccessException: свойство lateinit не было инициализировано
Мой веб-сайт использует html2pdf (javascript) для создания PDF-файла из html, но возникают проблемы с загрузкой в ​​веб-просмотре Android: kotlin
Могу ли я отправить данные из одного приложения в закрытое приложение на Android
ProcessLifecycleOwner не работает при запуске с определенными действиями
MutableStateListOf не перестраивается при обновлении одного элемента
Получить данные из комнаты в Android
Эмулятор Android, запущенный из кода vs, не открывает окно эмулятора на переднем плане, он всегда работает в фоновом режиме
Flutter_local_notifications ZonedSchedule уведомление не запускается на эмуляторе
Android: слияние манифеста не удалось, добавьте «tools:replace="android:resource»» в элемент <property>
Каков правильный способ связи между viewModel и компонуемым пользовательским интерфейсом, чтобы избежать ошибки одновременного изменения?