https://kotlinlang.org logo
#compose
Title
# compose
m

Mahdi

10/16/2020, 2:53 AM
Hi. I'm using liveData in compose and use
Copy code
observeAsState
It is very odd that after update liveData compose ui update is not happening, but on some events ( click on items) I can see that model is updated! Is it better way to have handle items state outside compose? Thanks
z

Zach Klippenstein (he/him) [MOD]

10/16/2020, 2:57 AM
Can you share any code showing how you’re using it and how you’re updating your composables?
m

Mahdi

10/16/2020, 2:59 AM
This is inside my compose that consuming live data:
Copy code
@Composable
fun acquiredCouponScreen(viewModel: AcquiredCouponViewModel) {

    val items by viewModel.acquiredCoupons.observeAsState(initial = emptyList())
    val lastIndex = items.lastIndex
    val state = viewModel.networkState
    when (state.value) {
        NetworkState.INITIAL -> viewModel.getCouponFromStart()
    }
And this is inside my viewModel:
Copy code
private val _acquiredCoupons = MutableLiveData<List<AcquiredCoupon>>(listOf())
val acquiredCoupons: LiveData<List<AcquiredCoupon>> = _acquiredCoupons
fun doExpand(coupon : AcquiredCoupon){

    val index = _acquiredCoupons.value?.indexOf(coupon) ?: -1
     val newItems = _acquiredCoupons.value?.toMutableList().also {
        it?.get(index)?.isExpanded = true
    }
    _acquiredCoupons.value = newItems
    Timber.i("Mahdi $index")
}
doExpand will calls from compose UI.
So after user click on expand a view it will call viewModel.doExpand() and also data is up dating but recompose is not happening there
z

Zach Klippenstein (he/him) [MOD]

10/16/2020, 3:05 AM
In the code you posted,
acquiredCouponScreen
seems like it should recompose, but you’re also not using
items
or
lastIndex
for anything, so i’m not sure what you’re expecting to update.
m

Mahdi

10/16/2020, 3:06 AM
Ok let me send full code
Copy code
@Composable
fun acquiredCouponScreen(viewModel: AcquiredCouponViewModel) {

    val items by viewModel.acquiredCoupons.observeAsState(initial = emptyList())
    val lastIndex = items.lastIndex
    val state = viewModel.networkState
    when (state.value) {
        NetworkState.INITIAL -> viewModel.getCouponFromStart()
    }

    ConstraintLayout {
        val (list, progress) = createRefs()

        if (state.value == NetworkState.LOADING || state.value == NetworkState.INITIAL) {
            CircularProgressIndicator(
                color = Color.Black,
                modifier = Modifier.fillMaxWidth().height(32.dp).width(32.dp)
                    .constrainAs(progress) {
                        top.linkTo(<http://parent.top|parent.top>)
                        start.linkTo(parent.start)
                        end.linkTo(parent.end)
                        bottom.linkTo(parent.bottom)
                    }
            )
        }
        LazyColumnForIndexed(
            items = items,
            contentPadding = PaddingValues(top = 8.dp),
            modifier = Modifier.constrainAs(list) {
                top.linkTo(<http://parent.top|parent.top>)
                start.linkTo(parent.start)
                end.linkTo(parent.end)
                bottom.linkTo(parent.bottom)
            }

        ) { i, coupon ->
            if (lastIndex == i) {
                onActive {
                    viewModel.getAcquiredCoupons(i + 1)
                }
            }
            Timber.i(coupon.campaignUrl)
            if (coupon.isExpanded) {
                couponExpandedItem(
                    coupon = coupon,
                    { viewModel.doCollapse(it) }, { _, _ -> }
                )
            } else {
                couponCollapsedItem(
                    coupon = coupon
                ) {
                    viewModel.doExpand(it)
                }
            }
        }
    }
}
z

Zach Klippenstein (he/him) [MOD]

10/16/2020, 3:09 AM
Occasionally
LazyColumn*
has bugs where it doesn’t update – if you replace your `LazyColumn*`s with regular `ScrollableColumn`s, does it work?
Is
viewModel.networkState
a
State
as well?
And
getCouponFromStart
is going to get called on every recomposition here, is that intentional?
Copy code
when (state.value) {
        NetworkState.INITIAL -> viewModel.getCouponFromStart()
    }
m

Mahdi

10/16/2020, 3:11 AM
O,let me check it. Yes it is like:
Copy code
private val _networkState = MutableStateFlow(NetworkState.INITIAL)
val networkState: StateFlow<NetworkState> = _networkState
For handle SUCCESS ,ERROR, LOADING state.
because I want to call getDataFRomStart() at first time then handle pagination loading.
i

Ian Lake

10/16/2020, 3:12 AM
Just calling
value
on a
StateFlow
isn't enough to recompose (it is just getting a snapshot value). You have to
collectAsState()
and
value
on that
☝️ 1
z

Zach Klippenstein (he/him) [MOD]

10/16/2020, 3:19 AM
Re: that
getCouponFromStart
call, I would probably advise against putting imperative logic that is unrelated to the UI in the composable at all. That’s something your view model, or something in a layer above it, should be responsible for. If your UI needs to send an event to signal it’s being shown for the first time, it should just send the event and the higher layer can decide whether it needs to actually do anything to handle the event depending what state it’s in. E.g.
Copy code
onActive {
  viewModel.notifyDisplayed()
}
🙌 1
m

Mahdi

10/16/2020, 3:25 AM
Let me check it first @Zach Klippenstein (he/him) [MOD] by replacing lazycolumn and see if it is recompose. Yes I like to have dummy view as possible. I will find a way to handle also that logic (get from start) in view model. @Ian Lake But NetworkState seems trigger fine.
@Zach Klippenstein (he/him) [MOD] But if I use ScrollableColumn I can not handle pagination and get data with offset from repository. How I can handle that with ScrollableColumn?
z

Zach Klippenstein (he/him) [MOD]

10/16/2020, 3:28 AM
You can’t. It’s not a permanent solution, just helps narrow down whether it’s a problem with the lazy composables or something in your own code
There’s also a lazy paging composable coming soon I think, which means you shouldn’t have to wire this all up manually if you can use the androidx paging library.
m

Mahdi

10/16/2020, 3:30 AM
Sound great, previously I using paging3 but for compose I refactor it to handle by myself.
So i'm looking forward
4 Views