| // 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 android.app.Activity; |
| import android.content.ComponentCallbacks2; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.os.Bundle; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import androidx.activity.OnBackPressedCallback; |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.annotation.VisibleForTesting; |
| import androidx.fragment.app.Fragment; |
| import androidx.fragment.app.FragmentActivity; |
| import androidx.lifecycle.Lifecycle; |
| import io.flutter.Log; |
| import io.flutter.embedding.engine.FlutterEngine; |
| import io.flutter.embedding.engine.FlutterShellArgs; |
| import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener; |
| import io.flutter.plugin.platform.PlatformPlugin; |
| import io.flutter.util.ViewUtils; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * {@code Fragment} which displays a Flutter UI that takes up all available {@code Fragment} space. |
| * |
| * <p>Using a {@code FlutterFragment} requires forwarding a number of calls from an {@code Activity} |
| * to ensure that the internal Flutter app behaves as expected: |
| * |
| * <ol> |
| * <li>{@link #onPostResume()} |
| * <li>{@link #onBackPressed()} |
| * <li>{@link #onRequestPermissionsResult(int, String[], int[])} |
| * <li>{@link #onNewIntent(Intent)} |
| * <li>{@link #onUserLeaveHint()} |
| * </ol> |
| * |
| * {@link #onBackPressed()} does not need to be called through if the fragment is constructed by one |
| * of the builders with {@code shouldAutomaticallyHandleOnBackPressed(true)}. |
| * |
| * <p>Additionally, when starting an {@code Activity} for a result from this {@code Fragment}, be |
| * sure to invoke {@link Fragment#startActivityForResult(Intent, int)} rather than {@link |
| * android.app.Activity#startActivityForResult(Intent, int)}. If the {@code Activity} version of the |
| * method is invoked then this {@code Fragment} will never receive its {@link |
| * Fragment#onActivityResult(int, int, Intent)} callback. |
| * |
| * <p>If convenient, consider using a {@link FlutterActivity} instead of a {@code FlutterFragment} |
| * to avoid the work of forwarding calls. |
| * |
| * <p>{@code FlutterFragment} supports the use of an existing, cached {@link |
| * io.flutter.embedding.engine.FlutterEngine}. To use a cached {@link |
| * io.flutter.embedding.engine.FlutterEngine}, ensure that the {@link |
| * io.flutter.embedding.engine.FlutterEngine} is stored in {@link |
| * io.flutter.embedding.engine.FlutterEngineCache} and then use {@link #withCachedEngine(String)} to |
| * build a {@code FlutterFragment} with the cached {@link |
| * io.flutter.embedding.engine.FlutterEngine}'s ID. |
| * |
| * <p>It is generally recommended to use a cached {@link io.flutter.embedding.engine.FlutterEngine} |
| * to avoid a momentary delay when initializing a new {@link |
| * io.flutter.embedding.engine.FlutterEngine}. The two exceptions to using a cached {@link |
| * FlutterEngine} are: |
| * |
| * <ul> |
| * <li>When {@code FlutterFragment} is in the first {@code Activity} displayed by the app, because |
| * pre-warming a {@link io.flutter.embedding.engine.FlutterEngine} would have no impact in |
| * this situation. |
| * <li>When you are unsure when/if you will need to display a Flutter experience. |
| * </ul> |
| * |
| * <p>The following illustrates how to pre-warm and cache a {@link |
| * io.flutter.embedding.engine.FlutterEngine}: |
| * |
| * <pre>{@code |
| * // Create and pre-warm a FlutterEngine. |
| * FlutterEngineGroup group = new FlutterEngineGroup(context); |
| * FlutterEngine flutterEngine = group.createAndRunDefaultEngine(context); |
| * flutterEngine |
| * .getDartExecutor() |
| * .executeDartEntrypoint(DartEntrypoint.createDefault()); |
| * |
| * // Cache the pre-warmed FlutterEngine in the FlutterEngineCache. |
| * FlutterEngineCache.getInstance().put("my_engine", flutterEngine); |
| * }</pre> |
| * |
| * <p>If Flutter is needed in a location that can only use a {@code View}, consider using a {@link |
| * io.flutter.embedding.android.FlutterView}. Using a {@link |
| * io.flutter.embedding.android.FlutterView} requires forwarding some calls from an {@code |
| * Activity}, as well as forwarding lifecycle calls from an {@code Activity} or a {@code Fragment}. |
| */ |
| public class FlutterFragment extends Fragment |
| implements FlutterActivityAndFragmentDelegate.Host, |
| ComponentCallbacks2, |
| FlutterActivityAndFragmentDelegate.DelegateFactory { |
| /** |
| * The ID of the {@code FlutterView} created by this activity. |
| * |
| * <p>This ID can be used to lookup {@code FlutterView} in the Android view hierarchy. For more, |
| * see {@link android.view.View#findViewById}. |
| */ |
| public static final int FLUTTER_VIEW_ID = ViewUtils.generateViewId(0xF1F2); |
| |
| private static final String TAG = "FlutterFragment"; |
| |
| /** The Dart entrypoint method name that is executed upon initialization. */ |
| protected static final String ARG_DART_ENTRYPOINT = "dart_entrypoint"; |
| /** The Dart entrypoint method's URI that is executed upon initialization. */ |
| protected static final String ARG_DART_ENTRYPOINT_URI = "dart_entrypoint_uri"; |
| /** The Dart entrypoint arguments that is executed upon initialization. */ |
| protected static final String ARG_DART_ENTRYPOINT_ARGS = "dart_entrypoint_args"; |
| /** Initial Flutter route that is rendered in a Navigator widget. */ |
| protected static final String ARG_INITIAL_ROUTE = "initial_route"; |
| /** Whether the activity delegate should handle the deeplinking request. */ |
| protected static final String ARG_HANDLE_DEEPLINKING = "handle_deeplinking"; |
| /** Path to Flutter's Dart code. */ |
| protected static final String ARG_APP_BUNDLE_PATH = "app_bundle_path"; |
| /** Whether to delay the Android drawing pass till after the Flutter UI has been displayed. */ |
| protected static final String ARG_SHOULD_DELAY_FIRST_ANDROID_VIEW_DRAW = |
| "should_delay_first_android_view_draw"; |
| |
| /** Flutter shell arguments. */ |
| protected static final String ARG_FLUTTER_INITIALIZATION_ARGS = "initialization_args"; |
| /** |
| * {@link RenderMode} to be used for the {@link io.flutter.embedding.android.FlutterView} in this |
| * {@code FlutterFragment} |
| */ |
| protected static final String ARG_FLUTTERVIEW_RENDER_MODE = "flutterview_render_mode"; |
| /** |
| * {@link TransparencyMode} to be used for the {@link io.flutter.embedding.android.FlutterView} in |
| * this {@code FlutterFragment} |
| */ |
| protected static final String ARG_FLUTTERVIEW_TRANSPARENCY_MODE = "flutterview_transparency_mode"; |
| /** See {@link #shouldAttachEngineToActivity()}. */ |
| protected static final String ARG_SHOULD_ATTACH_ENGINE_TO_ACTIVITY = |
| "should_attach_engine_to_activity"; |
| /** |
| * The ID of a {@link io.flutter.embedding.engine.FlutterEngine} cached in {@link |
| * io.flutter.embedding.engine.FlutterEngineCache} that will be used within the created {@code |
| * FlutterFragment}. |
| */ |
| protected static final String ARG_CACHED_ENGINE_ID = "cached_engine_id"; |
| |
| protected static final String ARG_CACHED_ENGINE_GROUP_ID = "cached_engine_group_id"; |
| |
| /** |
| * True if the {@link io.flutter.embedding.engine.FlutterEngine} in the created {@code |
| * FlutterFragment} should be destroyed when the {@code FlutterFragment} is destroyed, false if |
| * the {@link io.flutter.embedding.engine.FlutterEngine} should outlive the {@code |
| * FlutterFragment}. |
| */ |
| protected static final String ARG_DESTROY_ENGINE_WITH_FRAGMENT = "destroy_engine_with_fragment"; |
| /** |
| * True if the framework state in the engine attached to this engine should be stored and restored |
| * when this fragment is created and destroyed. |
| */ |
| protected static final String ARG_ENABLE_STATE_RESTORATION = "enable_state_restoration"; |
| /** |
| * True if the fragment should receive {@link #onBackPressed()} events automatically, without |
| * requiring an explicit activity call through. |
| */ |
| protected static final String ARG_SHOULD_AUTOMATICALLY_HANDLE_ON_BACK_PRESSED = |
| "should_automatically_handle_on_back_pressed"; |
| |
| /** |
| * Creates a {@code FlutterFragment} with a default configuration. |
| * |
| * <p>{@code FlutterFragment}'s default configuration creates a new {@link |
| * io.flutter.embedding.engine.FlutterEngine} within the {@code FlutterFragment} and uses the |
| * following settings: |
| * |
| * <ul> |
| * <li>Dart entrypoint: "main" |
| * <li>Initial route: "/" |
| * <li>Render mode: surface |
| * <li>Transparency mode: transparent |
| * </ul> |
| * |
| * <p>To use a new {@link io.flutter.embedding.engine.FlutterEngine} with different settings, use |
| * {@link #withNewEngine()}. |
| * |
| * <p>To use a cached {@link io.flutter.embedding.engine.FlutterEngine} instead of creating a new |
| * one, use {@link #withCachedEngine(String)}. |
| */ |
| @NonNull |
| public static FlutterFragment createDefault() { |
| return new NewEngineFragmentBuilder().build(); |
| } |
| |
| /** |
| * Returns a {@link NewEngineFragmentBuilder} to create a {@code FlutterFragment} with a new |
| * {@link io.flutter.embedding.engine.FlutterEngine} and a desired engine configuration. |
| */ |
| @NonNull |
| public static NewEngineFragmentBuilder withNewEngine() { |
| return new NewEngineFragmentBuilder(); |
| } |
| |
| /** |
| * Builder that creates a new {@code FlutterFragment} with {@code arguments} that correspond to |
| * the values set on this {@code NewEngineFragmentBuilder}. |
| * |
| * <p>To create a {@code FlutterFragment} with default {@code arguments}, invoke {@link |
| * #createDefault()}. |
| * |
| * <p>Subclasses of {@code FlutterFragment} that do not introduce any new arguments can use this |
| * {@code NewEngineFragmentBuilder} to construct instances of the subclass without subclassing |
| * this {@code NewEngineFragmentBuilder}. {@code MyFlutterFragment f = new |
| * FlutterFragment.NewEngineFragmentBuilder(MyFlutterFragment.class) .someProperty(...) |
| * .someOtherProperty(...) .build<MyFlutterFragment>(); } |
| * |
| * <p>Subclasses of {@code FlutterFragment} that introduce new arguments should subclass this |
| * {@code NewEngineFragmentBuilder} to add the new properties: |
| * |
| * <ol> |
| * <li>Ensure the {@code FlutterFragment} subclass has a no-arg constructor. |
| * <li>Subclass this {@code NewEngineFragmentBuilder}. |
| * <li>Override the new {@code NewEngineFragmentBuilder}'s no-arg constructor and invoke the |
| * super constructor to set the {@code FlutterFragment} subclass: {@code public MyBuilder() |
| * { super(MyFlutterFragment.class); } } |
| * <li>Add appropriate property methods for the new properties. |
| * <li>Override {@link NewEngineFragmentBuilder#createArgs()}, call through to the super method, |
| * then add the new properties as arguments in the {@link Bundle}. |
| * </ol> |
| * |
| * Once a {@code NewEngineFragmentBuilder} subclass is defined, the {@code FlutterFragment} |
| * subclass can be instantiated as follows. {@code MyFlutterFragment f = new MyBuilder() |
| * .someExistingProperty(...) .someNewProperty(...) .build<MyFlutterFragment>(); } |
| */ |
| public static class NewEngineFragmentBuilder { |
| private final Class<? extends FlutterFragment> fragmentClass; |
| private String dartEntrypoint = "main"; |
| private String dartLibraryUri = null; |
| private List<String> dartEntrypointArgs; |
| private String initialRoute = "/"; |
| private boolean handleDeeplinking = false; |
| private String appBundlePath = null; |
| private FlutterShellArgs shellArgs = null; |
| private RenderMode renderMode = RenderMode.surface; |
| private TransparencyMode transparencyMode = TransparencyMode.transparent; |
| private boolean shouldAttachEngineToActivity = true; |
| private boolean shouldAutomaticallyHandleOnBackPressed = false; |
| private boolean shouldDelayFirstAndroidViewDraw = false; |
| |
| /** |
| * Constructs a {@code NewEngineFragmentBuilder} that is configured to construct an instance of |
| * {@code FlutterFragment}. |
| */ |
| public NewEngineFragmentBuilder() { |
| fragmentClass = FlutterFragment.class; |
| } |
| |
| /** |
| * Constructs a {@code NewEngineFragmentBuilder} that is configured to construct an instance of |
| * {@code subclass}, which extends {@code FlutterFragment}. |
| */ |
| public NewEngineFragmentBuilder(@NonNull Class<? extends FlutterFragment> subclass) { |
| fragmentClass = subclass; |
| } |
| |
| /** The name of the initial Dart method to invoke, defaults to "main". */ |
| @NonNull |
| public NewEngineFragmentBuilder dartEntrypoint(@NonNull String dartEntrypoint) { |
| this.dartEntrypoint = dartEntrypoint; |
| return this; |
| } |
| |
| @NonNull |
| public NewEngineFragmentBuilder dartLibraryUri(@NonNull String dartLibraryUri) { |
| this.dartLibraryUri = dartLibraryUri; |
| return this; |
| } |
| |
| /** Arguments passed as a list of string to Dart's entrypoint function. */ |
| @NonNull |
| public NewEngineFragmentBuilder dartEntrypointArgs(@NonNull List<String> dartEntrypointArgs) { |
| this.dartEntrypointArgs = dartEntrypointArgs; |
| return this; |
| } |
| |
| /** |
| * The initial route that a Flutter app will render in this {@link FlutterFragment}, defaults to |
| * "/". |
| */ |
| @NonNull |
| public NewEngineFragmentBuilder initialRoute(@NonNull String initialRoute) { |
| this.initialRoute = initialRoute; |
| return this; |
| } |
| |
| /** |
| * Whether to handle the deeplinking from the {@code Intent} automatically if the {@code |
| * getInitialRoute} returns null. |
| */ |
| @NonNull |
| public NewEngineFragmentBuilder handleDeeplinking(@NonNull Boolean handleDeeplinking) { |
| this.handleDeeplinking = handleDeeplinking; |
| return this; |
| } |
| |
| /** |
| * The path to the app bundle which contains the Dart app to execute. Null when unspecified, |
| * which defaults to {@link |
| * io.flutter.embedding.engine.loader.FlutterLoader#findAppBundlePath()} |
| */ |
| @NonNull |
| public NewEngineFragmentBuilder appBundlePath(@NonNull String appBundlePath) { |
| this.appBundlePath = appBundlePath; |
| return this; |
| } |
| |
| /** Any special configuration arguments for the Flutter engine */ |
| @NonNull |
| public NewEngineFragmentBuilder flutterShellArgs(@NonNull FlutterShellArgs shellArgs) { |
| this.shellArgs = shellArgs; |
| return this; |
| } |
| |
| /** |
| * Render Flutter either as a {@link RenderMode#surface} or a {@link RenderMode#texture}. You |
| * should use {@code surface} unless you have a specific reason to use {@code texture}. {@code |
| * texture} comes with a significant performance impact, but {@code texture} can be displayed |
| * beneath other Android {@code View}s and animated, whereas {@code surface} cannot. |
| */ |
| @NonNull |
| public NewEngineFragmentBuilder renderMode(@NonNull RenderMode renderMode) { |
| this.renderMode = renderMode; |
| return this; |
| } |
| |
| /** |
| * Support a {@link TransparencyMode#transparent} background within {@link |
| * io.flutter.embedding.android.FlutterView}, or force an {@link TransparencyMode#opaque} |
| * background. |
| * |
| * <p>See {@link TransparencyMode} for implications of this selection. |
| */ |
| @NonNull |
| public NewEngineFragmentBuilder transparencyMode(@NonNull TransparencyMode transparencyMode) { |
| this.transparencyMode = transparencyMode; |
| return this; |
| } |
| |
| /** |
| * Whether or not this {@code FlutterFragment} should automatically attach its {@code Activity} |
| * as a control surface for its {@link io.flutter.embedding.engine.FlutterEngine}. |
| * |
| * <p>Control surfaces are used to provide Android resources and lifecycle events to plugins |
| * that are attached to the {@link io.flutter.embedding.engine.FlutterEngine}. If {@code |
| * shouldAttachEngineToActivity} is true then this {@code FlutterFragment} will connect its |
| * {@link io.flutter.embedding.engine.FlutterEngine} to the surrounding {@code Activity}, along |
| * with any plugins that are registered with that {@link FlutterEngine}. This allows plugins to |
| * access the {@code Activity}, as well as receive {@code Activity}-specific calls, e.g., {@link |
| * android.app.Activity#onNewIntent(Intent)}. If {@code shouldAttachEngineToActivity} is false, |
| * then this {@code FlutterFragment} will not automatically manage the connection between its |
| * {@link io.flutter.embedding.engine.FlutterEngine} and the surrounding {@code Activity}. The |
| * {@code Activity} will need to be manually connected to this {@code FlutterFragment}'s {@link |
| * io.flutter.embedding.engine.FlutterEngine} by the app developer. See {@link |
| * FlutterEngine#getActivityControlSurface()}. |
| * |
| * <p>One reason that a developer might choose to manually manage the relationship between the |
| * {@code Activity} and {@link io.flutter.embedding.engine.FlutterEngine} is if the developer |
| * wants to move the {@link FlutterEngine} somewhere else. For example, a developer might want |
| * the {@link io.flutter.embedding.engine.FlutterEngine} to outlive the surrounding {@code |
| * Activity} so that it can be used later in a different {@code Activity}. To accomplish this, |
| * the {@link io.flutter.embedding.engine.FlutterEngine} will need to be disconnected from the |
| * surrounding {@code Activity} at an unusual time, preventing this {@code FlutterFragment} from |
| * correctly managing the relationship between the {@link |
| * io.flutter.embedding.engine.FlutterEngine} and the surrounding {@code Activity}. |
| * |
| * <p>Another reason that a developer might choose to manually manage the relationship between |
| * the {@code Activity} and {@link io.flutter.embedding.engine.FlutterEngine} is if the |
| * developer wants to prevent, or explicitly control when the {@link |
| * io.flutter.embedding.engine.FlutterEngine}'s plugins have access to the surrounding {@code |
| * Activity}. For example, imagine that this {@code FlutterFragment} only takes up part of the |
| * screen and the app developer wants to ensure that none of the Flutter plugins are able to |
| * manipulate the surrounding {@code Activity}. In this case, the developer would not want the |
| * {@link io.flutter.embedding.engine.FlutterEngine} to have access to the {@code Activity}, |
| * which can be accomplished by setting {@code shouldAttachEngineToActivity} to {@code false}. |
| */ |
| @NonNull |
| public NewEngineFragmentBuilder shouldAttachEngineToActivity( |
| boolean shouldAttachEngineToActivity) { |
| this.shouldAttachEngineToActivity = shouldAttachEngineToActivity; |
| return this; |
| } |
| |
| /** |
| * Whether or not this {@code FlutterFragment} should automatically receive {@link |
| * #onBackPressed()} events, rather than requiring an explicit activity call through. Disabled |
| * by default. |
| * |
| * <p>When enabled, the activity will automatically dispatch back-press events to the fragment's |
| * {@link OnBackPressedCallback}, instead of requiring the activity to manually call {@link |
| * #onBackPressed()} in client code. If enabled, do <b>not</b> invoke {@link #onBackPressed()} |
| * manually. |
| * |
| * <p>This behavior relies on the implementation of {@link #popSystemNavigator()}. It's not |
| * recommended to override that method when enabling this attribute, but if you do, you should |
| * always fall back to calling {@code super.popSystemNavigator()} when not relying on custom |
| * behavior. |
| */ |
| @NonNull |
| public NewEngineFragmentBuilder shouldAutomaticallyHandleOnBackPressed( |
| boolean shouldAutomaticallyHandleOnBackPressed) { |
| this.shouldAutomaticallyHandleOnBackPressed = shouldAutomaticallyHandleOnBackPressed; |
| return this; |
| } |
| |
| /** |
| * Whether to delay the Android drawing pass till after the Flutter UI has been displayed. |
| * |
| * <p>See {#link FlutterActivityAndFragmentDelegate#onCreateView} for more details. |
| */ |
| @NonNull |
| public NewEngineFragmentBuilder shouldDelayFirstAndroidViewDraw( |
| boolean shouldDelayFirstAndroidViewDraw) { |
| this.shouldDelayFirstAndroidViewDraw = shouldDelayFirstAndroidViewDraw; |
| return this; |
| } |
| |
| /** |
| * Creates a {@link Bundle} of arguments that are assigned to the new {@code FlutterFragment}. |
| * |
| * <p>Subclasses should override this method to add new properties to the {@link Bundle}. |
| * Subclasses must call through to the super method to collect all existing property values. |
| */ |
| @NonNull |
| protected Bundle createArgs() { |
| Bundle args = new Bundle(); |
| args.putString(ARG_INITIAL_ROUTE, initialRoute); |
| args.putBoolean(ARG_HANDLE_DEEPLINKING, handleDeeplinking); |
| args.putString(ARG_APP_BUNDLE_PATH, appBundlePath); |
| args.putString(ARG_DART_ENTRYPOINT, dartEntrypoint); |
| args.putString(ARG_DART_ENTRYPOINT_URI, dartLibraryUri); |
| args.putStringArrayList( |
| ARG_DART_ENTRYPOINT_ARGS, |
| dartEntrypointArgs != null ? new ArrayList(dartEntrypointArgs) : null); |
| // TODO(mattcarroll): determine if we should have an explicit FlutterTestFragment instead of |
| // conflating. |
| if (null != shellArgs) { |
| args.putStringArray(ARG_FLUTTER_INITIALIZATION_ARGS, shellArgs.toArray()); |
| } |
| args.putString( |
| ARG_FLUTTERVIEW_RENDER_MODE, |
| renderMode != null ? renderMode.name() : RenderMode.surface.name()); |
| args.putString( |
| ARG_FLUTTERVIEW_TRANSPARENCY_MODE, |
| transparencyMode != null ? transparencyMode.name() : TransparencyMode.transparent.name()); |
| args.putBoolean(ARG_SHOULD_ATTACH_ENGINE_TO_ACTIVITY, shouldAttachEngineToActivity); |
| args.putBoolean(ARG_DESTROY_ENGINE_WITH_FRAGMENT, true); |
| args.putBoolean( |
| ARG_SHOULD_AUTOMATICALLY_HANDLE_ON_BACK_PRESSED, shouldAutomaticallyHandleOnBackPressed); |
| args.putBoolean(ARG_SHOULD_DELAY_FIRST_ANDROID_VIEW_DRAW, shouldDelayFirstAndroidViewDraw); |
| return args; |
| } |
| |
| /** |
| * Constructs a new {@code FlutterFragment} (or a subclass) that is configured based on |
| * properties set on this {@code Builder}. |
| */ |
| @NonNull |
| public <T extends FlutterFragment> T build() { |
| try { |
| @SuppressWarnings("unchecked") |
| T frag = (T) fragmentClass.getDeclaredConstructor().newInstance(); |
| if (frag == null) { |
| throw new RuntimeException( |
| "The FlutterFragment subclass sent in the constructor (" |
| + fragmentClass.getCanonicalName() |
| + ") does not match the expected return type."); |
| } |
| |
| Bundle args = createArgs(); |
| frag.setArguments(args); |
| |
| return frag; |
| } catch (Exception e) { |
| throw new RuntimeException( |
| "Could not instantiate FlutterFragment subclass (" + fragmentClass.getName() + ")", e); |
| } |
| } |
| } |
| |
| /** |
| * Returns a {@link CachedEngineFragmentBuilder} to create a {@code FlutterFragment} with a cached |
| * {@link io.flutter.embedding.engine.FlutterEngine} in {@link |
| * io.flutter.embedding.engine.FlutterEngineCache}. |
| * |
| * <p>An {@code IllegalStateException} will be thrown during the lifecycle of the {@code |
| * FlutterFragment} if a cached {@link io.flutter.embedding.engine.FlutterEngine} is requested but |
| * does not exist in the cache. |
| * |
| * <p>To create a {@code FlutterFragment} that uses a new {@link |
| * io.flutter.embedding.engine.FlutterEngine}, use {@link #createDefault()} or {@link |
| * #withNewEngine()}. |
| */ |
| @NonNull |
| public static CachedEngineFragmentBuilder withCachedEngine(@NonNull String engineId) { |
| return new CachedEngineFragmentBuilder(engineId); |
| } |
| |
| /** |
| * Builder that creates a new {@code FlutterFragment} that uses a cached {@link |
| * io.flutter.embedding.engine.FlutterEngine} with {@code arguments} that correspond to the values |
| * set on this {@code Builder}. |
| * |
| * <p>Subclasses of {@code FlutterFragment} that do not introduce any new arguments can use this |
| * {@code Builder} to construct instances of the subclass without subclassing this {@code |
| * Builder}. {@code MyFlutterFragment f = new |
| * FlutterFragment.CachedEngineFragmentBuilder(MyFlutterFragment.class) .someProperty(...) |
| * .someOtherProperty(...) .build<MyFlutterFragment>(); } |
| * |
| * <p>Subclasses of {@code FlutterFragment} that introduce new arguments should subclass this |
| * {@code CachedEngineFragmentBuilder} to add the new properties: |
| * |
| * <ol> |
| * <li>Ensure the {@code FlutterFragment} subclass has a no-arg constructor. |
| * <li>Subclass this {@code CachedEngineFragmentBuilder}. |
| * <li>Override the new {@code CachedEngineFragmentBuilder}'s no-arg constructor and invoke the |
| * super constructor to set the {@code FlutterFragment} subclass: {@code public MyBuilder() |
| * { super(MyFlutterFragment.class); } } |
| * <li>Add appropriate property methods for the new properties. |
| * <li>Override {@link CachedEngineFragmentBuilder#createArgs()}, call through to the super |
| * method, then add the new properties as arguments in the {@link Bundle}. |
| * </ol> |
| * |
| * Once a {@code CachedEngineFragmentBuilder} subclass is defined, the {@code FlutterFragment} |
| * subclass can be instantiated as follows. {@code MyFlutterFragment f = new MyBuilder() |
| * .someExistingProperty(...) .someNewProperty(...) .build<MyFlutterFragment>(); } |
| */ |
| public static class CachedEngineFragmentBuilder { |
| private final Class<? extends FlutterFragment> fragmentClass; |
| private final String engineId; |
| private boolean destroyEngineWithFragment = false; |
| private boolean handleDeeplinking = false; |
| private RenderMode renderMode = RenderMode.surface; |
| private TransparencyMode transparencyMode = TransparencyMode.transparent; |
| private boolean shouldAttachEngineToActivity = true; |
| private boolean shouldAutomaticallyHandleOnBackPressed = false; |
| private boolean shouldDelayFirstAndroidViewDraw = false; |
| |
| private CachedEngineFragmentBuilder(@NonNull String engineId) { |
| this(FlutterFragment.class, engineId); |
| } |
| |
| public CachedEngineFragmentBuilder( |
| @NonNull Class<? extends FlutterFragment> subclass, @NonNull String engineId) { |
| this.fragmentClass = subclass; |
| this.engineId = engineId; |
| } |
| |
| /** |
| * Pass {@code true} to destroy the cached {@link io.flutter.embedding.engine.FlutterEngine} |
| * when this {@code FlutterFragment} is destroyed, or {@code false} for the cached {@link |
| * io.flutter.embedding.engine.FlutterEngine} to outlive this {@code FlutterFragment}. |
| */ |
| @NonNull |
| public CachedEngineFragmentBuilder destroyEngineWithFragment( |
| boolean destroyEngineWithFragment) { |
| this.destroyEngineWithFragment = destroyEngineWithFragment; |
| return this; |
| } |
| |
| /** |
| * Render Flutter either as a {@link RenderMode#surface} or a {@link RenderMode#texture}. You |
| * should use {@code surface} unless you have a specific reason to use {@code texture}. {@code |
| * texture} comes with a significant performance impact, but {@code texture} can be displayed |
| * beneath other Android {@code View}s and animated, whereas {@code surface} cannot. |
| */ |
| @NonNull |
| public CachedEngineFragmentBuilder renderMode(@NonNull RenderMode renderMode) { |
| this.renderMode = renderMode; |
| return this; |
| } |
| |
| /** |
| * Support a {@link TransparencyMode#transparent} background within {@link |
| * io.flutter.embedding.android.FlutterView}, or force an {@link TransparencyMode#opaque} |
| * background. |
| * |
| * <p>See {@link TransparencyMode} for implications of this selection. |
| */ |
| @NonNull |
| public CachedEngineFragmentBuilder transparencyMode( |
| @NonNull TransparencyMode transparencyMode) { |
| this.transparencyMode = transparencyMode; |
| return this; |
| } |
| |
| /** |
| * Whether to handle the deeplinking from the {@code Intent} automatically if the {@code |
| * getInitialRoute} returns null. |
| */ |
| @NonNull |
| public CachedEngineFragmentBuilder handleDeeplinking(@NonNull Boolean handleDeeplinking) { |
| this.handleDeeplinking = handleDeeplinking; |
| return this; |
| } |
| |
| /** |
| * Whether or not this {@code FlutterFragment} should automatically attach its {@code Activity} |
| * as a control surface for its {@link io.flutter.embedding.engine.FlutterEngine}. |
| * |
| * <p>Control surfaces are used to provide Android resources and lifecycle events to plugins |
| * that are attached to the {@link io.flutter.embedding.engine.FlutterEngine}. If {@code |
| * shouldAttachEngineToActivity} is true then this {@code FlutterFragment} will connect its |
| * {@link io.flutter.embedding.engine.FlutterEngine} to the surrounding {@code Activity}, along |
| * with any plugins that are registered with that {@link FlutterEngine}. This allows plugins to |
| * access the {@code Activity}, as well as receive {@code Activity}-specific calls, e.g., {@link |
| * android.app.Activity#onNewIntent(Intent)}. If {@code shouldAttachEngineToActivity} is false, |
| * then this {@code FlutterFragment} will not automatically manage the connection between its |
| * {@link io.flutter.embedding.engine.FlutterEngine} and the surrounding {@code Activity}. The |
| * {@code Activity} will need to be manually connected to this {@code FlutterFragment}'s {@link |
| * io.flutter.embedding.engine.FlutterEngine} by the app developer. See {@link |
| * FlutterEngine#getActivityControlSurface()}. |
| * |
| * <p>One reason that a developer might choose to manually manage the relationship between the |
| * {@code Activity} and {@link io.flutter.embedding.engine.FlutterEngine} is if the developer |
| * wants to move the {@link FlutterEngine} somewhere else. For example, a developer might want |
| * the {@link io.flutter.embedding.engine.FlutterEngine} to outlive the surrounding {@code |
| * Activity} so that it can be used later in a different {@code Activity}. To accomplish this, |
| * the {@link io.flutter.embedding.engine.FlutterEngine} will need to be disconnected from the |
| * surrounding {@code Activity} at an unusual time, preventing this {@code FlutterFragment} from |
| * correctly managing the relationship between the {@link |
| * io.flutter.embedding.engine.FlutterEngine} and the surrounding {@code Activity}. |
| * |
| * <p>Another reason that a developer might choose to manually manage the relationship between |
| * the {@code Activity} and {@link io.flutter.embedding.engine.FlutterEngine} is if the |
| * developer wants to prevent, or explicitly control when the {@link |
| * io.flutter.embedding.engine.FlutterEngine}'s plugins have access to the surrounding {@code |
| * Activity}. For example, imagine that this {@code FlutterFragment} only takes up part of the |
| * screen and the app developer wants to ensure that none of the Flutter plugins are able to |
| * manipulate the surrounding {@code Activity}. In this case, the developer would not want the |
| * {@link io.flutter.embedding.engine.FlutterEngine} to have access to the {@code Activity}, |
| * which can be accomplished by setting {@code shouldAttachEngineToActivity} to {@code false}. |
| */ |
| @NonNull |
| public CachedEngineFragmentBuilder shouldAttachEngineToActivity( |
| boolean shouldAttachEngineToActivity) { |
| this.shouldAttachEngineToActivity = shouldAttachEngineToActivity; |
| return this; |
| } |
| |
| /** |
| * Whether or not this {@code FlutterFragment} should automatically receive {@link |
| * #onBackPressed()} events, rather than requiring an explicit activity call through. Disabled |
| * by default. |
| * |
| * <p>When enabled, the activity will automatically dispatch back-press events to the fragment's |
| * {@link OnBackPressedCallback}, instead of requiring the activity to manually call {@link |
| * #onBackPressed()} in client code. If enabled, do <b>not</b> invoke {@link #onBackPressed()} |
| * manually. |
| * |
| * <p>Enabling this behavior relies on explicit behavior in {@link #popSystemNavigator()}. It's |
| * not recommended to override that method when enabling this attribute, but if you do, you |
| * should always fall back to calling {@code super.popSystemNavigator()} when not relying on |
| * custom behavior. |
| */ |
| @NonNull |
| public CachedEngineFragmentBuilder shouldAutomaticallyHandleOnBackPressed( |
| boolean shouldAutomaticallyHandleOnBackPressed) { |
| this.shouldAutomaticallyHandleOnBackPressed = shouldAutomaticallyHandleOnBackPressed; |
| return this; |
| } |
| |
| /** |
| * Whether to delay the Android drawing pass till after the Flutter UI has been displayed. |
| * |
| * <p>See {#link FlutterActivityAndFragmentDelegate#onCreateView} for more details. |
| */ |
| @NonNull |
| public CachedEngineFragmentBuilder shouldDelayFirstAndroidViewDraw( |
| @NonNull boolean shouldDelayFirstAndroidViewDraw) { |
| this.shouldDelayFirstAndroidViewDraw = shouldDelayFirstAndroidViewDraw; |
| return this; |
| } |
| |
| /** |
| * Creates a {@link Bundle} of arguments that are assigned to the new {@code FlutterFragment}. |
| * |
| * <p>Subclasses should override this method to add new properties to the {@link Bundle}. |
| * Subclasses must call through to the super method to collect all existing property values. |
| */ |
| @NonNull |
| protected Bundle createArgs() { |
| Bundle args = new Bundle(); |
| args.putString(ARG_CACHED_ENGINE_ID, engineId); |
| args.putBoolean(ARG_DESTROY_ENGINE_WITH_FRAGMENT, destroyEngineWithFragment); |
| args.putBoolean(ARG_HANDLE_DEEPLINKING, handleDeeplinking); |
| args.putString( |
| ARG_FLUTTERVIEW_RENDER_MODE, |
| renderMode != null ? renderMode.name() : RenderMode.surface.name()); |
| args.putString( |
| ARG_FLUTTERVIEW_TRANSPARENCY_MODE, |
| transparencyMode != null ? transparencyMode.name() : TransparencyMode.transparent.name()); |
| args.putBoolean(ARG_SHOULD_ATTACH_ENGINE_TO_ACTIVITY, shouldAttachEngineToActivity); |
| args.putBoolean( |
| ARG_SHOULD_AUTOMATICALLY_HANDLE_ON_BACK_PRESSED, shouldAutomaticallyHandleOnBackPressed); |
| args.putBoolean(ARG_SHOULD_DELAY_FIRST_ANDROID_VIEW_DRAW, shouldDelayFirstAndroidViewDraw); |
| return args; |
| } |
| |
| /** |
| * Constructs a new {@code FlutterFragment} (or a subclass) that is configured based on |
| * properties set on this {@code CachedEngineFragmentBuilder}. |
| */ |
| @NonNull |
| public <T extends FlutterFragment> T build() { |
| try { |
| @SuppressWarnings("unchecked") |
| T frag = (T) fragmentClass.getDeclaredConstructor().newInstance(); |
| if (frag == null) { |
| throw new RuntimeException( |
| "The FlutterFragment subclass sent in the constructor (" |
| + fragmentClass.getCanonicalName() |
| + ") does not match the expected return type."); |
| } |
| |
| Bundle args = createArgs(); |
| frag.setArguments(args); |
| |
| return frag; |
| } catch (Exception e) { |
| throw new RuntimeException( |
| "Could not instantiate FlutterFragment subclass (" + fragmentClass.getName() + ")", e); |
| } |
| } |
| } |
| |
| /** |
| * Returns a {@link NewEngineInGroupFragmentBuilder} to create a {@code FlutterFragment} with a |
| * cached {@link io.flutter.embedding.engine.FlutterEngineGroup} in {@link |
| * io.flutter.embedding.engine.FlutterEngineGroupCache}. |
| * |
| * <p>An {@code IllegalStateException} will be thrown during the lifecycle of the {@code |
| * FlutterFragment} if a cached {@link io.flutter.embedding.engine.FlutterEngineGroup} is |
| * requested but does not exist in the {@link |
| * io.flutter.embedding.engine.FlutterEngineGroupCache}. |
| */ |
| @NonNull |
| public static NewEngineInGroupFragmentBuilder withNewEngineInGroup( |
| @NonNull String engineGroupId) { |
| return new NewEngineInGroupFragmentBuilder(engineGroupId); |
| } |
| |
| /** |
| * Builder that creates a new {@code FlutterFragment} that uses a cached {@link |
| * io.flutter.embedding.engine.FlutterEngineGroup} to create a new {@link |
| * io.flutter.embedding.engine.FlutterEngine} with {@code arguments} that correspond to the values |
| * set on this {@code Builder}. |
| * |
| * <p>Subclasses of {@code FlutterFragment} that do not introduce any new arguments can use this |
| * {@code Builder} to construct instances of the subclass without subclassing this {@code |
| * Builder}. {@code MyFlutterFragment f = new |
| * FlutterFragment.NewEngineInGroupFragmentBuilder(MyFlutterFragment.class, engineGroupId) |
| * .someProperty(...) .someOtherProperty(...) .build<MyFlutterFragment>(); } |
| * |
| * <p>Subclasses of {@code FlutterFragment} that introduce new arguments should subclass this |
| * {@code NewEngineInGroupFragmentBuilder} to add the new properties: |
| * |
| * <ol> |
| * <li>Ensure the {@code FlutterFragment} subclass has a no-arg constructor. |
| * <li>Subclass this {@code NewEngineInGroupFragmentBuilder}. |
| * <li>Override the new {@code NewEngineInGroupFragmentBuilder}'s no-arg constructor and invoke |
| * the super constructor to set the {@code FlutterFragment} subclass: {@code public |
| * MyBuilder() { super(MyFlutterFragment.class); } } |
| * <li>Add appropriate property methods for the new properties. |
| * <li>Override {@link NewEngineInGroupFragmentBuilder#createArgs()}, call through to the super |
| * method, then add the new properties as arguments in the {@link Bundle}. |
| * </ol> |
| * |
| * Once a {@code NewEngineInGroupFragmentBuilder} subclass is defined, the {@code FlutterFragment} |
| * subclass can be instantiated as follows. {@code MyFlutterFragment f = new MyBuilder() |
| * .someExistingProperty(...) .someNewProperty(...) .build<MyFlutterFragment>(); } |
| */ |
| public static class NewEngineInGroupFragmentBuilder { |
| private final Class<? extends FlutterFragment> fragmentClass; |
| private final String cachedEngineGroupId; |
| private @NonNull String dartEntrypoint = "main"; |
| private @NonNull String initialRoute = "/"; |
| private @NonNull boolean handleDeeplinking = false; |
| private @NonNull RenderMode renderMode = RenderMode.surface; |
| private @NonNull TransparencyMode transparencyMode = TransparencyMode.transparent; |
| private boolean shouldAttachEngineToActivity = true; |
| private boolean shouldAutomaticallyHandleOnBackPressed = false; |
| private boolean shouldDelayFirstAndroidViewDraw = false; |
| |
| public NewEngineInGroupFragmentBuilder(@NonNull String engineGroupId) { |
| this(FlutterFragment.class, engineGroupId); |
| } |
| |
| public NewEngineInGroupFragmentBuilder( |
| @NonNull Class<? extends FlutterFragment> fragmentClass, @NonNull String engineGroupId) { |
| this.fragmentClass = fragmentClass; |
| this.cachedEngineGroupId = engineGroupId; |
| } |
| |
| /** The name of the initial Dart method to invoke, defaults to "main". */ |
| @NonNull |
| public NewEngineInGroupFragmentBuilder dartEntrypoint(@NonNull String dartEntrypoint) { |
| this.dartEntrypoint = dartEntrypoint; |
| return this; |
| } |
| |
| /** |
| * The initial route that a Flutter app will render in this {@link FlutterFragment}, defaults to |
| * "/". |
| */ |
| @NonNull |
| public NewEngineInGroupFragmentBuilder initialRoute(@NonNull String initialRoute) { |
| this.initialRoute = initialRoute; |
| return this; |
| } |
| |
| /** |
| * Whether to handle the deeplinking from the {@code Intent} automatically if the {@code |
| * getInitialRoute} returns null. |
| */ |
| @NonNull |
| public NewEngineInGroupFragmentBuilder handleDeeplinking(@NonNull boolean handleDeeplinking) { |
| this.handleDeeplinking = handleDeeplinking; |
| return this; |
| } |
| |
| /** |
| * Render Flutter either as a {@link RenderMode#surface} or a {@link RenderMode#texture}. You |
| * should use {@code surface} unless you have a specific reason to use {@code texture}. {@code |
| * texture} comes with a significant performance impact, but {@code texture} can be displayed |
| * beneath other Android {@code View}s and animated, whereas {@code surface} cannot. |
| */ |
| @NonNull |
| public NewEngineInGroupFragmentBuilder renderMode(@NonNull RenderMode renderMode) { |
| this.renderMode = renderMode; |
| return this; |
| } |
| |
| /** |
| * Support a {@link TransparencyMode#transparent} background within {@link |
| * io.flutter.embedding.android.FlutterView}, or force an {@link TransparencyMode#opaque} |
| * background. |
| * |
| * <p>See {@link TransparencyMode} for implications of this selection. |
| */ |
| @NonNull |
| public NewEngineInGroupFragmentBuilder transparencyMode( |
| @NonNull TransparencyMode transparencyMode) { |
| this.transparencyMode = transparencyMode; |
| return this; |
| } |
| |
| /** |
| * Whether or not this {@code FlutterFragment} should automatically attach its {@code Activity} |
| * as a control surface for its {@link io.flutter.embedding.engine.FlutterEngine}. |
| * |
| * <p>Control surfaces are used to provide Android resources and lifecycle events to plugins |
| * that are attached to the {@link io.flutter.embedding.engine.FlutterEngine}. If {@code |
| * shouldAttachEngineToActivity} is true then this {@code FlutterFragment} will connect its |
| * {@link io.flutter.embedding.engine.FlutterEngine} to the surrounding {@code Activity}, along |
| * with any plugins that are registered with that {@link FlutterEngine}. This allows plugins to |
| * access the {@code Activity}, as well as receive {@code Activity}-specific calls, e.g., {@link |
| * android.app.Activity#onNewIntent(Intent)}. If {@code shouldAttachEngineToActivity} is false, |
| * then this {@code FlutterFragment} will not automatically manage the connection between its |
| * {@link io.flutter.embedding.engine.FlutterEngine} and the surrounding {@code Activity}. The |
| * {@code Activity} will need to be manually connected to this {@code FlutterFragment}'s {@link |
| * io.flutter.embedding.engine.FlutterEngine} by the app developer. See {@link |
| * FlutterEngine#getActivityControlSurface()}. |
| * |
| * <p>One reason that a developer might choose to manually manage the relationship between the |
| * {@code Activity} and {@link io.flutter.embedding.engine.FlutterEngine} is if the developer |
| * wants to move the {@link FlutterEngine} somewhere else. For example, a developer might want |
| * the {@link io.flutter.embedding.engine.FlutterEngine} to outlive the surrounding {@code |
| * Activity} so that it can be used later in a different {@code Activity}. To accomplish this, |
| * the {@link io.flutter.embedding.engine.FlutterEngine} will need to be disconnected from the |
| * surrounding {@code Activity} at an unusual time, preventing this {@code FlutterFragment} from |
| * correctly managing the relationship between the {@link |
| * io.flutter.embedding.engine.FlutterEngine} and the surrounding {@code Activity}. |
| * |
| * <p>Another reason that a developer might choose to manually manage the relationship between |
| * the {@code Activity} and {@link io.flutter.embedding.engine.FlutterEngine} is if the |
| * developer wants to prevent, or explicitly control when the {@link |
| * io.flutter.embedding.engine.FlutterEngine}'s plugins have access to the surrounding {@code |
| * Activity}. For example, imagine that this {@code FlutterFragment} only takes up part of the |
| * screen and the app developer wants to ensure that none of the Flutter plugins are able to |
| * manipulate the surrounding {@code Activity}. In this case, the developer would not want the |
| * {@link io.flutter.embedding.engine.FlutterEngine} to have access to the {@code Activity}, |
| * which can be accomplished by setting {@code shouldAttachEngineToActivity} to {@code false}. |
| */ |
| @NonNull |
| public NewEngineInGroupFragmentBuilder shouldAttachEngineToActivity( |
| boolean shouldAttachEngineToActivity) { |
| this.shouldAttachEngineToActivity = shouldAttachEngineToActivity; |
| return this; |
| } |
| |
| /** |
| * Whether or not this {@code FlutterFragment} should automatically receive {@link |
| * #onBackPressed()} events, rather than requiring an explicit activity call through. Disabled |
| * by default. |
| * |
| * <p>When enabled, the activity will automatically dispatch back-press events to the fragment's |
| * {@link OnBackPressedCallback}, instead of requiring the activity to manually call {@link |
| * #onBackPressed()} in client code. If enabled, do <b>not</b> invoke {@link #onBackPressed()} |
| * manually. |
| * |
| * <p>This behavior relies on the implementation of {@link #popSystemNavigator()}. It's not |
| * recommended to override that method when enabling this attribute, but if you do, you should |
| * always fall back to calling {@code super.popSystemNavigator()} when not relying on custom |
| * behavior. |
| */ |
| @NonNull |
| public NewEngineInGroupFragmentBuilder shouldAutomaticallyHandleOnBackPressed( |
| boolean shouldAutomaticallyHandleOnBackPressed) { |
| this.shouldAutomaticallyHandleOnBackPressed = shouldAutomaticallyHandleOnBackPressed; |
| return this; |
| } |
| |
| /** |
| * Whether to delay the Android drawing pass till after the Flutter UI has been displayed. |
| * |
| * <p>See {#link FlutterActivityAndFragmentDelegate#onCreateView} for more details. |
| */ |
| @NonNull |
| public NewEngineInGroupFragmentBuilder shouldDelayFirstAndroidViewDraw( |
| @NonNull boolean shouldDelayFirstAndroidViewDraw) { |
| this.shouldDelayFirstAndroidViewDraw = shouldDelayFirstAndroidViewDraw; |
| return this; |
| } |
| |
| /** |
| * Creates a {@link Bundle} of arguments that are assigned to the new {@code FlutterFragment}. |
| * |
| * <p>Subclasses should override this method to add new properties to the {@link Bundle}. |
| * Subclasses must call through to the super method to collect all existing property values. |
| */ |
| @NonNull |
| protected Bundle createArgs() { |
| Bundle args = new Bundle(); |
| args.putString(ARG_CACHED_ENGINE_GROUP_ID, cachedEngineGroupId); |
| args.putString(ARG_DART_ENTRYPOINT, dartEntrypoint); |
| args.putString(ARG_INITIAL_ROUTE, initialRoute); |
| args.putBoolean(ARG_HANDLE_DEEPLINKING, handleDeeplinking); |
| args.putString( |
| ARG_FLUTTERVIEW_RENDER_MODE, |
| renderMode != null ? renderMode.name() : RenderMode.surface.name()); |
| args.putString( |
| ARG_FLUTTERVIEW_TRANSPARENCY_MODE, |
| transparencyMode != null ? transparencyMode.name() : TransparencyMode.transparent.name()); |
| args.putBoolean(ARG_SHOULD_ATTACH_ENGINE_TO_ACTIVITY, shouldAttachEngineToActivity); |
| args.putBoolean(ARG_DESTROY_ENGINE_WITH_FRAGMENT, true); |
| args.putBoolean( |
| ARG_SHOULD_AUTOMATICALLY_HANDLE_ON_BACK_PRESSED, shouldAutomaticallyHandleOnBackPressed); |
| args.putBoolean(ARG_SHOULD_DELAY_FIRST_ANDROID_VIEW_DRAW, shouldDelayFirstAndroidViewDraw); |
| return args; |
| } |
| |
| /** |
| * Constructs a new {@code FlutterFragment} (or a subclass) that is configured based on |
| * properties set on this {@code Builder}. |
| */ |
| @NonNull |
| public <T extends FlutterFragment> T build() { |
| try { |
| @SuppressWarnings("unchecked") |
| T frag = (T) fragmentClass.getDeclaredConstructor().newInstance(); |
| if (frag == null) { |
| throw new RuntimeException( |
| "The FlutterFragment subclass sent in the constructor (" |
| + fragmentClass.getCanonicalName() |
| + ") does not match the expected return type."); |
| } |
| |
| Bundle args = createArgs(); |
| frag.setArguments(args); |
| |
| return frag; |
| } catch (Exception e) { |
| throw new RuntimeException( |
| "Could not instantiate FlutterFragment subclass (" + fragmentClass.getName() + ")", e); |
| } |
| } |
| } |
| |
| // Delegate that runs all lifecycle and OS hook logic that is common between |
| // FlutterActivity and FlutterFragment. See the FlutterActivityAndFragmentDelegate |
| // implementation for details about why it exists. |
| @VisibleForTesting @Nullable /* package */ FlutterActivityAndFragmentDelegate delegate; |
| |
| @NonNull private FlutterActivityAndFragmentDelegate.DelegateFactory delegateFactory = this; |
| |
| /** Default delegate factory that creates a simple FlutterActivityAndFragmentDelegate instance. */ |
| public FlutterActivityAndFragmentDelegate createDelegate( |
| FlutterActivityAndFragmentDelegate.Host host) { |
| return new FlutterActivityAndFragmentDelegate(host); |
| } |
| |
| private final OnBackPressedCallback onBackPressedCallback = |
| new OnBackPressedCallback(true) { |
| @Override |
| public void handleOnBackPressed() { |
| onBackPressed(); |
| } |
| }; |
| |
| public FlutterFragment() { |
| // Ensure that we at least have an empty Bundle of arguments so that we don't |
| // need to continually check for null arguments before grabbing one. |
| setArguments(new Bundle()); |
| } |
| |
| /** |
| * This method exists so that JVM tests can ensure that a delegate exists without putting this |
| * Fragment through any lifecycle events, because JVM tests cannot handle executing any lifecycle |
| * methods, at the time of writing this. |
| * |
| * <p>The testing infrastructure should be upgraded to make FlutterFragment tests easy to write |
| * while exercising real lifecycle methods. At such a time, this method should be removed. |
| */ |
| // TODO(mattcarroll): remove this when tests allow for it |
| // (https://github.com/flutter/flutter/issues/43798) |
| @VisibleForTesting |
| /* package */ void setDelegateFactory( |
| @NonNull FlutterActivityAndFragmentDelegate.DelegateFactory delegateFactory) { |
| this.delegateFactory = delegateFactory; |
| delegate = delegateFactory.createDelegate(this); |
| } |
| |
| /** |
| * Returns the Android App Component exclusively attached to {@link |
| * io.flutter.embedding.engine.FlutterEngine}. |
| */ |
| @Override |
| public ExclusiveAppComponent<Activity> getExclusiveAppComponent() { |
| return delegate; |
| } |
| |
| @Override |
| public void onAttach(@NonNull Context context) { |
| super.onAttach(context); |
| delegate = delegateFactory.createDelegate(this); |
| delegate.onAttach(context); |
| if (getArguments().getBoolean(ARG_SHOULD_AUTOMATICALLY_HANDLE_ON_BACK_PRESSED, false)) { |
| requireActivity().getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback); |
| } |
| context.registerComponentCallbacks(this); |
| } |
| |
| @Override |
| public void onCreate(@Nullable Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| delegate.onRestoreInstanceState(savedInstanceState); |
| } |
| |
| @Nullable |
| @Override |
| public View onCreateView( |
| LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { |
| return delegate.onCreateView( |
| inflater, |
| container, |
| savedInstanceState, |
| /*flutterViewId=*/ FLUTTER_VIEW_ID, |
| shouldDelayFirstAndroidViewDraw()); |
| } |
| |
| @Override |
| public void onStart() { |
| super.onStart(); |
| if (stillAttachedForEvent("onStart")) { |
| delegate.onStart(); |
| } |
| } |
| |
| @Override |
| public void onResume() { |
| super.onResume(); |
| if (stillAttachedForEvent("onResume")) { |
| delegate.onResume(); |
| } |
| } |
| |
| // TODO(mattcarroll): determine why this can't be in onResume(). Comment reason, or move if |
| // possible. |
| @ActivityCallThrough |
| public void onPostResume() { |
| if (stillAttachedForEvent("onPostResume")) { |
| delegate.onPostResume(); |
| } |
| } |
| |
| @Override |
| public void onPause() { |
| super.onPause(); |
| if (stillAttachedForEvent("onPause")) { |
| delegate.onPause(); |
| } |
| } |
| |
| @Override |
| public void onStop() { |
| super.onStop(); |
| if (stillAttachedForEvent("onStop")) { |
| delegate.onStop(); |
| } |
| } |
| |
| @Override |
| public void onDestroyView() { |
| super.onDestroyView(); |
| if (stillAttachedForEvent("onDestroyView")) { |
| delegate.onDestroyView(); |
| } |
| } |
| |
| @Override |
| public void onSaveInstanceState(Bundle outState) { |
| super.onSaveInstanceState(outState); |
| if (stillAttachedForEvent("onSaveInstanceState")) { |
| delegate.onSaveInstanceState(outState); |
| } |
| } |
| |
| @Override |
| public void detachFromFlutterEngine() { |
| Log.w( |
| TAG, |
| "FlutterFragment " |
| + this |
| + " connection to the engine " |
| + getFlutterEngine() |
| + " evicted by another attaching activity"); |
| if (delegate != null) { |
| // Redundant calls are ok. |
| delegate.onDestroyView(); |
| delegate.onDetach(); |
| } |
| } |
| |
| @Override |
| public void onDetach() { |
| getContext().unregisterComponentCallbacks(this); |
| super.onDetach(); |
| if (delegate != null) { |
| delegate.onDetach(); |
| delegate.release(); |
| delegate = null; |
| } else { |
| Log.v(TAG, "FlutterFragment " + this + " onDetach called after release."); |
| } |
| } |
| |
| /** |
| * The result of a permission request has been received. |
| * |
| * <p>See {@link android.app.Activity#onRequestPermissionsResult(int, String[], int[])} |
| * |
| * <p> |
| * |
| * @param requestCode identifier passed with the initial permission request |
| * @param permissions permissions that were requested |
| * @param grantResults permission grants or denials |
| */ |
| @ActivityCallThrough |
| public void onRequestPermissionsResult( |
| int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { |
| if (stillAttachedForEvent("onRequestPermissionsResult")) { |
| delegate.onRequestPermissionsResult(requestCode, permissions, grantResults); |
| } |
| } |
| |
| /** |
| * A new Intent was received by the {@link android.app.Activity} that currently owns this {@link |
| * Fragment}. |
| * |
| * <p>See {@link android.app.Activity#onNewIntent(Intent)} |
| * |
| * <p> |
| * |
| * @param intent new Intent |
| */ |
| @ActivityCallThrough |
| public void onNewIntent(@NonNull Intent intent) { |
| if (stillAttachedForEvent("onNewIntent")) { |
| delegate.onNewIntent(intent); |
| } |
| } |
| |
| /** |
| * The hardware back button was pressed. |
| * |
| * <p>If the fragment uses {@code shouldAutomaticallyHandleOnBackPressed(true)}, this method |
| * should not be called through. It will be called automatically instead. |
| * |
| * <p>See {@link android.app.Activity#onBackPressed()} |
| */ |
| @ActivityCallThrough |
| public void onBackPressed() { |
| if (stillAttachedForEvent("onBackPressed")) { |
| delegate.onBackPressed(); |
| } |
| } |
| |
| /** |
| * A result has been returned after an invocation of {@link |
| * Fragment#startActivityForResult(Intent, int)}. |
| * |
| * <p> |
| * |
| * @param requestCode request code sent with {@link Fragment#startActivityForResult(Intent, int)} |
| * @param resultCode code representing the result of the {@code Activity} that was launched |
| * @param data any corresponding return data, held within an {@code Intent} |
| */ |
| @Override |
| public void onActivityResult(int requestCode, int resultCode, Intent data) { |
| if (stillAttachedForEvent("onActivityResult")) { |
| delegate.onActivityResult(requestCode, resultCode, data); |
| } |
| } |
| |
| /** |
| * The {@link android.app.Activity} that owns this {@link Fragment} is about to go to the |
| * background as the result of a user's choice/action, i.e., not as the result of an OS decision. |
| * |
| * <p>See {@link android.app.Activity#onUserLeaveHint()} |
| */ |
| @ActivityCallThrough |
| public void onUserLeaveHint() { |
| if (stillAttachedForEvent("onUserLeaveHint")) { |
| delegate.onUserLeaveHint(); |
| } |
| } |
| |
| /** |
| * Callback invoked when memory is low. |
| * |
| * <p>This implementation forwards a memory pressure warning to the running Flutter app. |
| * |
| * <p> |
| * |
| * @param level level |
| */ |
| @Override |
| public void onTrimMemory(int level) { |
| if (stillAttachedForEvent("onTrimMemory")) { |
| delegate.onTrimMemory(level); |
| } |
| } |
| |
| /** |
| * {@link FlutterActivityAndFragmentDelegate.Host} method that is used by {@link |
| * FlutterActivityAndFragmentDelegate} to obtain Flutter shell arguments when initializing |
| * Flutter. |
| */ |
| @Override |
| @NonNull |
| public FlutterShellArgs getFlutterShellArgs() { |
| String[] flutterShellArgsArray = getArguments().getStringArray(ARG_FLUTTER_INITIALIZATION_ARGS); |
| return new FlutterShellArgs( |
| flutterShellArgsArray != null ? flutterShellArgsArray : new String[] {}); |
| } |
| |
| /** |
| * Returns the ID of a statically cached {@link io.flutter.embedding.engine.FlutterEngine} to use |
| * within this {@code FlutterFragment}, or {@code null} if this {@code FlutterFragment} does not |
| * want to use a cached {@link io.flutter.embedding.engine.FlutterEngine}. |
| */ |
| @Nullable |
| @Override |
| public String getCachedEngineId() { |
| return getArguments().getString(ARG_CACHED_ENGINE_ID, null); |
| } |
| |
| /** |
| * Returns the ID of a statically cached {@link io.flutter.embedding.engine.FlutterEngineGroup} to |
| * use within this {@code FlutterFragment}, or {@code null} if this {@code FlutterFragment} does |
| * not want to use a cached {@link io.flutter.embedding.engine.FlutterEngineGroup}. |
| */ |
| @Override |
| @Nullable |
| public String getCachedEngineGroupId() { |
| return getArguments().getString(ARG_CACHED_ENGINE_GROUP_ID, null); |
| } |
| |
| /** |
| * Returns true a {@code FlutterEngine} was explicitly created and injected into the {@code |
| * FlutterFragment} rather than one that was created implicitly in the {@code FlutterFragment}. |
| */ |
| /* package */ boolean isFlutterEngineInjected() { |
| return delegate.isFlutterEngineFromHost(); |
| } |
| |
| /** |
| * Returns false if the {@link io.flutter.embedding.engine.FlutterEngine} within this {@code |
| * FlutterFragment} should outlive the {@code FlutterFragment}, itself. |
| * |
| * <p>Defaults to true if no custom {@link io.flutter.embedding.engine.FlutterEngine is provided}, |
| * false if a custom {@link FlutterEngine} is provided. |
| */ |
| @Override |
| public boolean shouldDestroyEngineWithHost() { |
| boolean explicitDestructionRequested = |
| getArguments().getBoolean(ARG_DESTROY_ENGINE_WITH_FRAGMENT, false); |
| if (getCachedEngineId() != null || delegate.isFlutterEngineFromHost()) { |
| // Only destroy a cached engine if explicitly requested by app developer. |
| return explicitDestructionRequested; |
| } else { |
| // If this Fragment created the FlutterEngine, destroy it by default unless |
| // explicitly requested not to. |
| return getArguments().getBoolean(ARG_DESTROY_ENGINE_WITH_FRAGMENT, true); |
| } |
| } |
| |
| /** |
| * Returns the name of the Dart method that this {@code FlutterFragment} should execute to start a |
| * Flutter app. |
| * |
| * <p>Defaults to "main". |
| * |
| * <p>Used by this {@code FlutterFragment}'s {@link FlutterActivityAndFragmentDelegate.Host} |
| */ |
| @Override |
| @NonNull |
| public String getDartEntrypointFunctionName() { |
| return getArguments().getString(ARG_DART_ENTRYPOINT, "main"); |
| } |
| |
| /** |
| * The Dart entrypoint arguments will be passed as a list of string to Dart's entrypoint function. |
| * |
| * <p>A value of null means do not pass any arguments to Dart's entrypoint function. |
| * |
| * <p>Subclasses may override this method to directly control the Dart entrypoint arguments. |
| */ |
| @Override |
| @Nullable |
| public List<String> getDartEntrypointArgs() { |
| return getArguments().getStringArrayList(ARG_DART_ENTRYPOINT_ARGS); |
| } |
| |
| /** |
| * Returns the library URI of the Dart method that this {@code FlutterFragment} should execute to |
| * start a Flutter app. |
| * |
| * <p>Defaults to null (example value: "package:foo/bar.dart"). |
| * |
| * <p>Used by this {@code FlutterFragment}'s {@link FlutterActivityAndFragmentDelegate.Host} |
| */ |
| @Override |
| @Nullable |
| public String getDartEntrypointLibraryUri() { |
| return getArguments().getString(ARG_DART_ENTRYPOINT_URI); |
| } |
| |
| /** |
| * A custom path to the bundle that contains this Flutter app's resources, e.g., Dart code |
| * snapshots. |
| * |
| * <p>When unspecified, the value is null, which defaults to the app bundle path defined in {@link |
| * io.flutter.embedding.engine.loader.FlutterLoader#findAppBundlePath()}. |
| * |
| * <p>Used by this {@code FlutterFragment}'s {@link FlutterActivityAndFragmentDelegate.Host} |
| */ |
| @Override |
| @NonNull |
| public String getAppBundlePath() { |
| return getArguments().getString(ARG_APP_BUNDLE_PATH); |
| } |
| |
| /** |
| * Returns the initial route that should be rendered within Flutter, once the Flutter app starts. |
| * |
| * <p>Defaults to {@code null}, which signifies a route of "/" in Flutter. |
| * |
| * <p>Used by this {@code FlutterFragment}'s {@link FlutterActivityAndFragmentDelegate.Host} |
| */ |
| @Override |
| @Nullable |
| public String getInitialRoute() { |
| return getArguments().getString(ARG_INITIAL_ROUTE); |
| } |
| |
| /** |
| * Returns the desired {@link RenderMode} for the {@link io.flutter.embedding.android.FlutterView} |
| * displayed in this {@code FlutterFragment}. |
| * |
| * <p>Defaults to {@link RenderMode#surface}. |
| * |
| * <p>Used by this {@code FlutterFragment}'s {@link FlutterActivityAndFragmentDelegate.Host} |
| */ |
| @Override |
| @NonNull |
| public RenderMode getRenderMode() { |
| String renderModeName = |
| getArguments().getString(ARG_FLUTTERVIEW_RENDER_MODE, RenderMode.surface.name()); |
| return RenderMode.valueOf(renderModeName); |
| } |
| |
| /** |
| * Returns the desired {@link TransparencyMode} for the {@link |
| * io.flutter.embedding.android.FlutterView} displayed in this {@code FlutterFragment}. |
| * |
| * <p>Defaults to {@link TransparencyMode#transparent}. |
| * |
| * <p>Used by this {@code FlutterFragment}'s {@link FlutterActivityAndFragmentDelegate.Host} |
| */ |
| @Override |
| @NonNull |
| public TransparencyMode getTransparencyMode() { |
| String transparencyModeName = |
| getArguments() |
| .getString(ARG_FLUTTERVIEW_TRANSPARENCY_MODE, TransparencyMode.transparent.name()); |
| return TransparencyMode.valueOf(transparencyModeName); |
| } |
| |
| @Override |
| @Nullable |
| public SplashScreen provideSplashScreen() { |
| FragmentActivity parentActivity = getActivity(); |
| if (parentActivity instanceof SplashScreenProvider) { |
| SplashScreenProvider splashScreenProvider = (SplashScreenProvider) parentActivity; |
| return splashScreenProvider.provideSplashScreen(); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Hook for subclasses to return a {@link io.flutter.embedding.engine.FlutterEngine} with whatever |
| * configuration is desired. |
| * |
| * <p>By default this method defers to this {@code FlutterFragment}'s surrounding {@code |
| * Activity}, if that {@code Activity} implements {@link |
| * io.flutter.embedding.android.FlutterEngineProvider}. If this method is overridden, the |
| * surrounding {@code Activity} will no longer be given an opportunity to provide a {@link |
| * io.flutter.embedding.engine.FlutterEngine}, unless the subclass explicitly implements that |
| * behavior. |
| * |
| * <p>Consider returning a cached {@link io.flutter.embedding.engine.FlutterEngine} instance from |
| * this method to avoid the typical warm-up time that a new {@link |
| * io.flutter.embedding.engine.FlutterEngine} instance requires. |
| * |
| * <p>If null is returned then a new default {@link io.flutter.embedding.engine.FlutterEngine} |
| * will be created to back this {@code FlutterFragment}. |
| * |
| * <p>Used by this {@code FlutterFragment}'s {@link FlutterActivityAndFragmentDelegate.Host} |
| */ |
| @Override |
| @Nullable |
| public FlutterEngine provideFlutterEngine(@NonNull Context context) { |
| // Defer to the FragmentActivity that owns us to see if it wants to provide a |
| // FlutterEngine. |
| FlutterEngine flutterEngine = null; |
| FragmentActivity attachedActivity = getActivity(); |
| if (attachedActivity instanceof FlutterEngineProvider) { |
| // Defer to the Activity that owns us to provide a FlutterEngine. |
| Log.v(TAG, "Deferring to attached Activity to provide a FlutterEngine."); |
| FlutterEngineProvider flutterEngineProvider = (FlutterEngineProvider) attachedActivity; |
| flutterEngine = flutterEngineProvider.provideFlutterEngine(getContext()); |
| } |
| |
| return flutterEngine; |
| } |
| |
| /** |
| * Hook for subclasses to obtain a reference to the {@link |
| * io.flutter.embedding.engine.FlutterEngine} that is owned by this {@code FlutterActivity}. |
| */ |
| @Nullable |
| public FlutterEngine getFlutterEngine() { |
| return delegate.getFlutterEngine(); |
| } |
| |
| @Nullable |
| @Override |
| public PlatformPlugin providePlatformPlugin( |
| @Nullable Activity activity, @NonNull FlutterEngine flutterEngine) { |
| if (activity != null) { |
| return new PlatformPlugin(getActivity(), flutterEngine.getPlatformChannel(), this); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Configures a {@link io.flutter.embedding.engine.FlutterEngine} after its creation. |
| * |
| * <p>This method is called after {@link #provideFlutterEngine(Context)}, and after the given |
| * {@link io.flutter.embedding.engine.FlutterEngine} has been attached to the owning {@code |
| * FragmentActivity}. See {@link |
| * io.flutter.embedding.engine.plugins.activity.ActivityControlSurface#attachToActivity( |
| * ExclusiveAppComponent, Lifecycle)}. |
| * |
| * <p>It is possible that the owning {@code FragmentActivity} opted not to connect itself as an |
| * {@link io.flutter.embedding.engine.plugins.activity.ActivityControlSurface}. In that case, any |
| * configuration, e.g., plugins, must not expect or depend upon an available {@code Activity} at |
| * the time that this method is invoked. |
| * |
| * <p>The default behavior of this method is to defer to the owning {@code FragmentActivity} as a |
| * {@link io.flutter.embedding.android.FlutterEngineConfigurator}. Subclasses can override this |
| * method if the subclass needs to override the {@code FragmentActivity}'s behavior, or add to it. |
| * |
| * <p>Used by this {@code FlutterFragment}'s {@link FlutterActivityAndFragmentDelegate.Host} |
| */ |
| @Override |
| public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { |
| FragmentActivity attachedActivity = getActivity(); |
| if (attachedActivity instanceof FlutterEngineConfigurator) { |
| ((FlutterEngineConfigurator) attachedActivity).configureFlutterEngine(flutterEngine); |
| } |
| } |
| |
| /** |
| * Hook for the host to cleanup references that were established in {@link |
| * #configureFlutterEngine(FlutterEngine)} before the host is destroyed or detached. |
| * |
| * <p>This method is called in {@link #onDetach()}. |
| */ |
| @Override |
| public void cleanUpFlutterEngine(@NonNull FlutterEngine flutterEngine) { |
| FragmentActivity attachedActivity = getActivity(); |
| if (attachedActivity instanceof FlutterEngineConfigurator) { |
| ((FlutterEngineConfigurator) attachedActivity).cleanUpFlutterEngine(flutterEngine); |
| } |
| } |
| |
| /** |
| * See {@link NewEngineFragmentBuilder#shouldAttachEngineToActivity()} and {@link |
| * CachedEngineFragmentBuilder#shouldAttachEngineToActivity()}. |
| * |
| * <p>Used by this {@code FlutterFragment}'s {@link FlutterActivityAndFragmentDelegate} |
| */ |
| @Override |
| public boolean shouldAttachEngineToActivity() { |
| return getArguments().getBoolean(ARG_SHOULD_ATTACH_ENGINE_TO_ACTIVITY); |
| } |
| |
| /** |
| * Whether to handle the deeplinking from the {@code Intent} automatically if the {@code |
| * getInitialRoute} returns null. |
| */ |
| @Override |
| public boolean shouldHandleDeeplinking() { |
| return getArguments().getBoolean(ARG_HANDLE_DEEPLINKING); |
| } |
| |
| @Override |
| public void onFlutterSurfaceViewCreated(@NonNull FlutterSurfaceView flutterSurfaceView) { |
| // Hook for subclasses. |
| } |
| |
| @Override |
| public void onFlutterTextureViewCreated(@NonNull FlutterTextureView flutterTextureView) { |
| // Hook for subclasses. |
| } |
| |
| /** |
| * Invoked after the {@link io.flutter.embedding.android.FlutterView} within this {@code |
| * FlutterFragment} starts rendering pixels to the screen. |
| * |
| * <p>This method forwards {@code onFlutterUiDisplayed()} to its attached {@code Activity}, if the |
| * attached {@code Activity} implements {@link FlutterUiDisplayListener}. |
| * |
| * <p>Subclasses that override this method must call through to the {@code super} method. |
| * |
| * <p>Used by this {@code FlutterFragment}'s {@link FlutterActivityAndFragmentDelegate.Host} |
| */ |
| @Override |
| public void onFlutterUiDisplayed() { |
| FragmentActivity attachedActivity = getActivity(); |
| if (attachedActivity instanceof FlutterUiDisplayListener) { |
| ((FlutterUiDisplayListener) attachedActivity).onFlutterUiDisplayed(); |
| } |
| } |
| |
| /** |
| * Invoked after the {@link io.flutter.embedding.android.FlutterView} within this {@code |
| * FlutterFragment} stops rendering pixels to the screen. |
| * |
| * <p>This method forwards {@code onFlutterUiNoLongerDisplayed()} to its attached {@code |
| * Activity}, if the attached {@code Activity} implements {@link FlutterUiDisplayListener}. |
| * |
| * <p>Subclasses that override this method must call through to the {@code super} method. |
| * |
| * <p>Used by this {@code FlutterFragment}'s {@link FlutterActivityAndFragmentDelegate.Host} |
| */ |
| @Override |
| public void onFlutterUiNoLongerDisplayed() { |
| FragmentActivity attachedActivity = getActivity(); |
| if (attachedActivity instanceof FlutterUiDisplayListener) { |
| ((FlutterUiDisplayListener) attachedActivity).onFlutterUiNoLongerDisplayed(); |
| } |
| } |
| |
| @Override |
| public boolean shouldRestoreAndSaveState() { |
| if (getArguments().containsKey(ARG_ENABLE_STATE_RESTORATION)) { |
| return getArguments().getBoolean(ARG_ENABLE_STATE_RESTORATION); |
| } |
| if (getCachedEngineId() != null) { |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public void updateSystemUiOverlays() { |
| if (delegate != null) { |
| delegate.updateSystemUiOverlays(); |
| } |
| } |
| |
| /** |
| * Give the host application a chance to take control of the app lifecycle events. |
| * |
| * <p>Return {@code false} means the host application dispatches these app lifecycle events, while |
| * return {@code true} means the engine dispatches these events. |
| * |
| * <p>Defaults to {@code true}. |
| */ |
| @Override |
| public boolean shouldDispatchAppLifecycleState() { |
| return true; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * <p>Avoid overriding this method when using {@code |
| * shouldAutomaticallyHandleOnBackPressed(true)}. If you do, you must always {@code return |
| * super.popSystemNavigator()} rather than {@code return false}. Otherwise the navigation behavior |
| * will recurse infinitely between this method and {@link #onBackPressed()}, breaking navigation. |
| */ |
| @Override |
| public boolean popSystemNavigator() { |
| if (getArguments().getBoolean(ARG_SHOULD_AUTOMATICALLY_HANDLE_ON_BACK_PRESSED, false)) { |
| FragmentActivity activity = getActivity(); |
| if (activity != null) { |
| // Unless we disable the callback, the dispatcher call will trigger it. This will then |
| // trigger the fragment's onBackPressed() implementation, which will call through to the |
| // dart side and likely call back through to this method, creating an infinite call loop. |
| onBackPressedCallback.setEnabled(false); |
| activity.getOnBackPressedDispatcher().onBackPressed(); |
| onBackPressedCallback.setEnabled(true); |
| return true; |
| } |
| } |
| // Hook for subclass. No-op if returns false. |
| return false; |
| } |
| |
| @VisibleForTesting |
| @NonNull |
| boolean shouldDelayFirstAndroidViewDraw() { |
| return getArguments().getBoolean(ARG_SHOULD_DELAY_FIRST_ANDROID_VIEW_DRAW); |
| } |
| |
| private boolean stillAttachedForEvent(String event) { |
| if (delegate == null) { |
| Log.w(TAG, "FlutterFragment " + hashCode() + " " + event + " called after release."); |
| return false; |
| } |
| if (!delegate.isAttached()) { |
| Log.w(TAG, "FlutterFragment " + hashCode() + " " + event + " called after detach."); |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Annotates methods in {@code FlutterFragment} that must be called by the containing {@code |
| * Activity}. |
| */ |
| @interface ActivityCallThrough {} |
| } |