• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package io.flutter.embedding.android;
6 
7 import android.app.Activity;
8 import android.arch.lifecycle.Lifecycle;
9 import android.content.Context;
10 import android.content.Intent;
11 import android.os.Build;
12 import android.os.Bundle;
13 import android.support.annotation.NonNull;
14 import android.support.annotation.Nullable;
15 import android.support.v4.app.Fragment;
16 import android.support.v4.app.FragmentActivity;
17 import android.view.LayoutInflater;
18 import android.view.View;
19 import android.view.ViewGroup;
20 
21 import io.flutter.Log;
22 import io.flutter.embedding.engine.FlutterEngine;
23 import io.flutter.embedding.engine.FlutterShellArgs;
24 import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
25 import io.flutter.plugin.platform.PlatformPlugin;
26 import io.flutter.view.FlutterMain;
27 
28 /**
29  * {@code Fragment} which displays a Flutter UI that takes up all available {@code Fragment} space.
30  * <p>
31  * Using a {@code FlutterFragment} requires forwarding a number of calls from an {@code Activity} to
32  * ensure that the internal Flutter app behaves as expected:
33  * <ol>
34  *   <li>{@link #onPostResume()}</li>
35  *   <li>{@link #onBackPressed()}</li>
36  *   <li>{@link #onRequestPermissionsResult(int, String[], int[])} ()}</li>
37  *   <li>{@link #onNewIntent(Intent)} ()}</li>
38  *   <li>{@link #onUserLeaveHint()}</li>
39  *   <li>{@link #onTrimMemory(int)}</li>
40  * </ol>
41  * Additionally, when starting an {@code Activity} for a result from this {@code Fragment}, be sure
42  * to invoke {@link Fragment#startActivityForResult(Intent, int)} rather than
43  * {@link android.app.Activity#startActivityForResult(Intent, int)}. If the {@code Activity} version
44  * of the method is invoked then this {@code Fragment} will never receive its
45  * {@link Fragment#onActivityResult(int, int, Intent)} callback.
46  * <p>
47  * If convenient, consider using a {@link FlutterActivity} instead of a {@code FlutterFragment} to
48  * avoid the work of forwarding calls.
49  * <p>
50  * {@code FlutterFragment} supports the use of an existing, cached {@link FlutterEngine}. To use a
51  * cached {@link FlutterEngine}, ensure that the {@link FlutterEngine} is stored in
52  * {@link FlutterEngineCache} and then use {@link #withCachedEngine(String)} to build a
53  * {@code FlutterFragment} with the cached {@link FlutterEngine}'s ID.
54  * <p>
55  * It is generally recommended to use a cached {@link FlutterEngine} to avoid a momentary delay
56  * when initializing a new {@link FlutterEngine}. The two exceptions to using a cached
57  * {@link FlutterEngine} are:
58  * <p>
59  * <ul>
60  *   <li>When {@code FlutterFragment} is in the first {@code Activity} displayed by the app, because
61  *   pre-warming a {@link FlutterEngine} would have no impact in this situation.</li>
62  *   <li>When you are unsure when/if you will need to display a Flutter experience.</li>
63  * </ul>
64  * <p>
65  * The following illustrates how to pre-warm and cache a {@link FlutterEngine}:
66  * <p>
67  * {@code
68  *   // Create and pre-warm a FlutterEngine.
69  *   FlutterEngine flutterEngine = new FlutterEngine(context);
70  *   flutterEngine
71  *     .getDartExecutor()
72  *     .executeDartEntrypoint(DartEntrypoint.createDefault());
73  *
74  *   // Cache the pre-warmed FlutterEngine in the FlutterEngineCache.
75  *   FlutterEngineCache.getInstance().put("my_engine", flutterEngine);
76  * }
77  * <p>
78  * If Flutter is needed in a location that can only use a {@code View}, consider using a
79  * {@link FlutterView}. Using a {@link FlutterView} requires forwarding some calls from an
80  * {@code Activity}, as well as forwarding lifecycle calls from an {@code Activity} or a
81  * {@code Fragment}.
82  */
83 public class FlutterFragment extends Fragment implements FlutterActivityAndFragmentDelegate.Host {
84   private static final String TAG = "FlutterFragment";
85 
86   /**
87    * The Dart entrypoint method name that is executed upon initialization.
88    */
89   protected static final String ARG_DART_ENTRYPOINT = "dart_entrypoint";
90   /**
91    * Initial Flutter route that is rendered in a Navigator widget.
92    */
93   protected static final String ARG_INITIAL_ROUTE = "initial_route";
94   /**
95    * Path to Flutter's Dart code.
96    */
97   protected static final String ARG_APP_BUNDLE_PATH = "app_bundle_path";
98   /**
99    * Flutter shell arguments.
100    */
101   protected static final String ARG_FLUTTER_INITIALIZATION_ARGS = "initialization_args";
102   /**
103    * {@link FlutterView.RenderMode} to be used for the {@link FlutterView} in this
104    * {@code FlutterFragment}
105    */
106   protected static final String ARG_FLUTTERVIEW_RENDER_MODE = "flutterview_render_mode";
107   /**
108    * {@link FlutterView.TransparencyMode} to be used for the {@link FlutterView} in this
109    * {@code FlutterFragment}
110    */
111   protected static final String ARG_FLUTTERVIEW_TRANSPARENCY_MODE = "flutterview_transparency_mode";
112   /**
113    * See {@link #shouldAttachEngineToActivity()}.
114    */
115   protected static final String ARG_SHOULD_ATTACH_ENGINE_TO_ACTIVITY = "should_attach_engine_to_activity";
116   /**
117    * The ID of a {@link FlutterEngine} cached in {@link FlutterEngineCache} that will be used within
118    * the created {@code FlutterFragment}.
119    */
120   protected static final String ARG_CACHED_ENGINE_ID = "cached_engine_id";
121   /**
122    * True if the {@link FlutterEngine} in the created {@code FlutterFragment} should be destroyed
123    * when the {@code FlutterFragment} is destroyed, false if the {@link FlutterEngine} should
124    * outlive the {@code FlutterFragment}.
125    */
126   protected static final String ARG_DESTROY_ENGINE_WITH_FRAGMENT = "destroy_engine_with_fragment";
127 
128   /**
129    * Creates a {@code FlutterFragment} with a default configuration.
130    * <p>
131    * {@code FlutterFragment}'s default configuration creates a new {@link FlutterEngine} within
132    * the {@code FlutterFragment} and uses the following settings:
133    * <ul>
134    *   <li>Dart entrypoint: "main"</li>
135    *   <li>Initial route: "/"</li>
136    *   <li>Render mode: surface</li>
137    *   <li>Transparency mode: transparent</li>
138    * </ul>
139    * <p>
140    * To use a new {@link FlutterEngine} with different settings, use {@link #withNewEngine()}.
141    * <p>
142    * To use a cached {@link FlutterEngine} instead of creating a new one, use
143    * {@link #withCachedEngine(String)}.
144    */
145   @NonNull
createDefault()146   public static FlutterFragment createDefault() {
147     return new NewEngineFragmentBuilder().build();
148   }
149 
150   /**
151    * Returns a {@link NewEngineFragmentBuilder} to create a {@code FlutterFragment} with a new
152    * {@link FlutterEngine} and a desired engine configuration.
153    */
154   @NonNull
withNewEngine()155   public static NewEngineFragmentBuilder withNewEngine() {
156     return new NewEngineFragmentBuilder();
157   }
158 
159   /**
160    * Builder that creates a new {@code FlutterFragment} with {@code arguments} that correspond
161    * to the values set on this {@code NewEngineFragmentBuilder}.
162    * <p>
163    * To create a {@code FlutterFragment} with default {@code arguments}, invoke
164    * {@link #createDefault()}.
165    * <p>
166    * Subclasses of {@code FlutterFragment} that do not introduce any new arguments can use this
167    * {@code NewEngineFragmentBuilder} to construct instances of the subclass without subclassing
168    * this {@code NewEngineFragmentBuilder}.
169    * {@code
170    *   MyFlutterFragment f = new FlutterFragment.NewEngineFragmentBuilder(MyFlutterFragment.class)
171    *     .someProperty(...)
172    *     .someOtherProperty(...)
173    *     .build<MyFlutterFragment>();
174    * }
175    * <p>
176    * Subclasses of {@code FlutterFragment} that introduce new arguments should subclass this
177    * {@code NewEngineFragmentBuilder} to add the new properties:
178    * <ol>
179    *   <li>Ensure the {@code FlutterFragment} subclass has a no-arg constructor.</li>
180    *   <li>Subclass this {@code NewEngineFragmentBuilder}.</li>
181    *   <li>Override the new {@code NewEngineFragmentBuilder}'s no-arg constructor and invoke the
182    *   super constructor to set the {@code FlutterFragment} subclass: {@code
183    *     public MyBuilder() {
184    *       super(MyFlutterFragment.class);
185    *     }
186    *   }</li>
187    *   <li>Add appropriate property methods for the new properties.</li>
188    *   <li>Override {@link NewEngineFragmentBuilder#createArgs()}, call through to the super method,
189    *   then add the new properties as arguments in the {@link Bundle}.</li>
190    * </ol>
191    * Once a {@code NewEngineFragmentBuilder} subclass is defined, the {@code FlutterFragment}
192    * subclass can be instantiated as follows.
193    * {@code
194    *   MyFlutterFragment f = new MyBuilder()
195    *     .someExistingProperty(...)
196    *     .someNewProperty(...)
197    *     .build<MyFlutterFragment>();
198    * }
199    */
200   public static class NewEngineFragmentBuilder {
201     private final Class<? extends FlutterFragment> fragmentClass;
202     private String dartEntrypoint = "main";
203     private String initialRoute = "/";
204     private String appBundlePath = null;
205     private FlutterShellArgs shellArgs = null;
206     private FlutterView.RenderMode renderMode = FlutterView.RenderMode.surface;
207     private FlutterView.TransparencyMode transparencyMode = FlutterView.TransparencyMode.transparent;
208     private boolean shouldAttachEngineToActivity = true;
209 
210     /**
211      * Constructs a {@code NewEngineFragmentBuilder} that is configured to construct an instance of
212      * {@code FlutterFragment}.
213      */
NewEngineFragmentBuilder()214     public NewEngineFragmentBuilder() {
215       fragmentClass = FlutterFragment.class;
216     }
217 
218     /**
219      * Constructs a {@code NewEngineFragmentBuilder} that is configured to construct an instance of
220      * {@code subclass}, which extends {@code FlutterFragment}.
221      */
NewEngineFragmentBuilder(@onNull Class<? extends FlutterFragment> subclass)222     public NewEngineFragmentBuilder(@NonNull Class<? extends FlutterFragment> subclass) {
223       fragmentClass = subclass;
224     }
225 
226     /**
227      * The name of the initial Dart method to invoke, defaults to "main".
228      */
229     @NonNull
dartEntrypoint(@onNull String dartEntrypoint)230     public NewEngineFragmentBuilder dartEntrypoint(@NonNull String dartEntrypoint) {
231       this.dartEntrypoint = dartEntrypoint;
232       return this;
233     }
234 
235     /**
236      * The initial route that a Flutter app will render in this {@link FlutterFragment},
237      * defaults to "/".
238      */
239     @NonNull
initialRoute(@onNull String initialRoute)240     public NewEngineFragmentBuilder initialRoute(@NonNull String initialRoute) {
241       this.initialRoute = initialRoute;
242       return this;
243     }
244 
245     /**
246      * The path to the app bundle which contains the Dart app to execute, defaults
247      * to {@link FlutterMain#findAppBundlePath()}
248      */
249     @NonNull
appBundlePath(@onNull String appBundlePath)250     public NewEngineFragmentBuilder appBundlePath(@NonNull String appBundlePath) {
251       this.appBundlePath = appBundlePath;
252       return this;
253     }
254 
255     /**
256      * Any special configuration arguments for the Flutter engine
257      */
258     @NonNull
flutterShellArgs(@onNull FlutterShellArgs shellArgs)259     public NewEngineFragmentBuilder flutterShellArgs(@NonNull FlutterShellArgs shellArgs) {
260       this.shellArgs = shellArgs;
261       return this;
262     }
263 
264     /**
265      * Render Flutter either as a {@link FlutterView.RenderMode#surface} or a
266      * {@link FlutterView.RenderMode#texture}. You should use {@code surface} unless
267      * you have a specific reason to use {@code texture}. {@code texture} comes with
268      * a significant performance impact, but {@code texture} can be displayed
269      * beneath other Android {@code View}s and animated, whereas {@code surface}
270      * cannot.
271      */
272     @NonNull
renderMode(@onNull FlutterView.RenderMode renderMode)273     public NewEngineFragmentBuilder renderMode(@NonNull FlutterView.RenderMode renderMode) {
274       this.renderMode = renderMode;
275       return this;
276     }
277 
278     /**
279      * Support a {@link FlutterView.TransparencyMode#transparent} background within {@link FlutterView},
280      * or force an {@link FlutterView.TransparencyMode#opaque} background.
281      * <p>
282      * See {@link FlutterView.TransparencyMode} for implications of this selection.
283      */
284     @NonNull
transparencyMode(@onNull FlutterView.TransparencyMode transparencyMode)285     public NewEngineFragmentBuilder transparencyMode(@NonNull FlutterView.TransparencyMode transparencyMode) {
286       this.transparencyMode = transparencyMode;
287       return this;
288     }
289 
290     /**
291      * Whether or not this {@code FlutterFragment} should automatically attach its
292      * {@code Activity} as a control surface for its {@link FlutterEngine}.
293      * <p>
294      * Control surfaces are used to provide Android resources and lifecycle events to
295      * plugins that are attached to the {@link FlutterEngine}. If {@code shouldAttachEngineToActivity}
296      * is true then this {@code FlutterFragment} will connect its {@link FlutterEngine} to the
297      * surrounding {@code Activity}, along with any plugins that are registered with that
298      * {@link FlutterEngine}. This allows plugins to access the {@code Activity}, as well as
299      * receive {@code Activity}-specific calls, e.g., {@link android.app.Activity#onNewIntent(Intent)}.
300      * If {@code shouldAttachEngineToActivity} is false, then this {@code FlutterFragment} will not
301      * automatically manage the connection between its {@link FlutterEngine} and the surrounding
302      * {@code Activity}. The {@code Activity} will need to be manually connected to this
303      * {@code FlutterFragment}'s {@link FlutterEngine} by the app developer. See
304      * {@link FlutterEngine#getActivityControlSurface()}.
305      * <p>
306      * One reason that a developer might choose to manually manage the relationship between the
307      * {@code Activity} and {@link FlutterEngine} is if the developer wants to move the
308      * {@link FlutterEngine} somewhere else. For example, a developer might want the
309      * {@link FlutterEngine} to outlive the surrounding {@code Activity} so that it can be used
310      * later in a different {@code Activity}. To accomplish this, the {@link FlutterEngine} will
311      * need to be disconnected from the surrounding {@code Activity} at an unusual time, preventing
312      * this {@code FlutterFragment} from correctly managing the relationship between the
313      * {@link FlutterEngine} and the surrounding {@code Activity}.
314      * <p>
315      * Another reason that a developer might choose to manually manage the relationship between the
316      * {@code Activity} and {@link FlutterEngine} is if the developer wants to prevent, or explicitly
317      * control when the {@link FlutterEngine}'s plugins have access to the surrounding {@code Activity}.
318      * For example, imagine that this {@code FlutterFragment} only takes up part of the screen and
319      * the app developer wants to ensure that none of the Flutter plugins are able to manipulate
320      * the surrounding {@code Activity}. In this case, the developer would not want the
321      * {@link FlutterEngine} to have access to the {@code Activity}, which can be accomplished by
322      * setting {@code shouldAttachEngineToActivity} to {@code false}.
323      */
324     @NonNull
shouldAttachEngineToActivity(boolean shouldAttachEngineToActivity)325     public NewEngineFragmentBuilder shouldAttachEngineToActivity(boolean shouldAttachEngineToActivity) {
326       this.shouldAttachEngineToActivity = shouldAttachEngineToActivity;
327       return this;
328     }
329 
330     /**
331      * Creates a {@link Bundle} of arguments that are assigned to the new {@code FlutterFragment}.
332      * <p>
333      * Subclasses should override this method to add new properties to the {@link Bundle}. Subclasses
334      * must call through to the super method to collect all existing property values.
335      */
336     @NonNull
createArgs()337     protected Bundle createArgs() {
338       Bundle args = new Bundle();
339       args.putString(ARG_INITIAL_ROUTE, initialRoute);
340       args.putString(ARG_APP_BUNDLE_PATH, appBundlePath);
341       args.putString(ARG_DART_ENTRYPOINT, dartEntrypoint);
342       // TODO(mattcarroll): determine if we should have an explicit FlutterTestFragment instead of conflating.
343       if (null != shellArgs) {
344         args.putStringArray(ARG_FLUTTER_INITIALIZATION_ARGS, shellArgs.toArray());
345       }
346       args.putString(ARG_FLUTTERVIEW_RENDER_MODE, renderMode != null ? renderMode.name() : FlutterView.RenderMode.surface.name());
347       args.putString(ARG_FLUTTERVIEW_TRANSPARENCY_MODE, transparencyMode != null ? transparencyMode.name() : FlutterView.TransparencyMode.transparent.name());
348       args.putBoolean(ARG_SHOULD_ATTACH_ENGINE_TO_ACTIVITY, shouldAttachEngineToActivity);
349       args.putBoolean(ARG_DESTROY_ENGINE_WITH_FRAGMENT, true);
350       return args;
351     }
352 
353     /**
354      * Constructs a new {@code FlutterFragment} (or a subclass) that is configured based on
355      * properties set on this {@code Builder}.
356      */
357     @NonNull
build()358     public <T extends FlutterFragment> T build() {
359       try {
360         @SuppressWarnings("unchecked")
361         T frag = (T) fragmentClass.getDeclaredConstructor().newInstance();
362         if (frag == null) {
363           throw new RuntimeException("The FlutterFragment subclass sent in the constructor ("
364               + fragmentClass.getCanonicalName() + ") does not match the expected return type.");
365         }
366 
367         Bundle args = createArgs();
368         frag.setArguments(args);
369 
370         return frag;
371       } catch (Exception e) {
372         throw new RuntimeException("Could not instantiate FlutterFragment subclass (" + fragmentClass.getName() + ")", e);
373       }
374     }
375   }
376 
377   /**
378    * Returns a {@link CachedEngineFragmentBuilder} to create a {@code FlutterFragment} with a cached
379    * {@link FlutterEngine} in {@link FlutterEngineCache}.
380    * <p>
381    * An {@code IllegalStateException} will be thrown during the lifecycle of the
382    * {@code FlutterFragment} if a cached {@link FlutterEngine} is requested but does not exist in
383    * the cache.
384    * <p>
385    * To create a {@code FlutterFragment} that uses a new {@link FlutterEngine}, use
386    * {@link #createDefault()} or {@link #withNewEngine()}.
387    */
388   @NonNull
withCachedEngine(@onNull String engineId)389   public static CachedEngineFragmentBuilder withCachedEngine(@NonNull String engineId) {
390     return new CachedEngineFragmentBuilder(engineId);
391   }
392 
393   /**
394    * Builder that creates a new {@code FlutterFragment} that uses a cached {@link FlutterEngine}
395    * with {@code arguments} that correspond to the values set on this {@code Builder}.
396    * <p>
397    * Subclasses of {@code FlutterFragment} that do not introduce any new arguments can use this
398    * {@code Builder} to construct instances of the subclass without subclassing this {@code Builder}.
399    * {@code
400    *   MyFlutterFragment f = new FlutterFragment.CachedEngineFragmentBuilder(MyFlutterFragment.class)
401    *     .someProperty(...)
402    *     .someOtherProperty(...)
403    *     .build<MyFlutterFragment>();
404    * }
405    * <p>
406    * Subclasses of {@code FlutterFragment} that introduce new arguments should subclass this
407    * {@code CachedEngineFragmentBuilder} to add the new properties:
408    * <ol>
409    *   <li>Ensure the {@code FlutterFragment} subclass has a no-arg constructor.</li>
410    *   <li>Subclass this {@code CachedEngineFragmentBuilder}.</li>
411    *   <li>Override the new {@code CachedEngineFragmentBuilder}'s no-arg constructor and invoke the
412    *   super constructor to set the {@code FlutterFragment} subclass: {@code
413    *     public MyBuilder() {
414    *       super(MyFlutterFragment.class);
415    *     }
416    *   }</li>
417    *   <li>Add appropriate property methods for the new properties.</li>
418    *   <li>Override {@link CachedEngineFragmentBuilder#createArgs()}, call through to the super
419    *   method, then add the new properties as arguments in the {@link Bundle}.</li>
420    * </ol>
421    * Once a {@code CachedEngineFragmentBuilder} subclass is defined, the {@code FlutterFragment}
422    * subclass can be instantiated as follows.
423    * {@code
424    *   MyFlutterFragment f = new MyBuilder()
425    *     .someExistingProperty(...)
426    *     .someNewProperty(...)
427    *     .build<MyFlutterFragment>();
428    * }
429    */
430   public static class CachedEngineFragmentBuilder {
431     private final Class<? extends FlutterFragment> fragmentClass;
432     private final String engineId;
433     private boolean destroyEngineWithFragment = false;
434     private FlutterView.RenderMode renderMode = FlutterView.RenderMode.surface;
435     private FlutterView.TransparencyMode transparencyMode = FlutterView.TransparencyMode.transparent;
436     private boolean shouldAttachEngineToActivity = true;
437 
CachedEngineFragmentBuilder(@onNull String engineId)438     private CachedEngineFragmentBuilder(@NonNull String engineId) {
439       this(FlutterFragment.class, engineId);
440     }
441 
CachedEngineFragmentBuilder(@onNull Class<? extends FlutterFragment> subclass, @NonNull String engineId)442     protected CachedEngineFragmentBuilder(@NonNull Class<? extends FlutterFragment> subclass, @NonNull String engineId) {
443       this.fragmentClass = subclass;
444       this.engineId = engineId;
445     }
446 
447     /**
448      * Pass {@code true} to destroy the cached {@link FlutterEngine} when this
449      * {@code FlutterFragment} is destroyed, or {@code false} for the cached {@link FlutterEngine}
450      * to outlive this {@code FlutterFragment}.
451      */
452     @NonNull
destroyEngineWithFragment(boolean destroyEngineWithFragment)453     public CachedEngineFragmentBuilder destroyEngineWithFragment(boolean destroyEngineWithFragment) {
454       this.destroyEngineWithFragment = destroyEngineWithFragment;
455       return this;
456     }
457 
458     /**
459      * Render Flutter either as a {@link FlutterView.RenderMode#surface} or a
460      * {@link FlutterView.RenderMode#texture}. You should use {@code surface} unless
461      * you have a specific reason to use {@code texture}. {@code texture} comes with
462      * a significant performance impact, but {@code texture} can be displayed
463      * beneath other Android {@code View}s and animated, whereas {@code surface}
464      * cannot.
465      */
466     @NonNull
renderMode(@onNull FlutterView.RenderMode renderMode)467     public CachedEngineFragmentBuilder renderMode(@NonNull FlutterView.RenderMode renderMode) {
468       this.renderMode = renderMode;
469       return this;
470     }
471 
472     /**
473      * Support a {@link FlutterView.TransparencyMode#transparent} background within {@link FlutterView},
474      * or force an {@link FlutterView.TransparencyMode#opaque} background.
475      * <p>
476      * See {@link FlutterView.TransparencyMode} for implications of this selection.
477      */
478     @NonNull
transparencyMode(@onNull FlutterView.TransparencyMode transparencyMode)479     public CachedEngineFragmentBuilder transparencyMode(@NonNull FlutterView.TransparencyMode transparencyMode) {
480       this.transparencyMode = transparencyMode;
481       return this;
482     }
483 
484     /**
485      * Whether or not this {@code FlutterFragment} should automatically attach its
486      * {@code Activity} as a control surface for its {@link FlutterEngine}.
487      * <p>
488      * Control surfaces are used to provide Android resources and lifecycle events to
489      * plugins that are attached to the {@link FlutterEngine}. If {@code shouldAttachEngineToActivity}
490      * is true then this {@code FlutterFragment} will connect its {@link FlutterEngine} to the
491      * surrounding {@code Activity}, along with any plugins that are registered with that
492      * {@link FlutterEngine}. This allows plugins to access the {@code Activity}, as well as
493      * receive {@code Activity}-specific calls, e.g., {@link android.app.Activity#onNewIntent(Intent)}.
494      * If {@code shouldAttachEngineToActivity} is false, then this {@code FlutterFragment} will not
495      * automatically manage the connection between its {@link FlutterEngine} and the surrounding
496      * {@code Activity}. The {@code Activity} will need to be manually connected to this
497      * {@code FlutterFragment}'s {@link FlutterEngine} by the app developer. See
498      * {@link FlutterEngine#getActivityControlSurface()}.
499      * <p>
500      * One reason that a developer might choose to manually manage the relationship between the
501      * {@code Activity} and {@link FlutterEngine} is if the developer wants to move the
502      * {@link FlutterEngine} somewhere else. For example, a developer might want the
503      * {@link FlutterEngine} to outlive the surrounding {@code Activity} so that it can be used
504      * later in a different {@code Activity}. To accomplish this, the {@link FlutterEngine} will
505      * need to be disconnected from the surrounding {@code Activity} at an unusual time, preventing
506      * this {@code FlutterFragment} from correctly managing the relationship between the
507      * {@link FlutterEngine} and the surrounding {@code Activity}.
508      * <p>
509      * Another reason that a developer might choose to manually manage the relationship between the
510      * {@code Activity} and {@link FlutterEngine} is if the developer wants to prevent, or explicitly
511      * control when the {@link FlutterEngine}'s plugins have access to the surrounding {@code Activity}.
512      * For example, imagine that this {@code FlutterFragment} only takes up part of the screen and
513      * the app developer wants to ensure that none of the Flutter plugins are able to manipulate
514      * the surrounding {@code Activity}. In this case, the developer would not want the
515      * {@link FlutterEngine} to have access to the {@code Activity}, which can be accomplished by
516      * setting {@code shouldAttachEngineToActivity} to {@code false}.
517      */
518     @NonNull
shouldAttachEngineToActivity(boolean shouldAttachEngineToActivity)519     public CachedEngineFragmentBuilder shouldAttachEngineToActivity(boolean shouldAttachEngineToActivity) {
520       this.shouldAttachEngineToActivity = shouldAttachEngineToActivity;
521       return this;
522     }
523 
524     /**
525      * Creates a {@link Bundle} of arguments that are assigned to the new {@code FlutterFragment}.
526      * <p>
527      * Subclasses should override this method to add new properties to the {@link Bundle}. Subclasses
528      * must call through to the super method to collect all existing property values.
529      */
530     @NonNull
createArgs()531     protected Bundle createArgs() {
532       Bundle args = new Bundle();
533       args.putString(ARG_CACHED_ENGINE_ID, engineId);
534       args.putBoolean(ARG_DESTROY_ENGINE_WITH_FRAGMENT, destroyEngineWithFragment);
535       args.putString(ARG_FLUTTERVIEW_RENDER_MODE, renderMode != null ? renderMode.name() : FlutterView.RenderMode.surface.name());
536       args.putString(ARG_FLUTTERVIEW_TRANSPARENCY_MODE, transparencyMode != null ? transparencyMode.name() : FlutterView.TransparencyMode.transparent.name());
537       args.putBoolean(ARG_SHOULD_ATTACH_ENGINE_TO_ACTIVITY, shouldAttachEngineToActivity);
538       return args;
539     }
540 
541     /**
542      * Constructs a new {@code FlutterFragment} (or a subclass) that is configured based on
543      * properties set on this {@code CachedEngineFragmentBuilder}.
544      */
545     @NonNull
build()546     public <T extends FlutterFragment> T build() {
547       try {
548         @SuppressWarnings("unchecked")
549         T frag = (T) fragmentClass.getDeclaredConstructor().newInstance();
550         if (frag == null) {
551           throw new RuntimeException("The FlutterFragment subclass sent in the constructor ("
552               + fragmentClass.getCanonicalName() + ") does not match the expected return type.");
553         }
554 
555         Bundle args = createArgs();
556         frag.setArguments(args);
557 
558         return frag;
559       } catch (Exception e) {
560         throw new RuntimeException("Could not instantiate FlutterFragment subclass (" + fragmentClass.getName() + ")", e);
561       }
562     }
563   }
564 
565   // Delegate that runs all lifecycle and OS hook logic that is common between
566   // FlutterActivity and FlutterFragment. See the FlutterActivityAndFragmentDelegate
567   // implementation for details about why it exists.
568   private FlutterActivityAndFragmentDelegate delegate;
569 
570   private final OnFirstFrameRenderedListener onFirstFrameRenderedListener = new OnFirstFrameRenderedListener() {
571     @Override
572     public void onFirstFrameRendered() {
573       // Notify our subclasses that the first frame has been rendered.
574       FlutterFragment.this.onFirstFrameRendered();
575 
576       // Notify our owning Activity that the first frame has been rendered.
577       FragmentActivity fragmentActivity = getActivity();
578       if (fragmentActivity instanceof OnFirstFrameRenderedListener) {
579         OnFirstFrameRenderedListener activityAsListener = (OnFirstFrameRenderedListener) fragmentActivity;
580         activityAsListener.onFirstFrameRendered();
581       }
582     }
583   };
584 
FlutterFragment()585   public FlutterFragment() {
586     // Ensure that we at least have an empty Bundle of arguments so that we don't
587     // need to continually check for null arguments before grabbing one.
588     setArguments(new Bundle());
589   }
590 
591   @Override
onAttach(@onNull Context context)592   public void onAttach(@NonNull Context context) {
593     super.onAttach(context);
594     delegate = new FlutterActivityAndFragmentDelegate(this);
595     delegate.onAttach(context);
596   }
597 
598   @Nullable
599   @Override
onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)600   public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
601     return delegate.onCreateView(inflater, container, savedInstanceState);
602   }
603 
604   @Override
onStart()605   public void onStart() {
606     super.onStart();
607     delegate.onStart();
608   }
609 
610   @Override
onResume()611   public void onResume() {
612     super.onResume();
613     delegate.onResume();
614   }
615 
616   // TODO(mattcarroll): determine why this can't be in onResume(). Comment reason, or move if possible.
617   @ActivityCallThrough
onPostResume()618   public void onPostResume() {
619     delegate.onPostResume();
620   }
621 
622   @Override
onPause()623   public void onPause() {
624     super.onPause();
625     delegate.onPause();
626   }
627 
628   @Override
onStop()629   public void onStop() {
630     super.onStop();
631     delegate.onStop();
632   }
633 
634   @Override
onDestroyView()635   public void onDestroyView() {
636     super.onDestroyView();
637     delegate.onDestroyView();
638   }
639 
640   @Override
onDetach()641   public void onDetach() {
642     super.onDetach();
643     delegate.onDetach();
644     delegate.release();
645     delegate = null;
646   }
647 
648   /**
649    * The result of a permission request has been received.
650    * <p>
651    * See {@link android.app.Activity#onRequestPermissionsResult(int, String[], int[])}
652    * <p>
653    * @param requestCode identifier passed with the initial permission request
654    * @param permissions permissions that were requested
655    * @param grantResults permission grants or denials
656    */
657   @ActivityCallThrough
onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)658   public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
659     delegate.onRequestPermissionsResult(requestCode, permissions, grantResults);
660   }
661 
662   /**
663    * A new Intent was received by the {@link android.app.Activity} that currently owns this
664    * {@link Fragment}.
665    * <p>
666    * See {@link android.app.Activity#onNewIntent(Intent)}
667    * <p>
668    * @param intent new Intent
669    */
670   @ActivityCallThrough
onNewIntent(@onNull Intent intent)671   public void onNewIntent(@NonNull Intent intent) {
672     delegate.onNewIntent(intent);
673   }
674 
675   /**
676    * The hardware back button was pressed.
677    * <p>
678    * See {@link android.app.Activity#onBackPressed()}
679    */
680   @ActivityCallThrough
onBackPressed()681   public void onBackPressed() {
682     delegate.onBackPressed();
683   }
684 
685   /**
686    * A result has been returned after an invocation of {@link Fragment#startActivityForResult(Intent, int)}.
687    * <p>
688    * @param requestCode request code sent with {@link Fragment#startActivityForResult(Intent, int)}
689    * @param resultCode code representing the result of the {@code Activity} that was launched
690    * @param data any corresponding return data, held within an {@code Intent}
691    */
692   @Override
onActivityResult(int requestCode, int resultCode, Intent data)693   public void onActivityResult(int requestCode, int resultCode, Intent data) {
694     delegate.onActivityResult(requestCode, resultCode, data);
695   }
696 
697   /**
698    * The {@link android.app.Activity} that owns this {@link Fragment} is about to go to the background
699    * as the result of a user's choice/action, i.e., not as the result of an OS decision.
700    * <p>
701    * See {@link android.app.Activity#onUserLeaveHint()}
702    */
703   @ActivityCallThrough
onUserLeaveHint()704   public void onUserLeaveHint() {
705     delegate.onUserLeaveHint();
706   }
707 
708   /**
709    * Callback invoked when memory is low.
710    * <p>
711    * This implementation forwards a memory pressure warning to the running Flutter app.
712    * <p>
713    * @param level level
714    */
715   @ActivityCallThrough
onTrimMemory(int level)716   public void onTrimMemory(int level) {
717     delegate.onTrimMemory(level);
718   }
719 
720   /**
721    * Callback invoked when memory is low.
722    * <p>
723    * This implementation forwards a memory pressure warning to the running Flutter app.
724    */
725   @Override
onLowMemory()726   public void onLowMemory() {
727     super.onLowMemory();
728     delegate.onLowMemory();
729   }
730 
731   @NonNull
getContextCompat()732   private Context getContextCompat() {
733     return Build.VERSION.SDK_INT >= 23
734       ? getContext()
735       : getActivity();
736   }
737 
738   /**
739    * {@link FlutterActivityAndFragmentDelegate.Host} method that is used by
740    * {@link FlutterActivityAndFragmentDelegate} to obtain Flutter shell arguments when
741    * initializing Flutter.
742    */
743   @Override
744   @NonNull
getFlutterShellArgs()745   public FlutterShellArgs getFlutterShellArgs() {
746     String[] flutterShellArgsArray = getArguments().getStringArray(ARG_FLUTTER_INITIALIZATION_ARGS);
747     return new FlutterShellArgs(
748         flutterShellArgsArray != null ? flutterShellArgsArray : new String[] {}
749     );
750   }
751 
752   /**
753    * Returns the ID of a statically cached {@link FlutterEngine} to use within this
754    * {@code FlutterFragment}, or {@code null} if this {@code FlutterFragment} does not want to
755    * use a cached {@link FlutterEngine}.
756    */
757   @Nullable
758   @Override
getCachedEngineId()759   public String getCachedEngineId() {
760     return getArguments().getString(ARG_CACHED_ENGINE_ID, null);
761   }
762 
763   /**
764    * Returns false if the {@link FlutterEngine} within this {@code FlutterFragment} should outlive
765    * the {@code FlutterFragment}, itself.
766    * <p>
767    * Defaults to true if no custom {@link FlutterEngine is provided}, false if a custom
768    * {@link FlutterEngine} is provided.
769    */
770   @Override
shouldDestroyEngineWithHost()771   public boolean shouldDestroyEngineWithHost() {
772     return getArguments().getBoolean(ARG_DESTROY_ENGINE_WITH_FRAGMENT, false);
773   }
774 
775   /**
776    * Returns the name of the Dart method that this {@code FlutterFragment} should execute to
777    * start a Flutter app.
778    * <p>
779    * Defaults to "main".
780    * <p>
781    * Used by this {@code FlutterFragment}'s {@link FlutterActivityAndFragmentDelegate.Host}
782    */
783   @Override
784   @NonNull
getDartEntrypointFunctionName()785   public String getDartEntrypointFunctionName() {
786     return getArguments().getString(ARG_DART_ENTRYPOINT, "main");
787   }
788 
789   /**
790    * Returns the file path to the desired Flutter app's bundle of code.
791    * <p>
792    * Defaults to {@link FlutterMain#findAppBundlePath()}.
793    * <p>
794    * Used by this {@code FlutterFragment}'s {@link FlutterActivityAndFragmentDelegate.Host}
795    */
796   @Override
797   @NonNull
getAppBundlePath()798   public String getAppBundlePath() {
799     return getArguments().getString(ARG_APP_BUNDLE_PATH, FlutterMain.findAppBundlePath());
800   }
801 
802   /**
803    * Returns the initial route that should be rendered within Flutter, once the Flutter app starts.
804    * <p>
805    * Defaults to {@code null}, which signifies a route of "/" in Flutter.
806    * <p>
807    * Used by this {@code FlutterFragment}'s {@link FlutterActivityAndFragmentDelegate.Host}
808    */
809   @Override
810   @Nullable
getInitialRoute()811   public String getInitialRoute() {
812     return getArguments().getString(ARG_INITIAL_ROUTE);
813   }
814 
815   /**
816    * Returns the desired {@link FlutterView.RenderMode} for the {@link FlutterView} displayed in
817    * this {@code FlutterFragment}.
818    * <p>
819    * Defaults to {@link FlutterView.RenderMode#surface}.
820    * <p>
821    * Used by this {@code FlutterFragment}'s {@link FlutterActivityAndFragmentDelegate.Host}
822    */
823   @Override
824   @NonNull
getRenderMode()825   public FlutterView.RenderMode getRenderMode() {
826     String renderModeName = getArguments().getString(
827         ARG_FLUTTERVIEW_RENDER_MODE,
828         FlutterView.RenderMode.surface.name()
829     );
830     return FlutterView.RenderMode.valueOf(renderModeName);
831   }
832 
833   /**
834    * Returns the desired {@link FlutterView.TransparencyMode} for the {@link FlutterView} displayed in
835    * this {@code FlutterFragment}.
836    * <p>
837    * Defaults to {@link FlutterView.TransparencyMode#transparent}.
838    * <p>
839    * Used by this {@code FlutterFragment}'s {@link FlutterActivityAndFragmentDelegate.Host}
840    */
841   @Override
842   @NonNull
getTransparencyMode()843   public FlutterView.TransparencyMode getTransparencyMode() {
844     String transparencyModeName = getArguments().getString(
845         ARG_FLUTTERVIEW_TRANSPARENCY_MODE,
846         FlutterView.TransparencyMode.transparent.name()
847     );
848     return FlutterView.TransparencyMode.valueOf(transparencyModeName);
849   }
850 
851   @Override
852   @Nullable
provideSplashScreen()853   public SplashScreen provideSplashScreen() {
854     FragmentActivity parentActivity = getActivity();
855     if (parentActivity instanceof SplashScreenProvider) {
856       SplashScreenProvider splashScreenProvider = (SplashScreenProvider) parentActivity;
857       return splashScreenProvider.provideSplashScreen();
858     }
859 
860     return null;
861   }
862 
863   /**
864    * Hook for subclasses to return a {@link FlutterEngine} with whatever configuration
865    * is desired.
866    * <p>
867    * By default this method defers to this {@code FlutterFragment}'s surrounding {@code Activity},
868    * if that {@code Activity} implements {@link FlutterEngineProvider}. If this method is
869    * overridden, the surrounding {@code Activity} will no longer be given an opportunity to
870    * provide a {@link FlutterEngine}, unless the subclass explicitly implements that behavior.
871    * <p>
872    * Consider returning a cached {@link FlutterEngine} instance from this method to avoid the
873    * typical warm-up time that a new {@link FlutterEngine} instance requires.
874    * <p>
875    * If null is returned then a new default {@link FlutterEngine} will be created to back this
876    * {@code FlutterFragment}.
877    * <p>
878    * Used by this {@code FlutterFragment}'s {@link FlutterActivityAndFragmentDelegate.Host}
879    */
880   @Override
881   @Nullable
provideFlutterEngine(@onNull Context context)882   public FlutterEngine provideFlutterEngine(@NonNull Context context) {
883     // Defer to the FragmentActivity that owns us to see if it wants to provide a
884     // FlutterEngine.
885     FlutterEngine flutterEngine = null;
886     FragmentActivity attachedActivity = getActivity();
887     if (attachedActivity instanceof FlutterEngineProvider) {
888       // Defer to the Activity that owns us to provide a FlutterEngine.
889       Log.d(TAG, "Deferring to attached Activity to provide a FlutterEngine.");
890       FlutterEngineProvider flutterEngineProvider = (FlutterEngineProvider) attachedActivity;
891       flutterEngine = flutterEngineProvider.provideFlutterEngine(getContext());
892     }
893 
894     return flutterEngine;
895   }
896 
897   /**
898    * Hook for subclasses to obtain a reference to the {@link FlutterEngine} that is owned
899    * by this {@code FlutterActivity}.
900    */
901   @Nullable
getFlutterEngine()902   public FlutterEngine getFlutterEngine() {
903     return delegate.getFlutterEngine();
904   }
905 
906   @Nullable
907   @Override
providePlatformPlugin(@ullable Activity activity, @NonNull FlutterEngine flutterEngine)908   public PlatformPlugin providePlatformPlugin(@Nullable Activity activity, @NonNull FlutterEngine flutterEngine) {
909     if (activity != null) {
910       return new PlatformPlugin(getActivity(), flutterEngine.getPlatformChannel());
911     } else {
912       return null;
913     }
914   }
915 
916   /**
917    * Configures a {@link FlutterEngine} after its creation.
918    * <p>
919    * This method is called after {@link #provideFlutterEngine(Context)}, and after the given
920    * {@link FlutterEngine} has been attached to the owning {@code FragmentActivity}. See
921    * {@link io.flutter.embedding.engine.plugins.activity.ActivityControlSurface#attachToActivity(Activity, Lifecycle)}.
922    * <p>
923    * It is possible that the owning {@code FragmentActivity} opted not to connect itself as
924    * an {@link io.flutter.embedding.engine.plugins.activity.ActivityControlSurface}. In that
925    * case, any configuration, e.g., plugins, must not expect or depend upon an available
926    * {@code Activity} at the time that this method is invoked.
927    * <p>
928    * The default behavior of this method is to defer to the owning {@code FragmentActivity}
929    * as a {@link FlutterEngineConfigurator}. Subclasses can override this method if the
930    * subclass needs to override the {@code FragmentActivity}'s behavior, or add to it.
931    * <p>
932    * Used by this {@code FlutterFragment}'s {@link FlutterActivityAndFragmentDelegate.Host}
933    */
934   @Override
configureFlutterEngine(@onNull FlutterEngine flutterEngine)935   public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
936     FragmentActivity attachedActivity = getActivity();
937     if (attachedActivity instanceof FlutterEngineConfigurator) {
938       ((FlutterEngineConfigurator) attachedActivity).configureFlutterEngine(flutterEngine);
939     }
940   }
941 
942   /**
943    * See {@link NewEngineFragmentBuilder#shouldAttachEngineToActivity()} and
944    * {@link CachedEngineFragmentBuilder#shouldAttachEngineToActivity()}.
945    * <p>
946    * Used by this {@code FlutterFragment}'s {@link FlutterActivityAndFragmentDelegate}
947    */
948   @Override
shouldAttachEngineToActivity()949   public boolean shouldAttachEngineToActivity() {
950     return getArguments().getBoolean(ARG_SHOULD_ATTACH_ENGINE_TO_ACTIVITY);
951   }
952 
953   /**
954    * Invoked after the {@link FlutterView} within this {@code FlutterFragment} renders its first
955    * frame.
956    * <p>
957    * This method forwards {@code onFirstFrameRendered()} to its attached {@code Activity}, if
958    * the attached {@code Activity} implements {@link OnFirstFrameRenderedListener}.
959    * <p>
960    * Subclasses that override this method must call through to the {@code super} method.
961    * <p>
962    * Used by this {@code FlutterFragment}'s {@link FlutterActivityAndFragmentDelegate.Host}
963    */
964   @Override
onFirstFrameRendered()965   public void onFirstFrameRendered() {
966     FragmentActivity attachedActivity = getActivity();
967     if (attachedActivity instanceof OnFirstFrameRenderedListener) {
968       ((OnFirstFrameRenderedListener) attachedActivity).onFirstFrameRendered();
969     }
970   }
971 
972   /**
973    * Annotates methods in {@code FlutterFragment} that must be called by the containing
974    * {@code Activity}.
975    */
976   @interface ActivityCallThrough {}
977 
978 }
979