On iOS
A main.m
file was autogenerated in your Flutter projects ios/Runner
directory, which defines the regular, main function which a C program implements, int main(int argc, char* argv[]);
.
One compiled output can only have one main method, which the compiler will run immediately when the program is started. The following code creates a UIApplicationMain
, which "Creates the application object and the application delegate and sets up the event cycle":
#import "AppDelegate.h"
int main(int argc, char* argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
It's simpler in Swift, just annotate AppDelegate
with @UIApplicationMain
.
The AppDelegate is the class which launches Flutter, because it extends FlutterAppDelegate
. When FlutterAppDelegate
is instantiated, iOS will create the FlutterViewController
, which creates a FlutterEngine. It creates FlutterViewController
because the FlutterViewController
is configured in the Main.storyboard
, which has been specified in the applications Info.plist
. So technically, Flutter apps are Storyboard apps .

Anyway, when the storyboard is created by iOS, the window
property is set on the AppDelegate. You can get the FlutterViewController
in the AppDelegate using window.rootViewController
. An Objective-C++ file, FlutterViewController.mm
's sharedSetupWithProject
method creates a FlutterEngine
using [[FlutterEngine alloc]initWithName:...
:
- (void)sharedSetupWithProject:(nullable FlutterDartProject*)project
initialRoute:(nullable NSString*)initialRoute {
// Need the project to get settings for the view. Initializing it here means
// the Engine class won't initialize it later.
if (!project) {
project = [[[FlutterDartProject alloc] init] autorelease];
}
FlutterView.forceSoftwareRendering = project.settings.enable_software_rendering;
auto engine = fml::scoped_nsobject<FlutterEngine>{[[FlutterEngine alloc]
initWithName:@"io.flutter"
project:project
allowHeadlessExecution:self.engineAllowHeadlessExecution
restorationEnabled:[self restorationIdentifier] != nil]};
if (!engine) {
return;
}
_viewOpaque = YES;
_weakFactory = std::make_unique<fml::WeakPtrFactory<FlutterViewController>>(self);
_engine = std::move(engine);
_flutterView.reset([[FlutterView alloc] initWithDelegate:_engine opaque:self.isViewOpaque]);
[_engine.get() createShell:nil libraryURI:nil initialRoute:initialRoute];
_engineNeedsLaunch = YES;
_ongoingTouches.reset([[NSMutableSet alloc] init]);
[self loadDefaultSplashScreenView];
[self performCommonViewControllerInitialization];
}
Eventually, in FlutterEngine.mm
, launchEngine
is called, with the entrypoint (your dart's main function.)
- (void)launchEngine:(NSString*)entrypoint libraryURI:(NSString*)libraryOrNil {
// Launch the Dart application with the inferred run configuration.
self.shell.RunEngine([_dartProject.get() runConfigurationForEntrypoint:entrypoint
libraryOrNil:libraryOrNil]);
}
Shell::RunEngine
is a C++ function implemented in shell.cc. I'm going to stop there for now. This is probably where the event loop starts , using platform_runner->PostTask(...)
.
On Android
In a generated Flutter project's android directory, MainActivity
is declared in the AndroidManifest.xml
to be the application which launches from the home screen.
When the activity is launched, Android will call the activity's onCreate
method. Because MainActivity
extends FlutterActivity
, this onCreate
method is called. FlutterActivityAndFragmentDelegate
is instantiated, and its onAttach
method is called. Eventually, a Java representation of FlutterEngine
is created using:
flutterEngine =
new FlutterEngine(
host.getContext(),
host.getFlutterShellArgs().toArray(),
/*automaticallyRegisterPlugins=*/ false,
/*willProvideRestorationData=*/ host.shouldRestoreAndSaveState());
This communicates with the C++ FlutterEngine library using FlutterJNI
.
/**
* Interface between Flutter embedding's Java code and Flutter engine's C/C++ code.
*
* <p>Flutter's engine is built with C/C++. The Android Flutter embedding is responsible for
* coordinating Android OS events and app user interactions with the C/C++ engine. Such coordination
* requires messaging from an Android app in Java code to the C/C++ engine code. This communication
* requires a JNI (Java Native Interface) API to cross the Java/native boundary.
*
Later, in FlutterActivityAndFragmentDelegate
, doInitialFlutterViewRun
is called, which creates a DartEntryPoint
, your main method again, based on the FlutterActivity's ActivityInfo
. This gets the entrpoint function name using the following, but it will default to "main"
:
@NonNull
public String getDartEntrypointFunctionName() {
try {
Bundle metaData = getMetaData();
String desiredDartEntrypoint =
metaData != null ? metaData.getString(DART_ENTRYPOINT_META_DATA_KEY) : null;
return desiredDartEntrypoint != null ? desiredDartEntrypoint : DEFAULT_DART_ENTRYPOINT;
} catch (PackageManager.NameNotFoundException e) {
return DEFAULT_DART_ENTRYPOINT;
}
}
Then, flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint);
is called. So your main method has "been called". But this uses the FlutterJNI
and the FlutterEngine C++ code. First, flutterJNI.runBundleAndSnapshotFromLibrary
is called, and finally this JNI native method:
private native void nativeRunBundleAndSnapshotFromLibrary(
long nativeShellHolderId,
@NonNull String bundlePath,
@Nullable String entrypointFunctionName,
@Nullable String pathToEntrypointFunction,
@NonNull AssetManager manager);
This native method is defined in platform_view_adroid_jni_impl.cc
:
{
.name = "nativeRunBundleAndSnapshotFromLibrary",
.signature = "(JLjava/lang/String;Ljava/lang/String;"
"Ljava/lang/String;Landroid/content/res/AssetManager;)V",
.fnPtr = reinterpret_cast<void*>(&RunBundleAndSnapshotFromLibrary),
},
Where RunBundleAndSnapshotFromLibrary
is a C++ method:
static void RunBundleAndSnapshotFromLibrary(JNIEnv* env,
jobject jcaller,
jlong shell_holder,
jstring jBundlePath,
jstring jEntrypoint,
jstring jLibraryUrl,
jobject jAssetManager) {
auto asset_manager = std::make_shared<flutter::AssetManager>();
asset_manager->PushBack(std::make_unique<flutter::APKAssetProvider>(
env, // jni environment
jAssetManager, // asset manager
fml::jni::JavaStringToString(env, jBundlePath)) // apk asset dir
);
auto entrypoint = fml::jni::JavaStringToString(env, jEntrypoint);
auto libraryUrl = fml::jni::JavaStringToString(env, jLibraryUrl);
ANDROID_SHELL_HOLDER->Launch(asset_manager, entrypoint, libraryUrl);
}
Where AndroidShellHolder::Launch
is:
void AndroidShellHolder::Launch(std::shared_ptr<AssetManager> asset_manager,
const std::string& entrypoint,
const std::string& libraryUrl) {
if (!IsValid()) {
return;
}
asset_manager_ = asset_manager;
auto config = BuildRunConfiguration(asset_manager, entrypoint, libraryUrl);
if (!config) {
return;
}
shell_->RunEngine(std::move(config.value()));
}
Just like iOS, Shell::RunEngine
is called.