Adopting Jetpack Compose ComposeView

In the first article we had taken a look at how to reuse Material Theming in Compose using MDC theme adapter.

In this article, let’s go over how we can start using Compose in existing view groups. The reason why we would want to do something like this is to iteratively migrate older code to Compose instead of migrating the entire project at once.

In order to use Compose in Android UI toolkit, Compose ships with these 2 classes:

Both of these classes allow us to define Composable functions in our existing Android classes. So, the obvious question is how do we decided which one to use when.

ComposeView

ComposeView is a custom view that can host Compose UI content. We can define this in our layout XML or constructing it programatically and use ComposeView#setContent to define the composable function for the view.

ComposeView(context: Context, attrs: AttributeSet?, defStyleAttr: Int)

Let’s say we have a TextView we want to migrate to Compose UI. We can replace the TextView in our layout XML to ComposeView with same ID.

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout>

  <!--  <TextView-->
  <!--    android:id="@+id/textview"-->
  <!--    android:layout_width="match_parent"-->
  <!--    android:layout_height="wrap_content"-->
  <!--    android:gravity="center_horizontal"-->
  <!--    android:text="@string/hello_world" />-->

  <androidx.compose.ui.platform.ComposeView
    android:id="@+id/textview"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

</FrameLayout>

We can then get the ComposeView in our class that uses this layout XML, and setContent

binding.textView.setContent {
  AppTheme {
    Text(stringResource(R.string.hello_world))
  }
}

That’s it, we have now replaced our TextView from Android UI toolkit with Text from Compose UI. This is a simple example, but in general ComposeView is really useful for migrating simple UI components, that don’t have complex or no state to avoid bloating your class, or for reusing existing composable functions.

I personally like to avoid managing state any state in setContent and instead move it into the composable function or create a custom view using AbstractComposeView.

AbstractComposeView

AbstractComposeView, as the name suggests is an abstract class and a base class for custom views implemented using Compose UI. We cannot use this in layout XML directly and instead have to extend it. All the subclasses that extend AbstractComposeView should override Content function, where the composable function can be defined.

AbstractComposeView(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
)

The benefit of using AbstractComposeView over ComposeView is, we can create custom views that host Compose UI and use them in your layout XML or construct them programatically.

class ProgressButton(
    context: Context
) : AbstractComposeView(context) {
  private val progressState by mutableStateOf(ButtonState.IDLE)
  private val text by mutableStateOf("Click me!")
  @Composable
  override fun Content() {
    AppTheme {
      Button(
          modifier = Modifier
              .fillMaxWidth(),
          onClick = {
            // Handle clicks
          }
      ) {
        if (progressState == PROGRESS) {
          LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
        } else {
          Text(text = text)
        }
      }
    }
  }
}

AbstractComposeView is a great way to move your custom views or components that have a complex internal state to Compose to avoid bloating your current classes with the state code.

Conclusion

Obviously, this article might not have covered all the cases where you should choose one over the other. It depends on various scenarios and codebase. In general start with ComposeView to migrate small parts of the layout to Compose and if you think there is a lot of internal state, you can either move it to the composable function or if you want to reuse the component, you can create a custom view with AbstractComposeView.

Once all the parts of the screen are migrated to Compose, you can completely remove the layout file and build the composable UI in your Activity or Fragment or even use the composable function as screen.

if I missed something or got anything wrong, feel free to reach out to me on Twitter or write to hello@sasikanth.dev