Make FlutterEngineGroup support initialRoute (flutter/engine#28884)

This commit is contained in:
ColdPaleLight 2021-09-30 02:02:37 +08:00 committed by GitHub
parent a3553b7f41
commit 3bd57ac05a
23 changed files with 204 additions and 37 deletions

View File

@ -112,7 +112,8 @@ std::unique_ptr<Engine> Engine::Spawn(
Delegate& delegate,
const PointerDataDispatcherMaker& dispatcher_maker,
Settings settings,
std::unique_ptr<Animator> animator) const {
std::unique_ptr<Animator> animator,
const std::string& initial_route) const {
auto result = std::make_unique<Engine>(
/*delegate=*/delegate,
/*dispatcher_maker=*/dispatcher_maker,
@ -133,6 +134,7 @@ std::unique_ptr<Engine> Engine::Spawn(
settings_.isolate_shutdown_callback, // isolate shutdown callback
settings_.persistent_isolate_data // persistent isolate data
);
result->initial_route_ = initial_route;
return result;
}

View File

@ -375,7 +375,8 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate {
Delegate& delegate,
const PointerDataDispatcherMaker& dispatcher_maker,
Settings settings,
std::unique_ptr<Animator> animator) const;
std::unique_ptr<Animator> animator,
const std::string& initial_route) const;
//----------------------------------------------------------------------------
/// @brief Destroys the engine engine. Called by the shell on the UI task

View File

@ -268,13 +268,39 @@ TEST_F(EngineTest, SpawnSharesFontLibrary) {
/*font_collection=*/std::make_shared<FontCollection>(),
/*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<MockRuntimeController>(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<Engine>(
/*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<FontCollection>(),
/*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;

View File

@ -480,6 +480,7 @@ Shell::~Shell() {
std::unique_ptr<Shell> Shell::Spawn(
RunConfiguration run_configuration,
const std::string& initial_route,
const CreateCallback<PlatformView>& on_create_platform_view,
const CreateCallback<Rasterizer>& on_create_rasterizer) const {
FML_DCHECK(task_runners_.IsValid());
@ -488,7 +489,7 @@ std::unique_ptr<Shell> 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<const DartSnapshot> isolate_snapshot,
@ -501,7 +502,8 @@ std::unique_ptr<Shell> 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;

View File

@ -194,6 +194,7 @@ class Shell final : public PlatformView::Delegate,
/// @see http://flutter.dev/go/multiple-engines
std::unique_ptr<Shell> Spawn(
RunConfiguration run_configuration,
const std::string& initial_route,
const CreateCallback<PlatformView>& on_create_platform_view,
const CreateCallback<Rasterizer>& on_create_rasterizer) const;

View File

@ -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<MockPlatformView>(
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.

View File

@ -174,7 +174,8 @@ const flutter::Settings& AndroidShellHolder::GetSettings() const {
std::unique_ptr<AndroidShellHolder> AndroidShellHolder::Spawn(
std::shared_ptr<PlatformViewAndroidJNI> 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> AndroidShellHolder::Spawn(
return nullptr;
}
std::unique_ptr<flutter::Shell> shell = shell_->Spawn(
std::move(config.value()), on_create_platform_view, on_create_rasterizer);
std::unique_ptr<flutter::Shell> shell =
shell_->Spawn(std::move(config.value()), initial_route,
on_create_platform_view, on_create_rasterizer);
return std::unique_ptr<AndroidShellHolder>(
new AndroidShellHolder(GetSettings(), jni_facade, thread_host_,

View File

@ -78,7 +78,8 @@ class AndroidShellHolder {
std::unique_ptr<AndroidShellHolder> Spawn(
std::shared_ptr<PlatformViewAndroidJNI> 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<AssetManager> asset_manager,
const std::string& entrypoint,

View File

@ -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

View File

@ -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}.
*
* <p>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.
*
* <p>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);

View File

@ -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 {
* <p>This method must not be invoked if {@code FlutterJNI} is not already attached to native.
*
* <p>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.
*
* <p>It is permissible to re-attach this instance to native after detaching it from native.

View File

@ -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<void*>(&SpawnJNI),
},

View File

@ -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"));
}
}

View File

@ -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.
*

View File

@ -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

View File

@ -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<flutter::Rasterizer> on_create_rasterizer =
[](flutter::Shell& shell) { return std::make_unique<flutter::Rasterizer>(shell); };
std::unique_ptr<flutter::Shell> shell =
_shell->Spawn(std::move(configuration), on_create_platform_view, on_create_rasterizer);
std::string cppInitialRoute;
if (initialRoute) {
cppInitialRoute = [initialRoute UTF8String];
}
std::unique_ptr<flutter::Shell> shell = _shell->Spawn(
std::move(configuration), cppInitialRoute, on_create_platform_view, on_create_rasterizer);
result->_threadHost = _threadHost;
result->_profiler = _profiler;

View File

@ -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]];

View File

@ -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);
}

View File

@ -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);

View File

@ -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.

View File

@ -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

View File

@ -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];

View File

@ -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