blob: 93ff3e7165e0c15a9447c417494574b2f700a641 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package io.flutter.embedding.android;
import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.Lifecycle;
import io.flutter.Log;
import io.flutter.app.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterEngineCache;
import io.flutter.embedding.engine.FlutterShellArgs;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener;
import io.flutter.plugin.platform.PlatformPlugin;
import java.util.Arrays;
/**
* Delegate that implements all Flutter logic that is the same between a {@link FlutterActivity} and
* a {@link FlutterFragment}.
*
* <p><strong>Why does this class exist?</strong>
*
* <p>One might ask why an {@code Activity} and {@code Fragment} delegate needs to exist. Given that
* a {@code Fragment} can be placed within an {@code Activity}, it would make more sense to use a
* {@link FlutterFragment} within a {@link FlutterActivity}.
*
* <p>The {@code Fragment} support library adds 100k of binary size to an app, and full-Flutter apps
* do not otherwise require that binary hit. Therefore, it was concluded that Flutter must provide a
* {@link FlutterActivity} based on the AOSP {@code Activity}, and an independent {@link
* FlutterFragment} for add-to-app developers.
*
* <p>If a time ever comes where the inclusion of {@code Fragment}s in a full-Flutter app is no
* longer deemed an issue, this class should be immediately decomposed between {@link
* FlutterActivity} and {@link FlutterFragment} and then eliminated.
*
* <p><strong>Caution when modifying this class</strong>
*
* <p>Any time that a "delegate" is created with the purpose of encapsulating the internal behaviors
* of another object, that delegate is highly susceptible to degeneration. It is easy to tack new
* responsibilities on to the delegate which would not otherwise be added to the original object. It
* is also easy to begin hanging listeners and callbacks on a delegate object that likewise would
* not be added to the original object. A delegate can quickly become a complex web of dependencies
* and optional references that are very difficult to track.
*
* <p>Maintainers of this class should take care to only place code in this delegate that would
* otherwise be placed in either {@link FlutterActivity} or {@link FlutterFragment}, and in exactly
* the same form. <strong>Do not use this class as a convenient shortcut for any other
* behavior.</strong>
*/
/* package */ final class FlutterActivityAndFragmentDelegate {
private static final String TAG = "FlutterActivityAndFragmentDelegate";
// The FlutterActivity or FlutterFragment that is delegating most of its calls
// to this FlutterActivityAndFragmentDelegate.
@NonNull private Host host;
@Nullable private FlutterEngine flutterEngine;
@Nullable private FlutterSplashView flutterSplashView;
@Nullable private FlutterView flutterView;
@Nullable private PlatformPlugin platformPlugin;
private boolean isFlutterEngineFromHost;
@NonNull
private final FlutterUiDisplayListener flutterUiDisplayListener =
new FlutterUiDisplayListener() {
@Override
public void onFlutterUiDisplayed() {
host.onFlutterUiDisplayed();
}
@Override
public void onFlutterUiNoLongerDisplayed() {
host.onFlutterUiNoLongerDisplayed();
}
};
FlutterActivityAndFragmentDelegate(@NonNull Host host) {
this.host = host;
}
/**
* Disconnects this {@code FlutterActivityAndFragmentDelegate} from its host {@code Activity} or
* {@code Fragment}.
*
* <p>No further method invocations may occur on this {@code FlutterActivityAndFragmentDelegate}
* after invoking this method. If a method is invoked, an exception will occur.
*
* <p>This method only clears out references. It does not destroy its {@link FlutterEngine}. The
* behavior that destroys a {@link FlutterEngine} can be found in {@link #onDetach()}.
*/
void release() {
this.host = null;
this.flutterEngine = null;
this.flutterView = null;
this.platformPlugin = null;
}
/**
* Returns the {@link FlutterEngine} that is owned by this delegate and its host {@code Activity}
* or {@code Fragment}.
*/
@Nullable
/* package */ FlutterEngine getFlutterEngine() {
return flutterEngine;
}
/**
* Returns true if the host {@code Activity}/{@code Fragment} provided a {@code FlutterEngine}, as
* opposed to this delegate creating a new one.
*/
/* package */ boolean isFlutterEngineFromHost() {
return isFlutterEngineFromHost;
}
/**
* Invoke this method from {@code Activity#onCreate(Bundle)} or {@code
* Fragment#onAttach(Context)}.
*
* <p>This method does the following:
*
* <p>
*
* <ol>
* <li>Initializes the Flutter system.
* <li>Obtains or creates a {@link FlutterEngine}.
* <li>Creates and configures a {@link PlatformPlugin}.
* <li>Attaches the {@link FlutterEngine} to the surrounding {@code Activity}, if desired.
* <li>Configures the {@link FlutterEngine} via {@link
* Host#configureFlutterEngine(FlutterEngine)}.
* </ol>
*/
void onAttach(@NonNull Context context) {
ensureAlive();
// When "retain instance" is true, the FlutterEngine will survive configuration
// changes. Therefore, we create a new one only if one does not already exist.
if (flutterEngine == null) {
setupFlutterEngine();
}
// Regardless of whether or not a FlutterEngine already existed, the PlatformPlugin
// is bound to a specific Activity. Therefore, it needs to be created and configured
// every time this Fragment attaches to a new Activity.
// TODO(mattcarroll): the PlatformPlugin needs to be reimagined because it implicitly takes
// control of the entire window. This is unacceptable for non-fullscreen
// use-cases.
platformPlugin = host.providePlatformPlugin(host.getActivity(), flutterEngine);
if (host.shouldAttachEngineToActivity()) {
// Notify any plugins that are currently attached to our FlutterEngine that they
// are now attached to an Activity.
//
// Passing this Fragment's Lifecycle should be sufficient because as long as this Fragment
// is attached to its Activity, the lifecycles should be in sync. Once this Fragment is
// detached from its Activity, that Activity will be detached from the FlutterEngine, too,
// which means there shouldn't be any possibility for the Fragment Lifecycle to get out of
// sync with the Activity. We use the Fragment's Lifecycle because it is possible that the
// attached Activity is not a LifecycleOwner.
Log.v(TAG, "Attaching FlutterEngine to the Activity that owns this Fragment.");
flutterEngine
.getActivityControlSurface()
.attachToActivity(host.getActivity(), host.getLifecycle());
}
host.configureFlutterEngine(flutterEngine);
}
/**
* Obtains a reference to a FlutterEngine to back this delegate and its {@code host}.
*
* <p>
*
* <p>First, the {@code host} is asked if it would like to use a cached {@link FlutterEngine}, and
* if so, the cached {@link FlutterEngine} is retrieved.
*
* <p>Second, the {@code host} is given an opportunity to provide a {@link FlutterEngine} via
* {@link Host#provideFlutterEngine(Context)}.
*
* <p>If the {@code host} does not provide a {@link FlutterEngine}, then a new {@link
* FlutterEngine} is instantiated.
*/
@VisibleForTesting
/* package */ void setupFlutterEngine() {
Log.v(TAG, "Setting up FlutterEngine.");
// First, check if the host wants to use a cached FlutterEngine.
String cachedEngineId = host.getCachedEngineId();
if (cachedEngineId != null) {
flutterEngine = FlutterEngineCache.getInstance().get(cachedEngineId);
isFlutterEngineFromHost = true;
if (flutterEngine == null) {
throw new IllegalStateException(
"The requested cached FlutterEngine did not exist in the FlutterEngineCache: '"
+ cachedEngineId
+ "'");
}
return;
}
// Second, defer to subclasses for a custom FlutterEngine.
flutterEngine = host.provideFlutterEngine(host.getContext());
if (flutterEngine != null) {
isFlutterEngineFromHost = true;
return;
}
// Our host did not provide a custom FlutterEngine. Create a FlutterEngine to back our
// FlutterView.
Log.v(
TAG,
"No preferred FlutterEngine was provided. Creating a new FlutterEngine for"
+ " this FlutterFragment.");
flutterEngine =
new FlutterEngine(
host.getContext(),
host.getFlutterShellArgs().toArray(),
/*automaticallyRegisterPlugins=*/ false);
isFlutterEngineFromHost = false;
}
/**
* Invoke this method from {@code Activity#onCreate(Bundle)} to create the content {@code View},
* or from {@code Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)}.
*
* <p>{@code inflater} and {@code container} may be null when invoked from an {@code Activity}.
*
* <p>This method:
*
* <ol>
* <li>creates a new {@link FlutterView} in a {@code View} hierarchy
* <li>adds a {@link FlutterUiDisplayListener} to it
* <li>attaches a {@link FlutterEngine} to the new {@link FlutterView}
* <li>returns the new {@code View} hierarchy
* </ol>
*/
@NonNull
View onCreateView(
LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
Log.v(TAG, "Creating FlutterView.");
ensureAlive();
if (host.getRenderMode() == RenderMode.surface) {
FlutterSurfaceView flutterSurfaceView =
new FlutterSurfaceView(
host.getActivity(), host.getTransparencyMode() == TransparencyMode.transparent);
// Allow our host to customize FlutterSurfaceView, if desired.
host.onFlutterSurfaceViewCreated(flutterSurfaceView);
// Create the FlutterView that owns the FlutterSurfaceView.
flutterView = new FlutterView(host.getActivity(), flutterSurfaceView);
} else {
FlutterTextureView flutterTextureView = new FlutterTextureView(host.getActivity());
// Allow our host to customize FlutterSurfaceView, if desired.
host.onFlutterTextureViewCreated(flutterTextureView);
// Create the FlutterView that owns the FlutterTextureView.
flutterView = new FlutterView(host.getActivity(), flutterTextureView);
}
// Add listener to be notified when Flutter renders its first frame.
flutterView.addOnFirstFrameRenderedListener(flutterUiDisplayListener);
flutterSplashView = new FlutterSplashView(host.getContext());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
flutterSplashView.setId(View.generateViewId());
} else {
// TODO(mattcarroll): Find a better solution to this ID. This is a random, static ID.
// It might conflict with other Views, and it means that only a single FlutterSplashView
// can exist in a View hierarchy at one time.
flutterSplashView.setId(486947586);
}
flutterSplashView.displayFlutterViewWithSplash(flutterView, host.provideSplashScreen());
Log.v(TAG, "Attaching FlutterEngine to FlutterView.");
flutterView.attachToFlutterEngine(flutterEngine);
return flutterSplashView;
}
void onActivityCreated(@Nullable Bundle bundle) {
Log.v(TAG, "onActivityCreated. Giving plugins an opportunity to restore state.");
ensureAlive();
if (host.shouldAttachEngineToActivity()) {
flutterEngine.getActivityControlSurface().onRestoreInstanceState(bundle);
}
}
/**
* Invoke this from {@code Activity#onStart()} or {@code Fragment#onStart()}.
*
* <p>This method:
*
* <p>
*
* <ol>
* <li>Begins executing Dart code, if it is not already executing.
* </ol>
*/
void onStart() {
Log.v(TAG, "onStart()");
ensureAlive();
doInitialFlutterViewRun();
}
/**
* Starts running Dart within the FlutterView for the first time.
*
* <p>Reloading/restarting Dart within a given FlutterView is not supported. If this method is
* invoked while Dart is already executing then it does nothing.
*
* <p>{@code flutterEngine} must be non-null when invoking this method.
*/
private void doInitialFlutterViewRun() {
// Don't attempt to start a FlutterEngine if we're using a cached FlutterEngine.
if (host.getCachedEngineId() != null) {
return;
}
if (flutterEngine.getDartExecutor().isExecutingDart()) {
// No warning is logged because this situation will happen on every config
// change if the developer does not choose to retain the Fragment instance.
// So this is expected behavior in many cases.
return;
}
Log.v(
TAG,
"Executing Dart entrypoint: "
+ host.getDartEntrypointFunctionName()
+ ", and sending initial route: "
+ host.getInitialRoute());
// The engine needs to receive the Flutter app's initial route before executing any
// Dart code to ensure that the initial route arrives in time to be applied.
if (host.getInitialRoute() != null) {
flutterEngine.getNavigationChannel().setInitialRoute(host.getInitialRoute());
}
// Configure the Dart entrypoint and execute it.
DartExecutor.DartEntrypoint entrypoint =
new DartExecutor.DartEntrypoint(
host.getAppBundlePath(), host.getDartEntrypointFunctionName());
flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint);
}
/**
* Invoke this from {@code Activity#onResume()} or {@code Fragment#onResume()}.
*
* <p>This method notifies the running Flutter app that it is "resumed" as per the Flutter app
* lifecycle.
*/
void onResume() {
Log.v(TAG, "onResume()");
ensureAlive();
flutterEngine.getLifecycleChannel().appIsResumed();
}
/**
* Invoke this from {@code Activity#onPostResume()}.
*
* <p>A {@code Fragment} host must have its containing {@code Activity} forward this call so that
* the {@code Fragment} can then invoke this method.
*
* <p>This method informs the {@link PlatformPlugin} that {@code onPostResume()} has run, which is
* used to update system UI overlays.
*/
// TODO(mattcarroll): determine why this can't be in onResume(). Comment reason, or move if
// possible.
void onPostResume() {
Log.v(TAG, "onPostResume()");
ensureAlive();
if (flutterEngine != null) {
if (platformPlugin != null) {
// TODO(mattcarroll): find a better way to handle the update of UI overlays than calling
// through
// to platformPlugin. We're implicitly entangling the Window, Activity,
// Fragment,
// and engine all with this one call.
platformPlugin.updateSystemUiOverlays();
}
} else {
Log.w(TAG, "onPostResume() invoked before FlutterFragment was attached to an Activity.");
}
}
/**
* Invoke this from {@code Activity#onPause()} or {@code Fragment#onPause()}.
*
* <p>This method notifies the running Flutter app that it is "inactive" as per the Flutter app
* lifecycle.
*/
void onPause() {
Log.v(TAG, "onPause()");
ensureAlive();
flutterEngine.getLifecycleChannel().appIsInactive();
}
/**
* Invoke this from {@code Activity#onStop()} or {@code Fragment#onStop()}.
*
* <p>This method:
*
* <p>
*
* <ol>
* <li>This method notifies the running Flutter app that it is "paused" as per the Flutter app
* lifecycle.
* <li>Detaches this delegate's {@link FlutterEngine} from this delegate's {@link FlutterView}.
* </ol>
*/
void onStop() {
Log.v(TAG, "onStop()");
ensureAlive();
flutterEngine.getLifecycleChannel().appIsPaused();
}
/**
* Invoke this from {@code Activity#onDestroy()} or {@code Fragment#onDestroyView()}.
*
* <p>This method removes this delegate's {@link FlutterView}'s {@link FlutterUiDisplayListener}.
*/
void onDestroyView() {
Log.v(TAG, "onDestroyView()");
ensureAlive();
flutterView.detachFromFlutterEngine();
flutterView.removeOnFirstFrameRenderedListener(flutterUiDisplayListener);
}
void onSaveInstanceState(@Nullable Bundle bundle) {
Log.v(TAG, "onSaveInstanceState. Giving plugins an opportunity to save state.");
ensureAlive();
if (host.shouldAttachEngineToActivity()) {
flutterEngine.getActivityControlSurface().onSaveInstanceState(bundle);
}
}
/**
* Invoke this from {@code Activity#onDestroy()} or {@code Fragment#onDetach()}.
*
* <p>This method:
*
* <p>
*
* <ol>
* <li>Detaches this delegate's {@link FlutterEngine} from its surrounding {@code Activity}, if
* it was previously attached.
* <li>Destroys this delegate's {@link PlatformPlugin}.
* <li>Destroys this delegate's {@link FlutterEngine} if {@link
* Host#shouldDestroyEngineWithHost()} ()} returns true.
* </ol>
*/
void onDetach() {
Log.v(TAG, "onDetach()");
ensureAlive();
// Give the host an opportunity to cleanup any references that were created in
// configureFlutterEngine().
host.cleanUpFlutterEngine(flutterEngine);
if (host.shouldAttachEngineToActivity()) {
// Notify plugins that they are no longer attached to an Activity.
Log.v(TAG, "Detaching FlutterEngine from the Activity that owns this Fragment.");
if (host.getActivity().isChangingConfigurations()) {
flutterEngine.getActivityControlSurface().detachFromActivityForConfigChanges();
} else {
flutterEngine.getActivityControlSurface().detachFromActivity();
}
}
// Null out the platformPlugin to avoid a possible retain cycle between the plugin, this
// Fragment,
// and this Fragment's Activity.
if (platformPlugin != null) {
platformPlugin.destroy();
platformPlugin = null;
}
flutterEngine.getLifecycleChannel().appIsDetached();
// Destroy our FlutterEngine if we're not set to retain it.
if (host.shouldDestroyEngineWithHost()) {
flutterEngine.destroy();
if (host.getCachedEngineId() != null) {
FlutterEngineCache.getInstance().remove(host.getCachedEngineId());
}
flutterEngine = null;
}
}
/**
* Invoke this from {@link Activity#onBackPressed()}.
*
* <p>A {@code Fragment} host must have its containing {@code Activity} forward this call so that
* the {@code Fragment} can then invoke this method.
*
* <p>This method instructs Flutter's navigation system to "pop route".
*/
void onBackPressed() {
ensureAlive();
if (flutterEngine != null) {
Log.v(TAG, "Forwarding onBackPressed() to FlutterEngine.");
flutterEngine.getNavigationChannel().popRoute();
} else {
Log.w(TAG, "Invoked onBackPressed() before FlutterFragment was attached to an Activity.");
}
}
/**
* Invoke this from {@link Activity#onRequestPermissionsResult(int, String[], int[])} or {@code
* Fragment#onRequestPermissionsResult(int, String[], int[])}.
*
* <p>This method forwards to interested Flutter plugins.
*/
void onRequestPermissionsResult(
int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
ensureAlive();
if (flutterEngine != null) {
Log.v(
TAG,
"Forwarding onRequestPermissionsResult() to FlutterEngine:\n"
+ "requestCode: "
+ requestCode
+ "\n"
+ "permissions: "
+ Arrays.toString(permissions)
+ "\n"
+ "grantResults: "
+ Arrays.toString(grantResults));
flutterEngine
.getActivityControlSurface()
.onRequestPermissionsResult(requestCode, permissions, grantResults);
} else {
Log.w(
TAG,
"onRequestPermissionResult() invoked before FlutterFragment was attached to an Activity.");
}
}
/**
* Invoke this from {@code Activity#onNewIntent(Intent)}.
*
* <p>A {@code Fragment} host must have its containing {@code Activity} forward this call so that
* the {@code Fragment} can then invoke this method.
*
* <p>This method forwards to interested Flutter plugins.
*/
void onNewIntent(@NonNull Intent intent) {
ensureAlive();
if (flutterEngine != null) {
Log.v(TAG, "Forwarding onNewIntent() to FlutterEngine.");
flutterEngine.getActivityControlSurface().onNewIntent(intent);
} else {
Log.w(TAG, "onNewIntent() invoked before FlutterFragment was attached to an Activity.");
}
}
/**
* Invoke this from {@code Activity#onActivityResult(int, int, Intent)} or {@code
* Fragment#onActivityResult(int, int, Intent)}.
*
* <p>This method forwards to interested Flutter plugins.
*/
void onActivityResult(int requestCode, int resultCode, Intent data) {
ensureAlive();
if (flutterEngine != null) {
Log.v(
TAG,
"Forwarding onActivityResult() to FlutterEngine:\n"
+ "requestCode: "
+ requestCode
+ "\n"
+ "resultCode: "
+ resultCode
+ "\n"
+ "data: "
+ data);
flutterEngine.getActivityControlSurface().onActivityResult(requestCode, resultCode, data);
} else {
Log.w(TAG, "onActivityResult() invoked before FlutterFragment was attached to an Activity.");
}
}
/**
* Invoke this from {@code Activity#onUserLeaveHint()}.
*
* <p>A {@code Fragment} host must have its containing {@code Activity} forward this call so that
* the {@code Fragment} can then invoke this method.
*
* <p>This method forwards to interested Flutter plugins.
*/
void onUserLeaveHint() {
ensureAlive();
if (flutterEngine != null) {
Log.v(TAG, "Forwarding onUserLeaveHint() to FlutterEngine.");
flutterEngine.getActivityControlSurface().onUserLeaveHint();
} else {
Log.w(TAG, "onUserLeaveHint() invoked before FlutterFragment was attached to an Activity.");
}
}
/**
* Invoke this from {@link Activity#onTrimMemory(int)}.
*
* <p>A {@code Fragment} host must have its containing {@code Activity} forward this call so that
* the {@code Fragment} can then invoke this method.
*
* <p>This method sends a "memory pressure warning" message to Flutter over the "system channel".
*/
void onTrimMemory(int level) {
ensureAlive();
if (flutterEngine != null) {
// Use a trim level delivered while the application is running so the
// framework has a chance to react to the notification.
if (level == TRIM_MEMORY_RUNNING_LOW) {
Log.v(TAG, "Forwarding onTrimMemory() to FlutterEngine. Level: " + level);
flutterEngine.getSystemChannel().sendMemoryPressureWarning();
}
} else {
Log.w(TAG, "onTrimMemory() invoked before FlutterFragment was attached to an Activity.");
}
}
/**
* Invoke this from {@link Activity#onLowMemory()}.
*
* <p>A {@code Fragment} host must have its containing {@code Activity} forward this call so that
* the {@code Fragment} can then invoke this method.
*
* <p>This method sends a "memory pressure warning" message to Flutter over the "system channel".
*/
void onLowMemory() {
Log.v(TAG, "Forwarding onLowMemory() to FlutterEngine.");
ensureAlive();
flutterEngine.getSystemChannel().sendMemoryPressureWarning();
}
/**
* Ensures that this delegate has not been {@link #release()}'ed.
*
* <p>An {@code IllegalStateException} is thrown if this delegate has been {@link #release()}'ed.
*/
private void ensureAlive() {
if (host == null) {
throw new IllegalStateException(
"Cannot execute method on a destroyed FlutterActivityAndFragmentDelegate.");
}
}
/**
* The {@link FlutterActivity} or {@link FlutterFragment} that owns this {@code
* FlutterActivityAndFragmentDelegate}.
*/
/* package */ interface Host
extends SplashScreenProvider, FlutterEngineProvider, FlutterEngineConfigurator {
/** Returns the {@link Context} that backs the host {@link Activity} or {@code Fragment}. */
@NonNull
Context getContext();
/**
* Returns the host {@link Activity} or the {@code Activity} that is currently attached to the
* host {@code Fragment}.
*/
@Nullable
Activity getActivity();
/** Returns the {@link Lifecycle} that backs the host {@link Activity} or {@code Fragment}. */
@NonNull
Lifecycle getLifecycle();
/** Returns the {@link FlutterShellArgs} that should be used when initializing Flutter. */
@NonNull
FlutterShellArgs getFlutterShellArgs();
/**
* Returns the ID of a statically cached {@link FlutterEngine} to use within this delegate's
* host, or {@code null} if this delegate's host does not want to use a cached {@link
* FlutterEngine}.
*/
@Nullable
String getCachedEngineId();
/**
* Returns true if the {@link FlutterEngine} used in this delegate should be destroyed when the
* host/delegate are destroyed.
*
* <p>The default value is {@code true} in cases where {@code FlutterFragment} created its own
* {@link FlutterEngine}, and {@code false} in cases where a cached {@link FlutterEngine} was
* provided.
*/
boolean shouldDestroyEngineWithHost();
/** Returns the Dart entrypoint that should run when a new {@link FlutterEngine} is created. */
@NonNull
String getDartEntrypointFunctionName();
/** Returns the path to the app bundle where the Dart code exists. */
@NonNull
String getAppBundlePath();
/** Returns the initial route that Flutter renders. */
@Nullable
String getInitialRoute();
/**
* Returns the {@link RenderMode} used by the {@link FlutterView} that displays the {@link
* FlutterEngine}'s content.
*/
@NonNull
RenderMode getRenderMode();
/**
* Returns the {@link TransparencyMode} used by the {@link FlutterView} that displays the {@link
* FlutterEngine}'s content.
*/
@NonNull
TransparencyMode getTransparencyMode();
@Nullable
SplashScreen provideSplashScreen();
/**
* Returns the {@link FlutterEngine} that should be rendered to a {@link FlutterView}.
*
* <p>If {@code null} is returned, a new {@link FlutterEngine} will be created automatically.
*/
@Nullable
FlutterEngine provideFlutterEngine(@NonNull Context context);
/**
* Hook for the host to create/provide a {@link PlatformPlugin} if the associated Flutter
* experience should control system chrome.
*/
@Nullable
PlatformPlugin providePlatformPlugin(
@Nullable Activity activity, @NonNull FlutterEngine flutterEngine);
/** Hook for the host to configure the {@link FlutterEngine} as desired. */
void configureFlutterEngine(@NonNull FlutterEngine flutterEngine);
/**
* Hook for the host to cleanup references that were established in {@link
* #configureFlutterEngine(FlutterEngine)} before the host is destroyed or detached.
*/
void cleanUpFlutterEngine(@NonNull FlutterEngine flutterEngine);
/**
* Returns true if the {@link FlutterEngine}'s plugin system should be connected to the host
* {@link Activity}, allowing plugins to interact with it.
*/
boolean shouldAttachEngineToActivity();
/**
* Invoked by this delegate when the {@link FlutterSurfaceView} that renders the Flutter UI is
* initially instantiated.
*
* <p>This method is only invoked if the {@link
* io.flutter.embedding.android.FlutterView.RenderMode} is set to {@link
* io.flutter.embedding.android.FlutterView.RenderMode#surface}. Otherwise, {@link
* #onFlutterTextureViewCreated(FlutterTextureView)} is invoked.
*
* <p>This method is invoked before the given {@link FlutterSurfaceView} is attached to the
* {@code View} hierarchy. Implementers should not attempt to climb the {@code View} hierarchy
* or make assumptions about relationships with other {@code View}s.
*/
void onFlutterSurfaceViewCreated(@NonNull FlutterSurfaceView flutterSurfaceView);
/**
* Invoked by this delegate when the {@link FlutterTextureView} that renders the Flutter UI is
* initially instantiated.
*
* <p>This method is only invoked if the {@link
* io.flutter.embedding.android.FlutterView.RenderMode} is set to {@link
* io.flutter.embedding.android.FlutterView.RenderMode#texture}. Otherwise, {@link
* #onFlutterSurfaceViewCreated(FlutterSurfaceView)} is invoked.
*
* <p>This method is invoked before the given {@link FlutterTextureView} is attached to the
* {@code View} hierarchy. Implementers should not attempt to climb the {@code View} hierarchy
* or make assumptions about relationships with other {@code View}s.
*/
void onFlutterTextureViewCreated(@NonNull FlutterTextureView flutterTextureView);
/** Invoked by this delegate when its {@link FlutterView} starts painting pixels. */
void onFlutterUiDisplayed();
/** Invoked by this delegate when its {@link FlutterView} stops painting pixels. */
void onFlutterUiNoLongerDisplayed();
}
}