One Off the Slack: The Role of AndroidView

Google’s Jim Sproch wrote:

I’m always a little surprised when I see people using AndroidView directly in their apps, and maybe it’s because we made the API too pleasant to use or maybe it’s because we never really communicated the intent to the community. 🤷 Certainly I never expected to see people remembering the view and then just referencing that view in the viewBlock, I suppose no API survives first encounter with the user.

Composables are epitomized by their convenient declarative APIs. The older android Views often have imperative APIs with sometimes surprising semantics that need to be papered over, and this wallpaper isn’t always straight forward to get right. The intent/expectation was that users would write a composable function who’s sole purpose was to wrap a particular Android View, and would provide this wallpaper, such that the widget was pleasant to use in the rest of their application.

Basically, what Jim is expecting is a single-purpose composable, just to wrap the legacy View:

I would not expect AndroidView to ever have any siblings within a given widget. I would expect it to ALWAYS be of the form:

@Composable
fun EditText(text: String, onChange: (newValue: String)->Unit, ...) {
    AndroidView(
        viewBlock={
           android.widget.EditText(context)
            .apply {
                layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
            }
        },
        editBlock={
            // Deal with logic to add/remove change listeners, etc.
        }
    )
}

Now EditText looks like a composable function and has a declarative API, hiding the details of adding/updating change listeners, etc. It now provides an API that makes it look much more like a native composable.

There is an inherent impedance mismatch when crossing between declarative and imperative APIs. The intent was that people would deal with that impedance mismatch within a tightly-scoped composable that encapsulates/hides the ugly details, and would provide only an idiomatic declarative API to callers.

Jim’s concerns seem to stem from a separate thread, where a developer used an AndroidView() wrapper around an EditText subclass, directly in a more complex composable along with a Column() and Text() composables. Jim would prefer that the AndroidView() be isolated in its own composable, with that wrapper composable then used alongside “native” composables.

Various developers have already been working on this sort of concern. Zach Klippenstein wrote:

All our interop between Android views and compose is expected to happen at very well defined, explicit boundaries and I think we’ll probably try pretty hard to discourage any other integration. So whatever this api ends up being, we only need to wrap it in one place. I think the AndroidView api makes sense and pretty elegant, but I would probably be excited if you decided to make it higher-friction if that extra friction also made it harder to use incorrectly and helped reduce potential bugs. It would actually make my job easier.

…while Colton Idle is thinking more about tooling:

I’m thinking of writing a custom lint rule to prevent my team from using an AndroidView directly and guide them towards creating a wrapper composable.

Early on, there will be more use of AndroidView() than there will be over time, because:

Even in cases where the underlying implementation remains a View (e.g., WebView), we should wind up with official composable wrappers, and we can aim to migrate to those as they become available.


Read the original thread in the kotlinlang Slack workspace. Not a member? Join that Slack workspace here!