Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit fe15149

Browse files
Android Embedding PR 12: Add lifecycle methods to FlutterActivity. (#7974)
1 parent 6145e90 commit fe15149

File tree

2 files changed

+264
-8
lines changed

2 files changed

+264
-8
lines changed

shell/platform/android/io/flutter/embedding/engine/android/FlutterActivity.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import android.view.ViewGroup;
1919
import android.widget.FrameLayout;
2020

21+
import io.flutter.embedding.engine.FlutterEngine;
2122
import io.flutter.embedding.engine.FlutterShellArgs;
2223
import io.flutter.view.FlutterMain;
2324

@@ -132,6 +133,45 @@ protected FlutterFragment createFlutterFragment() {
132133
);
133134
}
134135

136+
@Override
137+
public void onPostResume() {
138+
super.onPostResume();
139+
flutterFragment.onPostResume();
140+
}
141+
142+
@Override
143+
protected void onNewIntent(Intent intent) {
144+
// Forward Intents to our FlutterFragment in case it cares.
145+
flutterFragment.onNewIntent(intent);
146+
}
147+
148+
@Override
149+
public void onBackPressed() {
150+
flutterFragment.onBackPressed();
151+
}
152+
153+
@Override
154+
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
155+
flutterFragment.onRequestPermissionsResult(requestCode, permissions, grantResults);
156+
}
157+
158+
@Override
159+
public void onUserLeaveHint() {
160+
flutterFragment.onUserLeaveHint();
161+
}
162+
163+
@Override
164+
public void onTrimMemory(int level) {
165+
super.onTrimMemory(level);
166+
flutterFragment.onTrimMemory(level);
167+
}
168+
169+
@SuppressWarnings("unused")
170+
@Nullable
171+
protected FlutterEngine getFlutterEngine() {
172+
return flutterFragment.getFlutterEngine();
173+
}
174+
135175
/**
136176
* The path to the bundle that contains this Flutter app's resources, e.g., Dart code snapshots.
137177
* <p>

shell/platform/android/io/flutter/embedding/engine/android/FlutterFragment.java

Lines changed: 224 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,23 @@
66

77
import android.content.Context;
88
import android.content.Intent;
9+
import android.os.Build;
910
import android.os.Bundle;
1011
import android.support.annotation.NonNull;
1112
import android.support.annotation.Nullable;
1213
import android.support.v4.app.Fragment;
1314
import android.util.Log;
15+
import android.view.LayoutInflater;
16+
import android.view.View;
17+
import android.view.ViewGroup;
1418

1519
import io.flutter.embedding.engine.FlutterEngine;
1620
import io.flutter.embedding.engine.FlutterShellArgs;
21+
import io.flutter.embedding.engine.dart.DartExecutor;
1722
import io.flutter.view.FlutterMain;
1823

24+
import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
25+
1926
/**
2027
* {@code Fragment} which displays a Flutter UI that takes up all available {@code Fragment} space.
2128
* <p>
@@ -25,17 +32,17 @@
2532
* Using a {@code FlutterFragment} requires forwarding a number of calls from an {@code Activity} to
2633
* ensure that the internal Flutter app behaves as expected:
2734
* <ol>
28-
* <li>{@link Activity#onPostResume()}</li>
29-
* <li>{@link Activity#onBackPressed()}</li>
30-
* <li>{@link Activity#onRequestPermissionsResult(int, String[], int[])} ()}</li>
31-
* <li>{@link Activity#onNewIntent(Intent)} ()}</li>
32-
* <li>{@link Activity#onUserLeaveHint()}</li>
33-
* <li>{@link Activity#onTrimMemory(int)}</li>
35+
* <li>{@link android.app.Activity#onPostResume()}</li>
36+
* <li>{@link android.app.Activity#onBackPressed()}</li>
37+
* <li>{@link android.app.Activity#onRequestPermissionsResult(int, String[], int[])} ()}</li>
38+
* <li>{@link android.app.Activity#onNewIntent(Intent)} ()}</li>
39+
* <li>{@link android.app.Activity#onUserLeaveHint()}</li>
40+
* <li>{@link android.app.Activity#onTrimMemory(int)}</li>
3441
* </ol>
3542
* Additionally, when starting an {@code Activity} for a result from this {@code Fragment}, be sure
3643
* to invoke {@link Fragment#startActivityForResult(Intent, int)} rather than
37-
* {@link Activity#startActivityForResult(Intent, int)}. If the {@code Activity} version of the
38-
* method is invoked then this {@code Fragment} will never receive its
44+
* {@link android.app.Activity#startActivityForResult(Intent, int)}. If the {@code Activity} version
45+
* of the method is invoked then this {@code Fragment} will never receive its
3946
* {@link Fragment#onActivityResult(int, int, Intent)} callback.
4047
* <p>
4148
* If convenient, consider using a {@link FlutterActivity} instead of a {@code FlutterFragment} to
@@ -151,13 +158,25 @@ protected static Bundle createArgsBundle(@Nullable String dartEntrypoint,
151158

152159
@Nullable
153160
private FlutterEngine flutterEngine;
161+
@Nullable
162+
private FlutterView flutterView;
154163

155164
public FlutterFragment() {
156165
// Ensure that we at least have an empty Bundle of arguments so that we don't
157166
// need to continually check for null arguments before grabbing one.
158167
setArguments(new Bundle());
159168
}
160169

170+
/**
171+
* The {@link FlutterEngine} that backs the Flutter content presented by this {@code Fragment}.
172+
*
173+
* @return the {@link FlutterEngine} held by this {@code Fragment}
174+
*/
175+
@Nullable
176+
public FlutterEngine getFlutterEngine() {
177+
return flutterEngine;
178+
}
179+
161180
@Override
162181
public void onAttach(Context context) {
163182
super.onAttach(context);
@@ -207,4 +226,201 @@ protected FlutterEngine onCreateFlutterEngine(@NonNull Context context) {
207226
protected void onFlutterEngineCreated(@NonNull FlutterEngine flutterEngine) {
208227
// no-op
209228
}
229+
230+
@Nullable
231+
@Override
232+
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
233+
flutterView = new FlutterView(getContext());
234+
flutterView.attachToFlutterEngine(flutterEngine);
235+
236+
// TODO(mattcarroll): the following call should exist here, but the plugin system needs to be revamped.
237+
// The existing attach() method does not know how to handle this kind of FlutterView.
238+
//flutterEngine.getPluginRegistry().attach(this, getActivity());
239+
240+
doInitialFlutterViewRun();
241+
242+
return flutterView;
243+
}
244+
245+
/**
246+
* Starts running Dart within the FlutterView for the first time.
247+
*
248+
* Reloading/restarting Dart within a given FlutterView is not supported. If this method is
249+
* invoked while Dart is already executing then it does nothing.
250+
*
251+
* {@code flutterEngine} must be non-null when invoking this method.
252+
*/
253+
private void doInitialFlutterViewRun() {
254+
if (flutterEngine.getDartExecutor().isExecutingDart()) {
255+
// No warning is logged because this situation will happen on every config
256+
// change if the developer does not choose to retain the Fragment instance.
257+
// So this is expected behavior in many cases.
258+
return;
259+
}
260+
261+
// The engine needs to receive the Flutter app's initial route before executing any
262+
// Dart code to ensure that the initial route arrives in time to be applied.
263+
if (getInitialRoute() != null) {
264+
flutterEngine.getNavigationChannel().setInitialRoute(getInitialRoute());
265+
}
266+
267+
// Configure the Dart entrypoint and execute it.
268+
DartExecutor.DartEntrypoint entrypoint = new DartExecutor.DartEntrypoint(
269+
getResources().getAssets(),
270+
getAppBundlePath(),
271+
getDartEntrypointFunctionName()
272+
);
273+
flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint);
274+
}
275+
276+
/**
277+
* Returns the initial route that should be rendered within Flutter, once the Flutter app starts.
278+
*
279+
* Defaults to {@code null}, which signifies a route of "/" in Flutter.
280+
*/
281+
@Nullable
282+
protected String getInitialRoute() {
283+
return getArguments().getString(ARG_INITIAL_ROUTE);
284+
}
285+
286+
/**
287+
* Returns the file path to the desired Flutter app's bundle of code.
288+
*
289+
* Defaults to {@link FlutterMain#findAppBundlePath(Context)}.
290+
*/
291+
@NonNull
292+
protected String getAppBundlePath() {
293+
return getArguments().getString(ARG_APP_BUNDLE_PATH, FlutterMain.findAppBundlePath(getContextCompat()));
294+
}
295+
296+
/**
297+
* Returns the name of the Dart method that this {@code FlutterFragment} should execute to
298+
* start a Flutter app.
299+
*
300+
* Defaults to "main".
301+
*/
302+
@NonNull
303+
protected String getDartEntrypointFunctionName() {
304+
return getArguments().getString(ARG_DART_ENTRYPOINT, "main");
305+
}
306+
307+
// TODO(mattcarroll): determine why this can't be in onResume(). Comment reason, or move if possible.
308+
public void onPostResume() {
309+
Log.d(TAG, "onPostResume()");
310+
flutterEngine.getLifecycleChannel().appIsResumed();
311+
}
312+
313+
/**
314+
* The hardware back button was pressed.
315+
*
316+
* See {@link android.app.Activity#onBackPressed()}
317+
*/
318+
public void onBackPressed() {
319+
Log.d(TAG, "onBackPressed()");
320+
if (flutterEngine != null) {
321+
flutterEngine.getNavigationChannel().popRoute();
322+
} else {
323+
Log.w(TAG, "Invoked onBackPressed() before FlutterFragment was attached to an Activity.");
324+
}
325+
}
326+
327+
/**
328+
* The result of a permission request has been received.
329+
*
330+
* See {@link android.app.Activity#onRequestPermissionsResult(int, String[], int[])}
331+
*
332+
* @param requestCode identifier passed with the initial permission request
333+
* @param permissions permissions that were requested
334+
* @param grantResults permission grants or denials
335+
*/
336+
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
337+
if (flutterEngine != null) {
338+
flutterEngine.getPluginRegistry().onRequestPermissionsResult(requestCode, permissions, grantResults);
339+
} else {
340+
Log.w(TAG, "onRequestPermissionResult() invoked before FlutterFragment was attached to an Activity.");
341+
}
342+
}
343+
344+
/**
345+
* A new Intent was received by the {@link android.app.Activity} that currently owns this
346+
* {@link Fragment}.
347+
*
348+
* See {@link android.app.Activity#onNewIntent(Intent)}
349+
*
350+
* @param intent new Intent
351+
*/
352+
public void onNewIntent(@NonNull Intent intent) {
353+
if (flutterEngine != null) {
354+
flutterEngine.getPluginRegistry().onNewIntent(intent);
355+
} else {
356+
Log.w(TAG, "onNewIntent() invoked before FlutterFragment was attached to an Activity.");
357+
}
358+
}
359+
360+
/**
361+
* A result has been returned after an invocation of {@link Fragment#startActivityForResult(Intent, int)}.
362+
*
363+
* @param requestCode request code sent with {@link Fragment#startActivityForResult(Intent, int)}
364+
* @param resultCode code representing the result of the {@code Activity} that was launched
365+
* @param data any corresponding return data, held within an {@code Intent}
366+
*/
367+
@Override
368+
public void onActivityResult(int requestCode, int resultCode, Intent data) {
369+
if (flutterEngine != null) {
370+
flutterEngine.getPluginRegistry().onActivityResult(requestCode, resultCode, data);
371+
} else {
372+
Log.w(TAG, "onActivityResult() invoked before FlutterFragment was attached to an Activity.");
373+
}
374+
}
375+
376+
/**
377+
* The {@link android.app.Activity} that owns this {@link Fragment} is about to go to the background
378+
* as the result of a user's choice/action, i.e., not as the result of an OS decision.
379+
*
380+
* See {@link android.app.Activity#onUserLeaveHint()}
381+
*/
382+
public void onUserLeaveHint() {
383+
if (flutterEngine != null) {
384+
flutterEngine.getPluginRegistry().onUserLeaveHint();
385+
} else {
386+
Log.w(TAG, "onUserLeaveHint() invoked before FlutterFragment was attached to an Activity.");
387+
}
388+
}
389+
390+
/**
391+
* Callback invoked when memory is low.
392+
*
393+
* This implementation forwards a memory pressure warning to the running Flutter app.
394+
*
395+
* @param level level
396+
*/
397+
public void onTrimMemory(int level) {
398+
if (flutterEngine != null) {
399+
// Use a trim level delivered while the application is running so the
400+
// framework has a chance to react to the notification.
401+
if (level == TRIM_MEMORY_RUNNING_LOW) {
402+
flutterEngine.getSystemChannel().sendMemoryPressureWarning();
403+
}
404+
} else {
405+
Log.w(TAG, "onTrimMemory() invoked before FlutterFragment was attached to an Activity.");
406+
}
407+
}
408+
409+
/**
410+
* Callback invoked when memory is low.
411+
*
412+
* This implementation forwards a memory pressure warning to the running Flutter app.
413+
*/
414+
@Override
415+
public void onLowMemory() {
416+
super.onLowMemory();
417+
flutterEngine.getSystemChannel().sendMemoryPressureWarning();
418+
}
419+
420+
@NonNull
421+
private Context getContextCompat() {
422+
return Build.VERSION.SDK_INT >= 23
423+
? getContext()
424+
: getActivity();
425+
}
210426
}

0 commit comments

Comments
 (0)