Add Jetpack Compose to your existing Android XML base and Navigation Component

Add Jetpack Compose to your existing Android XML base and Navigation Component

25-Jun-2024 - Sophoun

As an Android developer, I love what's new in Android like, Jetpack libraries and Kotlin multiplatform.

For me, the Jetpack Compose is interesting and has a lot of benefits that it gives to us. But to get those benefits we have to write or restructure our project in different ways to support it.

But today I will show you how I integrate Jetpack Compose to my existing XML base project and using Navigation Component.

Adding compose library

To use Jetpack Compose you need to add several dependencies to your app.

Inside your app/build.gradle file add the dependencies below inside your dependencies :

// Jetpack compose def composeBom = platform('androidx.compose:compose-bom:2024.06.00') implementation composeBom androidTestImplementation composeBom implementation "androidx.activity:activity-compose:1.9.0" // Material Design 3 implementation 'androidx.compose.material3:material3' // Android Studio Preview support implementation 'androidx.compose.ui:ui-tooling-preview' debugImplementation 'androidx.compose.ui:ui-tooling'/

Still inside your app/build.gradle , let enable the compose feature:

android { buildFeatures { compose true } }

After all these, you have Compose your project.

Using Compose in your Fragment

After all setup, you're ready to use compose. Just using ComposeView in side your overridden onCreateView the method like below:

override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return ComposeView(requireContext()).apply { setContent { Scaffold { paddingValues -> Box( modifier = Modifier .padding(top = paddingValues.calculateTopPadding()) ) { Text(text = "Hello Compose") } } } } }

Doing so, seems to be correct right? But after you run the compiler will start yelling at you something like,

java.lang.IllegalStateException: ViewTreeLifecycleOwner not found from android.widget.FrameLayout{2c1459d V.E...... ......ID 0,0-1080,2340 #7f0a0378 app:id/nav_host_fragment} at androidx.compose.ui.platform.WindowRecomposer_androidKt.createLifecycleAwareWindowRecomposer(...

The error above shows you that, the nav_host_fragment doesn't implement lifecycle aware.

Solution

To make this work you have to wrap your nav_host_fragment with a layout that implements lifecycle aware like below.

<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <com.example.widget.FrameLayoutLifecycle android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent"> <androidx.fragment.app.FragmentContainerView android:id="@+id/nav_host_fragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" app:defaultNavHost="true" /> </com.example.widget.FrameLayoutLifecycle> </layout>

Yeah, but not so fast.

As you see in the code above I used the FrameLayoutLifecycle, it's a custom layout that we need to extend from the existing view and implement lifecycle.

package com.example.widget import android.content.Context import android.util.AttributeSet import android.widget.FrameLayout import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import androidx.lifecycle.setViewTreeLifecycleOwner import androidx.savedstate.SavedStateRegistry import androidx.savedstate.SavedStateRegistryController import androidx.savedstate.SavedStateRegistryOwner class FrameLayoutLifecycle : FrameLayout, LifecycleOwner, SavedStateRegistryOwner { constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super( context, attrs, defStyle ) private val lifecycleRegistry = LifecycleRegistry(this) private val savedStateRegistryController = SavedStateRegistryController.create(this) override val lifecycle: Lifecycle get() = lifecycleRegistry override val savedStateRegistry: SavedStateRegistry get() = savedStateRegistryController.savedStateRegistry override fun onAttachedToWindow() { super.onAttachedToWindow() setViewTreeLifecycleOwner(this) savedStateRegistryController.performAttach() savedStateRegistryController.performRestore(null) lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE) lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START) lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) } override fun onDetachedFromWindow() { super.onDetachedFromWindow() lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE) lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP) } }

Above you have to implement LifecycleOwner and SavedStateRegistryOwner and create a lifecycle object to pass to it.

After that, you need to handle the lifecycle event in the onAttachedToWindow method ON_CREATE, ON_START and ON_RESUME also performAttach to the state too.

Then in the onDetachedFromWindow you must handle the event ON_PAUSE and ON_STOP to prevent the leaking.

After everything is set, you're ready to go.

Conclusion

The technology is moving so fast which makes your knowledge and project deprecate really quick too. So keep learning and share your knowledge to keep it up to date. :)

See you!!!