2

Jetpack Compose: A use case for view interop migration strategy

 1 year ago
source link: https://touchlab.co/jetpack-compose-a-use-case-for-view-interop-migration-strategy/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Jetpack Compose: A use case for view interop migration strategy

Jetpack Compose: A use case for view interop migration strategy

Approach

Recently, we had an opportunity to redesign the sample app for an SDK we’re developing. We were only redesigning one screen (home page), so we decided to introduce

Jetpack Compose
Jetpack Compose in the app.

Based on what we read, most migration or view interop posts are about keeping

Fragments
Fragments and
fragment navigation
fragment navigation as they are and moving the content of the fragments into
ComposeView
ComposeView. In fact, official docs also mention that strategy.

For that, we would still have to touch other fragments in the app rather than just the home page. So it felt like more work than what we intended to do. On top of that, the sample app extensively uses

PreferenceFragmentCompat
PreferenceFragmentCompat for various setting screens. To move its content to
ComposeView
ComposeView, we would have to re-create a kind of preferences screen from scratch because compose doesn’t have a straight replacement for
PreferenceFragment
PreferenceFragment.

So we decided to go with the Fragments in Compose approach. Along with that, we also moved our navigation using compose navigation component.

Are you looking for more than just insights to guide your team? Touchlab’s team of experts is available for guidance, training, and more. To learn more about working with Touchlab’s team, let’s start a conversation!

Fragments in Compose

Layout

We created new layout files for each fragment with FragmentContainerView in each.

<?xml version="1.0" encoding="utf-8"?>
<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container_view_events"
android:name="com.example.fragment.analytics.AnalyticsEventsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.fragment.app.FragmentContainerView>
<?xml version="1.0" encoding="utf-8"?>
<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragment_container_view_events"
    android:name="com.example.fragment.analytics.AnalyticsEventsFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

</androidx.fragment.app.FragmentContainerView>

Composable

We wrote a generic

FragmentHolder
FragmentHolder composable with AndroidViewBinding. It composes an Android layout resource. The layout files we created above would have their
Binding
Binding class generated that we can inflate and pass as
BindingFactory
BindingFactory in the
AndroidViewBinding
AndroidViewBinding.
@Composable
fun <T : ViewBinding> FragmentHolderScreen(
topBarTitle: String,
androidViewBindingFactory: (inflater: LayoutInflater, parent: ViewGroup, attachToParent: Boolean) -> T,
onBackPress: () -> Unit = {},
androidViewBindingUpdate: T.() -> Unit = {},
Scaffold(
topBar = {
TopBar(title = topBarTitle, onBackPress = onBackPress)
content = { paddingValues ->
AndroidViewBinding(
factory = androidViewBindingFactory,
modifier = Modifier.padding(paddingValues),
update = androidViewBindingUpdate,
@Composable
fun <T : ViewBinding> FragmentHolderScreen(
    topBarTitle: String,
    androidViewBindingFactory: (inflater: LayoutInflater, parent: ViewGroup, attachToParent: Boolean) -> T,
    onBackPress: () -> Unit = {},
    androidViewBindingUpdate: T.() -> Unit = {},
) {
    Scaffold(
        topBar = {
            TopBar(title = topBarTitle, onBackPress = onBackPress)
        },
        content = { paddingValues ->
            AndroidViewBinding(
                factory = androidViewBindingFactory,
                modifier = Modifier.padding(paddingValues),
                update = androidViewBindingUpdate,
            )
        },
    )
}

Navigation

After creating the composable, we defined a navigation component with a

Screen
Screen enum.

With above defined layout binding and

FragmentHolderScreen
FragmentHolderScreen for
AnalyticsEventsFragment
AnalyticsEventsFragment, the minimized
Scaffold
Scaffold composables look like this:
Scaffold(
content = { contentPadding ->
NavHost(
navController = navController,
startDestination = Screen.Settings.route,
Modifier.padding(contentPadding),
composable(Screen.Events.route) {
FragmentHolderScreen(
topBarTitle = getString(R.string.toolbar_title_events),
androidViewBindingFactory = ComposeFragmentEventsBinding::inflate,
androidViewBindingUpdate = {
with(fragmentContainerViewEvents.getFragment<AnalyticsEventsFragment>()) {
// Reference of AnalyticsEventsFragment is available here
onBackPress = {
onBackPress(navController)
Scaffold(
    content = { contentPadding ->
        NavHost(
            navController = navController,
            startDestination = Screen.Settings.route,
            Modifier.padding(contentPadding),
        ) {
            composable(Screen.Events.route) {
                FragmentHolderScreen(
                    topBarTitle = getString(R.string.toolbar_title_events),
                    androidViewBindingFactory = ComposeFragmentEventsBinding::inflate,
                    androidViewBindingUpdate = {
                        with(fragmentContainerViewEvents.getFragment<AnalyticsEventsFragment>()) {
                            // Reference of AnalyticsEventsFragment is available here
                        }
                    },
                    onBackPress = {
                        onBackPress(navController)
                    },
                )
            }
        }
    },
)

Inner/child fragments

Some of our fragments internally had navigation where a user would go to child fragments. While doing the compose-based navigation, we added those child fragments as part of our

Screen/route
Screen/route enum. Then, we set a lambda function (
val navigateTo: (Screen) -> Unit
val navigateTo: (Screen) -> Unit) in the base fragment class to handle the click actions to move to child fragments.

SharedPreferences

The sample app uses

SharedPreferences
SharedPreferences, and we didn’t have time to move to something like
DataStore
DataStore. It still worked out well after moving one screen to
Compose
Compose. We just made sure to define the reference of
SharedPreferences
SharedPreferences in
onCreate
onCreate of the main activity and then pass the reference down to composable. Since we didn’t touch existing fragments, they use the
SharedPreferences
SharedPreferences as defined already.

UI Testing

We have extensive UI testing in the sample app that tests the SDK. After moving the sample app’s home screen to

Compose
Compose, we broke the entry point of all our tests.

Luckily, compose UI testing and espresso testing work well with each other. You can write a compose test in one-line testing the compose-based screen, and the second line can be an espresso test step looking at the view layer.

The biggest problem we faced around updating unit tests was the delay between the two systems. After clicking on a button on the compose-based screen, the app would go to SDK (a view-based UI). Many tests failed because the view would not be available on the screen when a test executes. After trying out several things, nothing worked well except adding

Thread.sleep()
Thread.sleep() just before and after we handle button clicks on the compose-based screen. The delay probably gave espresso enough time to find the correct view-based screen.

Conclusion

Overall, we’re satisfied with how the whole experiment went. We learned a bit about compose-view interop and got the opportunity to share our findings via this blog post.

Thanks for reading! Let me know in the comments if you have questions. Also, you can reach out to me at @shaktiman_droid on Twitter, LinkedIn or Kotlin Slack. And if you find all this interesting, maybe you’d like to work with or work at Touchlab.

Share:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK