Use AndroidView
Required dependencies
This page is standalone. AndroidView is provided by viewcompose-host-android; no separate
interop artifact is required:
repositories { mavenCentral() }
dependencies {
implementation("com.viewcompose:viewcompose-material3-android:0.1.0-alpha02")
implementation("androidx.activity:activity:1.12.4")
implementation("com.google.android.material:material:1.13.0")
}
Embed and update a TextView
Create AndroidViewTutorialActivity.kt:
package com.viewcompose.samples.tutorials
import android.os.Bundle
import android.view.View
import android.widget.TextView
import androidx.activity.ComponentActivity
import com.viewcompose.host.android.AndroidView
import com.viewcompose.material3.android.setMaterial3UiContent
import com.viewcompose.runtime.mutableStateOf
import com.viewcompose.ui.modifier.Modifier
import com.viewcompose.ui.modifier.fillMaxSize
import com.viewcompose.ui.modifier.fillMaxWidth
import com.viewcompose.ui.modifier.padding
import com.viewcompose.ui.unit.dp
import com.viewcompose.ui.foundation.Button
import com.viewcompose.ui.foundation.Column
import com.viewcompose.ui.foundation.remember
class AndroidViewTutorialActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setMaterial3UiContent {
val count = remember { mutableStateOf(0) }
val largeText = remember { mutableStateOf(false) }
Column(
spacing = 12.dp,
modifier = Modifier.fillMaxSize().padding(24.dp),
) {
AndroidView(
factory = { context ->
TextView(context).apply {
id = View.generateViewId()
textSize = if (largeText.value) 20f else 14f
}
},
update = { view ->
(view as TextView).text =
"Native TextView #${view.id} count: ${count.value}"
},
modifier = Modifier.fillMaxWidth(),
constructionKey = largeText.value,
)
Button("Increment", onClick = { count.value += 1 })
Button(
if (largeText.value) "Use compact native text" else "Use large native text",
onClick = { largeText.value = !largeText.value },
)
}
}
}
}
factory creates the native View only when reconciliation needs a new construction identity, so
the generated View ID makes replacement visible. update applies the latest count to the retained
View and must be safe to run again during rollback or rebind. This sample intentionally treats the
text-size choice as factory-owned construction configuration and therefore also passes it as
constructionKey: changing the count retains the View, while changing that key creates and
atomically replaces it. Keep external one-shot work out of update. This callback form is the
concise low-level escape hatch.
Extract a reusable typed adapter
Use AndroidViewAdapter<V, S> when the integration is reused or owns lifecycle callbacks. The View
type and complete state snapshot remain checked across every callback:
fun typedAndroidViewAdapterSample(builder: UiTreeBuilder) {
builder.AndroidView(
adapter = NativeLabelAdapter,
state = NativeLabelState(
text = "Typed native label",
enabled = true,
),
key = "label",
constructionKey = "default-text-appearance",
)
}
private data class NativeLabelState(
val text: String,
val enabled: Boolean,
)
private object NativeLabelAdapter : AndroidViewAdapter<TextView, NativeLabelState> {
override val reusePolicy: AndroidViewReusePolicy = AndroidViewReusePolicy.Resettable
override fun create(scope: AndroidViewCreateScope): TextView = TextView(scope.context)
override fun update(scope: AndroidViewUpdateScope<TextView>, state: NativeLabelState) {
scope.view.text = state.text
scope.view.isEnabled = state.enabled
}
override fun onReset(
scope: AndroidViewResetScope<TextView>,
reason: AndroidViewResetReason,
) {
scope.view.text = null
scope.view.isEnabled = false
}
}
NativeLabelState keeps the adapter-owned text and enabled properties in one immutable snapshot,
so update can replay the complete configuration without an untyped side channel.
key identifies the logical item. The adapter implementation class plus constructionKey
identifies constructor-sensitive View state. A changed state reuses the View and calls only
update; a changed construction identity creates and binds a candidate, then replaces the old
View only if the complete transaction succeeds. onReset is reserved for integrations that opt
into cross-key mounted-tree reuse with AndroidViewReusePolicy.Resettable.
Verify the result
Record the number after Native TextView #. Press Increment and confirm that the count changes
while the ID remains the same. Then press the native text-size button and confirm that the text
size and ID both change; the construction identity changed, so replacement is expected. Compile
with:
./gradlew :samples:tutorials:assembleDebug
See Hosts, lifecycle, and Android interop for ownership and cleanup rules.