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