Thread
#compose
    Stylianos Gakis

    Stylianos Gakis

    11 months ago
    When using a normal
    RecyclerView
    that holds
    ViewHolders
    that just hold a
    ComposeView
    inside it, I am not getting the expected recomposition behaviour I am expecting. More details in thread 🧵
    I am using a normal
    RecyclerView
    backed by a
    ListAdapter
    with diffUtil. My
    ViewHolder
    I am referring to looks something like this:
    class MyComposeViewHolder(
        private val composeView: ComposeView,
    ) : MainViewHolder(composeView) {
        init { composeView.setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) }
    
        override fun bind(model: MainModel) {
            composeView.setContent {
                AppTheme {
                    Row {
                        AnimatedVisibility(model.someBoolean) {
                            // Should trigger to showing/not showing backed by `model.someBoolean`
                            Row {
                                Canvas(Modifier.size(8.dp)) { drawCircle(Color.Red) }
                            }
                        }
                        // Always showing composable
                    }
                }
            }
        }
    }
    Now my problem is that I can’t manage to make it so that whenever the data changes, what actually happens is that the new model is fed inside the composable, and proper recomposition happens. Now what happens is that a whole new view is created, and instead of getting the nice
    AnimatedVisibility
    animation I get the generic
    RecyclerView
    flickering. The DiffUtil does indeed return true for
    areItemsTheSame
    and false for
    areContentsTheSame
    so my expected behaviour here is that the new data gets send to the bind method, not that the entire ViewHolder is initialised. I feel like I am either not understanding something correctly, or I am doing some silly
    RecyclerView
    mistake after not having used it for so many months. Does anyone have an example of a properly implemented
    RecyclerView
    with
    ComposableView
    items inside of it? I’d love to look as some open sourced examples.
    Chuck Jazdzewski [G]

    Chuck Jazdzewski [G]

    11 months ago
    Is
    model.someBoolean
    a
    mutableStateOf<Boolean>()
    ?
    Albert Chang

    Albert Chang

    11 months ago
    I guess you are calling
    bind()
    whenever the data changes, right? If so, you are basically changing the entire content every time discarding the old composition. You should probably do something like this:
    class MyComposeViewHolder(
        composeView: ComposeView,
    ) : MainViewHolder(composeView) {
        private var model by mutableStateOf<MainModel?>(null)
    
        init {
            composeView.setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
            composeView.setContent {
                // Use `model` here
            }
        }
    
        override fun bind(model: MainModel) {
            this.model = model
        }
    }
    a

    Arjun Achatz

    11 months ago
    This ^
    Note, unless you need diff util, go with lazy column, you're taking a performance hit with interop
    Also make sure you're not calling dispose
    Stylianos Gakis

    Stylianos Gakis

    11 months ago
    My
    model.boolean
    is in fact not a mutableState. And I see how initialising the composable once in the init and then changing the local nullable
    mutableState
    makes sense, these seem to be good pointers. I feel like after the changes you’ve suggested it should work, and I am just doing something wrong on the normal RecyclerView implementation, like it runs
    init{}
    on the ViewHolder every time even if areItemsTheSame returns true. I swear I’ve just forgotten how RV works after doing so much Compose. Does anyone have a link to a properly implemented RV that extends
    ListAdapter
    so I can do a sanity check and see what I’m messing up? If you have a sample of an RV with composables in some of its VH too that’d be perfect! p.s: As far as lazy column goes, I’d love to, but I’m editing a RecyclerView with many ViewHolder types so I’d rather just not do more than I need to right now and risk other type of regressions. Just want to add one more type which happens to be a composable and has some animation inside of it.
    And in general, is there explicit documentation or a codelab for this use case? This feels like it’s gonna be how people implement RecyclerViews for years to come with most projects being a combination of Compose and non-Compose code. I’d love to see a codelab by Google about this that simply explains everything I’d need to know about this super common use-case. Who from Google, should I give this suggestion to? It really feels like a no-brainer to me.
    The only way I made it work was by setting the
    itemAnimator
    on my
    recyclerView
    as null or by setting the
    supportsChangeAnimations
    to false on the existing
    itemAnimator
    like this:
    (itemAnimator as? SimpleItemAnimator)?.supportsChangeAnimations = false
    But is this what we’re supposed to do? This way I am removing the default change animation for the other non-compose views which I might actually want to have. I absolutely must be missing something super obvious here, it shouldn’t be that hard to achieve what I want.
    Albert Chang

    Albert Chang

    11 months ago
    This is obviously the intended behavior of RecyclerView as it runs the animation whenever the content changes. It doesn't know whether you are using Compose UI.
    Stylianos Gakis

    Stylianos Gakis

    11 months ago
    That is totally understandable. However, is this the best way to achieve this result? As I said before, my guess is that for many years going forward, people will be using these interoperability features to add compose UI in their existing code, and one of the first points of entry will be adding ViewHolder types that have compose under the hood. I am just a bit surprised if this is the best we can get. And I still believe that this should be part of a codelab to explain these limitations and possibly provide better workarounds than my hack.
    Albert Chang

    Albert Chang

    11 months ago
    I don't think using RecyclerView with ComposeView children is a recommended way at the first place. There are some issues such as interoperability issues and performance issues. Even using LazyColumn with AndroidViews is better IMO.
    Basically there isn't official support of Compose UI in RecyclerView but there is official support for AndroidView in Compose UI.
    Stylianos Gakis

    Stylianos Gakis

    11 months ago
    Hmm interesting. So we have an existing codebase. With a recyclerView with let’s say 20 different item types. And one wants to add another type but the team has decided to go full compose on all new views. What is the optimal approach?
    Albert Chang

    Albert Chang

    11 months ago
    It is your own decision after all, but you need to understand that both approaches has limitations.
    Chris Johnson

    Chris Johnson

    11 months ago
    Honestly I understand the hesitancy to change more than you need to. In our project we've been converting all our recycler views to LazyColumns/Rows and it's been very easy even with 10+ viewholder types. If you're worried about list item animations you can checkout the compose cookbook which has a lot of really good samples on how to do animations like that. https://github.com/Gurupreet/ComposeCookBook My recommendation would be to just take the hit up front and convert the recycler view. Otherwise you're just incurring tech debt with interop.
    Stylianos Gakis

    Stylianos Gakis

    11 months ago
    Thank you for the suggestion! Do you also have some examples/tips on how to properly implement AndroidViews as LazyColumn items? The hesitancy probably comes from the fact that I’ve never done it and don’t want to risk breaking existing stuff that have proven to work for months now if I am not 101% sure about what I’m doing.
    Chris Johnson

    Chris Johnson

    11 months ago
    I'm not quite sure what your lazyColumn items are doing in terms of animations, but we've added shimmers that animate in LazyColumns. I think you just have to make sure you use best practices in your items to make sure you're not doing unnecessary compositions or allocating animation objects you don't need to on every composition. Things like
    derivedStateOf
    and
    remember
    That's if you want to convert your items to composables. Using AndroidView should be easy to use. If you have performance problems, first remember on debug builds LazyColumns are a bit slower, and second, that's probably when you wanna look into converting your list items to compose 😅
    You'll probably just use the default LazyColumn IMPL and inside the LazyColumnScope DSL then use a when statement for your item types
    Stylianos Gakis

    Stylianos Gakis

    11 months ago
    I’ll have a talk with my team and see which approach makes more sense. Thanks for the tips btw, I’m sure they’ll prove to be super valuable!
    Chris Johnson

    Chris Johnson

    11 months ago
    Sure! Sorry I couldn't share more information. I'm also not at my computer at the moment 😅
    Stylianos Gakis

    Stylianos Gakis

    11 months ago
    Hehe yeah I don’t have any good questions yet anyway, probably more will arise as I get down to try implementing some changes like that. Maybe sometime in the near future, who knows
    Chris Johnson

    Chris Johnson

    11 months ago
    Feel free to ping me when you do