te')); return $arr; } /* 遍历用户所有主题 * @param $uid 用户ID * @param int $page 页数 * @param int $pagesize 每页记录条数 * @param bool $desc 排序方式 TRUE降序 FALSE升序 * @param string $key 返回的数组用那一列的值作为 key * @param array $col 查询哪些列 */ function thread_tid_find_by_uid($uid, $page = 1, $pagesize = 1000, $desc = TRUE, $key = 'tid', $col = array()) { if (empty($uid)) return array(); $orderby = TRUE == $desc ? -1 : 1; $arr = thread_tid__find($cond = array('uid' => $uid), array('tid' => $orderby), $page, $pagesize, $key, $col); return $arr; } // 遍历栏目下tid 支持数组 $fid = array(1,2,3) function thread_tid_find_by_fid($fid, $page = 1, $pagesize = 1000, $desc = TRUE) { if (empty($fid)) return array(); $orderby = TRUE == $desc ? -1 : 1; $arr = thread_tid__find($cond = array('fid' => $fid), array('tid' => $orderby), $page, $pagesize, 'tid', array('tid', 'verify_date')); return $arr; } function thread_tid_delete($tid) { if (empty($tid)) return FALSE; $r = thread_tid__delete(array('tid' => $tid)); return $r; } function thread_tid_count() { $n = thread_tid__count(); return $n; } // 统计用户主题数 大数量下严谨使用非主键统计 function thread_uid_count($uid) { $n = thread_tid__count(array('uid' => $uid)); return $n; } // 统计栏目主题数 大数量下严谨使用非主键统计 function thread_fid_count($fid) { $n = thread_tid__count(array('fid' => $fid)); return $n; } ?>How to display a progress indicator in MVVM using Jetpack Compose correctly? - Stack Overflow
最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

How to display a progress indicator in MVVM using Jetpack Compose correctly? - Stack Overflow

programmeradmin3浏览0评论

I need to add an item in Firebase using MVVM and Jetpack Compose. So I'm looking to start a progress indicator when the operation of the addition starts and dismiss it when the operation is complete. This is the function in the interface:

suspend fun addItem(item: Item): Response<String>

This is the implementation:

override suspend fun addItem(item: Item) = try {
    val itemId = itemsRef.add(item).await().id
    Response.Success(itemId)
} catch (ex: Exception) {
    Response.Failure(ex)
}

In the ViewModel I call the addItem like this:

class ItemViewModel @Inject constructor(
    private val repo: ItemRepository
): ViewModel() {
    var addItemResponse by mutableStateOf<Response<String>>(Response.Loading)
        private set

    fun addItem(item: Item) = viewModelScope.launch {
        addItemResponse = repo.addItem(item)
    }
}

And inside the UI, I have a button. When I click it, I add the item to Firebase:

var addingItem by remember { mutableStateOf(false) }

Button(
    onClick = {
        viewModel.addItem(Item("New Item"))
        addingItem = true
    }
) {
    Text(text = "Add Item")
}

if (addingItem) {
    when(val addItemResponse = viewModel.addItemResponse) {
        is Response.Loading -> CircularProgressIndicator()
        is Response.Success -> addingItem = false
        is Response.Failure -> print(addItemResponse.ex)
    }
}

The problem is the use of addingItem. If don't use it in the if statement, I end up with a progress indicator that never stops. So I'm using it like this, because (not sure) of the side effect. I feel that this is not the right approach. Is there any other way of loading a progress bar as long as the addition of the item takes place?

I need to add an item in Firebase using MVVM and Jetpack Compose. So I'm looking to start a progress indicator when the operation of the addition starts and dismiss it when the operation is complete. This is the function in the interface:

suspend fun addItem(item: Item): Response<String>

This is the implementation:

override suspend fun addItem(item: Item) = try {
    val itemId = itemsRef.add(item).await().id
    Response.Success(itemId)
} catch (ex: Exception) {
    Response.Failure(ex)
}

In the ViewModel I call the addItem like this:

class ItemViewModel @Inject constructor(
    private val repo: ItemRepository
): ViewModel() {
    var addItemResponse by mutableStateOf<Response<String>>(Response.Loading)
        private set

    fun addItem(item: Item) = viewModelScope.launch {
        addItemResponse = repo.addItem(item)
    }
}

And inside the UI, I have a button. When I click it, I add the item to Firebase:

var addingItem by remember { mutableStateOf(false) }

Button(
    onClick = {
        viewModel.addItem(Item("New Item"))
        addingItem = true
    }
) {
    Text(text = "Add Item")
}

if (addingItem) {
    when(val addItemResponse = viewModel.addItemResponse) {
        is Response.Loading -> CircularProgressIndicator()
        is Response.Success -> addingItem = false
        is Response.Failure -> print(addItemResponse.ex)
    }
}

The problem is the use of addingItem. If don't use it in the if statement, I end up with a progress indicator that never stops. So I'm using it like this, because (not sure) of the side effect. I feel that this is not the right approach. Is there any other way of loading a progress bar as long as the addition of the item takes place?

Share Improve this question edited Feb 17 at 12:50 tomerpacific 6,47518 gold badges41 silver badges60 bronze badges asked Feb 17 at 12:08 Always LearnerAlways Learner 3,0167 gold badges41 silver badges88 bronze badges 2
  • Do you have a flow for the data from the adding item request you are exposing from your view model that your composable can use? – tomerpacific Commented Feb 17 at 12:50
  • @tomerpacific It's not a flow it's just an operation that completes ones. Do need more information? – Always Learner Commented Feb 17 at 16:14
Add a comment  | 

1 Answer 1

Reset to default 1

Compose is state-based. I'm not sure what you want to do with the result (success/error), especially when multiple items are added in succession. What should the state be that your app is in? But assuming you just want to display the status of adding the latest item, your app can be in any of the following four states:

  1. Nothing was added yet.
  2. An item is currently being added.
  3. The last item was successfully added
  4. An error occurred while adding the last item.

The view model only accommodates for the last three states, the first is handled in Compose with addingItem.

You should move 1. to the view model as well. An easy fix would be to initialize the view model's addItemResponse with null, not with Response.Loading. After all, there isn't anything loading when the view model is first initialized:

var addItemResponse by mutableStateOf<Response<String>?>(null)
    private set

You would only set the loading state when you actually start loading something:

fun addItem(item: Item) {
    addItemResponse = Response.Loading

    viewModelScope.launch {
        addItemResponse = repo.addItem(item)
    }
}

In your composable you can now remove addingItem and add an additional case to the when statement, maybe like this:

when (val addItemResponse = viewModel.addItemResponse) {
    null -> Text("No item was added yet")
    is Response.Loading -> CircularProgressIndicator()
    is Response.Success -> Text("Successfully added the latest item $addItemResponse")
    is Response.Failure -> print(addItemResponse.ex)
}

The failure case still looks suspicious since what you do here is triggereing a side effect (i.e. printing to stdout). It would be executed on every recomposition, not just once. This should be moved to the view model or the repository, and replaced here with something like Text("Error").


Closing note: You shouldn't use MutableState in the view model. There are some nasty edge cases where this won't work as expected. Instead you should employ Kotlin Flows. Your repository should already return flows which are transformed into a StateFlow in the view model and observed in your composables by collectAsStateWithLifecycle(). You can have a look at how it is done in the official sample app Now in Android.

发布评论

评论列表(0)

  1. 暂无评论