From fe15149d1a4a08e51692c3aec8e8c8b33ec6d116 Mon Sep 17 00:00:00 2001 From: Matt Carroll Date: Thu, 28 Feb 2019 16:51:17 -0800 Subject: [PATCH] Android Embedding PR 12: Add lifecycle methods to FlutterActivity. (#7974) --- .../engine/android/FlutterActivity.java | 40 +++ .../engine/android/FlutterFragment.java | 232 +++++++++++++++++- 2 files changed, 264 insertions(+), 8 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/android/FlutterActivity.java b/shell/platform/android/io/flutter/embedding/engine/android/FlutterActivity.java index 182a097f9c..cdaab64827 100644 --- a/shell/platform/android/io/flutter/embedding/engine/android/FlutterActivity.java +++ b/shell/platform/android/io/flutter/embedding/engine/android/FlutterActivity.java @@ -18,6 +18,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; +import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterShellArgs; import io.flutter.view.FlutterMain; @@ -132,6 +133,45 @@ public class FlutterActivity extends FragmentActivity { ); } + @Override + public void onPostResume() { + super.onPostResume(); + flutterFragment.onPostResume(); + } + + @Override + protected void onNewIntent(Intent intent) { + // Forward Intents to our FlutterFragment in case it cares. + flutterFragment.onNewIntent(intent); + } + + @Override + public void onBackPressed() { + flutterFragment.onBackPressed(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + flutterFragment.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + + @Override + public void onUserLeaveHint() { + flutterFragment.onUserLeaveHint(); + } + + @Override + public void onTrimMemory(int level) { + super.onTrimMemory(level); + flutterFragment.onTrimMemory(level); + } + + @SuppressWarnings("unused") + @Nullable + protected FlutterEngine getFlutterEngine() { + return flutterFragment.getFlutterEngine(); + } + /** * The path to the bundle that contains this Flutter app's resources, e.g., Dart code snapshots. *

diff --git a/shell/platform/android/io/flutter/embedding/engine/android/FlutterFragment.java b/shell/platform/android/io/flutter/embedding/engine/android/FlutterFragment.java index d4a535bc05..e4042bbaaa 100644 --- a/shell/platform/android/io/flutter/embedding/engine/android/FlutterFragment.java +++ b/shell/platform/android/io/flutter/embedding/engine/android/FlutterFragment.java @@ -6,16 +6,23 @@ package io.flutter.embedding.engine.android; import android.content.Context; import android.content.Intent; +import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; 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.view.FlutterMain; +import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW; + /** * {@code Fragment} which displays a Flutter UI that takes up all available {@code Fragment} space. *

@@ -25,17 +32,17 @@ import io.flutter.view.FlutterMain; * Using a {@code FlutterFragment} requires forwarding a number of calls from an {@code Activity} to * ensure that the internal Flutter app behaves as expected: *

    - *
  1. {@link Activity#onPostResume()}
  2. - *
  3. {@link Activity#onBackPressed()}
  4. - *
  5. {@link Activity#onRequestPermissionsResult(int, String[], int[])} ()}
  6. - *
  7. {@link Activity#onNewIntent(Intent)} ()}
  8. - *
  9. {@link Activity#onUserLeaveHint()}
  10. - *
  11. {@link Activity#onTrimMemory(int)}
  12. + *
  13. {@link android.app.Activity#onPostResume()}
  14. + *
  15. {@link android.app.Activity#onBackPressed()}
  16. + *
  17. {@link android.app.Activity#onRequestPermissionsResult(int, String[], int[])} ()}
  18. + *
  19. {@link android.app.Activity#onNewIntent(Intent)} ()}
  20. + *
  21. {@link android.app.Activity#onUserLeaveHint()}
  22. + *
  23. {@link android.app.Activity#onTrimMemory(int)}
  24. *
* 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 Activity#startActivityForResult(Intent, int)}. If the {@code Activity} version of the - * method is invoked then this {@code Fragment} will never receive its + * {@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. *

* If convenient, consider using a {@link FlutterActivity} instead of a {@code FlutterFragment} to @@ -151,6 +158,8 @@ public class FlutterFragment extends Fragment { @Nullable private FlutterEngine flutterEngine; + @Nullable + private FlutterView flutterView; public FlutterFragment() { // Ensure that we at least have an empty Bundle of arguments so that we don't @@ -158,6 +167,16 @@ public class FlutterFragment extends Fragment { 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); @@ -207,4 +226,201 @@ public class FlutterFragment extends Fragment { protected void onFlutterEngineCreated(@NonNull FlutterEngine flutterEngine) { // no-op } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + flutterView = new FlutterView(getContext()); + 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.getPluginRegistry().attach(this, getActivity()); + + doInitialFlutterViewRun(); + + 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"); + } + + // TODO(mattcarroll): determine why this can't be in onResume(). Comment reason, or move if possible. + public void onPostResume() { + Log.d(TAG, "onPostResume()"); + flutterEngine.getLifecycleChannel().appIsResumed(); + } + + /** + * 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.getPluginRegistry().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.getPluginRegistry().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.getPluginRegistry().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.getPluginRegistry().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(); + } } -- GitLab