blob: b5596ac3e6184b636d6702c6c2c4600bae5526c6 [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.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterShellArgs;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
import io.flutter.plugin.platform.PlatformPlugin;
import io.flutter.view.FlutterMain;
/**
* {@code Fragment} which displays a Flutter UI that takes up all available {@code Fragment} space.
* <p>
* WARNING: THIS CLASS IS EXPERIMENTAL. DO NOT SHIP A DEPENDENCY ON THIS CODE.
* IF YOU USE IT, WE WILL BREAK YOU.
* <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 android.app.Activity#onPostResume()}</li>
* <li>{@link android.app.Activity#onBackPressed()}</li>
* <li>{@link android.app.Activity#onRequestPermissionsResult(int, String[], int[])} ()}</li>
* <li>{@link android.app.Activity#onNewIntent(Intent)} ()}</li>
* <li>{@link android.app.Activity#onUserLeaveHint()}</li>
* <li>{@link android.app.Activity#onTrimMemory(int)}</li>
* </ol>
* 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>
* If Flutter is needed in a location that can only use a {@code View}, consider using a
* {@link FlutterView}. Using a {@link 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 {
private static final String TAG = "FlutterFragment";
protected static final String ARG_DART_ENTRYPOINT = "dart_entrypoint";
protected static final String ARG_INITIAL_ROUTE = "initial_route";
protected static final String ARG_APP_BUNDLE_PATH = "app_bundle_path";
protected static final String ARG_FLUTTER_INITIALIZATION_ARGS = "initialization_args";
protected static final String ARG_FLUTTERVIEW_RENDER_MODE = "flutterview_render_mode";
protected static final String ARG_FLUTTERVIEW_TRANSPARENCY_MODE = "flutterview_transparency_mode";
protected static final String ARG_SHOULD_ATTACH_ENGINE_TO_ACTIVITY = "should_attach_engine_to_activity";
/**
* Builder that creates a new {@code FlutterFragment} with {@code arguments} that correspond
* to the values set on this {@code Builder}.
* <p>
* To create a {@code FlutterFragment} with default {@code arguments}, invoke {@code build()}
* without setting any builder properties:
* {@code
* FlutterFragment fragment = new FlutterFragment.Builder().build();
* }
* <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.Builder(MyFlutterFragment.class)
* .someProperty(...)
* .someOtherProperty(...)
* .build<MyFlutterFragment>();
* }
* <p>
* Subclasses of {@code FlutterFragment} that introduce new arguments should subclass this
* {@code Builder} to add the new properties:
* <ol>
* <li>Ensure the {@code FlutterFragment} subclass has a no-arg constructor.</li>
* <li>Subclass this {@code Builder}.</li>
* <li>Override the new {@code Builder}'s no-arg constructor and invoke the super constructor
* to set the {@code FlutterFragment} subclass: {@code
* public MyBuilder() {
* super(MyFlutterFragment.class);
* }
* }</li>
* <li>Add appropriate property methods for the new properties.</li>
* <li>Override {@link Builder#createArgs()}, call through to the super method, then add
* the new properties as arguments in the {@link Bundle}.</li>
* </ol>
* Once a {@code Builder} subclass is defined, the {@code FlutterFragment} subclass can be
* instantiated as follows.
* {@code
* MyFlutterFragment f = new MyBuilder()
* .someExistingProperty(...)
* .someNewProperty(...)
* .build<MyFlutterFragment>();
* }
*/
public static class Builder {
private final Class<? extends FlutterFragment> fragmentClass;
private String dartEntrypoint = "main";
private String initialRoute = "/";
private String appBundlePath = null;
private FlutterShellArgs shellArgs = null;
private FlutterView.RenderMode renderMode = FlutterView.RenderMode.surface;
private FlutterView.TransparencyMode transparencyMode = FlutterView.TransparencyMode.transparent;
private boolean shouldAttachEngineToActivity = true;
/**
* Constructs a {@code Builder} that is configured to construct an instance of
* {@code FlutterFragment}.
*/
public Builder() {
fragmentClass = FlutterFragment.class;
}
/**
* Constructs a {@code Builder} that is configured to construct an instance of
* {@code subclass}, which extends {@code FlutterFragment}.
*/
public Builder(@NonNull Class<? extends FlutterFragment> subclass) {
fragmentClass = subclass;
}
/**
* The name of the initial Dart method to invoke, defaults to "main".
*/
@NonNull
public Builder 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 Builder initialRoute(@NonNull String initialRoute) {
this.initialRoute = initialRoute;
return this;
}
/**
* The path to the app bundle which contains the Dart app to execute, defaults
* to {@link FlutterMain#findAppBundlePath(Context)}
*/
@NonNull
public Builder appBundlePath(@NonNull String appBundlePath) {
this.appBundlePath = appBundlePath;
return this;
}
/**
* Any special configuration arguments for the Flutter engine
*/
@NonNull
public Builder flutterShellArgs(@NonNull FlutterShellArgs shellArgs) {
this.shellArgs = shellArgs;
return this;
}
/**
* Render Flutter either as a {@link FlutterView.RenderMode#surface} or a
* {@link FlutterView.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 Builder renderMode(@NonNull FlutterView.RenderMode renderMode) {
this.renderMode = renderMode;
return this;
}
/**
* Support a {@link FlutterView.TransparencyMode#transparent} background within {@link FlutterView},
* or force an {@link FlutterView.TransparencyMode#opaque} background.
* <p>
* See {@link FlutterView.TransparencyMode} for implications of this selection.
*/
@NonNull
public Builder transparencyMode(@NonNull FlutterView.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 FlutterEngine}.
* <p>
* Control surfaces are used to provide Android resources and lifecycle events to
* plugins that are attached to the {@link FlutterEngine}. If {@code shouldAttachEngineToActivity}
* is true then this {@code FlutterFragment} will connect its {@link 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 FlutterEngine} and the surrounding
* {@code Activity}. The {@code Activity} will need to be manually connected to this
* {@code FlutterFragment}'s {@link 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 FlutterEngine} is if the developer wants to move the
* {@link FlutterEngine} somewhere else. For example, a developer might want the
* {@link FlutterEngine} to outlive the surrounding {@code Activity} so that it can be used
* later in a different {@code Activity}. To accomplish this, the {@link 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 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 FlutterEngine} is if the developer wants to prevent, or explicitly
* control when the {@link 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 FlutterEngine} to have access to the {@code Activity}, which can be accomplished by
* setting {@code shouldAttachEngineToActivity} to {@code false}.
*/
@NonNull
public Builder shouldAttachEngineToActivity(boolean shouldAttachEngineToActivity) {
this.shouldAttachEngineToActivity = shouldAttachEngineToActivity;
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.putString(ARG_APP_BUNDLE_PATH, appBundlePath);
args.putString(ARG_DART_ENTRYPOINT, dartEntrypoint);
// 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() : FlutterView.RenderMode.surface.name());
args.putString(ARG_FLUTTERVIEW_TRANSPARENCY_MODE, transparencyMode != null ? transparencyMode.name() : FlutterView.TransparencyMode.transparent.name());
args.putBoolean(ARG_SHOULD_ATTACH_ENGINE_TO_ACTIVITY, shouldAttachEngineToActivity);
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);
}
}
}
@Nullable
private FlutterEngine flutterEngine;
private boolean isFlutterEngineFromActivity;
@Nullable
private FlutterView flutterView;
@Nullable
private PlatformPlugin platformPlugin;
private final OnFirstFrameRenderedListener onFirstFrameRenderedListener = new OnFirstFrameRenderedListener() {
@Override
public void onFirstFrameRendered() {
// Notify our subclasses that the first frame has been rendered.
FlutterFragment.this.onFirstFrameRendered();
// Notify our owning Activity that the first frame has been rendered.
FragmentActivity fragmentActivity = getActivity();
if (fragmentActivity != null && fragmentActivity instanceof OnFirstFrameRenderedListener) {
OnFirstFrameRenderedListener activityAsListener = (OnFirstFrameRenderedListener) fragmentActivity;
activityAsListener.onFirstFrameRendered();
}
}
};
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());
}
/**
* The {@link FlutterEngine} that backs the Flutter content presented by this {@code Fragment}.
*
* @return the {@link FlutterEngine} held by this {@code Fragment}
*/
@Nullable
public FlutterEngine getFlutterEngine() {
return flutterEngine;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
initializeFlutter(getContextCompat());
// 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 = new PlatformPlugin(getActivity(), flutterEngine.getPlatformChannel());
if (shouldAttachEngineToActivity()) {
// Notify any plugins that are currently attached to our FlutterEngine that they
// are now attached to an Activity.
// TODO(mattcarroll): send in a real lifecycle.
flutterEngine.getActivityControlSurface().attachToActivity(getActivity(), null);
}
}
private void initializeFlutter(@NonNull Context context) {
String[] flutterShellArgsArray = getArguments().getStringArray(ARG_FLUTTER_INITIALIZATION_ARGS);
FlutterShellArgs flutterShellArgs = new FlutterShellArgs(
flutterShellArgsArray != null ? flutterShellArgsArray : new String[] {}
);
FlutterMain.ensureInitializationComplete(context.getApplicationContext(), flutterShellArgs.toArray());
}
/**
* Obtains a reference to a FlutterEngine to back this {@code FlutterFragment}.
* <p>
* First, {@code FlutterFragment} subclasses are given an opportunity to provide a
* {@link FlutterEngine} by overriding {@link #createFlutterEngine(Context)}.
* <p>
* Second, the {@link FragmentActivity} that owns this {@code FlutterFragment} is
* given the opportunity to provide a {@link FlutterEngine} as a {@link FlutterEngineProvider}.
* <p>
* If subclasses do not provide a {@link FlutterEngine}, and the owning {@link FragmentActivity}
* does not implement {@link FlutterEngineProvider} or chooses to return {@code null}, then a new
* {@link FlutterEngine} is instantiated.
*/
private void setupFlutterEngine() {
// First, defer to subclasses for a custom FlutterEngine.
flutterEngine = createFlutterEngine(getContextCompat());
if (flutterEngine != null) {
return;
}
// Second, defer to the FragmentActivity that owns us to see if it wants to provide a
// FlutterEngine.
FragmentActivity attachedActivity = getActivity();
if (attachedActivity instanceof FlutterEngineProvider) {
// Defer to the Activity that owns us to provide a FlutterEngine.
Log.d(TAG, "Deferring to attached Activity to provide a FlutterEngine.");
FlutterEngineProvider flutterEngineProvider = (FlutterEngineProvider) attachedActivity;
flutterEngine = flutterEngineProvider.getFlutterEngine(getContext());
if (flutterEngine != null) {
isFlutterEngineFromActivity = true;
}
return;
}
// Neither our subclass, nor our owning Activity wanted to provide a custom FlutterEngine.
// Create a FlutterEngine to back our FlutterView.
Log.d(TAG, "No subclass or our attached Activity provided a custom FlutterEngine. Creating a "
+ "new FlutterEngine for this FlutterFragment.");
flutterEngine = new FlutterEngine(getContext());
isFlutterEngineFromActivity = false;
}
/**
* Hook for subclasses to return a {@link FlutterEngine} with whatever configuration
* is desired.
* <p>
* This method takes precedence for creation of a {@link FlutterEngine} over any owning
* {@code Activity} that may implement {@link FlutterEngineProvider}.
* <p>
* Consider returning a cached {@link FlutterEngine} instance from this method to avoid the
* typical warm-up time that a new {@link FlutterEngine} instance requires.
* <p>
* If null is returned then a new default {@link FlutterEngine} will be created to back this
* {@code FlutterFragment}.
*/
@Nullable
protected FlutterEngine createFlutterEngine(@NonNull Context context) {
return null;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
flutterView = new FlutterView(getContext(), getRenderMode(), getTransparencyMode());
flutterView.addOnFirstFrameRenderedListener(onFirstFrameRenderedListener);
return flutterView;
}
/**
* Starts running Dart within the FlutterView for the first time.
*
* Reloading/restarting Dart within a given FlutterView is not supported. If this method is
* invoked while Dart is already executing then it does nothing.
*
* {@code flutterEngine} must be non-null when invoking this method.
*/
private void doInitialFlutterViewRun() {
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;
}
// 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 (getInitialRoute() != null) {
flutterEngine.getNavigationChannel().setInitialRoute(getInitialRoute());
}
// Configure the Dart entrypoint and execute it.
DartExecutor.DartEntrypoint entrypoint = new DartExecutor.DartEntrypoint(
getResources().getAssets(),
getAppBundlePath(),
getDartEntrypointFunctionName()
);
flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint);
}
/**
* Returns the initial route that should be rendered within Flutter, once the Flutter app starts.
*
* Defaults to {@code null}, which signifies a route of "/" in Flutter.
*/
@Nullable
protected String getInitialRoute() {
return getArguments().getString(ARG_INITIAL_ROUTE);
}
/**
* Returns the file path to the desired Flutter app's bundle of code.
*
* Defaults to {@link FlutterMain#findAppBundlePath(Context)}.
*/
@NonNull
protected String getAppBundlePath() {
return getArguments().getString(ARG_APP_BUNDLE_PATH, FlutterMain.findAppBundlePath(getContextCompat()));
}
/**
* Returns the name of the Dart method that this {@code FlutterFragment} should execute to
* start a Flutter app.
*
* Defaults to "main".
*/
@NonNull
protected String getDartEntrypointFunctionName() {
return getArguments().getString(ARG_DART_ENTRYPOINT, "main");
}
/**
* Returns the desired {@link FlutterView.RenderMode} for the {@link FlutterView} displayed in
* this {@code FlutterFragment}.
*
* Defaults to {@link FlutterView.RenderMode#surface}.
*/
@NonNull
protected FlutterView.RenderMode getRenderMode() {
String renderModeName = getArguments().getString(ARG_FLUTTERVIEW_RENDER_MODE, FlutterView.RenderMode.surface.name());
return FlutterView.RenderMode.valueOf(renderModeName);
}
/**
* Returns the desired {@link FlutterView.TransparencyMode} for the {@link FlutterView} displayed in
* this {@code FlutterFragment}.
* <p>
* Defaults to {@link FlutterView.TransparencyMode#transparent}.
*/
@NonNull
protected FlutterView.TransparencyMode getTransparencyMode() {
String transparencyModeName = getArguments().getString(ARG_FLUTTERVIEW_TRANSPARENCY_MODE, FlutterView.TransparencyMode.transparent.name());
return FlutterView.TransparencyMode.valueOf(transparencyModeName);
}
@Override
public void onStart() {
super.onStart();
Log.d(TAG, "onStart()");
// We post() the code that attaches the FlutterEngine to our FlutterView because there is
// some kind of blocking logic on the native side when the surface is connected. That lag
// causes launching Activitys to wait a second or two before launching. By post()'ing this
// behavior we are able to move this blocking logic to after the Activity's launch.
// TODO(mattcarroll): figure out how to avoid blocking the MAIN thread when connecting a surface
new Handler().post(new Runnable() {
@Override
public void run() {
flutterView.attachToFlutterEngine(flutterEngine);
// TODO(mattcarroll): the following call should exist here, but the plugin system needs to be revamped.
// The existing attach() method does not know how to handle this kind of FlutterView.
//flutterEngine.getPlugins().attach(this, getActivity());
doInitialFlutterViewRun();
}
});
}
@Override
public void onResume() {
super.onResume();
Log.d(TAG, "onResume()");
flutterEngine.getLifecycleChannel().appIsResumed();
}
// TODO(mattcarroll): determine why this can't be in onResume(). Comment reason, or move if possible.
public void onPostResume() {
Log.d(TAG, "onPostResume()");
if (flutterEngine != 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.onPostResume();
} else {
Log.w(TAG, "onPostResume() invoked before FlutterFragment was attached to an Activity.");
}
}
@Override
public void onPause() {
super.onPause();
Log.d(TAG, "onPause()");
flutterEngine.getLifecycleChannel().appIsInactive();
}
@Override
public void onStop() {
super.onStop();
Log.d(TAG, "onStop()");
flutterEngine.getLifecycleChannel().appIsPaused();
flutterView.detachFromFlutterEngine();
}
@Override
public void onDestroyView() {
super.onDestroyView();
Log.d(TAG, "onDestroyView()");
flutterView.removeOnFirstFrameRenderedListener(onFirstFrameRenderedListener);
}
@Override
public void onDetach() {
super.onDetach();
Log.d(TAG, "onDetach()");
if (shouldAttachEngineToActivity()) {
// Notify plugins that they are no longer attached to an Activity.
// TODO(mattcarroll): differentiate between detaching for config changes and otherwise.
flutterEngine.getActivityControlSurface().detachFromActivity();
}
// Null out the platformPlugin to avoid a possible retain cycle between the plugin, this Fragment,
// and this Fragment's Activity.
platformPlugin = null;
// Destroy our FlutterEngine if we're not set to retain it.
if (!retainFlutterEngineAfterFragmentDestruction() && !isFlutterEngineFromActivity) {
flutterEngine.destroy();
flutterEngine = null;
}
}
/**
* Returns true if the {@link FlutterEngine} within this {@code FlutterFragment} should outlive
* the {@code FlutterFragment}, itself.
*
* Defaults to false. This method can be overridden in subclasses to retain the
* {@link FlutterEngine}.
*/
// TODO(mattcarroll): consider a dynamic determination of this preference based on whether the
// engine was created automatically, or if the engine was provided manually.
// Manually provided engines should probably not be destroyed.
protected boolean retainFlutterEngineAfterFragmentDestruction() {
return false;
}
protected boolean shouldAttachEngineToActivity() {
return getArguments().getBoolean(ARG_SHOULD_ATTACH_ENGINE_TO_ACTIVITY);
}
/**
* The hardware back button was pressed.
*
* See {@link android.app.Activity#onBackPressed()}
*/
public void onBackPressed() {
Log.d(TAG, "onBackPressed()");
if (flutterEngine != null) {
flutterEngine.getNavigationChannel().popRoute();
} else {
Log.w(TAG, "Invoked onBackPressed() before FlutterFragment was attached to an Activity.");
}
}
/**
* The result of a permission request has been received.
*
* See {@link android.app.Activity#onRequestPermissionsResult(int, String[], int[])}
*
* @param requestCode identifier passed with the initial permission request
* @param permissions permissions that were requested
* @param grantResults permission grants or denials
*/
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (flutterEngine != null) {
flutterEngine.getActivityControlSurface().onRequestPermissionsResult(requestCode, permissions, grantResults);
} else {
Log.w(TAG, "onRequestPermissionResult() invoked before FlutterFragment was attached to an Activity.");
}
}
/**
* A new Intent was received by the {@link android.app.Activity} that currently owns this
* {@link Fragment}.
*
* See {@link android.app.Activity#onNewIntent(Intent)}
*
* @param intent new Intent
*/
public void onNewIntent(@NonNull Intent intent) {
if (flutterEngine != null) {
flutterEngine.getActivityControlSurface().onNewIntent(intent);
} else {
Log.w(TAG, "onNewIntent() invoked before FlutterFragment was attached to an Activity.");
}
}
/**
* A result has been returned after an invocation of {@link Fragment#startActivityForResult(Intent, int)}.
*
* @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 (flutterEngine != null) {
flutterEngine.getActivityControlSurface().onActivityResult(requestCode, resultCode, data);
} else {
Log.w(TAG, "onActivityResult() invoked before FlutterFragment was attached to an Activity.");
}
}
/**
* 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.
*
* See {@link android.app.Activity#onUserLeaveHint()}
*/
public void onUserLeaveHint() {
if (flutterEngine != null) {
flutterEngine.getActivityControlSurface().onUserLeaveHint();
} else {
Log.w(TAG, "onUserLeaveHint() invoked before FlutterFragment was attached to an Activity.");
}
}
/**
* Callback invoked when memory is low.
*
* This implementation forwards a memory pressure warning to the running Flutter app.
*
* @param level level
*/
public void onTrimMemory(int level) {
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) {
flutterEngine.getSystemChannel().sendMemoryPressureWarning();
}
} else {
Log.w(TAG, "onTrimMemory() invoked before FlutterFragment was attached to an Activity.");
}
}
/**
* Callback invoked when memory is low.
*
* This implementation forwards a memory pressure warning to the running Flutter app.
*/
@Override
public void onLowMemory() {
super.onLowMemory();
flutterEngine.getSystemChannel().sendMemoryPressureWarning();
}
@NonNull
private Context getContextCompat() {
return Build.VERSION.SDK_INT >= 23
? getContext()
: getActivity();
}
/**
* Invoked after the {@link FlutterView} within this {@code FlutterFragment} renders its first
* frame.
* <p>
* The owning {@code Activity} is also sent this message, if it implements
* {@link OnFirstFrameRenderedListener}. This method is invoked before the {@code Activity}'s
* version.
*/
protected void onFirstFrameRendered() {}
/**
* Provides a {@link FlutterEngine} instance to be used by a {@code FlutterFragment}.
* <p>
* {@link FlutterEngine} instances require significant time to warm up. Therefore, a developer
* might choose to hold onto an existing {@link FlutterEngine} and connect it to various
* {@link FlutterActivity}s and/or {@code FlutterFragments}.
* <p>
* If the {@link FragmentActivity} that owns this {@code FlutterFragment} implements
* {@code FlutterEngineProvider}, that {@link FlutterActivity} will be given an opportunity
* to provide a {@link FlutterEngine} instead of the {@code FlutterFragment} creating a
* new one. The {@link FragmentActivity} can provide an existing, pre-warmed {@link FlutterEngine},
* if desired.
* <p>
* See {@link #setupFlutterEngine()} for more information.
*/
public interface FlutterEngineProvider {
/**
* Returns the {@link FlutterEngine} that should be used by a child {@code FlutterFragment}.
* <p>
* This method may return a new {@link FlutterEngine}, an existing, cached {@link FlutterEngine},
* or null to express that the {@code FlutterEngineProvider} would like the {@code FlutterFragment}
* to provide its own {@code FlutterEngine} instance.
*/
@Nullable
FlutterEngine getFlutterEngine(@NonNull Context context);
}
}