App startup, from first principles
App startup, from first principles
Section titled “App startup, from first principles”A six-part walkthrough of what happens between tapping the icon and seeing a film poster — why that used to take so long (and sometimes never finished at all), and what the current design does instead.
Written for someone who has not worked on this codebase’s startup path before. Each doc builds on the last, and every claim points at a real file.
Read them in order the first time through:
- The old startup — the sequence as it was: five network calls in single file, no deadline, nothing cached, and a 2.9 MB animation decoding on top of it. This is the “why”.
- The network layer — why a fresh
Dio()per request was the single most expensive line in the app, what a missingconnectTimeoutactually does on a bad network, and the shared client that replaced it. - Cache-first boot — the disk cache, how it is keyed, and the repository pattern that lets a screen paint from disk and upgrade to fresh data behind the user.
- The bootstrap and the splash — the three startup phases, what each is allowed to block, how the splash became a router decision instead of a loader, and why the profile gate may only ever be asked once per launch.
- The first frame and after — what still runs
before
runApp, what moved behind it, why four screens used to boot at once, and the two native launch screens nobody had touched. - Before and after — the side-by-side comparison, how to measure it yourself, and what is deliberately still open.
The one-paragraph version
Section titled “The one-paragraph version”Startup used to be a chain: the splash awaited /v2/settings, then a DNS
probe, then /user/profiles, then the home feed, then continue-watching —
each waiting on the one before it, each on a brand-new TCP connection,
with no connectTimeout and nothing cached anywhere. On a healthy
network that is five round trips; on a dead one it is the OS socket
timeout five times over, which is why the app could sit on the looping
splash GIF apparently forever. It now works the other way round: a
returning user’s session, settings, nav items and home feed are restored
from disk, the app routes straight to home, and the network refresh
runs behind the user and swaps fresh content in when it lands. Only a
genuine first install waits on the network, and that wait is parallel and
capped.
Which state this describes
Section titled “Which state this describes”main, as of August 2026, after the cache-first startup work. Everything
described here is implemented — where something was deliberately left
undone, doc 6 says so explicitly.
Map to the code
Section titled “Map to the code”| Doc | Primary files |
|---|---|
| 1. The old startup | (history) lib/presentation/bloc/splash_screen/splash_screen_bloc.dart, lib/main.dart |
| 2. The network layer | lib/core/network/api_client.dart, lib/core/network/dio_api_provider.dart, lib/core/network/api_result.dart |
| 3. Cache-first boot | lib/data/datasources/local/api_cache_store.dart, lib/data/datasources/local/{home,content}_local_data_source.dart, lib/data/repositories/{home,content}_repository_impl.dart |
| 4. Bootstrap & splash | lib/domain/services/app_bootstrap.dart, lib/data/services/app_bootstrap_impl.dart, lib/presentation/bloc/splash_screen/splash_screen_bloc.dart, lib/presentation/pages/splash_screen.dart |
| 5. First frame & after | lib/main.dart, lib/presentation/pages/home_pages/main_scaffold.dart, lib/presentation/bloc/home_screen/home_screen_bloc.dart, android/app/src/main/res/drawable*/launch_background.xml, ios/Runner/Base.lproj/LaunchScreen.storyboard |
| 6. Before and after | all of the above, plus test/data/datasources/local/api_cache_store_test.dart, test/data/repositories/home_repository_impl_test.dart, test/presentation/bloc/splash_screen/splash_screen_routing_test.dart |
Related reading
Section titled “Related reading”../playback/— how a film actually plays once you have got this far.../api/README.md— the REST surface these startup calls are drawn from.