Skip to main content

deep_links_kit 0.1.1 copy "deep_links_kit: ^0.1.1" to clipboard
deep_links_kit: ^0.1.1 copied to clipboard

One config for Android App Links and iOS Universal Links: a CLI that sets up the native projects and server files, plus router-agnostic link handling.

example/example.md

deep_links_kit example #

A shop app with go_router, built with Clean Architecture: a splash, a home page, a product list and product details. Tap myapp://products/42 and the app opens on that product after the splash, with Home under it so Back works.

Four steps, the same as in the package README. Using auto_route? Only two lines change, see "Using auto_route" below.

deep_links_kit:
  hosts: [app.example.com]
  scheme: myapp

Then run this. Run it again whenever you change these keys.

dart run deep_links_kit setup

It edits the native projects, so you never touch these files by hand.

Android: in android/app/src/main/AndroidManifest.xml, inside MainActivity, it adds one block and replaces it on later runs:

<!-- deep_links_kit:start: generated by `dart run deep_links_kit setup` -->
<meta-data
    android:name="flutter_deeplinking_enabled"
    android:value="false" />
<intent-filter android:autoVerify="true">
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="https" />
    <data android:host="app.example.com" />
</intent-filter>
<intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="myapp" />
</intent-filter>
<!-- deep_links_kit:end -->

iOS:

  • ios/Runner/Runner.entitlements: applinks:app.example.com under Associated Domains. The file is created and linked in the Xcode project when it does not exist yet.
  • ios/Runner/Info.plist: the myapp URL scheme.
  • deep_links_kit.lock, next to pubspec.yaml: what it added on iOS, so a later run removes only its own entries. Commit this file.

On both, it sets flutter_deeplinking_enabled / FlutterDeepLinkingEnabled to false. Otherwise Flutter's own deep linking opens every link a second time.

One change is yours to make. setup warns when MainActivity uses launchMode="singleTop", the Flutter default. Change it to singleTask in AndroidManifest.xml. With singleTop, a link tapped in another app starts a second copy of your app, and the splash runs again.

All the deep link code of the app is in this file. router is your GoRouter, from lib/core/router/app_router.dart.

final deepLinks = DeepLinksKit(
  navigator: DeepLinkNavigator.custom(push: router.push, replace: router.go),
  routes: [
    // myapp://products opens the product list
    DeepLinkRoute('/products', to: (_) => AppRoutes.products),

    // myapp://products/42 opens product 42
    DeepLinkRoute('/products/:id', to: (m) => AppRoutes.product(m['id']!)),

    // https://app.example.com/p/42, a short link, also opens product 42
    DeepLinkRoute('/p/:id', to: (m) => AppRoutes.product(m['id']!)),
  ],
  fallback: (_) => AppRoutes.home, // optional: any other link opens home
);
  • Each DeepLinkRoute says where a link goes: the link on the left, the page in your app in to:.
  • :id is the part of the link you get back as m['id'].
  • fallback is optional. It is where a link goes when no route matches it, like https://app.example.com/unknown. It replaces the open screens, so Back does not return to them. Without fallback, such links are ignored and the app stays where it is.

3. lib/main.dart: start listening #

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  initDependencies();
  deepLinks.init(); // before runApp, so the launch link is kept
  runApp(const App());
}

Links wait until you call ready(), so they never open behind the splash. Call it right after the splash opens the first screen:

        listener: (context, state) {
          if (state is SplashDone) {
            context.go(AppRoutes.home);
            deepLinks.ready(); // links that arrived meanwhile open now
          }
        },

No splash? Call ready() in main, after the first frame, so the link opens on top of your first page and Back returns to it:

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  deepLinks.init();
  runApp(const App());
  // No splash: open links once the first page is on screen.
  WidgetsBinding.instance.addPostFrameCallback((_) => deepLinks.ready());
}

Don't call ready() before runApp: the link would open before the router has built your first page, and Back would have nothing to return to.

That's it. The rest of the app is ordinary Clean Architecture and knows nothing about deep links:

lib/
  main.dart
  app.dart
  core/
    deep_links/   deepLinks (step 2)
    di/           get_it, for the features' cubits and repositories
    router/       go_router and the page paths
    error/        exceptions and failures
    usecases/     the UseCase base class
    fake_api/     stands in for your backend
    widgets/      widgets more than one feature uses
  features/
    splash/           data / domain / presentation (step 4)
    home/             data / domain / presentation
    product_list/     data / domain / presentation
    product_details/  data / domain / presentation

Using auto_route #

The steps are the same. Only two lines change. A small auto_route app is in lib/with_auto_route/.

lib/with_auto_route/deep_links.dart #

  navigator: DeepLinkNavigator.custom(
    push: router.pushPath,
    replace: router.navigatePath,
  ),

lib/with_auto_route/screens.dart #

    context.router.replace(const HomeRoute());
    deepLinks.ready(); // links that arrived meanwhile open now

lib/with_auto_route/app_router.dart #

Give each route a path:. Those are the paths deep links open:

    AutoRoute(page: HomeRoute.page, path: '/home'),
    AutoRoute(page: ProductRoute.page, path: '/products/:id'),
    AutoRoute(page: UserRoute.page, path: '/users/:id'),

Run this example #

From the GitHub repo, the Android and iOS folders are ready. In the copy from pub.dev they are not included, so create them first. This is also how you try everything from scratch:

flutter create --platforms=android,ios .
rm test/widget_test.dart  # flutter create adds it for an app this one is not
dart run deep_links_kit setup

Then change launchMode to singleTask, as in step 1, and run one of the two apps:

flutter run                              # go_router, Clean Architecture
flutter run -t lib/main_auto_route.dart  # auto_route

Tap the sample links on the home page to open them inside the app. To open a link from another app, use the share button next to a web link, or on a product page, and tap the link in WhatsApp or Notes. On Android 11 and older, pick the app under "Open with". On Android 12 and later, and on iOS, web links open the app only for a domain you own, with the files from dart run deep_links_kit generate on it. Put that domain in hosts and in appLinksHost in lib/core/deep_links/app_deep_links.dart.

You can also send a link from your computer:

adb shell am start -a android.intent.action.VIEW -d "myapp://products/42"
xcrun simctl openurl booted "myapp://products/42"
5
likes
160
points
141
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

One config for Android App Links and iOS Universal Links: a CLI that sets up the native projects and server files, plus router-agnostic link handling.

Repository (GitHub)
View/report issues

Topics

#deep-linking #app-links #universal-links #navigation #cli

License

MIT (license)

Dependencies

app_links, args, flutter, yaml

More

Packages that depend on deep_links_kit