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

Andy Himberger

03/28/2022, 3:46 PM
How to get AndroidView factory to run again on recompose? I have a composable helper we use to create some of our legacy views, and even on recompose or changing the factory function instance, the factory doesn't create a new item -- this is the one workaround I've found that works but looking for a proper way to do it -- (component is an enum value passed into the function, I have tried different factory function implementations)
Copy code
ReusableContent(component) {
    AndroidView(factory = {
        createView(it, component)
    }, Modifier.fillMaxSize(), { })
}
I tried some other things like recomposer.invalidate when the component changed, creating different factory function instances that differed slightly, but that didn't change anything... I was really expecting when the args to AndroidView changed, it would create a new view underneath and clean up the old one. Maybe I'm missing something
a

annsofi

03/28/2022, 3:50 PM
There is another argument besides
factory
, I think it's named
update
or something along those lines.
a

Adam Powell

03/28/2022, 3:56 PM
yes, generally you want to update the view that you created once, not recreate views on recomposition. What are you trying to do here?
a

Andy Himberger

03/28/2022, 4:25 PM
One place I'm running into this is in a test app, we have a list detail view, you choose on the left some item and on the right we look up the thing to show. Some of the items are legacy views, some are compose, the show detail compose function invokes a compose function for the new ones, and creates the legacy ones using AndroidView, but they are completely different xml views, its not that i'm updating a View's properties, I'd need to delete and inflate a different tree If I did things in update, I'd need to notice that the type of item I want to have here is different, remember my currently created view, remove the view, run my factory/create function manually?
p

Paul Woitaschek

03/28/2022, 4:33 PM
Then return a frame layout and do the adding and removal in the update function
a

Andy Himberger

03/28/2022, 5:10 PM
OK I'll look into that, there is no way to wrap this in something so it looks like other composables? (having different args recomposes to a new thing). One other way I found I will get separate androidview's created is if I wrap it in an animatedcontent or crossfade
p

Paul Woitaschek

03/28/2022, 5:15 PM
Well if you do a when(enum X-> AndroidView , ... It should work?
a

Andy Himberger

03/28/2022, 5:28 PM
ah yeah that does work, if I list out each enum value it will run the factory each time, since our legacy codebase without compose already had a helper to create based on enum value the factory can be the same:
Copy code
when (component) {
    Component.DATE_FORMAT -> AndroidView(factory = factory)
    Component.DIALOGS -> AndroidView(factory = factory)
    ...(20 more)..
}
p

Paul Woitaschek

03/28/2022, 5:36 PM
Can you post the whole body?
a

Andy Himberger

03/28/2022, 6:05 PM
Doing the replacement in the update function is less code than the big when, that seems to work. I hesitate to show the whole real body since its using fragments and not using AndroidViewBinding since this is leveraging old code as a temporary bridge, but here is the original. I was able to move things to the update function and that works, was just looking for different ways to do it cleanly. btw the reason why I needed multiple views or different viewids is trying to support animations, in the animation case it was creating an additional view with the same id, and dispose would delete the wrong one. This is close to the original which didn't try to create different factory instances
Copy code
@Composable
fun DemoSecondaryPane(
    activity: ComponentActivity,
    component: Component?,
) {
    if (component == null) return

    if (component.composeConfig != null) {
        component.composeConfig.content.invoke()
    } else {
        val supportFragmentManager = (activity as AppCompatActivity).supportFragmentManager
        val fragment = remember(component) {
component.getFragment(supportFragmentManager.fragmentFactory)
        }
        val viewId = remember(component) { View.generateViewId() }
        AndroidView(factory = {
                FragmentContainerView(it).apply {
                    id = viewId
                }
            }, Modifier.fillMaxSize(),
            {}
        )

        DisposableEffect(fragment) {
            supportFragmentManager.beginTransaction().apply {
                disallowAddToBackStack()
                replace(viewId, fragment)
                commit()
            }

            onDispose {
                if (!supportFragmentManager.isStateSaved) {
                    supportFragmentManager.beginTransaction().apply {
                        remove(fragment)
                        commit()
                    }
                }
            }
        }
    }
}
a

Adam Powell

03/28/2022, 9:29 PM
the
key(...) { ... }
composable is likely to be your friend here if there are changes that should really result in completely recreating the view
a

Andy Himberger

03/29/2022, 12:18 AM
thank you that was what I was looking for! Though the update function version can be made to work too
498 Views