1
votes

Came across a curious situation with AndroidView this morning.

I have a ProductCard interface that looks like this

interface ProductCard {
    val view: View
    fun setup(
        productState: ProductState,
        interactionListener: ProductCardView.InteractionListener
    )
}

This interface can be implemented by a number of views.

A composable that renders a list of AndroidView uses ProductCard to get a view and pass in state updates when recomposition happens.

@Composable
fun BasketItemsList(
    modifier: Modifier,
    basketItems: List<ProductState>,
    provider: ProductCard,
    interactionListener: ProductCardView.InteractionListener
) {
    LazyColumn(modifier = modifier) {
        items(basketItems) { product ->
            AndroidView(factory = { provider.view }) {
                Timber.tag("BasketItemsList").v(product.toString())
                provider.setup(product, interactionListener)
            }
        }
    }
}

With this sample, any interaction with the ProductCard view’s (calling ProductCard.setup()) doesn’t update the screen. Logging shows that the state gets updated but the catch is that it’s only updated once per button. For example, I have a favourites button. Clicking it once pushes a state update only once, any subsequent clicks doesn’t propagate. Also the view itself doesn’t update. It’s as if it was never clicked.

Now changing the block of AndroidView.update to use it and casting it as a concrete view type works as expected. All clicks propagate correctly and the card view gets updated to reflect the state.

@Composable
fun BasketItemsList(
    modifier: Modifier,
    basketItems: List<ProductState>,
    provider: ProductCard,
    interactionListener: ProductCardView.InteractionListener
) {
    LazyColumn(modifier = modifier) {
        items(basketItems) { product ->
            AndroidView(factory = { provider.view }) {
                Timber.tag("BasketItemsList").v(product.toString())
//                provider.setup(product, interactionListener)
                (it as ProductCardView).setup(product, interactionListener)
            }
        }
    }
}

What am I missing here? why does using ProductCard not work while casting the view to its type works as expected?


Update 1

Seems like casting to ProductCard also works

    @Composable
    fun BasketItemsList(
        modifier: Modifier,
        basketItems: List<ProductState>,
        provider: ProductCard,
        interactionListener: ProductCardView.InteractionListener
    ) {
        LazyColumn(modifier = modifier) {
            items(basketItems) { product ->
                AndroidView(factory = { provider.view }) {
                    Timber.tag("BasketItemsList").v(product.toString())
    //                provider.setup(product, interactionListener)
                    (it as ProductCard).setup(product, interactionListener)
                }
            }
        }
    }

So the question is why do we have to use it inside AndroidView.update instead of any other references to the view?

1

1 Answers

0
votes

The answer here is I was missing the key value which is needed for LazyColumn items in order for compose to know which item has changed and call update on it.