Как правильно использовать поток и ProgressBar в Jetpack Compose?

У меня есть этот код, который увеличивает значение в базе данных:

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

Жаль, что я не видел этот вопрос раньше :). В любом случае уже есть хорошие ответы. Вам просто нужно преобразовать Flow в State с помощью расширения Flow collectAsState. Если вы не сделаете запрос, как только Composable войдет в состав, вы можете рассмотреть другое состояние, поскольку ничего не происходит. Люди не хотят бесконечно видеть индикатор выполнения, если вы ничего не запрашиваете без взаимодействия с пользователем.

Thracian 20.09.2022 17:05

@Thracian Так действительно ли необходимо использовать collectAsState, поскольку я собираю ответ не внутри составной функции, а внутри класса ViewModel?

Joan P. 20.09.2022 17:09

Это не обязательно, но самый простой способ. Вы также можете создать экземпляр MutableState внутри своей ViewModel и обновить этот MutableState с помощью flow.collect{myState =it} или flow.onEach{myState =it}. Если вы не хотите иметь MutableState внутри ViewModel, используйте produceState в Compoable, который преобразует Flow в MutableState, как я ответил в вашем другом вопросе. Вам нужна Composa State, чтобы вызвать рекомпозицию. Вы можете проверить этот вопрос, зачем нужно состояние. stackoverflow.com/q/65368007/5457853

Thracian 20.09.2022 17:13
0
3
400
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

В компоновке реактивного ранца вам не нужно переключать видимость вручную. Вы можете просто играть со штатами. Вы можете создать отдельный компонент индикатора выполнения, который можно использовать и для других экранов. Код ниже будет запускаться только в том случае, если состояние загружается, если состояние изменяется, оно автоматически скрывается.

@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 с примером инкремента мастерская по созданию реактивного ранца

Спасибо, Рохан, за то, что нашли время предоставить и ответить. Да, это хороший подход, но на самом деле он не отвечает на мой вопрос об использовании потока. Только что проголосовал.

Joan P. 17.09.2022 13:11

Я обновил свой ответ, можете ли вы проверить, получили ли вы то, что хотите?

Rohan Sharma 17.09.2022 13:18

Как я уже сказал, это хороший подход, но мой вопрос: является ли плохим подходом добавление загрузки в поток? Или лучше не использовать поток?

Joan P. 17.09.2022 13:53

первое, что вы будете использовать mutableStateOf вместо потока. Теперь вам нужно передать ModelStateName, который будет содержать успешную и неудачную загрузку, и вам нужно переключить эти состояния из модели представления. Загрузка будет в состоянии по умолчанию. Flow не поддерживает жизненный цикл, поэтому мы не можем использовать в компоновке функции mutableStateOf. знает о жизненном цикле и может использоваться в функциях компоновки и рекомендуется

Rohan Sharma 17.09.2022 14:08

Я уже использую объект состояния. incrementResponse является таким объектом. Пожалуйста, проверьте мой обновленный вопрос.

Joan P. 17.09.2022 14:16

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

Joan P. 17.09.2022 14:20

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

Rohan Sharma 17.09.2022 14:27

Да, увеличение может происходить каждый раз, когда пользователь хочет увеличить количество.

Joan P. 17.09.2022 14:28
youtu.be/qvDo0SKR8-k вы можете просмотреть это видео, в нем есть пример увеличения. вы найдете тот же вариант использования в этом видео
Rohan Sharma 17.09.2022 14:32

Я спрашиваю здесь не об операции приращения, а о любой другой операции, которая может выполняться с использованием потока.

Joan P. 17.09.2022 14:43
Ответ принят как подходящий

Можно вернуть поток из репозитория. Вы можете собрать его в 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()
}

Спасибо, что нашли время ответить на мой вопрос. Позвольте мне взглянуть и вернуться к вам.

Joan P. 20.09.2022 13:44

Когда я подаю в суд на последний подход, индикатор выполнения начинает вращаться даже с самого начала, так как он имеет начальное состояние «Загрузка». Я хочу, чтобы отображалось только тогда, когда я нажимаю кнопку увеличения, пока операция увеличения не будет завершена.

Joan P. 20.09.2022 14:20

Кроме того, вы используете private val _incrementResultFlow = MutableStateFlow(Result.Loading) и val incrementResultFlow = _incrementResultFlow.asStateFlow() внутри класса VIewMode. Можно ли упростить это с помощью чего-то вроде var incrementResponse by mutableStateOf(Result.Loading) private set?

Joan P. 20.09.2022 14:41

Что касается вашего второго комментария, да, второй подход на самом деле короче, поэтому я использую его в своих проектах. Вы можете пойти с этим.

Arpit Shukla 20.09.2022 14:46

Я отредактировал ответ, чтобы удовлетворить ваше требование, которое вы упомянули в 1-м комментарии. Взгляни, пожалуйста.

Arpit Shukla 20.09.2022 14:49

Да, но можно ли использовать MutableStateFlow с частным set? Как это работает в случае mutableStateOf?

Joan P. 20.09.2022 15:04

В случае mutableStateOf private set используется для вспомогательного поля, которое мы используем (обратите внимание на делегирование by). Использование частного сеттера с MutableStateFlow не имеет смысла, потому что мы все равно не собираемся менять эту переменную потока, можно изменить только свойство flow.value.

Arpit Shukla 20.09.2022 15:58

Спасибо. Я только что добавил дополнительный вопрос. Может быть, вы можете взглянуть.

Joan P. 20.09.2022 16:15

Ссылка на вопрос неверная. Прямо сейчас он указывает на мой профиль.

Arpit Shukla 20.09.2022 16:17

Извините, я только что отредактировал это. Пожалуйста, проверьте.

Joan P. 20.09.2022 16:18

Эй, извините, я немного неправильно понял вопрос. Я думал, что функция incrementQuantity находится в вашей модели просмотра. Поскольку это функция репо, вы можете продолжать возвращать поток оттуда. И тогда вы можете собрать его в своей модели просмотра, чтобы обновить состояние (составить State или StateFlow)

Arpit Shukla 20.09.2022 16:26

Нет, incrementQuantity находится внутри класса репозитория. Итак, вы говорите, что нет ничего плохого в том, чтобы сгенерировать состояние Loading в первый раз, а затем, когда данные станут доступны, сгенерировать результат в классе репо. Это верно?

Joan P. 20.09.2022 16:31

Да, вы можете это сделать. Но как личное предпочтение, мне не нравится возвращать поток из репозитория только для обработки этого состояния загрузки. Я делаю это в самой viewModel и сохраняю функцию репозитория нормальной suspend функцией. Но все зависит от вас. (Позвольте мне также добавить этот подход в ответ, вы можете выбрать то, что вам больше нравится)

Arpit Shukla 20.09.2022 16:35

С нетерпением жду возможности увидеть это. Еще раз спасибо.

Joan P. 20.09.2022 16:39

Написал по другому вопросу. Посмотри.

Arpit Shukla 20.09.2022 16:42

Позвольте мне взглянуть и вернуться к вам.

Joan P. 20.09.2022 16:48

Если вы сможете адаптировать ответ в соответствии с моим вопросом, чтобы от этого выиграли посетители, я приму ответ и вознагражу вас наградой.

Joan P. 20.09.2022 17:01

Конечно. Дай мне минуту.

Arpit Shukla 20.09.2022 17:02

Отредактировано. Посмотрите, хорошо ли это выглядит для вас.

Arpit Shukla 20.09.2022 18:03

Привет. Может быть, вы можете помочь мне с этим тоже.

Joan P. 28.09.2022 13:59

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