跪拜 Guibai
← Back to the summary

Flutter's Default Activity Now Hosts Jetpack Compose: ViewTree Owners Land for Lifecycle, SavedState, and ViewModel


theme: smartblue

PR #195523 is seemingly starting to support Jetpack Compose's ComposeView. It appears Flutter is adding three AndroidX ViewTree Owners to the default FlutterActivity, so that Compose can find the host for lifecycle, state restoration, and ViewModel.

In theory, later it should directly support putting Compose into the four scenarios of HC, TLHC, VD, and HCPP.

Actually, previously using AndroidView to host ComposeView didn't work well. For example, a Flutter app wanted to reuse a Compose device control panel written by the Android team, and then on the Flutter side put an AndroidView(viewType: 'device_controls'), the native plugin creates a ComposeView through PlatformViewFactory, and then uses setContent { DeviceControls() } to draw the interface.

Generally, a normal Android View would display from there, but Compose, when creating a Composition, also needs to look up the AndroidX-provided host information along the current View's parent hierarchy.

Three Owners are required:

The problem is that Flutter's default FlutterActivity directly inherits from Android Activity. Although it has long implemented LifecycleOwner, it never published it to the ViewTree of DecorView, nor did it provide the full SavedStateRegistry and ViewModelStore.

That is, when ComposeView looks up the tree, it may hit ViewTreeLifecycleOwner not found. Previously, under a normal ViewTree, FlutterFragmentActivity or manually providing Owner could solve some scenarios, because the inherited FragmentActivity itself has these AndroidX capabilities.

But this time it's finally being officially made right. Looking at the draft, FlutterActivity now adds the SavedStateRegistryOwner and ViewModelStoreOwner interfaces, and after setContentView(createFlutterView()), sets the three Owners onto the DecorView.

Also, onCreate() will restore the SavedStateRegistry before Lifecycle enters ON_CREATE, onSaveInstanceState() is responsible for saving, and the ViewModelStore will try to be reused across configuration changes like rotation via onRetainNonConfigurationInstance(), and be cleaned up when the Activity is truly destroyed.

The general situation is similar to this:

Object Compose needs Previous default FlutterActivity problem Capability added this time
LifecycleOwner Activity has a lifecycle, but the ViewTree may not be able to look it up Published to DecorView
SavedStateRegistryOwner Missing AndroidX state-save entry point Integrated with Activity's save and restore
ViewModelStoreOwner Missing ViewModel scope Create, reuse, and clean up ViewModelStore

Note that SavedState here is an AndroidX mechanism, a separate path from Flutter Widgets restoring state through the Restoration Channel.

The real trouble is Virtual Display

And just fixing FlutterActivity isn't enough. The old Virtual Display is still in service, and different Platform View synthesis methods place the Android View in different locations.

In the HC, TLHC, and HCPP scenarios, ComposeView can find the host along the Android ViewTree it's in, but Virtual Display puts the native View into a SingleViewPresentation, which is a separate Window and shares no common parent View with the Activity's DecorView.

That means even if the DecorView has the Owner set, the ComposeView inside it still can't see it:

So the PR adds ViewTreeOwners:

When creating the Presentation, read the three Owners that the current FlutterView can find via ViewTreeOwners.findFrom(flutterView), and then call installOn(rootView) before SingleViewPresentation.onCreate()'s setContentView(rootView).

In this way, when ComposeView attaches to the virtual window, it can also look up along its own parent View and find the corresponding host from the Presentation root node.

Additionally, if you have cached a FlutterEngine, attached it to Activity A, and after A exits attach the same Engine to Activity B, that scenario also needs to be compatible. If the Owner were permanently cached at the first creation of the Platform View, then the ComposeView in B might still be holding the Lifecycle of the destroyed A.

So this PR uses callbacks to get the current FlutterView's Owner when the Presentation is created and recreated, and the resize and resetSurface() paths also re-set it, to avoid being bound to the old host forever.

Based on the current commits, it probably won't make it into next month's release, but the 2027 version should be fine.