diff --git a/shell/common/engine.cc b/shell/common/engine.cc index 387223644128c..28321a9fd166c 100644 --- a/shell/common/engine.cc +++ b/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/shell/common/engine.h b/shell/common/engine.h index a6f29905b5dde..ca73c591432a6 100644 --- a/shell/common/engine.h +++ b/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/shell/common/engine_unittests.cc b/shell/common/engine_unittests.cc index 3a83cc7a8684c..e841060c636ac 100644 --- a/shell/common/engine_unittests.cc +++ b/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/shell/common/shell.cc b/shell/common/shell.cc index 49d3ab16dc8a7..c6c723b51ab27 100644 --- a/shell/common/shell.cc +++ b/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/shell/common/shell.h b/shell/common/shell.h index 23754936c4e81..9fbdd5b1a423f 100644 --- a/shell/common/shell.h +++ b/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/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc index 2f16650f80a2d..02872fe562753 100644 --- a/shell/common/shell_unittests.cc +++ b/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/shell/platform/android/android_shell_holder.cc b/shell/platform/android/android_shell_holder.cc index a933c2a416e79..b85db32a6d1cc 100644 --- a/shell/platform/android/android_shell_holder.cc +++ b/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/shell/platform/android/android_shell_holder.h b/shell/platform/android/android_shell_holder.h index d6ac5272667cf..9ea264f366722 100644 --- a/shell/platform/android/android_shell_holder.h +++ b/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/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index b6d7814253769..828bb94586832 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -384,7 +384,9 @@ private boolean isAttachedToJni() { */ @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 @@ private boolean isAttachedToJni() { 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/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java index e62aad2bc70a5..4dd8f43922fbe 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java @@ -96,6 +96,27 @@ public FlutterEngine createAndRunDefaultEngine(@NonNull Context context) { */ 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 FlutterEngine createAndRunEngine( 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/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index 2c4baa80d7bb2..5da49e89157cc 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -335,11 +335,14 @@ public long performNativeAttach(@NonNull FlutterJNI 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 FlutterJNI spawn( 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 @@ private native FlutterJNI nativeSpawn( *

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/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index ac5d5cdf7be8b..376ac155516d0 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/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/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java index 363ca14fc8514..e836560e009c9 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java @@ -11,6 +11,7 @@ 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 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 void canSpawnMoreEngines() { 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 void canSpawnMoreEngines() { // 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 void canCreateAndRunCustomEntrypoints() { 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/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h b/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h index f3b63dea5369a..48f9d4bf56701 100644 --- a/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h +++ b/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/shell/platform/darwin/ios/framework/Headers/FlutterEngineGroup.h b/shell/platform/darwin/ios/framework/Headers/FlutterEngineGroup.h index 3ce923676ffdf..7bf295ddbb1cc 100644 --- a/shell/platform/darwin/ios/framework/Headers/FlutterEngineGroup.h +++ b/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/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index 818b5d39fde8b..c19ea2a00c966 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -998,7 +998,8 @@ - (void)waitForFirstFrame:(NSTimeInterval)timeout } - (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 @@ - (FlutterEngine*)spawnWithEntrypoint:(/*nullable*/ NSString*)entrypoint 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/shell/platform/darwin/ios/framework/Source/FlutterEngineGroup.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngineGroup.mm index 8b12094a4c4de..6c03558b146b9 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngineGroup.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngineGroup.mm @@ -35,14 +35,22 @@ - (void)dealloc { - (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/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm index 6ce03ebbc1218..1263b71adf707 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm @@ -194,7 +194,7 @@ - (void)testWaitForFirstFrameTimeout { - (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/shell/platform/darwin/ios/framework/Source/FlutterEngineTest_mrc.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngineTest_mrc.mm index a13b669b13c2f..612404f926ff4 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngineTest_mrc.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngineTest_mrc.mm @@ -38,7 +38,7 @@ - (void)tearDown { - (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/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h index c3ae89ca8372e..d81eaf6a6c2e7 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h +++ b/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/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h b/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h index e241d22b1e8da..9d251a24b1c6a 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h +++ b/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/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m b/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m index 86326d23cfae0..09cde8103446a 100644 --- a/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m +++ b/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m @@ -88,7 +88,7 @@ - (FlutterEngine*)engineForTest:(NSString*)scenarioIdentifier { 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/testing/scenario_app/ios/Scenarios/Scenarios/FlutterEngine+ScenariosTest.h b/testing/scenario_app/ios/Scenarios/Scenarios/FlutterEngine+ScenariosTest.h index aef6bae0aa5d6..80ad6c8fcc558 100644 --- a/testing/scenario_app/ios/Scenarios/Scenarios/FlutterEngine+ScenariosTest.h +++ b/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