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"