# Inlay Add Route

> Add or change inlay routes, dialogs, and shared stores in a Flutter add-to-app module — edit the annotated Dart schema, regenerate Dart/Kotlin/Swift, and wire both the Flutter and native sides. Use when adding a Flutter screen or dialog reachable from native, a native screen reachable from Flutter, or state shared between native and Flutter, in a project that already uses inlay.

- Skill: `leancodepl/inlay-add-route` (Agent Skill)
- Install (CLI): `npx skillmds@latest add leancodepl/inlay-add-route`
- Raw SKILL.md: https://api.skillmd.com/api/skills/leancodepl/inlay-add-route/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: leancodepl (https://skillmd.com/u/leancodepl)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/leancodepl/inlay-add-route

---


# 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:

```bash
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 `GoRoute` for the path; dialogs use `pageBuilder` with `InlayDialogPage` /
    `InlayBottomSheetPage` —
    https://github.com/leancodepl/inlay/blob/main/docs/navigation.md#go_router-example
  - auto_route: a `NamedRouteDef`; dialogs use `InlayDialogLauncher` inside a transparent
    zero-transition route —
    https://github.com/leancodepl/inlay/blob/main/docs/navigation.md#auto_route-example
  - imperative: add a `case` to the `switch` over 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
- **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 with
  `InlayCubit` — 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, needs
  `inlay_compose`) — https://github.com/leancodepl/inlay/blob/main/docs/navigation.md#navigating
- **Native route** — the regenerated `NativeRouteHandler` base class gains a typed `on<Name>`
  method; implement it in each host's handler (routes with `result:` also get a `completion`
  callback 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 with
  `startObserving` + `containsChanges` —
  https://github.com/leancodepl/inlay/blob/main/docs/state.md#using-stores-from-native-code

## 5. Verify

1. `dart analyze` in the module — the imperative integration surfaces missed routes as
   non-exhaustive `switch` errors here.
2. **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.
3. 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
  `scaffoldBackgroundColor` to transparent when the initial route is a dialog; `runInlayDialog`
  handles it in the imperative setup.
- Store writes don't notify the writer (self-notification suppression) — don't wait for your
  own change on `stream`.

