From 3bd57ac05a13546c0ef8dce5b9922d050400d585 Mon Sep 17 00:00:00 2001 From: ColdPaleLight <31977171+ColdPaleLight@users.noreply.github.com> Date: Thu, 30 Sep 2021 02:02:37 +0800 Subject: [PATCH] Make FlutterEngineGroup support initialRoute (flutter/engine#28884) --- engine/src/flutter/shell/common/engine.cc | 4 ++- engine/src/flutter/shell/common/engine.h | 3 +- .../flutter/shell/common/engine_unittests.cc | 30 ++++++++++++++-- engine/src/flutter/shell/common/shell.cc | 6 ++-- engine/src/flutter/shell/common/shell.h | 1 + .../flutter/shell/common/shell_unittests.cc | 10 ++++-- .../platform/android/android_shell_holder.cc | 8 +++-- .../platform/android/android_shell_holder.h | 3 +- .../embedding/engine/FlutterEngine.java | 8 +++-- .../embedding/engine/FlutterEngineGroup.java | 26 +++++++++++++- .../flutter/embedding/engine/FlutterJNI.java | 12 ++++--- .../android/platform_view_android_jni_impl.cc | 11 +++--- .../FlutterEngineGroupComponentTest.java | 34 +++++++++++++++++-- .../ios/framework/Headers/FlutterEngine.h | 21 ++++++++++++ .../framework/Headers/FlutterEngineGroup.h | 25 ++++++++++++++ .../ios/framework/Source/FlutterEngine.mm | 12 +++++-- .../framework/Source/FlutterEngineGroup.mm | 12 +++++-- .../ios/framework/Source/FlutterEngineTest.mm | 2 +- .../framework/Source/FlutterEngineTest_mrc.mm | 2 +- .../framework/Source/FlutterEngine_Internal.h | 3 +- .../ios/framework/Source/FlutterEngine_Test.h | 3 +- .../ios/Scenarios/Scenarios/AppDelegate.m | 2 +- .../Scenarios/FlutterEngine+ScenariosTest.h | 3 +- 23 files changed, 204 insertions(+), 37 deletions(-) diff --git a/engine/src/flutter/shell/common/engine.cc b/engine/src/flutter/shell/common/engine.cc index 38722364412..28321a9fd16 100644 --- a/engine/src/flutter/shell/common/engine.cc +++ b/engine/src/flutter/shell/common/engine.cc @@ -112,7 +112,8 @@ std::unique_ptr Engine::Spawn( Delegate& delegate, const PointerDataDispatcherMaker& dispatcher_maker, Settings settings, - std::unique_ptr animator) const { + std::unique_ptr animator, + const std::string& initial_route) const { auto result = std::make_unique( /*delegate=*/delegate, /*dispatcher_maker=*/dispatcher_maker, @@ -133,6 +134,7 @@ std::unique_ptr Engine::Spawn( settings_.isolate_shutdown_callback, // isolate shutdown callback settings_.persistent_isolate_data // persistent isolate data ); + result->initial_route_ = initial_route; return result; } diff --git a/engine/src/flutter/shell/common/engine.h b/engine/src/flutter/shell/common/engine.h index a6f29905b5d..ca73c591432 100644 --- a/engine/src/flutter/shell/common/engine.h +++ b/engine/src/flutter/shell/common/engine.h @@ -375,7 +375,8 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { Delegate& delegate, const PointerDataDispatcherMaker& dispatcher_maker, Settings settings, - std::unique_ptr animator) const; + std::unique_ptr animator, + const std::string& initial_route) const; //---------------------------------------------------------------------------- /// @brief Destroys the engine engine. Called by the shell on the UI task diff --git a/engine/src/flutter/shell/common/engine_unittests.cc b/engine/src/flutter/shell/common/engine_unittests.cc index 3a83cc7a868..e841060c636 100644 --- a/engine/src/flutter/shell/common/engine_unittests.cc +++ b/engine/src/flutter/shell/common/engine_unittests.cc @@ -268,13 +268,39 @@ TEST_F(EngineTest, SpawnSharesFontLibrary) { /*font_collection=*/std::make_shared(), /*runtime_controller=*/std::move(mock_runtime_controller)); - auto spawn = - engine->Spawn(delegate_, dispatcher_maker_, settings_, nullptr); + auto spawn = engine->Spawn(delegate_, dispatcher_maker_, settings_, nullptr, + std::string()); EXPECT_TRUE(spawn != nullptr); EXPECT_EQ(&engine->GetFontCollection(), &spawn->GetFontCollection()); }); } +TEST_F(EngineTest, SpawnWithCustomInitialRoute) { + PostUITaskSync([this] { + MockRuntimeDelegate client; + auto mock_runtime_controller = + std::make_unique(client, task_runners_); + auto vm_ref = DartVMRef::Create(settings_); + EXPECT_CALL(*mock_runtime_controller, GetDartVM()) + .WillRepeatedly(::testing::Return(vm_ref.get())); + auto engine = std::make_unique( + /*delegate=*/delegate_, + /*dispatcher_maker=*/dispatcher_maker_, + /*image_decoder_task_runner=*/image_decoder_task_runner_, + /*task_runners=*/task_runners_, + /*settings=*/settings_, + /*animator=*/std::move(animator_), + /*io_manager=*/io_manager_, + /*font_collection=*/std::make_shared(), + /*runtime_controller=*/std::move(mock_runtime_controller)); + + auto spawn = + engine->Spawn(delegate_, dispatcher_maker_, settings_, nullptr, "/foo"); + EXPECT_TRUE(spawn != nullptr); + ASSERT_EQ("/foo", spawn->InitialRoute()); + }); +} + TEST_F(EngineTest, PassesLoadDartDeferredLibraryErrorToRuntime) { PostUITaskSync([this] { intptr_t error_id = 123; diff --git a/engine/src/flutter/shell/common/shell.cc b/engine/src/flutter/shell/common/shell.cc index 49d3ab16dc8..c6c723b51ab 100644 --- a/engine/src/flutter/shell/common/shell.cc +++ b/engine/src/flutter/shell/common/shell.cc @@ -480,6 +480,7 @@ Shell::~Shell() { std::unique_ptr Shell::Spawn( RunConfiguration run_configuration, + const std::string& initial_route, const CreateCallback& on_create_platform_view, const CreateCallback& on_create_rasterizer) const { FML_DCHECK(task_runners_.IsValid()); @@ -488,7 +489,7 @@ std::unique_ptr Shell::Spawn( PlatformData{}, task_runners_, rasterizer_->GetRasterThreadMerger(), GetSettings(), vm_, vm_->GetVMData()->GetIsolateSnapshot(), on_create_platform_view, on_create_rasterizer, - [engine = this->engine_.get()]( + [engine = this->engine_.get(), initial_route]( Engine::Delegate& delegate, const PointerDataDispatcherMaker& dispatcher_maker, DartVM& vm, fml::RefPtr isolate_snapshot, @@ -501,7 +502,8 @@ std::unique_ptr Shell::Spawn( return engine->Spawn(/*delegate=*/delegate, /*dispatcher_maker=*/dispatcher_maker, /*settings=*/settings, - /*animator=*/std::move(animator)); + /*animator=*/std::move(animator), + /*initial_route=*/initial_route); }, is_gpu_disabled)); return result; diff --git a/engine/src/flutter/shell/common/shell.h b/engine/src/flutter/shell/common/shell.h index 23754936c4e..9fbdd5b1a42 100644 --- a/engine/src/flutter/shell/common/shell.h +++ b/engine/src/flutter/shell/common/shell.h @@ -194,6 +194,7 @@ class Shell final : public PlatformView::Delegate, /// @see http://flutter.dev/go/multiple-engines std::unique_ptr Spawn( RunConfiguration run_configuration, + const std::string& initial_route, const CreateCallback& on_create_platform_view, const CreateCallback& on_create_rasterizer) const; diff --git a/engine/src/flutter/shell/common/shell_unittests.cc b/engine/src/flutter/shell/common/shell_unittests.cc index 2f16650f80a..02872fe5627 100644 --- a/engine/src/flutter/shell/common/shell_unittests.cc +++ b/engine/src/flutter/shell/common/shell_unittests.cc @@ -2695,6 +2695,8 @@ TEST_F(ShellTest, Spawn) { ASSERT_TRUE(second_configuration.IsValid()); second_configuration.SetEntrypoint("testCanLaunchSecondaryIsolate"); + const std::string initial_route("/foo"); + fml::AutoResetWaitableEvent main_latch; std::string last_entry_point; // Fulfill native function for the first Shell's entrypoint. @@ -2719,10 +2721,11 @@ TEST_F(ShellTest, Spawn) { PostSync( shell->GetTaskRunners().GetPlatformTaskRunner(), - [this, &spawner = shell, &second_configuration, &second_latch]() { + [this, &spawner = shell, &second_configuration, &second_latch, + initial_route]() { MockPlatformViewDelegate platform_view_delegate; auto spawn = spawner->Spawn( - std::move(second_configuration), + std::move(second_configuration), initial_route, [&platform_view_delegate](Shell& shell) { auto result = std::make_unique( platform_view_delegate, shell.GetTaskRunners()); @@ -2736,10 +2739,11 @@ TEST_F(ShellTest, Spawn) { ASSERT_TRUE(ValidateShell(spawn.get())); PostSync(spawner->GetTaskRunners().GetUITaskRunner(), - [&spawn, &spawner] { + [&spawn, &spawner, initial_route] { // Check second shell ran the second entrypoint. ASSERT_EQ("testCanLaunchSecondaryIsolate", spawn->GetEngine()->GetLastEntrypoint()); + ASSERT_EQ(initial_route, spawn->GetEngine()->InitialRoute()); // TODO(74520): Remove conditional once isolate groups are // supported by JIT. diff --git a/engine/src/flutter/shell/platform/android/android_shell_holder.cc b/engine/src/flutter/shell/platform/android/android_shell_holder.cc index a933c2a416e..b85db32a6d1 100644 --- a/engine/src/flutter/shell/platform/android/android_shell_holder.cc +++ b/engine/src/flutter/shell/platform/android/android_shell_holder.cc @@ -174,7 +174,8 @@ const flutter::Settings& AndroidShellHolder::GetSettings() const { std::unique_ptr AndroidShellHolder::Spawn( std::shared_ptr jni_facade, const std::string& entrypoint, - const std::string& libraryUrl) const { + const std::string& libraryUrl, + const std::string& initial_route) const { FML_DCHECK(shell_ && shell_->IsSetup()) << "A new Shell can only be spawned " "if the current Shell is properly constructed"; @@ -226,8 +227,9 @@ std::unique_ptr AndroidShellHolder::Spawn( return nullptr; } - std::unique_ptr shell = shell_->Spawn( - std::move(config.value()), on_create_platform_view, on_create_rasterizer); + std::unique_ptr shell = + shell_->Spawn(std::move(config.value()), initial_route, + on_create_platform_view, on_create_rasterizer); return std::unique_ptr( new AndroidShellHolder(GetSettings(), jni_facade, thread_host_, diff --git a/engine/src/flutter/shell/platform/android/android_shell_holder.h b/engine/src/flutter/shell/platform/android/android_shell_holder.h index d6ac5272667..9ea264f3667 100644 --- a/engine/src/flutter/shell/platform/android/android_shell_holder.h +++ b/engine/src/flutter/shell/platform/android/android_shell_holder.h @@ -78,7 +78,8 @@ class AndroidShellHolder { std::unique_ptr Spawn( std::shared_ptr jni_facade, const std::string& entrypoint, - const std::string& libraryUrl) const; + const std::string& libraryUrl, + const std::string& initial_route) const; void Launch(std::shared_ptr asset_manager, const std::string& entrypoint, diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index b6d78142537..828bb945868 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -384,7 +384,9 @@ public class FlutterEngine { */ @NonNull /*package*/ FlutterEngine spawn( - @NonNull Context context, @NonNull DartEntrypoint dartEntrypoint) { + @NonNull Context context, + @NonNull DartEntrypoint dartEntrypoint, + @Nullable String initialRoute) { if (!isAttachedToJni()) { throw new IllegalStateException( "Spawn can only be called on a fully constructed FlutterEngine"); @@ -392,7 +394,9 @@ public class FlutterEngine { FlutterJNI newFlutterJNI = flutterJNI.spawn( - dartEntrypoint.dartEntrypointFunctionName, dartEntrypoint.dartEntrypointLibrary); + dartEntrypoint.dartEntrypointFunctionName, + dartEntrypoint.dartEntrypointLibrary, + initialRoute); return new FlutterEngine( context, // Context. null, // FlutterLoader. A null value passed here causes the constructor to get it from the diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java index e62aad2bc70..4dd8f43922f 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java @@ -96,6 +96,27 @@ public class FlutterEngineGroup { */ public FlutterEngine createAndRunEngine( @NonNull Context context, @Nullable DartEntrypoint dartEntrypoint) { + return createAndRunEngine(context, dartEntrypoint, null); + } + + /** + * Creates a {@link io.flutter.embedding.engine.FlutterEngine} in this group and run its {@link + * io.flutter.embedding.engine.dart.DartExecutor} with the specified {@link DartEntrypoint} and + * the specified {@code initialRoute}. + * + *

If no prior {@link io.flutter.embedding.engine.FlutterEngine} were created in this group, + * the initialization cost will be slightly higher than subsequent engines. The very first {@link + * io.flutter.embedding.engine.FlutterEngine} created per program, regardless of + * FlutterEngineGroup, also incurs the Dart VM creation time. + * + *

Subsequent engine creations will share resources with existing engines. However, if all + * existing engines were {@link io.flutter.embedding.engine.FlutterEngine#destroy()}ed, the next + * engine created will recreate its dependencies. + */ + public FlutterEngine createAndRunEngine( + @NonNull Context context, + @Nullable DartEntrypoint dartEntrypoint, + @Nullable String initialRoute) { FlutterEngine engine = null; if (dartEntrypoint == null) { @@ -104,9 +125,12 @@ public class FlutterEngineGroup { if (activeEngines.size() == 0) { engine = createEngine(context); + if (initialRoute != null) { + engine.getNavigationChannel().setInitialRoute(initialRoute); + } engine.getDartExecutor().executeDartEntrypoint(dartEntrypoint); } else { - engine = activeEngines.get(0).spawn(context, dartEntrypoint); + engine = activeEngines.get(0).spawn(context, dartEntrypoint, initialRoute); } activeEngines.add(engine); diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index 2c4baa80d7b..5da49e89157 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -335,11 +335,14 @@ public class FlutterJNI { @UiThread @NonNull public FlutterJNI spawn( - @Nullable String entrypointFunctionName, @Nullable String pathToEntrypointFunction) { + @Nullable String entrypointFunctionName, + @Nullable String pathToEntrypointFunction, + @Nullable String initialRoute) { ensureRunningOnMainThread(); ensureAttachedToNative(); FlutterJNI spawnedJNI = - nativeSpawn(nativeShellHolderId, entrypointFunctionName, pathToEntrypointFunction); + nativeSpawn( + nativeShellHolderId, entrypointFunctionName, pathToEntrypointFunction, initialRoute); Preconditions.checkState( spawnedJNI.nativeShellHolderId != null && spawnedJNI.nativeShellHolderId != 0, "Failed to spawn new JNI connected shell from existing shell."); @@ -350,7 +353,8 @@ public class FlutterJNI { private native FlutterJNI nativeSpawn( long nativeSpawningShellId, @Nullable String entrypointFunctionName, - @Nullable String pathToEntrypointFunction); + @Nullable String pathToEntrypointFunction, + @Nullable String initialRoute); /** * Detaches this {@code FlutterJNI} instance from Flutter's native engine, which precludes any @@ -359,7 +363,7 @@ public class FlutterJNI { *

This method must not be invoked if {@code FlutterJNI} is not already attached to native. * *

Invoking this method will result in the release of all native-side resources that were set - * up during {@link #attachToNative()} or {@link #spawn(String, String)}, or accumulated + * up during {@link #attachToNative()} or {@link #spawn(String, String, String)}, or accumulated * thereafter. * *

It is permissible to re-attach this instance to native after detaching it from native. diff --git a/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc b/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc index ac5d5cdf7be..376ac155516 100644 --- a/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc +++ b/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc @@ -154,7 +154,8 @@ static jobject SpawnJNI(JNIEnv* env, jobject jcaller, jlong shell_holder, jstring jEntrypoint, - jstring jLibraryUrl) { + jstring jLibraryUrl, + jstring jInitialRoute) { jobject jni = env->NewObject(g_flutter_jni_class->obj(), g_jni_constructor); if (jni == nullptr) { FML_LOG(ERROR) << "Could not create a FlutterJNI instance"; @@ -167,9 +168,10 @@ static jobject SpawnJNI(JNIEnv* env, auto entrypoint = fml::jni::JavaStringToString(env, jEntrypoint); auto libraryUrl = fml::jni::JavaStringToString(env, jLibraryUrl); + auto initial_route = fml::jni::JavaStringToString(env, jInitialRoute); - auto spawned_shell_holder = - ANDROID_SHELL_HOLDER->Spawn(jni_facade, entrypoint, libraryUrl); + auto spawned_shell_holder = ANDROID_SHELL_HOLDER->Spawn( + jni_facade, entrypoint, libraryUrl, initial_route); if (spawned_shell_holder == nullptr || !spawned_shell_holder->IsValid()) { FML_LOG(ERROR) << "Could not spawn Shell"; @@ -620,7 +622,8 @@ bool RegisterApi(JNIEnv* env) { }, { .name = "nativeSpawn", - .signature = "(JLjava/lang/String;Ljava/lang/String;)Lio/flutter/" + .signature = "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/" + "String;)Lio/flutter/" "embedding/engine/FlutterJNI;", .fnPtr = reinterpret_cast(&SpawnJNI), }, diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java index 363ca14fc85..e836560e009 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java @@ -11,6 +11,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.isNull; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.nullable; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -21,6 +22,7 @@ import android.content.res.AssetManager; import io.flutter.FlutterInjector; import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint; import io.flutter.embedding.engine.loader.FlutterLoader; +import io.flutter.embedding.engine.systemchannels.NavigationChannel; import io.flutter.plugins.GeneratedPluginRegistrant; import org.junit.After; import org.junit.Before; @@ -119,7 +121,7 @@ public class FlutterEngineGroupComponentTest { doReturn(mock(FlutterEngine.class)) .when(firstEngine) - .spawn(any(Context.class), any(DartEntrypoint.class)); + .spawn(any(Context.class), any(DartEntrypoint.class), nullable(String.class)); FlutterEngine secondEngine = engineGroupUnderTest.createAndRunEngine( @@ -131,7 +133,7 @@ public class FlutterEngineGroupComponentTest { // Now the second spawned engine is the only one left and it will be called to spawn the next // engine in the chain. - when(secondEngine.spawn(any(Context.class), any(DartEntrypoint.class))) + when(secondEngine.spawn(any(Context.class), any(DartEntrypoint.class), nullable(String.class))) .thenReturn(mock(FlutterEngine.class)); FlutterEngine thirdEngine = @@ -156,4 +158,32 @@ public class FlutterEngineGroupComponentTest { isNull(String.class), any(AssetManager.class)); } + + @Test + public void canCreateAndRunWithCustomInitialRoute() { + when(firstEngineUnderTest.getNavigationChannel()).thenReturn(mock(NavigationChannel.class)); + + FlutterEngine firstEngine = + engineGroupUnderTest.createAndRunEngine( + RuntimeEnvironment.application, mock(DartEntrypoint.class), "/foo"); + assertEquals(1, engineGroupUnderTest.activeEngines.size()); + verify(firstEngine.getNavigationChannel(), times(1)).setInitialRoute("/foo"); + + when(mockflutterJNI.isAttached()).thenReturn(true); + jniAttached = false; + FlutterJNI secondMockflutterJNI = mock(FlutterJNI.class); + when(secondMockflutterJNI.isAttached()).thenAnswer(invocation -> jniAttached); + doAnswer(invocation -> jniAttached = true).when(secondMockflutterJNI).attachToNative(); + doReturn(secondMockflutterJNI) + .when(mockflutterJNI) + .spawn(nullable(String.class), nullable(String.class), nullable(String.class)); + + FlutterEngine secondEngine = + engineGroupUnderTest.createAndRunEngine( + RuntimeEnvironment.application, mock(DartEntrypoint.class), "/bar"); + + assertEquals(2, engineGroupUnderTest.activeEngines.size()); + verify(mockflutterJNI, times(1)) + .spawn(nullable(String.class), nullable(String.class), eq("/bar")); + } } diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h index f3b63dea536..48f9d4bf567 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h @@ -225,6 +225,27 @@ FLUTTER_DARWIN_EXPORT */ - (BOOL)runWithEntrypoint:(nullable NSString*)entrypoint libraryURI:(nullable NSString*)uri; +/** + * Runs a Dart program on an Isolate using the specified entrypoint and Dart library, + * which may not be the same as the library containing the Dart program's `main()` function. + * + * The first call to this method will create a new Isolate. Subsequent calls will return + * immediately and have no effect. + * + * @param entrypoint The name of a top-level function from a Dart library. If this is + * FlutterDefaultDartEntrypoint (or nil); this will default to `main()`. If it is not the app's + * main() function, that function must be decorated with `@pragma(vm:entry-point)` to ensure the + * method is not tree-shaken by the Dart compiler. + * @param libraryURI The URI of the Dart library which contains the entrypoint method. IF nil, + * this will default to the same library as the `main()` function in the Dart program. + * @param initialRoute The name of the initial Flutter `Navigator` `Route` to load. If this is + * FlutterDefaultInitialRoute (or nil), it will default to the "/" route. + * @return YES if the call succeeds in creating and running a Flutter Engine instance; NO otherwise. + */ +- (BOOL)runWithEntrypoint:(nullable NSString*)entrypoint + libraryURI:(nullable NSString*)libraryURI + initialRoute:(nullable NSString*)initialRoute; + /** * Destroy running context for an engine. * diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngineGroup.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngineGroup.h index 3ce923676ff..7bf295ddbb1 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngineGroup.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngineGroup.h @@ -37,10 +37,35 @@ FLUTTER_DARWIN_EXPORT /** * Creates a running `FlutterEngine` that shares components with this group. * + * @param entrypoint The name of a top-level function from a Dart library. If this is + * FlutterDefaultDartEntrypoint (or nil); this will default to `main()`. If it is not the app's + * main() function, that function must be decorated with `@pragma(vm:entry-point)` to ensure the + * method is not tree-shaken by the Dart compiler. + * @param libraryURI The URI of the Dart library which contains the entrypoint method. IF nil, + * this will default to the same library as the `main()` function in the Dart program. + * * @see FlutterEngineGroup */ - (FlutterEngine*)makeEngineWithEntrypoint:(nullable NSString*)entrypoint libraryURI:(nullable NSString*)libraryURI; + +/** + * Creates a running `FlutterEngine` that shares components with this group. + * + * @param entrypoint The name of a top-level function from a Dart library. If this is + * FlutterDefaultDartEntrypoint (or nil); this will default to `main()`. If it is not the app's + * main() function, that function must be decorated with `@pragma(vm:entry-point)` to ensure the + * method is not tree-shaken by the Dart compiler. + * @param libraryURI The URI of the Dart library which contains the entrypoint method. IF nil, + * this will default to the same library as the `main()` function in the Dart program. + * @param initialRoute The name of the initial Flutter `Navigator` `Route` to load. If this is + * FlutterDefaultInitialRoute (or nil), it will default to the "/" route. + * + * @see FlutterEngineGroup + */ +- (FlutterEngine*)makeEngineWithEntrypoint:(nullable NSString*)entrypoint + libraryURI:(nullable NSString*)libraryURI + initialRoute:(nullable NSString*)initialRoute; @end NS_ASSUME_NONNULL_END diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index 818b5d39fde..c19ea2a00c9 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -998,7 +998,8 @@ static void SetEntryPoint(flutter::Settings* settings, NSString* entrypoint, NSS } - (FlutterEngine*)spawnWithEntrypoint:(/*nullable*/ NSString*)entrypoint - libraryURI:(/*nullable*/ NSString*)libraryURI { + libraryURI:(/*nullable*/ NSString*)libraryURI + initialRoute:(/*nullable*/ NSString*)initialRoute { NSAssert(_shell, @"Spawning from an engine without a shell (possibly not run)."); FlutterEngine* result = [[FlutterEngine alloc] initWithName:_labelPrefix project:_dartProject.get() @@ -1027,8 +1028,13 @@ static void SetEntryPoint(flutter::Settings* settings, NSString* entrypoint, NSS flutter::Shell::CreateCallback on_create_rasterizer = [](flutter::Shell& shell) { return std::make_unique(shell); }; - std::unique_ptr shell = - _shell->Spawn(std::move(configuration), on_create_platform_view, on_create_rasterizer); + std::string cppInitialRoute; + if (initialRoute) { + cppInitialRoute = [initialRoute UTF8String]; + } + + std::unique_ptr shell = _shell->Spawn( + std::move(configuration), cppInitialRoute, on_create_platform_view, on_create_rasterizer); result->_threadHost = _threadHost; result->_profiler = _profiler; diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineGroup.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineGroup.mm index 8b12094a4c4..6c03558b146 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineGroup.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineGroup.mm @@ -35,14 +35,22 @@ - (FlutterEngine*)makeEngineWithEntrypoint:(nullable NSString*)entrypoint libraryURI:(nullable NSString*)libraryURI { + return [self makeEngineWithEntrypoint:entrypoint libraryURI:libraryURI initialRoute:nil]; +} + +- (FlutterEngine*)makeEngineWithEntrypoint:(nullable NSString*)entrypoint + libraryURI:(nullable NSString*)libraryURI + initialRoute:(nullable NSString*)initialRoute { NSString* engineName = [NSString stringWithFormat:@"%@.%d", self.name, ++_enginesCreatedCount]; FlutterEngine* engine; if (self.engines.count <= 0) { engine = [[FlutterEngine alloc] initWithName:engineName project:self.project]; - [engine runWithEntrypoint:entrypoint libraryURI:libraryURI]; + [engine runWithEntrypoint:entrypoint libraryURI:libraryURI initialRoute:initialRoute]; } else { FlutterEngine* spawner = (FlutterEngine*)[self.engines[0] pointerValue]; - engine = [spawner spawnWithEntrypoint:entrypoint libraryURI:libraryURI]; + engine = [spawner spawnWithEntrypoint:entrypoint + libraryURI:libraryURI + initialRoute:initialRoute]; } [_engines addObject:[NSValue valueWithPointer:engine]]; diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm index 6ce03ebbc12..1263b71adf7 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm @@ -194,7 +194,7 @@ FLUTTER_ASSERT_ARC - (void)testSpawn { FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"]; [engine run]; - FlutterEngine* spawn = [engine spawnWithEntrypoint:nil libraryURI:nil]; + FlutterEngine* spawn = [engine spawnWithEntrypoint:nil libraryURI:nil initialRoute:nil]; XCTAssertNotNil(spawn); } diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineTest_mrc.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineTest_mrc.mm index a13b669b13c..612404f926f 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineTest_mrc.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineTest_mrc.mm @@ -38,7 +38,7 @@ FLUTTER_ASSERT_NOT_ARC - (void)testSpawnsShareGpuContext { FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"]; [engine run]; - FlutterEngine* spawn = [engine spawnWithEntrypoint:nil libraryURI:nil]; + FlutterEngine* spawn = [engine spawnWithEntrypoint:nil libraryURI:nil initialRoute:nil]; XCTAssertNotNil(spawn); XCTAssertTrue([engine iosPlatformView] != nullptr); XCTAssertTrue([spawn iosPlatformView] != nullptr); diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h index c3ae89ca837..d81eaf6a6c2 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h @@ -67,7 +67,8 @@ extern NSString* _Nonnull const FlutterEngineWillDealloc; * This should only be called on a FlutterEngine that is running. */ - (nonnull FlutterEngine*)spawnWithEntrypoint:(nullable NSString*)entrypoint - libraryURI:(nullable NSString*)libraryURI; + libraryURI:(nullable NSString*)libraryURI + initialRoute:(nullable NSString*)initialRoute; /** * Dispatches the given key event data to the framework through the engine. diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h index e241d22b1e8..9d251a24b1c 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h @@ -19,6 +19,7 @@ class ThreadHost; - (flutter::IOSRenderingAPI)platformViewsRenderingAPI; - (void)waitForFirstFrame:(NSTimeInterval)timeout callback:(void (^)(BOOL didTimeout))callback; - (FlutterEngine*)spawnWithEntrypoint:(/*nullable*/ NSString*)entrypoint - libraryURI:(/*nullable*/ NSString*)libraryURI; + libraryURI:(/*nullable*/ NSString*)libraryURI + initialRoute:(/*nullable*/ NSString*)initialRoute; - (const flutter::ThreadHost&)threadHost; @end diff --git a/engine/src/flutter/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m b/engine/src/flutter/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m index e0da4961ff4..b806e34541a 100644 --- a/engine/src/flutter/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m +++ b/engine/src/flutter/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m @@ -91,7 +91,7 @@ FlutterEngine* spawner = [[FlutterEngine alloc] initWithName:@"FlutterControllerTest" project:nil]; [spawner run]; - return [spawner spawnWithEntrypoint:nil libraryURI:nil]; + return [spawner spawnWithEntrypoint:nil libraryURI:nil initialRoute:nil]; } else { FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"FlutterControllerTest" project:nil]; diff --git a/engine/src/flutter/testing/scenario_app/ios/Scenarios/Scenarios/FlutterEngine+ScenariosTest.h b/engine/src/flutter/testing/scenario_app/ios/Scenarios/Scenarios/FlutterEngine+ScenariosTest.h index aef6bae0aa5..80ad6c8fcc5 100644 --- a/engine/src/flutter/testing/scenario_app/ios/Scenarios/Scenarios/FlutterEngine+ScenariosTest.h +++ b/engine/src/flutter/testing/scenario_app/ios/Scenarios/Scenarios/FlutterEngine+ScenariosTest.h @@ -9,6 +9,7 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithScenario:(NSString*)scenario withCompletion:(nullable void (^)(void))engineRunCompletion; - (FlutterEngine*)spawnWithEntrypoint:(nullable NSString*)entrypoint - libraryURI:(nullable NSString*)libraryURI; + libraryURI:(nullable NSString*)libraryURI + initialRoute:(nullable NSString*)initialRoute; @end NS_ASSUME_NONNULL_END