androidandroid-jetpack-composeandroid-custom-viewandroid-jetpackcompose-multiplatform

How to migrate my custom view to Jetpack Compose?


I have a custom view created the traditional way: subclassing from the View class.

Specifically, my custom view has many custom attributes (and their corresponding Kotlin properties) that can be assigned in the XML layout:

<com.example.MyCustomView
    app:myCustomAttr1 = "..."
    app:myCustomAttr2 = "..."/>

How to provide a View version of my composable so users can use it in XML?

Besides using AndroidView class to load a conventional Android view in Jetpack Compose, how can I convert my view to be a "true" Compose component?

Should I provide a separate composable for every part of my custom view? For example, a composable for the pie, another composable for the legend box?

Android developer documentation does not mention how to transform custom views to Compose.


Solution

  • Assuming I have created a composable version of my view like this:

    @Composable fun MyComposable(title: String) {
        Text(title)
    }
    

    to use that composable like a regular View (with the ability to specify its attributes in XML), we should create a custom view subclassing from AbstractComposeView:

    // Do not forget these two imports for the delegation (by) to work
    import androidx.compose.runtime.getValue
    import androidx.compose.runtime.setValue
    
    class MyCustomView @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null,
        defStyle: Int = 0
    ) : AbstractComposeView(context, attrs, defStyle) {
    
        var myProperty by mutableStateOf("A string")
    
        init {
            // See the footnote
            context.withStyledAttributes(attrs, R.styleable.MyStyleable) {
                myProperty = getString(R.styleable.MyStyleable_myAttribute)
            }
        }
    
        // The important part
        @Composable override fun Content() {
            MyComposable(title = myProperty)
        }
    }
    

    And this is how we would use it just like a regular view in XML:

    <my.package.name.MyCustomView
        android:id="@+id/myView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:myAttribute="Helloooooooooo!" />
    

    and/or in an activity:

    val myCustomView = findViewById<MyCustomView>(R.id.myView)
    myCustomView.myProperty = "Woohoo!"
    

    Thanks to ProAndroidDev for this article.

    Footnote

    To define your own custom attributes for your view, see this post.
    Also, make sure to use -ktx version of the AndroidX Core library to be able to access useful Kotlin extension functions like Context::withStyledAttributes:

    implementation("androidx.core:core-ktx:1.6.0")