У меня есть этот код, который увеличивает значение в базе данных:
override fun incrementQuantity() = flow {
try {
emit(Result.Loading)
heroIdRef.update("quantity", FieldValue.increment(1)).await()
emit(Result.Success(true))
} catch (e: Exception) {
emit(Result.Failure(e))
}
}
Эта функция вызывается из класса ViewModel, а затем из составной функции я читаю ответ следующим образом:
when(val response = viewModel.incrementResponse) {
is Result.Loading -> showProgressBar()
is Result.Success -> Log.d("TAG", "Quantity incremented.")
is Result.Failure -> Log.d("TAG", response.e.message)
}
Это означает, что в потоке я получаю два события, первое — Загрузка, а второе — данные или сбой. Мой вопрос:
Рекомендуется ли делать это именно так? Или лучше не использовать поток и иметь что-то вроде этого:
override fun incrementQuantity(): Result<Boolean> {
try {
heroIdRef.update("quantity", FieldValue.increment(1)).await()
Result.Success(true)
} catch (e: Exception) {
Result.Failure(e)
}
}
И внутри составного:
showProgressBar()
when(val response = viewModel.incrementResponse) {
is Result.Success -> {
hideProgressBar()
Log.d("TAG", "Quantity incremented.")
}
is Result.Failure -> {
hideProgressBar()
Log.d("TAG", response.e.message)
}
}
Есть ли недостатки при использовании потока Kotlin?
Вот мой объект incrementResponse:
var incrementResponse by mutableStateOf<Response<Boolean>>(Response.Success(false))
private set
@Thracian Так действительно ли необходимо использовать collectAsState, поскольку я собираю ответ не внутри составной функции, а внутри класса ViewModel?
Это не обязательно, но самый простой способ. Вы также можете создать экземпляр MutableState внутри своей ViewModel и обновить этот MutableState с помощью flow.collect{myState =it} или flow.onEach{myState =it}. Если вы не хотите иметь MutableState внутри ViewModel, используйте produceState в Compoable, который преобразует Flow в MutableState, как я ответил в вашем другом вопросе. Вам нужна Composa State, чтобы вызвать рекомпозицию. Вы можете проверить этот вопрос, зачем нужно состояние. stackoverflow.com/q/65368007/5457853
В компоновке реактивного ранца вам не нужно переключать видимость вручную. Вы можете просто играть со штатами. Вы можете создать отдельный компонент индикатора выполнения, который можно использовать и для других экранов. Код ниже будет запускаться только в том случае, если состояние загружается, если состояние изменяется, оно автоматически скрывается.
@Composable
fun CircularIndeterminateProgressBar(
uiState: UiState
) {
if (uiState is UiState.Loading) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
CircularProgressIndicator()// provided by compose library
}
}
}
а затем вызовите этот компонент из составной функции главного экрана и передайте состояние внутри него
CircularIndeterminateProgressBar(uiState = uiState)
uiState you will get from viewmodel
В модели представления вы будете использовать
private val _uiState = mutableStateOf<ModelStateName>()
вышеизложенное является изменяемым состоянием, которое известно о жизненном цикле и будет напрямую указано в составной функции.
val uiState by remember {
viewmodel.uiState
}
Подробнее об изменяемых состояниях читайте здесь изменяемые состояния в компоновке реактивного ранца
Мастер-класс по созданию Jetpack с примером инкремента мастерская по созданию реактивного ранца
Спасибо, Рохан, за то, что нашли время предоставить и ответить. Да, это хороший подход, но на самом деле он не отвечает на мой вопрос об использовании потока. Только что проголосовал.
Я обновил свой ответ, можете ли вы проверить, получили ли вы то, что хотите?
Как я уже сказал, это хороший подход, но мой вопрос: является ли плохим подходом добавление загрузки в поток? Или лучше не использовать поток?
первое, что вы будете использовать mutableStateOf вместо потока. Теперь вам нужно передать ModelStateName, который будет содержать успешную и неудачную загрузку, и вам нужно переключить эти состояния из модели представления. Загрузка будет в состоянии по умолчанию. Flow не поддерживает жизненный цикл, поэтому мы не можем использовать в компоновке функции mutableStateOf. знает о жизненном цикле и может использоваться в функциях компоновки и рекомендуется
Я уже использую объект состояния. incrementResponse является таким объектом. Пожалуйста, проверьте мой обновленный вопрос.
По сути, мой вопрос заключается в том, можно ли использовать поток для отображения ProgressBar или мне следует обрабатывать это внутри составного экрана. Это дополнительный вопрос к этому вопросу.
это зависит от вашего варианта использования, если это одноразовый вызов, то поток не требуется, вы можете использовать функцию приостановки, однако, если ваш вариант использования просит вас постоянно получать обновления, тогда вам нужно использовать поток в этом случае. Это мое понимание. если это не поможет, может быть, я не могу понять контекст, в котором ты пытаешься спросить, поэтому может помочь какой-то другой ответ !!
Да, увеличение может происходить каждый раз, когда пользователь хочет увеличить количество.
Я спрашиваю здесь не об операции приращения, а о любой другой операции, которая может выполняться с использованием потока.
Можно вернуть поток из репозитория. Вы можете собрать его в ViewModel и соответствующим образом обновить состояние.
Также обратите внимание, что Compose следует декларативной модели пользовательского интерфейса, поэтому вам не следует вызывать showProgressBar(), когда состояние равно Result.Loading. Вместо этого вы должны просто назвать индикатор выполнения компонуемым. Точно так же для двух других случаев Success и Failure вы должны разместить там необходимые элементы пользовательского интерфейса. Это будет что-то вроде:
// In your composable function
val result = yourFlow.collectAsState()
when(result) {
Result.Loading -> ProgressBarComposable()
Result.Success -> SuccessComposable()
Result.Failure -> FailureComposable()
}
Используя StateFlow, вы можете сделать это так:
// In ViewModel:
private val _incrementResultFlow = MutableStateFlow<Result<Boolean>?>(null) // Set the initial value to be null to avoid a Progress Bar in the beginning
val incrementResultFlow = _incrementResultFlow.asStateFlow()
fun onIncrementButtonClick() {
repository.incrementQuantity().collect { result->
_incrementResultFlow.value = result
}
}
// In your composable
val result by viewModel.incrementResultFlow.collectAsState()
if (result != null) {
when(result) {
...
}
}
Альтернативой StateFlow является прямое использование компоновки State внутри вашей ViewModel. Если в вашем проекте вы не возражаете против компоновки зависимостей в ViewModel, я предлагаю использовать этот подход.
// In ViewModel:
var incrementResponse by mutableStateOf<Result<Boolean>?>(null)
private set // So that it can only be modified from the ViewModel
fun onIncrementButtonClick() {
repository.incrementQuantity().collect { result->
incrementResponse = result
}
}
// In your composable
viewModel.incrementResponse?.let {
when(it) {
Result.Loading -> ProgressBarComposable()
Result.Success -> SuccessComposable()
Result.Failure -> FailureComposable()
}
}
Другой альтернативой является изменение функции репозитория, чтобы она возвращала Result<Boolean> вместо flow и обрабатывала логику загрузки в ViewModel.
// Repository
suspend fun incrementQuantity(): Result<Boolean> {
return try {
heroIdRef.update("quantity", FieldValue.increment(1)).await()
Result.Success(true)
} catch (e: Exception) {
Result.Failure(e)
}
}
// ViewModel
var incrementResponse by mutableStateOf<Result<Boolean>?>(null)
private set
fun onIncrementButtonClick() {
incrementResponse = Result.Loading
incrementResponse = repository.incrementQuantity()
}
Спасибо, что нашли время ответить на мой вопрос. Позвольте мне взглянуть и вернуться к вам.
Когда я подаю в суд на последний подход, индикатор выполнения начинает вращаться даже с самого начала, так как он имеет начальное состояние «Загрузка». Я хочу, чтобы отображалось только тогда, когда я нажимаю кнопку увеличения, пока операция увеличения не будет завершена.
Кроме того, вы используете private val _incrementResultFlow = MutableStateFlow(Result.Loading) и val incrementResultFlow = _incrementResultFlow.asStateFlow() внутри класса VIewMode. Можно ли упростить это с помощью чего-то вроде var incrementResponse by mutableStateOf(Result.Loading) private set?
Что касается вашего второго комментария, да, второй подход на самом деле короче, поэтому я использую его в своих проектах. Вы можете пойти с этим.
Я отредактировал ответ, чтобы удовлетворить ваше требование, которое вы упомянули в 1-м комментарии. Взгляни, пожалуйста.
Да, но можно ли использовать MutableStateFlow с частным set? Как это работает в случае mutableStateOf?
В случае mutableStateOf private set используется для вспомогательного поля, которое мы используем (обратите внимание на делегирование by). Использование частного сеттера с MutableStateFlow не имеет смысла, потому что мы все равно не собираемся менять эту переменную потока, можно изменить только свойство flow.value.
Спасибо. Я только что добавил дополнительный вопрос. Может быть, вы можете взглянуть.
Ссылка на вопрос неверная. Прямо сейчас он указывает на мой профиль.
Извините, я только что отредактировал это. Пожалуйста, проверьте.
Эй, извините, я немного неправильно понял вопрос. Я думал, что функция incrementQuantity находится в вашей модели просмотра. Поскольку это функция репо, вы можете продолжать возвращать поток оттуда. И тогда вы можете собрать его в своей модели просмотра, чтобы обновить состояние (составить State или StateFlow)
Нет, incrementQuantity находится внутри класса репозитория. Итак, вы говорите, что нет ничего плохого в том, чтобы сгенерировать состояние Loading в первый раз, а затем, когда данные станут доступны, сгенерировать результат в классе репо. Это верно?
Да, вы можете это сделать. Но как личное предпочтение, мне не нравится возвращать поток из репозитория только для обработки этого состояния загрузки. Я делаю это в самой viewModel и сохраняю функцию репозитория нормальной suspend функцией. Но все зависит от вас. (Позвольте мне также добавить этот подход в ответ, вы можете выбрать то, что вам больше нравится)
С нетерпением жду возможности увидеть это. Еще раз спасибо.
Написал по другому вопросу. Посмотри.
Позвольте мне взглянуть и вернуться к вам.
Если вы сможете адаптировать ответ в соответствии с моим вопросом, чтобы от этого выиграли посетители, я приму ответ и вознагражу вас наградой.
Конечно. Дай мне минуту.
Отредактировано. Посмотрите, хорошо ли это выглядит для вас.
Привет. Может быть, вы можете помочь мне с этим тоже.
Жаль, что я не видел этот вопрос раньше :). В любом случае уже есть хорошие ответы. Вам просто нужно преобразовать
FlowвStateс помощью расширения FlowcollectAsState. Если вы не сделаете запрос, как только Composable войдет в состав, вы можете рассмотреть другое состояние, поскольку ничего не происходит. Люди не хотят бесконечно видеть индикатор выполнения, если вы ничего не запрашиваете без взаимодействия с пользователем.