# AppFlowy Startup Initialization Flow: From entry_point.dart to SplashScreen

> Explore the AppFlowy startup initialization flow from entry_point.dart to SplashScreen. Understand how dependency injection and backend setup lead to the initial UI render.

- Repository: [AppFlowy-IO/AppFlowy](https://github.com/AppFlowy-IO/AppFlowy)
- Tags: internals
- Published: 2026-03-03

---

**The AppFlowy startup initialization flow orchestrates dependency injection, backend initialization, and widget tree construction through a sequential task runner that ultimately renders the SplashScreen widget from the registered EntryPoint implementation.**

The AppFlowy-IO/AppFlowy repository implements a sophisticated boot sequence that manages everything from Rust SDK initialization to user authentication before displaying the first UI. Understanding this startup initialization flow is essential for developers contributing to the codebase or debugging launch-time issues. The pipeline begins in `main.dart` and terminates with the `SplashScreen` widget that handles initial navigation logic.

## Entry Point and Mode Detection

### The main.dart Trigger

The initialization begins in `frontend/appflowy_flutter/lib/main.dart`, which serves as the minimal Dart entry point. After ensuring a 1:1 UI scale factor, it awaits `runAppFlowy()` to kick off the launch pipeline.

```dart
Future<void> main() async {
  ScaledWidgetsFlutterBinding.ensureInitialized(
    scaleFactor: (_) => 1.0,
  );

  await runAppFlowy();   // ← launches the whole startup pipeline
}

```

### Integration Mode Selection

Located in `frontend/appflowy_flutter/lib/startup/startup.dart`, the `runAppFlowy()` function first determines the `IntegrationMode` (release, develop, or test) before delegating execution to `FlowyRunner.run()`. This abstraction allows the application to boot with environment-specific configurations.

```dart
Future<void> runAppFlowy({bool isAnon = false}) async {
  // decide mode (release / develop / test)
  final mode = kReleaseMode ? integrationMode() : integrationMode();

  await FlowyRunner.run(
    AppFlowyApplication(),
    mode,
    isAnon: isAnon,
  );
}

```

## FlowyRunner Orchestrates the Launch Pipeline

### LaunchConfiguration Assembly

The `FlowyRunner.run()` method acts as the central orchestrator for the startup initialization flow. It constructs a `LaunchConfiguration` object containing the app version, anonymity flag, and Rust environment variables. Immediately after, it invokes `initGetIt()` to populate the dependency injection container.

According to the AppFlowy source code in `startup.dart`, `FlowyRunner` performs three critical operations before displaying any UI:

1. **Configuration assembly** - Gathers runtime parameters into a `LaunchConfiguration` instance
2. **Dependency registration** - Calls `initGetIt()` to register the concrete `EntryPoint` implementation (`AppFlowyApplication`) and backend services
3. **Task queuing** - Registers a list of `LaunchTask` objects including error catchers, memory-leak detectors, localization setup, window initialization, Rust SDK initialization, and `InitAppWidgetTask`

### LaunchTask Registration and Execution

Once registration completes, `FlowyRunner` executes `AppLauncher.launch()`, which iterates through the queued tasks and invokes each task's `initialize` method sequentially. This ensures that critical background services initialize before the widget tree builds.

## Dependency Injection and the EntryPoint Abstraction

The `initGetIt()` function establishes the service locator pattern used throughout the application. It registers `AppFlowyApplication` as the concrete implementation of the abstract `EntryPoint` interface defined in `frontend/appflowy_flutter/lib/startup/entry_point.dart`.

This abstraction allows the startup system to remain decoupled from specific UI implementations. The `EntryPoint` interface exposes a single `create(LaunchConfiguration config)` method that returns the root widget. In `AppFlowyApplication`, this method instantiates the `SplashScreen`:

```dart
class AppFlowyApplication implements EntryPoint {
  @override
  Widget create(LaunchConfiguration config) {
    return SplashScreen(isAnon: config.isAnon);
  }
}

```

## Widget Tree Construction

### InitAppWidgetTask.initialize() Execution

The `InitAppWidgetTask` class in `frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart` handles the critical transition from background initialization to visible UI. When `AppLauncher.launch()` processes this task, it executes `InitAppWidgetTask.initialize()`.

This method performs essential Flutter setup before building the widget tree:

```dart
class InitAppWidgetTask extends LaunchTask {
  const InitAppWidgetTask();

  @override
  Future<void> initialize(LaunchContext context) async {
    WidgetsFlutterBinding.ensureInitialized();

    // Load notification service, icon groups, user settings...
    
    // Build the root widget (SplashScreen)
    final widget = context.getIt<EntryPoint>().create(context.config);

    final app = ApplicationWidget(
      appearanceSetting: await UserSettingsBackendService().getAppearanceSetting(),
      dateTimeSettings: await UserSettingsBackendService().getDateTimeSettings(),
      appTheme: await appTheme(appearanceSetting.theme),
      child: widget,
    );

    runApp(
      EasyLocalization(
        supportedLocales: const [Locale('en', 'US')],
        path: 'assets/translations',
        child: Builder(builder: (_) => app),
      ),
    );
  }
}

```

The task retrieves the registered `EntryPoint` from the dependency injection container, invokes `create()` to obtain the `SplashScreen`, wraps it in `ApplicationWidget` and `EasyLocalization` for internationalization, and finally calls `runApp()` with a `MaterialApp.router` configuration.

## First UI: SplashScreen Logic

### Anonymous Mode Handling

The `SplashScreen` widget in `frontend/appflowy_flutter/lib/user/presentation/screens/splash_screen.dart` serves as the first visible interface. It accepts the `isAnon` parameter from the launch configuration to determine whether to operate in anonymous mode.

In anonymous mode, the widget first ensures a guest user exists through `_registerIfNeeded()`. It then provides a `SplashBloc` that dispatches `SplashEvent.getUser()` to check authentication status.

### Authentication State Navigation

Based on the `SplashBloc` state, the screen handles routing:

- **Authenticated** - Queries the backend for the current workspace and navigates to the home screen via `SplashRouter.goHomeScreen`
- **Unauthenticated** - Routes to `SignInScreen` or `SkipLogInScreen` when cloud authentication is disabled

```dart
class SplashScreen extends StatelessWidget {
  const SplashScreen({super.key, required this.isAnon});

  @override
  Widget build(BuildContext context) {
    // In anonymous mode we may need to auto‑register a guest user.
    if (isAnon) {
      return FutureBuilder<void>(
        future: _registerIfNeeded(),
        builder: (c, snapshot) =>
            snapshot.connectionState == ConnectionState.done
                ? _buildChild(c)
                : const SizedBox.shrink(),
      );
    }
    return _buildChild(context);
  }

  // _buildChild contains BlocProvider and navigation logic
}

```

## Summary

- The **startup initialization flow** in AppFlowy begins at `main.dart` and progresses through `runAppFlowy()` to `FlowyRunner.run()` in `startup.dart`
- **Dependency injection** is established early via `initGetIt()`, which registers `AppFlowyApplication` as the `EntryPoint` implementation
- **Launch tasks** execute sequentially through `AppLauncher.launch()`, with `InitAppWidgetTask` responsible for calling `runApp()`
- The **SplashScreen** widget is created through the `EntryPoint.create()` abstraction and handles the first user-visible logic, including anonymous user registration and authentication routing
- All backend initialization, including Rust SDK setup and plugin loading, occurs within the launch task queue before the UI appears

## Frequently Asked Questions

### What is the role of FlowyRunner in the AppFlowy startup process?

**FlowyRunner** serves as the central orchestrator that coordinates the entire boot sequence. It assembles the `LaunchConfiguration`, initializes the dependency injection container via `initGetIt()`, registers all `LaunchTask` instances, and triggers sequential execution through `AppLauncher.launch()`. This design ensures that critical background services like the Rust SDK and error handling are ready before any UI renders.

### How does AppFlowy handle dependency injection during startup?

AppFlowy uses the **GetIt** service locator pattern through the `initGetIt()` function called within `FlowyRunner.run()`. This registration phase binds the `AppFlowyApplication` class to the `EntryPoint` interface and configures singletons for backend services, plugin management, and navigation observers. The `InitAppWidgetTask` later retrieves the `EntryPoint` from this container to create the root widget.

### What happens when AppFlowy starts in anonymous mode?

When the `isAnon` flag is set to true in the `LaunchConfiguration`, the **SplashScreen** executes `_registerIfNeeded()` to ensure a guest user exists in the local database before proceeding. This allows users to interact with the application immediately without authentication credentials, while still maintaining user-specific data isolation through the guest account mechanism.

### Why does AppFlowy use a task-based launch system instead of direct initialization?

The **LaunchTask** architecture provides modularity and testability by decomposing startup logic into discrete, ordered units. Each task—whether initializing the Rust SDK, loading plugins, or building the widget tree—implements a consistent `initialize()` interface. This allows developers to inject custom tasks, reorder initialization sequences, and mock specific boot stages during integration testing without modifying the core runner logic.