Add an inlay route, dialog, or store
The recurring inlay workflow: schema → codegen → wire Dart side → wire native side. Canonical docs are the source of truth for the code at each step:
- Routes, dialogs, navigation, results — https://github.com/leancodepl/inlay/blob/main/docs/navigation.md
- Stores and shared state — https://github.com/leancodepl/inlay/blob/main/docs/state.md
- Complete reference schema and wiring — https://github.com/leancodepl/inlay/tree/main/example/example_module
Everything crossing the platform boundary is typed — never pass strings or maps where a generated class exists.
1. Edit the schema
Schema files are the plain annotated Dart classes listed in the module's inlay.yaml
(routes: / stores:). Pick the annotation:
| You're adding | Annotation | Notes |
|---|---|---|
| Flutter screen | @InlayFlutterRoute('/path/:param') |
Path params (:param) map to required fields; other fields travel as extra data. |
| Flutter dialog / bottom sheet over a native screen | @InlayFlutterDialog('/path/:param') |
Same rules as routes; presented in a transparent native container. |
| Native screen callable from Flutter | @InlayNativeRoute() |
Fields become the typed payload. |
| Shared state | @InlayStore(key: 'prefix') + @InlayStoreKey() on scoping fields |
Value fields: primitives, enums, List/Map/data classes. Non-nullable complex fields need a default. |
A screen that returns a value to its caller declares result: on the annotation (e.g.
@InlayFlutterDialog('/confirm/:id', result: bool)) — this generates typed result plumbing on
both sides. See
https://github.com/leancodepl/inlay/blob/main/docs/navigation.md#returning-results-from-screens
2. Regenerate
From the module root:
dart run build_runner build
# or: dart run inlay_gen:inlay_gen --config inlay.yaml
This rewrites the Dart output and the Kotlin/Swift outputs configured in inlay.yaml.
Never edit *.g.dart / *.g.kt / *.g.swift by hand. Run dart format on the Dart output
directory afterwards — the generator's raw output is not formatter-clean, and unformatted
generated files will fail dart format --set-exit-if-changed checks in CI.
3. Wire the Flutter side
- Flutter route/dialog — register the path with whichever integration the entrypoint uses
(check the module's
inlayMain):- go_router: a
GoRoutefor the path; dialogs usepageBuilderwithInlayDialogPage/InlayBottomSheetPage— https://github.com/leancodepl/inlay/blob/main/docs/navigation.md#go_router-example - auto_route: a
NamedRouteDef; dialogs useInlayDialogLauncherinside a transparent zero-transition route — https://github.com/leancodepl/inlay/blob/main/docs/navigation.md#auto_route-example - imperative: add a
caseto theswitchover the sealed route — the compiler forces this (non-exhaustive switch error) — https://github.com/leancodepl/inlay/blob/main/docs/navigation.md#imperative-sealed-class--pattern-matching
- go_router: a
- Native route — Flutter calls it via
InlayNavigator.instance.push(MyNativePage(...).toNativeRoute()); nothing else Dart-side. - Store — instantiate the generated store with
KeyValueStorage.instance, or pair it withInlayCubit— https://github.com/leancodepl/inlay/blob/main/docs/state.md#using-stores-from-flutter-dart
4. Wire the native side
Do both platforms the project has:
- Flutter route/dialog — call it where needed:
InlayNavigator.shared.push/present(Dialog)(Swift),InlayNavigator.push/presentDialog/createFragment(Kotlin),InlayFlutterView/.inlayDialog(SwiftUI),InlayFlutterScreen/InlayFlutterDialog(Compose, needsinlay_compose) — https://github.com/leancodepl/inlay/blob/main/docs/navigation.md#navigating - Native route — the regenerated
NativeRouteHandlerbase class gains a typedon<Name>method; implement it in each host's handler (routes withresult:also get acompletioncallback to invoke) — https://github.com/leancodepl/inlay/blob/main/docs/navigation.md#handling-native-routes-flutter--native - Store — construct the generated wrapper over a
NativeStorageScope; observe withstartObserving+containsChanges— https://github.com/leancodepl/inlay/blob/main/docs/state.md#using-stores-from-native-code
5. Verify
dart analyzein the module — the imperative integration surfaces missed routes as non-exhaustiveswitcherrors here.- Rebuild both native hosts. Generated code embeds a schema fingerprint; a host built from
pre-change Kotlin/Swift fails at navigation time with
InlaySchemaMismatchException(Dart) /IllegalStateException(Android) /fatalError(iOS) rather than corrupting data. A schema change is not done until the hosts compile against the regenerated code. - Exercise the new route/store once end-to-end on each platform if a device/simulator is available.
Gotchas
- The Kotlin/Swift outputs live in the module's companion native plugin (see
inlay.yaml) — if new generated files aren't picked up by the iOS host, check they're inside the directory the app target/podspec already compiles. - Dialog engines need a transparent background: go_router setups switch
scaffoldBackgroundColorto transparent when the initial route is a dialog;runInlayDialoghandles it in the imperative setup. - Store writes don't notify the writer (self-notification suppression) — don't wait for your
own change on
stream.