Deep Link Navigation - Navigation Library for Flutter

Elegant abstraction for deep linking navigation in Flutter. 60+ stars on Github.

demo

Flutter is a application framework that compiles natively for iOS, Android, and the web. Flutter uses the Dart programming language and my library is published on pub.dev (the Dart equivalent of npm).

The demo is an app from the examples folder that demonstrates deep linking using my library. I compiled Flutter for the web, and stuffed it into an iframe.

Sample app

This is the page hierarchy for the demo application above. A deep link allows you to directly navigate to any page in the hierarchy (like a URL).

Deep links correspond to a page in the application. They're represented as simple dart classes. Some deep links have associated data, which is supported with generics.

class LibraryDL extends DeepLink {
  LibraryDL() : super("library");
}

class FavoritesDL extends DeepLink {
  FavoritesDL() : super("favorites");
}

class ArtistDL extends ValueDeepLink<Artist> {
  ArtistDL(Artist artist) : super("artist", artist, toString: (artist) => artist.id);
}

class SongDL extends ValueDeepLink<Song> {
  SongDL(Song song) : super("song", song, toString: (song) => song.id);
}

class ErrorDL<E extends Exception> extends ValueDeepLink<E> {
  ErrorDL(E e) : super("error", e);
}

The tree of deep links is specified as a extensions-based DSL (domain specific language).

void main() => runApp(MusicApp());

class MusicApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) => DeepLinkMaterialApp(
    // This is where the magic happens
    navigation: Dispatcher()
      ..path<LibraryDL>(
        (route) => LibraryPage(),
        subNavigation: Dispatcher()
          ..value<Artist, ArtistDL>(
            (artist, route) => ArtistPage(artist: artist),
            subNavigation: (artist) => Dispatcher()
              ..song()
          )
          ..path<FavoritesDL>(
            (route) => FavoritesPage(),
            subNavigation: Dispatcher()
              ..song()
          )
          ..value<RouteNotFound, ErrorDL<RouteNotFound>>((exception, route) => ErrorPage(exception)),
      )
      // Exception handling mappings and route dispatchers are specified independently
      ..exception<RouteNotFound>((exception, route) => [LibraryDL(), ErrorDL<RouteNotFound>(exception)]),
    defaultRoute: [LibraryDL()],
    splashScreen: SplashPage(),
    // Non-navigation related fields are still available
    themeMode: ThemeMode.light,
  );
}

/// Reusing code through static extension methods.
extension DispatcherExtensions on Dispatcher {
  void song() => value<Song, SongDL>((song, route) => SongPage(song: song));
}

Now to navigate to any page you call

DeepLinkNavigator.of(context).navigateTo([LibraryDL(), ArtistDL(...)])
.

BDD Testing

I used behaviour driven development (BDD) to test that my library works as expected.

Feature: Deep link navigator direct navigation
  Routes should be accessed from any other route.

  Scenario: Navigation from artists' song page to artist page
    Given I open the artist "John Lennon"
    And I open the song "Yesterday"
    And the title is "Yesterday"
    When I navigate to the song's artist
    Then the title is "John Lennon"
    # Once like normal
    Then I go back 1 time
    And the title is "Library"

  Scenario: Navigation from favorites' song page to artist page
    Given I open my favorite songs
    And I open the song "Yesterday"
    And the title is "Yesterday"
    When I navigate to the song's artist
    Then the title is "John Lennon"
    # Once instead of twice
    Then I go back 1 time
    And the title is "Library"

  Scenario: Navigation to an unknown route shows error page
    Given the title is "Library"
    When I tap the "Non-existant navigate" button
    Then the title is "ERROR"
    And the text "Route not found: [library, song/6363]" appears
    Then I go back 1 time
    And the title is "Library"