├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── example ├── README.md ├── pubspec.lock └── pubspec.yaml ├── examples ├── multiple_base_routes │ ├── .gitignore │ ├── .metadata │ ├── README.md │ ├── android │ │ ├── app │ │ │ ├── build.gradle │ │ │ └── src │ │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ │ ├── main │ │ │ │ ├── AndroidManifest.xml │ │ │ │ ├── kotlin │ │ │ │ │ └── dev │ │ │ │ │ │ └── krasnov │ │ │ │ │ │ └── multiple_base_routes │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── res │ │ │ │ │ ├── drawable │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ └── values │ │ │ │ │ └── styles.xml │ │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ ├── build.gradle │ │ ├── gradle.properties │ │ ├── gradle │ │ │ └── wrapper │ │ │ │ └── gradle-wrapper.properties │ │ └── settings.gradle │ ├── ios │ │ ├── Flutter │ │ │ ├── AppFrameworkInfo.plist │ │ │ ├── Debug.xcconfig │ │ │ └── Release.xcconfig │ │ ├── Runner.xcodeproj │ │ │ ├── project.pbxproj │ │ │ ├── project.xcworkspace │ │ │ │ └── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ └── xcschemes │ │ │ │ └── Runner.xcscheme │ │ ├── Runner.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── Runner │ │ │ ├── AppDelegate.swift │ │ │ ├── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ ├── Contents.json │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ │ └── LaunchImage.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ └── README.md │ │ │ ├── Base.lproj │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ │ ├── Info.plist │ │ │ └── Runner-Bridging-Header.h │ ├── lib │ │ ├── data.dart │ │ ├── deep_links.dart │ │ ├── main.dart │ │ ├── model.dart │ │ └── widgets │ │ │ ├── artist_page.dart │ │ │ ├── authentication_page.dart │ │ │ ├── error_page.dart │ │ │ ├── favorites_page.dart │ │ │ ├── library_page.dart │ │ │ ├── login_page.dart │ │ │ ├── song_list_tile.dart │ │ │ ├── song_page.dart │ │ │ ├── splash_page.dart │ │ │ └── user_page.dart │ ├── navigation.png │ ├── pubspec.lock │ ├── pubspec.yaml │ └── test_driver │ │ ├── app.dart │ │ ├── app_test.dart │ │ ├── features │ │ ├── bottom_navigation.feature │ │ └── promise.feature │ │ └── steps │ │ ├── go_back.dart │ │ ├── steps.dart │ │ ├── text_disappears.dart │ │ └── title_is.dart └── single_base_route │ ├── .gitignore │ ├── .metadata │ ├── README.md │ ├── android │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── dev │ │ │ │ │ └── krasnov │ │ │ │ │ └── single_base_route │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle │ ├── ios │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ └── contents.xcworkspacedata │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h │ ├── lib │ ├── data.dart │ ├── deep_links.dart │ ├── main.dart │ ├── model.dart │ └── widgets │ │ ├── artist_page.dart │ │ ├── error_page.dart │ │ ├── favorites_page.dart │ │ ├── library_page.dart │ │ ├── song_list_tile.dart │ │ ├── song_page.dart │ │ └── splash_page.dart │ ├── navigation.png │ ├── pubspec.lock │ ├── pubspec.yaml │ └── test_driver │ ├── app.dart │ ├── app_test.dart │ ├── features │ ├── navigate.feature │ └── push_pop.feature │ └── steps │ ├── go_back.dart │ ├── navigate_to_artist.dart │ ├── open_artist.dart │ ├── open_favorites.dart │ ├── open_song.dart │ ├── steps.dart │ ├── text_appears.dart │ └── title_is.dart ├── lib ├── deep_link_navigation.dart └── src │ ├── deep_link.dart │ ├── deep_link_material_app.dart │ ├── deep_link_navigator.dart │ ├── default_page_loader.dart │ ├── dispatchers.dart │ ├── exceptions.dart │ ├── pop_observer.dart │ └── utils.dart ├── pubspec.lock ├── pubspec.yaml └── test └── deep_link_navigation_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | build/ 31 | 32 | # Android related 33 | **/android/**/gradle-wrapper.jar 34 | **/android/.gradle 35 | **/android/captures/ 36 | **/android/gradlew 37 | **/android/gradlew.bat 38 | **/android/local.properties 39 | **/android/**/GeneratedPluginRegistrant.java 40 | 41 | # iOS/XCode related 42 | **/ios/**/*.mode1v3 43 | **/ios/**/*.mode2v3 44 | **/ios/**/*.moved-aside 45 | **/ios/**/*.pbxuser 46 | **/ios/**/*.perspectivev3 47 | **/ios/**/*sync/ 48 | **/ios/**/.sconsign.dblite 49 | **/ios/**/.tags* 50 | **/ios/**/.vagrant/ 51 | **/ios/**/DerivedData/ 52 | **/ios/**/Icon? 53 | **/ios/**/Pods/ 54 | **/ios/**/.symlinks/ 55 | **/ios/**/profile 56 | **/ios/**/xcuserdata 57 | **/ios/.generated/ 58 | **/ios/Flutter/App.framework 59 | **/ios/Flutter/Flutter.framework 60 | **/ios/Flutter/Generated.xcconfig 61 | **/ios/Flutter/app.flx 62 | **/ios/Flutter/app.zip 63 | **/ios/Flutter/flutter_assets/ 64 | **/ios/Flutter/flutter_export_environment.sh 65 | **/ios/ServiceDefinitions.json 66 | **/ios/Runner/GeneratedPluginRegistrant.* 67 | 68 | # Exceptions to above rules. 69 | !**/ios/**/default.mode1v3 70 | !**/ios/**/default.mode2v3 71 | !**/ios/**/default.pbxuser 72 | !**/ios/**/default.perspectivev3 73 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 74 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: cc949a8e8b9cf394b9290a8e80f87af3e207dce5 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.3.1] - 2019/12/09 2 | 3 | * Breaking change: Changed path (string) to route (List) in configuration 4 | * Breaking change: navigation and subNavigation no longer takes context as argument, no function for path 5 | * Updated documentation and examples 6 | 7 | ## [1.2.1] - 2019/11/16 8 | 9 | * Removed context from exception mapping 10 | 11 | ## [1.1.1] - 2019/11/15 12 | 13 | * Updated configuration syntax to allow passing value deeper in hierarchy 14 | * Added a future return for DeepLinkNavigator.push 15 | * Added promise route for multi_base_routes example, added to UI test 16 | * Refactored and documented project 17 | * Added readme documentation with diagrams and examples 18 | 19 | ## [1.0.1] - 2019/11/14 20 | 21 | * Completely overhauled configuration syntax 22 | * Simplified deep link processing logic 23 | * Generalized exception handling by mapping exceptions to routes 24 | * Remove unnecessary exception dispatchers 25 | * Updated examples with new configuration syntax 26 | * Fixed examples' deep link typing 27 | * Checked in multiple_base_route example configuration files 28 | 29 | ## [0.1.2] - 2019/11/06 30 | 31 | * Made defaultRoute field optional 32 | 33 | ## [0.1.1] - 2019/11/02 34 | 35 | * Initial release 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Dennis Krasnov 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flutter Deep Link Navigation 2 | 3 | [![pub.dev package](https://img.shields.io/badge/pub.dev-1.3.1-red?style=flat)](https://pub.dev/packages/deep_link_navigation) 4 | [![Github stars](https://img.shields.io/github/stars/Dennis-Krasnov/Flutter-Deep-Link-Navigation?style=flat)](https://github.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation) 5 | [![Open source license](https://img.shields.io/github/license/Dennis-Krasnov/Flutter-Deep-Link-Navigation.svg?style=flat)](https://github.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/blob/master/LICENSE) 6 | [![Awesome Flutter](https://img.shields.io/badge/Awesome-Flutter-blue.svg?longCache=true&style=flat)](https://github.com/Solido/awesome-flutter#navigation) 7 | 8 | 9 | 10 | 11 | 12 | 13 | Provides an elegant abstraction for complete deep linking navigation in Flutter. 14 | 15 | This package **only provides deep linking for internal navigation**. Any external platform-level deep linking solution can optionally be used in conjuction with this package. 16 | 17 | The target audience of the documentation is experienced Flutter developers. 18 | 19 | ## Motivation 20 | There's nothing wrong with not using deep links for internal navigation. 21 | 22 | Partially implementing deep links would either have **limited benefits** or be **extremely complicated** (not to mention confusing). 23 | 24 | Hence why if you decide to use deep links, it makes sense to exclusively use deep links. 25 | 26 | **If you try to implement complete deep linking yourself here's some of the issues you'll run into:** 27 | * Widgets become coupled with their place in the navigation hierarchy 28 | - Becomes an issue if you want to reuse a page 29 | - No amount of fancy iteration or recursion will help 30 | * It's difficult to 'pass down' deep link values used in higher levels 31 | - Given the hierarchy is `Artist` --> `Song` 32 | - Being able to do `(artist) => ArtistPage(artist)` and later `(song) => SongPage(artist, song)` 33 | - I actually published a [rest-like router package](https://pub.dev/packages/rest_router) while trying to solve this issue 34 | * How do you represent deep links internally? 35 | - How to keep configuration as terse as possible? 36 | - How to represent current route in a string-friendly format (eg. for analytics, debugging) 37 | - How to serialize route to be used with a platform-level deep linking solution 38 | - How to handle native navigator pops (eg. back button)? 39 | - How to handle a route that doesn't exist (or any other exception that occurs during dispatch)? 40 | * How do you integrate custom logic for validating deep link navigation? 41 | - Ideally provide context to be able to access state management 42 | - eg. certain subtrees of the navigation hierarchy are available only for subscribed or authenticated users 43 | 44 | **TL;DR** 45 | 46 | I separated the navigation system from [Diet Driven](https://github.com/Dennis-Krasnov/Diet-Driven) (shameless plug, [please hire me](https://denniskrasnov.com/)) into its own package and published it. 47 | 48 | This package provides a solution for all the aforementioned difficulties. 49 | 50 | ## Examples 51 | ### [Single base route](https://github.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/tree/master/examples/single_base_route) 52 | **This example demonstrates:** 53 | * Dispatchers with path-only deep links 54 | * Dispatchers with value deep links (ArtistDL, SongDL) 55 | * Exception handling (RouteNotFoundDL) 56 | * Cross-branch navigation (from favorite's song page to artist page) 57 | ![Navigation diagram for multiple base routes example](examples/single_base_route/navigation.png) 58 | 59 | ### [Multiple base routes](https://github.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/tree/master/examples/multiple_base_routes) 60 | **This example demonstrates:** 61 | * Everything from single base route example 62 | * Bottom navigation (library, favorites, user pages) persists across navigation 63 | * Login and error pages are full screen (hide bottom navigation) 64 | * Using the asynchronous result of push (AuthenticationDL from UserDL) 65 | * Custom `Authenticated` mixin ensures user is authenticated (LibraryDL, FavoritesDL, UserDL) 66 | ![Navigation diagram for multiple base routes example](examples/multiple_base_routes/navigation.png) 67 | 68 | ## Configuration 69 | #### Deep links 70 | `DeepLink` is base unit of routes, a deep link is mapped to a `Widget` by a `Dispatcher`. 71 | 72 | Deep links may be reused in different levels of the navigation hierarchy. 73 | 74 | A route is the full location of a page, represented by `List`. 75 | 76 | `path` is the string representation of the route aka `route.join("/")`. 77 | 78 | **Path** 79 | ```dart 80 | class LibraryDL extends DeepLink { 81 | LibraryDL() : super("library"); 82 | } 83 | ``` 84 | 85 | **Value** 86 | 87 | Deep links can also store data, value dispatchers are strongly typed. 88 | ```dart 89 | class SongDL extends ValueDeepLink { 90 | SongDL(Song song) : super("song", song); 91 | } 92 | 93 | class SongDL extends ValueDeepLink { 94 | // Override toString 95 | SongDL(Song song) : super("song", song, toString: (song) => song.id); 96 | } 97 | ``` 98 | 99 | **Mixin for sake of inheritence** 100 | 101 | This could also be achieved by implementing an abstract class. 102 | 103 | See use in *Child builder* section. 104 | ```dart 105 | mixin FullScreen on DeepLink {} 106 | 107 | class LoginDL extends DeepLink with FullScreen { 108 | LoginDL() : super("login"); 109 | } 110 | ``` 111 | 112 | **Mixin with logic** 113 | ```dart 114 | mixin Authenticated on DeepLink { 115 | @override 116 | void onDispatch(BuildContext context) { 117 | // Get state from context or global/static variable 118 | final isAuthenticated = Provider.of(context, listen: false).authenticated; 119 | 120 | // Throw custom exception 121 | if (!isAuthenticated) { 122 | throw Unauthenticated(); 123 | } 124 | } 125 | } 126 | 127 | // ... 128 | 129 | navigation: (context) => Dispatcher() 130 | // Unauthenticated login page 131 | ..exception((exception, route) => [LoginDL()]) 132 | ..path((route) => LoginPage()), 133 | ``` 134 | 135 | #### Application 136 | Use `DeepLinkMaterialApp` instead of using Flutter's `MaterialApp`. 137 | 138 | This replaces native navigation options with their deep link counterparts. 139 | 140 | At most one deep link of a type can exist on a dispatcher. 141 | 142 | **Deep link material app** 143 | ```dart 144 | DeepLinkMaterialApp( 145 | navigation: (context) => Dispatcher() // see next section ... 146 | defaultRoute: [LibraryDL()], // if ommited, the splash screen is shown until explicit navigation 147 | splashScreen: SplashPage(), 148 | childBuilder: // see child builder section ... 149 | 150 | // Non-navigation related fields are still available 151 | themeMode: ThemeMode.light, 152 | // ... 153 | ); 154 | ``` 155 | 156 | **Path dispatcher** 157 | ```dart 158 | ..path((route) => LoginPage()), 159 | ``` 160 | 161 | **Value dispatcher** 162 | ```dart 163 | ..value((song, route) => SongPage(song: song)), 164 | ``` 165 | 166 | **Sub navigation** 167 | 168 | ```dart 169 | ..path( 170 | (route) => LibraryPage(), 171 | subNavigation: Dispatcher() // ... 172 | ), 173 | 174 | ..value( 175 | (song, route) => SongPage(song: song), 176 | subNavigation: (song) => Dispatcher() // song may be used from this point onward 177 | ), 178 | ``` 179 | 180 | **Exception mapping** 181 | 182 | Exceptions that are thrown while running through the navigation hierarchy are mapped to routes. 183 | 184 | `..exception` **MUST** be defined on the base-level dispatcher. 185 | 186 | If multiple mappings of the same type are found thoughout the hierarchy, the deep-most mapping is used. 187 | ```dart 188 | ..exception((exception, route) => [ErrorDL(exception)]) 189 | ``` 190 | 191 | #### Child builder 192 | The widget specified in `childBuilder` is rebuilt when the route in `deepLinkNavigator` changes. 193 | ```dart 194 | /// [DeepLink]s associated with the bottom navigation. 195 | final bottomNavigationDeepLinks = [LibraryDL(), FavoritesDL(), UserDL()]; 196 | 197 | /// Current index of bottom navigation based on [currentRoute]. 198 | int currentIndex(List currentRoute) { 199 | final index = bottomNavigationDeepLinks.indexOf(currentRoute?.first); 200 | return index != -1 ? index : 0; 201 | } 202 | 203 | /// ... 204 | 205 | childBuilder: (BuildContext context, DeepLinkNavigator deepLinkNavigator, Widget child) => Scaffold( 206 | body: child, 207 | // Don't show bottom navigation while [currentRoute] is null, or any deep list is [FullScreen] 208 | bottomNavigationBar: deepLinkNavigator.currentRoute?.any((dl) => dl is FullScreen) ?? true ? null : BottomNavigationBar( 209 | currentIndex: currentIndex(deepLinkNavigator.currentRoute), 210 | onTap: (int index) => deepLinkNavigator.navigateTo([bottomNavigationDeepLinks[index]]), 211 | items: [ 212 | BottomNavigationBarItem(title: Text("Library"), icon: Icon(Icons.queue_music)), 213 | BottomNavigationBarItem(title: Text("Favorites"), icon: Icon(Icons.favorite)), 214 | BottomNavigationBarItem(title: Text("User"), icon: Icon(Icons.person)), 215 | ], 216 | ), 217 | ) 218 | ``` 219 | 220 | #### In-app navigation 221 | `DeepLinkNavigator` mirrors `Navigator`'s interface as much as possible (including push and pop futures). 222 | 223 | All methods internally orchestrate a native flutter navigator. 224 | 225 | **Push a deep link** 226 | ```dart 227 | await DeepLinkNavigator.of(context).push(ArtistDL(...)); 228 | ``` 229 | 230 | **Pop a value** 231 | ```dart 232 | DeepLinkNavigator.of(context).pop(...); 233 | 234 | // or 235 | 236 | Navigator.of(context).pop(...); 237 | ``` 238 | 239 | **Navigate to specific route** 240 | ```dart 241 | DeepLinkNavigator.of(context).navigateTo([ 242 | LibraryDL(), 243 | ArtistDL(...), 244 | ]); 245 | ``` 246 | 247 | **Return to default route (if any)** 248 | ```dart 249 | DeepLinkNavigator.of(context).replaceWithDefault(); 250 | ``` 251 | 252 | **TODO: Throw exception to be caught by mapper** 253 | ```dart 254 | // TODO DeepLinkNavigator.of(context).throw(Exception(...)); 255 | ``` 256 | 257 | **TODO: Access deep link navigator from anywhere** 258 | ```dart 259 | // TODO DeepLinkNavigator()... 260 | ``` 261 | 262 | **TODO: Page transitions** 263 | ```dart 264 | // await DeepLinkNavigator.of(context).push( 265 | // ArtistDL(...), 266 | // transition: ..., 267 | // duration: ..., 268 | // ); 269 | 270 | // Possibly: 271 | // await DeepLinkNavigator.of(context).fadeIn(...); 272 | ``` 273 | 274 | ## Platform deep links 275 | // TODO: serialize `List` 276 | 277 | ## Limitations 278 | * Must **FULLY** specify **ALL** generic types since this is how deep links are matched internally 279 | * Please look very carefully when debugging 280 | * Fails by throwing `RouteNotFound` if the route doesn't exist 281 | * Fails by entering infinite recursion if `RouteNotFound` maps to a route that doesn't exist 282 | * Can't currently define arbitrarily deep navigation hierarchies (think Spotify) 283 | * Can't store separate persisted navigation states for a multi-base route application (think Instagram) 284 | 285 | ## What's left to do 286 | 287 | [ ] Custom/predefined page transitions 288 | 289 | [ ] Access deep link navigator from anywhere using static method and factory pattern 290 | 291 | [ ] Assert `RouteNotFound` dispatcher exists by running through navigation tree 292 | 293 | [ ] Unit test deep link navigator logic 294 | 295 | [ ] Cupertino and Widget apps 296 | 297 | [ ] Explicit exception throwing 298 | 299 | [ ] Platform deep links example + documentation 300 | 301 | [ ] Route changed callback for analytics, etc -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | ## Examples 2 | ### [Single base route](https://github.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/tree/master/examples/single_base_route) 3 | **This example demonstrates:** 4 | * Dispatchers with path-only deep links 5 | * Dispatchers with value deep links (ArtistDL, SongDL) 6 | * Cross-branch navigation (from favorite's song page to artist page) 7 | ![Navigation diagram for multiple base routes example](examples/single_base_route/navigation.png) 8 | 9 | ### [Multiple base routes](https://github.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/tree/master/examples/multiple_base_routes) 10 | **This example demonstrates:** 11 | * Everything from single base route example 12 | * Bottom navigation (library, favorites, user pages) persists across navigation 13 | * Login and error pages are full screen (hide bottom navigation) 14 | * Using the future result of push in user/authentication page 15 | * Custom `Authenticated` mixin ensures user is authenticated (LibraryDL, FavoritesDL, UserDL) 16 | ![Navigation diagram for multiple base routes example](examples/multiple_base_routes/navigation.png) -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | after_layout: 5 | dependency: transitive 6 | description: 7 | name: after_layout 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "1.0.7+2" 11 | collection: 12 | dependency: transitive 13 | description: 14 | name: collection 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "1.14.11" 18 | deep_link_navigation: 19 | dependency: "direct main" 20 | description: 21 | path: ".." 22 | relative: true 23 | source: path 24 | version: "1.3.1" 25 | flutter: 26 | dependency: transitive 27 | description: flutter 28 | source: sdk 29 | version: "0.0.0" 30 | merge_map: 31 | dependency: transitive 32 | description: 33 | name: merge_map 34 | url: "https://pub.dartlang.org" 35 | source: hosted 36 | version: "1.0.2" 37 | meta: 38 | dependency: transitive 39 | description: 40 | name: meta 41 | url: "https://pub.dartlang.org" 42 | source: hosted 43 | version: "1.1.8" 44 | nested: 45 | dependency: transitive 46 | description: 47 | name: nested 48 | url: "https://pub.dartlang.org" 49 | source: hosted 50 | version: "0.0.4" 51 | provider: 52 | dependency: transitive 53 | description: 54 | name: provider 55 | url: "https://pub.dartlang.org" 56 | source: hosted 57 | version: "4.0.1" 58 | sky_engine: 59 | dependency: transitive 60 | description: flutter 61 | source: sdk 62 | version: "0.0.99" 63 | typed_data: 64 | dependency: transitive 65 | description: 66 | name: typed_data 67 | url: "https://pub.dartlang.org" 68 | source: hosted 69 | version: "1.1.6" 70 | vector_math: 71 | dependency: transitive 72 | description: 73 | name: vector_math 74 | url: "https://pub.dartlang.org" 75 | source: hosted 76 | version: "2.0.8" 77 | sdks: 78 | dart: ">=2.2.2 <3.0.0" 79 | flutter: ">=1.12.1" 80 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: deep_link_navigation_example 2 | description: Deep link navigation for Flutter apps with an elegant configuation internally orchestrating a native Flutter navigator. 3 | version: 1.0.0 4 | author: Dennis Krasnov 5 | homepage: https://github.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation 6 | repository: https://github.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation 7 | issue_tracker: https://github.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/issues 8 | 9 | environment: 10 | sdk: ">=2.0.0 <3.0.0" 11 | 12 | dependencies: 13 | deep_link_navigation: 14 | path: ../ -------------------------------------------------------------------------------- /examples/multiple_base_routes/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | /build/ 31 | 32 | # Android related 33 | **/android/**/gradle-wrapper.jar 34 | **/android/.gradle 35 | **/android/captures/ 36 | **/android/gradlew 37 | **/android/gradlew.bat 38 | **/android/local.properties 39 | **/android/**/GeneratedPluginRegistrant.java 40 | 41 | # iOS/XCode related 42 | **/ios/**/*.mode1v3 43 | **/ios/**/*.mode2v3 44 | **/ios/**/*.moved-aside 45 | **/ios/**/*.pbxuser 46 | **/ios/**/*.perspectivev3 47 | **/ios/**/*sync/ 48 | **/ios/**/.sconsign.dblite 49 | **/ios/**/.tags* 50 | **/ios/**/.vagrant/ 51 | **/ios/**/DerivedData/ 52 | **/ios/**/Icon? 53 | **/ios/**/Pods/ 54 | **/ios/**/.symlinks/ 55 | **/ios/**/profile 56 | **/ios/**/xcuserdata 57 | **/ios/.generated/ 58 | **/ios/Flutter/App.framework 59 | **/ios/Flutter/Flutter.framework 60 | **/ios/Flutter/Generated.xcconfig 61 | **/ios/Flutter/app.flx 62 | **/ios/Flutter/app.zip 63 | **/ios/Flutter/flutter_assets/ 64 | **/ios/Flutter/flutter_export_environment.sh 65 | **/ios/ServiceDefinitions.json 66 | **/ios/Runner/GeneratedPluginRegistrant.* 67 | 68 | # Exceptions to above rules. 69 | !**/ios/**/default.mode1v3 70 | !**/ios/**/default.mode2v3 71 | !**/ios/**/default.pbxuser 72 | !**/ios/**/default.perspectivev3 73 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 74 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: cc949a8e8b9cf394b9290a8e80f87af3e207dce5 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/README.md: -------------------------------------------------------------------------------- 1 | # Multiple Base Routes 2 | 3 | **This example demonstrates:** 4 | * Everything from single base route example 5 | * Bottom navigation (library, favorites, user pages) persists across navigation 6 | * Login and error pages are full screen (hide bottom navigation) 7 | * Using the future result of push in user/authentication page 8 | * Custom `Authenticated` mixin ensures user is authenticated (LibraryDL, FavoritesDL, UserDL) -------------------------------------------------------------------------------- /examples/multiple_base_routes/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 28 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "dev.krasnov.multiple_base_routes" 42 | minSdkVersion 16 43 | targetSdkVersion 28 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 47 | } 48 | 49 | buildTypes { 50 | release { 51 | // TODO: Add your own signing config for the release build. 52 | // Signing with the debug keys for now, so `flutter run --release` works. 53 | signingConfig signingConfigs.debug 54 | } 55 | } 56 | } 57 | 58 | flutter { 59 | source '../..' 60 | } 61 | 62 | dependencies { 63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 64 | testImplementation 'junit:junit:4.12' 65 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 66 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 67 | } 68 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 13 | 20 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/android/app/src/main/kotlin/dev/krasnov/multiple_base_routes/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package dev.krasnov.multiple_base_routes 2 | 3 | import android.os.Bundle 4 | 5 | import io.flutter.app.FlutterActivity 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | GeneratedPluginRegistrant.registerWith(this) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/multiple_base_routes/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/multiple_base_routes/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/multiple_base_routes/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/multiple_base_routes/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/multiple_base_routes/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/multiple_base_routes/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/multiple_base_routes/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/multiple_base_routes/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/multiple_base_routes/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/multiple_base_routes/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.2.71' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.2.1' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | 3 | android.enableR8=true 4 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip 7 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/multiple_base_routes/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/multiple_base_routes/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/multiple_base_routes/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/multiple_base_routes/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/multiple_base_routes/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/multiple_base_routes/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/multiple_base_routes/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/multiple_base_routes/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/multiple_base_routes/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/multiple_base_routes/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/multiple_base_routes/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/multiple_base_routes/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/multiple_base_routes/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/multiple_base_routes/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/multiple_base_routes/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/multiple_base_routes/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/multiple_base_routes/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/multiple_base_routes/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | multiple_base_routes 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /examples/multiple_base_routes/lib/data.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:multiple_base_routes/model.dart'; 4 | 5 | /// Dummy state management. 6 | class AuthenticationService with ChangeNotifier { 7 | bool _authenticated = false; 8 | bool get authenticated => _authenticated; 9 | 10 | void login() { 11 | _authenticated = true; 12 | notifyListeners(); 13 | } 14 | 15 | void logout() { 16 | _authenticated = false; 17 | notifyListeners(); 18 | } 19 | } 20 | 21 | /// Random placeholder data meant to be replaced with a database. 22 | class Data { 23 | static final artists = { 24 | "1234": Artist( 25 | "1234", 26 | "John Lennon", 27 | [ 28 | Song( 29 | "4312", 30 | "1234", 31 | "Yesterday", 32 | ), 33 | Song( 34 | "4313", 35 | "1234", 36 | "Yellow Submarine", 37 | ), 38 | ], 39 | ), 40 | "1235": Artist( 41 | "1235", 42 | "Ludwig van beethoven", 43 | [ 44 | Song( 45 | "6363", 46 | "1235", 47 | "Symphony No. 5", 48 | ), 49 | Song( 50 | "6364", 51 | "1235", 52 | "Symphony No. 9", 53 | ), 54 | ], 55 | ), 56 | }; 57 | 58 | static final favoriteSongs = [ 59 | artists["1234"].songs[0], 60 | artists["1235"].songs[0], 61 | artists["1235"].songs[1], 62 | ]; 63 | } -------------------------------------------------------------------------------- /examples/multiple_base_routes/lib/deep_links.dart: -------------------------------------------------------------------------------- 1 | import 'package:deep_link_navigation/deep_link_navigation.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | 4 | import 'package:multiple_base_routes/model.dart'; 5 | import 'package:provider/provider.dart'; 6 | 7 | import 'data.dart'; 8 | 9 | /// Custom mixins 10 | 11 | mixin FullScreen on DeepLink {} 12 | 13 | mixin Authenticated on DeepLink { 14 | @override 15 | void onDispatch(BuildContext context) { 16 | // Get state from context or global/static variable 17 | final isAuthenticated = Provider.of(context, listen: false).authenticated; 18 | 19 | // Throw custom exception 20 | if (!isAuthenticated) { 21 | throw Unauthenticated(); 22 | } 23 | } 24 | } 25 | 26 | /// Custom exceptions 27 | 28 | class Unauthenticated implements Exception { 29 | @override 30 | String toString() => "Unauthenticated :("; 31 | } 32 | 33 | /// Deep links 34 | 35 | class LoginDL extends DeepLink with FullScreen { 36 | LoginDL() : super("login"); 37 | } 38 | 39 | class LibraryDL extends DeepLink with Authenticated { 40 | LibraryDL() : super("library"); 41 | } 42 | 43 | class FavoritesDL extends DeepLink with Authenticated { 44 | FavoritesDL() : super("favorites"); 45 | } 46 | 47 | class UserDL extends DeepLink with Authenticated { 48 | UserDL() : super("user"); 49 | } 50 | 51 | class AuthenticationDL extends DeepLink { 52 | AuthenticationDL() : super("authentication"); 53 | } 54 | 55 | class ArtistDL extends ValueDeepLink { 56 | ArtistDL(Artist artist) : super("artist", artist, toString: (artist) => artist.id); 57 | } 58 | 59 | class SongDL extends ValueDeepLink { 60 | SongDL(Song song) : super("song", song, toString: (song) => song.id); 61 | } 62 | 63 | class ErrorDL extends ValueDeepLink with FullScreen { 64 | ErrorDL(E e) : super("error", e); 65 | } -------------------------------------------------------------------------------- /examples/multiple_base_routes/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:deep_link_navigation/deep_link_navigation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | import 'package:multiple_base_routes/data.dart'; 6 | import 'package:multiple_base_routes/deep_links.dart'; 7 | import 'package:multiple_base_routes/model.dart'; 8 | import 'package:multiple_base_routes/widgets/artist_page.dart'; 9 | import 'package:multiple_base_routes/widgets/authentication_page.dart'; 10 | import 'package:multiple_base_routes/widgets/error_page.dart'; 11 | import 'package:multiple_base_routes/widgets/favorites_page.dart'; 12 | import 'package:multiple_base_routes/widgets/library_page.dart'; 13 | import 'package:multiple_base_routes/widgets/login_page.dart'; 14 | import 'package:multiple_base_routes/widgets/song_page.dart'; 15 | import 'package:multiple_base_routes/widgets/splash_page.dart'; 16 | import 'package:multiple_base_routes/widgets/user_page.dart'; 17 | 18 | void main() => runApp( 19 | // State management of your choice 20 | ChangeNotifierProvider( 21 | create: (_) => AuthenticationService(), 22 | child: MusicApp(), 23 | ) 24 | ); 25 | 26 | /// [DeepLink]s associated with the bottom navigation. 27 | final bottomNavigationDeepLinks = [LibraryDL(), FavoritesDL(), UserDL()]; 28 | 29 | /// Current index of bottom navigation based on [currentRoute]. 30 | int currentIndex(List currentRoute) { 31 | final index = bottomNavigationDeepLinks.indexOf(currentRoute?.first); 32 | return index != -1 ? index : 0; 33 | } 34 | 35 | class MusicApp extends StatelessWidget { 36 | @override 37 | Widget build(BuildContext context) => DeepLinkMaterialApp( 38 | // This is where the magic happens 39 | navigation: Dispatcher() 40 | // RouteNotFound error page 41 | ..exception((exception, route) => [ErrorDL(exception)]) 42 | ..value>((exception, route) => ErrorPage(exception)) 43 | // Unauthenticated login page 44 | ..exception((exception, route) => [LoginDL()]) 45 | ..path((path) => LoginPage()) 46 | // The rest of the app 47 | ..path( 48 | (route) => LibraryPage(), 49 | subNavigation: Dispatcher() 50 | ..value( 51 | (artist, route) => ArtistPage(artist: artist), 52 | subNavigation: (artist) => Dispatcher() 53 | ..song() 54 | ), 55 | ) 56 | ..path( 57 | (route) => FavoritesPage(), 58 | subNavigation: Dispatcher() 59 | ..song() 60 | ) 61 | ..path( 62 | (route) => UserPage(), 63 | subNavigation: Dispatcher()..path((route) => AuthenticationPage()), 64 | ), 65 | defaultRoute: [LibraryDL()], 66 | splashScreen: SplashPage(), 67 | // Optionally specify always visible widgets 68 | childBuilder: (BuildContext context, DeepLinkNavigator deepLinkNavigator, Widget child) => Scaffold( 69 | body: child, 70 | // Don't show bottom navigation while [currentRoute] is null, or any deep list is [FullScreen] 71 | bottomNavigationBar: deepLinkNavigator.currentRoute?.any((dl) => dl is FullScreen) ?? true ? null : BottomNavigationBar( 72 | currentIndex: currentIndex(deepLinkNavigator.currentRoute), 73 | onTap: (int index) => deepLinkNavigator.navigateTo([bottomNavigationDeepLinks[index]]), 74 | items: [ 75 | BottomNavigationBarItem(title: Text("Library"), icon: Icon(Icons.queue_music, key: Key("library"))), 76 | BottomNavigationBarItem(title: Text("Favorites"), icon: Icon(Icons.favorite, key: Key("favorites"))), 77 | BottomNavigationBarItem(title: Text("User"), icon: Icon(Icons.person, key: Key("user"))), 78 | ], 79 | ), 80 | ), 81 | // Non-navigation related fields are still available 82 | themeMode: ThemeMode.light, 83 | ); 84 | } 85 | 86 | /// Reusing code through static extension methods. 87 | extension DispatcherExtensions on Dispatcher { 88 | void song() => value((song, route) => SongPage(song: song)); 89 | } -------------------------------------------------------------------------------- /examples/multiple_base_routes/lib/model.dart: -------------------------------------------------------------------------------- 1 | import 'package:meta/meta.dart'; 2 | 3 | @immutable 4 | class Artist { 5 | final String id; 6 | 7 | final String name; 8 | 9 | final List songs; 10 | 11 | Artist(this.id, this.name, this.songs); 12 | } 13 | 14 | @immutable 15 | class Song { 16 | final String id; 17 | 18 | /// Foreign key for artist 19 | final String artistId; 20 | 21 | final String name; 22 | 23 | Song(this.id, this.artistId, this.name); 24 | } -------------------------------------------------------------------------------- /examples/multiple_base_routes/lib/widgets/artist_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:multiple_base_routes/model.dart'; 4 | import 'package:multiple_base_routes/widgets/song_list_tile.dart'; 5 | 6 | class ArtistPage extends StatelessWidget { 7 | final Artist artist; 8 | 9 | const ArtistPage({Key key, this.artist}) : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Scaffold( 14 | appBar: AppBar( 15 | title: Text( 16 | artist.name, 17 | key: Key("title"), 18 | ), 19 | ), 20 | body: ListView( 21 | children: [ 22 | for (final song in artist.songs) 23 | SongListTile(song: song), 24 | ], 25 | ), 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/lib/widgets/authentication_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:deep_link_navigation/deep_link_navigation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | import 'package:multiple_base_routes/data.dart'; 6 | 7 | class AuthenticationPage extends StatelessWidget { 8 | @override 9 | Widget build(BuildContext context) { 10 | final isAuthenticated = Provider.of(context).authenticated; 11 | 12 | return Scaffold( 13 | appBar: AppBar( 14 | title: Text( 15 | "Authentication", 16 | key: Key("title"), 17 | ), 18 | ), 19 | body: Center( 20 | child: Column( 21 | mainAxisAlignment: MainAxisAlignment.center, 22 | children: [ 23 | Text("You're currently ${isAuthenticated ? "" : "un"}authenticated."), 24 | SizedBox(height: 10), 25 | RaisedButton( 26 | key: Key("authenticate"), 27 | child: Text("Authenticate"), 28 | onPressed: () => DeepLinkNavigator.of(context).pop(true), 29 | ), 30 | SizedBox(height: 10), 31 | RaisedButton( 32 | key: Key("unauthenticate"), 33 | child: Text("Unuthenticate"), 34 | onPressed: () => DeepLinkNavigator.of(context).pop(false), 35 | ), 36 | ], 37 | ), 38 | ), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/lib/widgets/error_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ErrorPage extends StatelessWidget { 4 | final Exception e; 5 | 6 | ErrorPage(this.e); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Scaffold( 11 | appBar: AppBar( 12 | title: Text( 13 | "ERROR", 14 | key: Key("title"), 15 | ) 16 | ), 17 | body: Center( 18 | child: Text("$e"), 19 | ), 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/lib/widgets/favorites_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:multiple_base_routes/data.dart'; 4 | import 'package:multiple_base_routes/widgets/song_list_tile.dart'; 5 | 6 | class FavoritesPage extends StatelessWidget { 7 | @override 8 | Widget build(BuildContext context) { 9 | return Scaffold( 10 | appBar: AppBar( 11 | title: Text( 12 | "My favorites (${Data.favoriteSongs.length})", 13 | key: Key("title"), 14 | ), 15 | ), 16 | body: ListView( 17 | children: [ 18 | for (final song in Data.favoriteSongs) 19 | SongListTile(song: song), 20 | ], 21 | ), 22 | ); 23 | } 24 | } -------------------------------------------------------------------------------- /examples/multiple_base_routes/lib/widgets/library_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:deep_link_navigation/deep_link_navigation.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'package:multiple_base_routes/data.dart'; 5 | import 'package:multiple_base_routes/deep_links.dart'; 6 | 7 | class LibraryPage extends StatelessWidget { 8 | @override 9 | Widget build(BuildContext context) { 10 | return Scaffold( 11 | appBar: AppBar( 12 | title: Text( 13 | "Library", 14 | key: Key("title"), 15 | ), 16 | ), 17 | body: ListView( 18 | children: [ 19 | for (final artist in Data.artists.values) 20 | ListTile( 21 | key: Key(artist.name), 22 | leading: CircleAvatar(child: Icon(Icons.person)), 23 | title: Text(artist.name), 24 | subtitle: Text("${artist.songs.length} songs"), 25 | onTap: () => DeepLinkNavigator.of(context).push(ArtistDL(artist)), 26 | ), 27 | Divider(), 28 | ListTile( 29 | key: Key("Non-existant push"), 30 | leading: CircleAvatar(child: Icon(Icons.bug_report)), 31 | title: Text("Non-existant push"), 32 | // Route doesn't exist 33 | onTap: () => DeepLinkNavigator.of(context).push(SongDL(Data.favoriteSongs[0])), 34 | ), 35 | ListTile( 36 | key: Key("Non-existant navigate"), 37 | leading: CircleAvatar(child: Icon(Icons.bug_report)), 38 | title: Text("Non-existant navigate"), 39 | // Route doesn't exist 40 | onTap: () => DeepLinkNavigator.of(context).navigateTo([LibraryDL(), SongDL(Data.favoriteSongs[1])]), 41 | ) 42 | ], 43 | ), 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/lib/widgets/login_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:deep_link_navigation/deep_link_navigation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | import '../data.dart'; 6 | 7 | class LoginPage extends StatelessWidget { 8 | @override 9 | Widget build(BuildContext context) { 10 | return Scaffold( 11 | appBar: AppBar( 12 | title: Text( 13 | "Login", 14 | key: Key("title"), 15 | ) 16 | ), 17 | body: Center( 18 | child: RaisedButton( 19 | key: Key("login"), 20 | child: Text("Login"), 21 | onPressed: () { 22 | Provider.of(context, listen: false).login(); 23 | DeepLinkNavigator.of(context).replaceWithDefault(); 24 | } 25 | ), 26 | ), 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/lib/widgets/song_list_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:deep_link_navigation/deep_link_navigation.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'package:multiple_base_routes/deep_links.dart'; 5 | import 'package:multiple_base_routes/model.dart'; 6 | 7 | class SongListTile extends StatelessWidget { 8 | final Song song; 9 | 10 | const SongListTile({Key key, this.song}) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return ListTile( 15 | key: Key(song.name), 16 | leading: Icon(Icons.music_note), 17 | title: Text(song.name), 18 | onTap: () => DeepLinkNavigator.of(context).push(SongDL(song)), 19 | ); 20 | } 21 | } -------------------------------------------------------------------------------- /examples/multiple_base_routes/lib/widgets/song_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:deep_link_navigation/deep_link_navigation.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'package:multiple_base_routes/data.dart'; 5 | import 'package:multiple_base_routes/deep_links.dart'; 6 | import 'package:multiple_base_routes/model.dart'; 7 | 8 | class SongPage extends StatelessWidget { 9 | final Song song; 10 | 11 | const SongPage({Key key, this.song}) : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Scaffold( 16 | appBar: AppBar( 17 | title: Text( 18 | song.name, 19 | key: Key("title") 20 | ), 21 | ), 22 | body: Center( 23 | child: Column( 24 | mainAxisAlignment: MainAxisAlignment.center, 25 | children: [ 26 | Icon( 27 | Icons.music_video, 28 | size: 200, 29 | ), 30 | RaisedButton( 31 | child: Text("Go to artist"), 32 | onPressed: () => DeepLinkNavigator.of(context).navigateTo([ 33 | LibraryDL(), 34 | ArtistDL(Data.artists[song.artistId]), 35 | ]), 36 | ), 37 | ], 38 | ), 39 | ), 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/lib/widgets/splash_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SplashPage extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return Scaffold( 7 | appBar: AppBar( 8 | title: Text( 9 | "splash", 10 | key: Key("title"), 11 | ) 12 | ), 13 | body: IconButton( 14 | icon: Icon(Icons.autorenew), 15 | onPressed: () => null, 16 | ), 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/lib/widgets/user_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:deep_link_navigation/deep_link_navigation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | import 'package:multiple_base_routes/data.dart'; 6 | import 'package:multiple_base_routes/deep_links.dart'; 7 | 8 | class UserPage extends StatelessWidget { 9 | @override 10 | Widget build(BuildContext context) { 11 | return Scaffold( 12 | appBar: AppBar( 13 | title: Text( 14 | "User", 15 | key: Key("title"), 16 | ), 17 | ), 18 | body: Center( 19 | child: RaisedButton( 20 | key: Key("authentication chooser"), 21 | child: Text("Choose authentication status"), 22 | onPressed: () async { 23 | final isNowAuthenticated = await DeepLinkNavigator.of(context).push(AuthenticationDL()); 24 | 25 | if (isNowAuthenticated != null) { 26 | if (isNowAuthenticated) { 27 | Provider.of(context, listen: false).login(); 28 | Scaffold.of(context).showSnackBar(SnackBar( 29 | content: Text("You're already authenticated, nothing should change") 30 | )); 31 | } 32 | else { 33 | Provider.of(context, listen: false).logout(); 34 | Scaffold.of(context).showSnackBar(SnackBar( 35 | content: Text("Try navigating to a page that requires authentication") 36 | )); 37 | } 38 | } 39 | }, 40 | ), 41 | ), 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/navigation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/multiple_base_routes/navigation.png -------------------------------------------------------------------------------- /examples/multiple_base_routes/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | after_layout: 5 | dependency: transitive 6 | description: 7 | name: after_layout 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "1.0.7+2" 11 | analyzer: 12 | dependency: transitive 13 | description: 14 | name: analyzer 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "0.36.4" 18 | archive: 19 | dependency: transitive 20 | description: 21 | name: archive 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.0.11" 25 | args: 26 | dependency: transitive 27 | description: 28 | name: args 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.5.2" 32 | async: 33 | dependency: transitive 34 | description: 35 | name: async 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "2.4.0" 39 | boolean_selector: 40 | dependency: transitive 41 | description: 42 | name: boolean_selector 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.0.5" 46 | charcode: 47 | dependency: transitive 48 | description: 49 | name: charcode 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.1.2" 53 | collection: 54 | dependency: transitive 55 | description: 56 | name: collection 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "1.14.11" 60 | convert: 61 | dependency: transitive 62 | description: 63 | name: convert 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "2.1.1" 67 | coverage: 68 | dependency: transitive 69 | description: 70 | name: coverage 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "0.13.3+3" 74 | crypto: 75 | dependency: transitive 76 | description: 77 | name: crypto 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "2.1.3" 81 | csslib: 82 | dependency: transitive 83 | description: 84 | name: csslib 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "0.16.1" 88 | deep_link_navigation: 89 | dependency: "direct main" 90 | description: 91 | path: "../.." 92 | relative: true 93 | source: path 94 | version: "1.3.1" 95 | file: 96 | dependency: transitive 97 | description: 98 | name: file 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "5.1.0" 102 | flutter: 103 | dependency: "direct main" 104 | description: flutter 105 | source: sdk 106 | version: "0.0.0" 107 | flutter_driver: 108 | dependency: "direct dev" 109 | description: flutter 110 | source: sdk 111 | version: "0.0.0" 112 | flutter_gherkin: 113 | dependency: "direct dev" 114 | description: 115 | name: flutter_gherkin 116 | url: "https://pub.dartlang.org" 117 | source: hosted 118 | version: "1.1.4" 119 | flutter_test: 120 | dependency: "direct dev" 121 | description: flutter 122 | source: sdk 123 | version: "0.0.0" 124 | front_end: 125 | dependency: transitive 126 | description: 127 | name: front_end 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "0.1.19" 131 | fuchsia_remote_debug_protocol: 132 | dependency: transitive 133 | description: flutter 134 | source: sdk 135 | version: "0.0.0" 136 | gherkin: 137 | dependency: transitive 138 | description: 139 | name: gherkin 140 | url: "https://pub.dartlang.org" 141 | source: hosted 142 | version: "1.1.3" 143 | glob: 144 | dependency: transitive 145 | description: 146 | name: glob 147 | url: "https://pub.dartlang.org" 148 | source: hosted 149 | version: "1.2.0" 150 | html: 151 | dependency: transitive 152 | description: 153 | name: html 154 | url: "https://pub.dartlang.org" 155 | source: hosted 156 | version: "0.14.0+3" 157 | http: 158 | dependency: transitive 159 | description: 160 | name: http 161 | url: "https://pub.dartlang.org" 162 | source: hosted 163 | version: "0.12.0+2" 164 | http_multi_server: 165 | dependency: transitive 166 | description: 167 | name: http_multi_server 168 | url: "https://pub.dartlang.org" 169 | source: hosted 170 | version: "2.1.0" 171 | http_parser: 172 | dependency: transitive 173 | description: 174 | name: http_parser 175 | url: "https://pub.dartlang.org" 176 | source: hosted 177 | version: "3.1.3" 178 | image: 179 | dependency: transitive 180 | description: 181 | name: image 182 | url: "https://pub.dartlang.org" 183 | source: hosted 184 | version: "2.1.4" 185 | intl: 186 | dependency: transitive 187 | description: 188 | name: intl 189 | url: "https://pub.dartlang.org" 190 | source: hosted 191 | version: "0.16.0" 192 | io: 193 | dependency: transitive 194 | description: 195 | name: io 196 | url: "https://pub.dartlang.org" 197 | source: hosted 198 | version: "0.3.3" 199 | js: 200 | dependency: transitive 201 | description: 202 | name: js 203 | url: "https://pub.dartlang.org" 204 | source: hosted 205 | version: "0.6.1+1" 206 | json_rpc_2: 207 | dependency: transitive 208 | description: 209 | name: json_rpc_2 210 | url: "https://pub.dartlang.org" 211 | source: hosted 212 | version: "2.1.0" 213 | kernel: 214 | dependency: transitive 215 | description: 216 | name: kernel 217 | url: "https://pub.dartlang.org" 218 | source: hosted 219 | version: "0.3.19" 220 | logging: 221 | dependency: transitive 222 | description: 223 | name: logging 224 | url: "https://pub.dartlang.org" 225 | source: hosted 226 | version: "0.11.3+2" 227 | matcher: 228 | dependency: transitive 229 | description: 230 | name: matcher 231 | url: "https://pub.dartlang.org" 232 | source: hosted 233 | version: "0.12.6" 234 | merge_map: 235 | dependency: transitive 236 | description: 237 | name: merge_map 238 | url: "https://pub.dartlang.org" 239 | source: hosted 240 | version: "1.0.2" 241 | meta: 242 | dependency: transitive 243 | description: 244 | name: meta 245 | url: "https://pub.dartlang.org" 246 | source: hosted 247 | version: "1.1.8" 248 | mime: 249 | dependency: transitive 250 | description: 251 | name: mime 252 | url: "https://pub.dartlang.org" 253 | source: hosted 254 | version: "0.9.6+3" 255 | multi_server_socket: 256 | dependency: transitive 257 | description: 258 | name: multi_server_socket 259 | url: "https://pub.dartlang.org" 260 | source: hosted 261 | version: "1.0.2" 262 | nested: 263 | dependency: transitive 264 | description: 265 | name: nested 266 | url: "https://pub.dartlang.org" 267 | source: hosted 268 | version: "0.0.4" 269 | node_interop: 270 | dependency: transitive 271 | description: 272 | name: node_interop 273 | url: "https://pub.dartlang.org" 274 | source: hosted 275 | version: "1.0.3" 276 | node_io: 277 | dependency: transitive 278 | description: 279 | name: node_io 280 | url: "https://pub.dartlang.org" 281 | source: hosted 282 | version: "1.0.1+2" 283 | node_preamble: 284 | dependency: transitive 285 | description: 286 | name: node_preamble 287 | url: "https://pub.dartlang.org" 288 | source: hosted 289 | version: "1.4.8" 290 | package_config: 291 | dependency: transitive 292 | description: 293 | name: package_config 294 | url: "https://pub.dartlang.org" 295 | source: hosted 296 | version: "1.1.0" 297 | package_resolver: 298 | dependency: transitive 299 | description: 300 | name: package_resolver 301 | url: "https://pub.dartlang.org" 302 | source: hosted 303 | version: "1.0.10" 304 | path: 305 | dependency: transitive 306 | description: 307 | name: path 308 | url: "https://pub.dartlang.org" 309 | source: hosted 310 | version: "1.6.4" 311 | pedantic: 312 | dependency: transitive 313 | description: 314 | name: pedantic 315 | url: "https://pub.dartlang.org" 316 | source: hosted 317 | version: "1.8.0+1" 318 | petitparser: 319 | dependency: transitive 320 | description: 321 | name: petitparser 322 | url: "https://pub.dartlang.org" 323 | source: hosted 324 | version: "2.4.0" 325 | platform: 326 | dependency: transitive 327 | description: 328 | name: platform 329 | url: "https://pub.dartlang.org" 330 | source: hosted 331 | version: "2.2.1" 332 | pool: 333 | dependency: transitive 334 | description: 335 | name: pool 336 | url: "https://pub.dartlang.org" 337 | source: hosted 338 | version: "1.4.0" 339 | process: 340 | dependency: transitive 341 | description: 342 | name: process 343 | url: "https://pub.dartlang.org" 344 | source: hosted 345 | version: "3.0.12" 346 | provider: 347 | dependency: "direct main" 348 | description: 349 | name: provider 350 | url: "https://pub.dartlang.org" 351 | source: hosted 352 | version: "4.0.1" 353 | pub_semver: 354 | dependency: transitive 355 | description: 356 | name: pub_semver 357 | url: "https://pub.dartlang.org" 358 | source: hosted 359 | version: "1.4.2" 360 | quiver: 361 | dependency: transitive 362 | description: 363 | name: quiver 364 | url: "https://pub.dartlang.org" 365 | source: hosted 366 | version: "2.0.5" 367 | shelf: 368 | dependency: transitive 369 | description: 370 | name: shelf 371 | url: "https://pub.dartlang.org" 372 | source: hosted 373 | version: "0.7.5" 374 | shelf_packages_handler: 375 | dependency: transitive 376 | description: 377 | name: shelf_packages_handler 378 | url: "https://pub.dartlang.org" 379 | source: hosted 380 | version: "1.0.4" 381 | shelf_static: 382 | dependency: transitive 383 | description: 384 | name: shelf_static 385 | url: "https://pub.dartlang.org" 386 | source: hosted 387 | version: "0.2.8" 388 | shelf_web_socket: 389 | dependency: transitive 390 | description: 391 | name: shelf_web_socket 392 | url: "https://pub.dartlang.org" 393 | source: hosted 394 | version: "0.2.3" 395 | sky_engine: 396 | dependency: transitive 397 | description: flutter 398 | source: sdk 399 | version: "0.0.99" 400 | source_map_stack_trace: 401 | dependency: transitive 402 | description: 403 | name: source_map_stack_trace 404 | url: "https://pub.dartlang.org" 405 | source: hosted 406 | version: "1.1.5" 407 | source_maps: 408 | dependency: transitive 409 | description: 410 | name: source_maps 411 | url: "https://pub.dartlang.org" 412 | source: hosted 413 | version: "0.10.8" 414 | source_span: 415 | dependency: transitive 416 | description: 417 | name: source_span 418 | url: "https://pub.dartlang.org" 419 | source: hosted 420 | version: "1.5.5" 421 | stack_trace: 422 | dependency: transitive 423 | description: 424 | name: stack_trace 425 | url: "https://pub.dartlang.org" 426 | source: hosted 427 | version: "1.9.3" 428 | stream_channel: 429 | dependency: transitive 430 | description: 431 | name: stream_channel 432 | url: "https://pub.dartlang.org" 433 | source: hosted 434 | version: "2.0.0" 435 | string_scanner: 436 | dependency: transitive 437 | description: 438 | name: string_scanner 439 | url: "https://pub.dartlang.org" 440 | source: hosted 441 | version: "1.0.5" 442 | term_glyph: 443 | dependency: transitive 444 | description: 445 | name: term_glyph 446 | url: "https://pub.dartlang.org" 447 | source: hosted 448 | version: "1.1.0" 449 | test: 450 | dependency: "direct dev" 451 | description: 452 | name: test 453 | url: "https://pub.dartlang.org" 454 | source: hosted 455 | version: "1.9.4" 456 | test_api: 457 | dependency: transitive 458 | description: 459 | name: test_api 460 | url: "https://pub.dartlang.org" 461 | source: hosted 462 | version: "0.2.11" 463 | test_core: 464 | dependency: transitive 465 | description: 466 | name: test_core 467 | url: "https://pub.dartlang.org" 468 | source: hosted 469 | version: "0.2.15" 470 | typed_data: 471 | dependency: transitive 472 | description: 473 | name: typed_data 474 | url: "https://pub.dartlang.org" 475 | source: hosted 476 | version: "1.1.6" 477 | vector_math: 478 | dependency: transitive 479 | description: 480 | name: vector_math 481 | url: "https://pub.dartlang.org" 482 | source: hosted 483 | version: "2.0.8" 484 | vm_service: 485 | dependency: transitive 486 | description: 487 | name: vm_service 488 | url: "https://pub.dartlang.org" 489 | source: hosted 490 | version: "2.2.0" 491 | vm_service_client: 492 | dependency: transitive 493 | description: 494 | name: vm_service_client 495 | url: "https://pub.dartlang.org" 496 | source: hosted 497 | version: "0.2.6+2" 498 | watcher: 499 | dependency: transitive 500 | description: 501 | name: watcher 502 | url: "https://pub.dartlang.org" 503 | source: hosted 504 | version: "0.9.7+12" 505 | web_socket_channel: 506 | dependency: transitive 507 | description: 508 | name: web_socket_channel 509 | url: "https://pub.dartlang.org" 510 | source: hosted 511 | version: "1.1.0" 512 | xml: 513 | dependency: transitive 514 | description: 515 | name: xml 516 | url: "https://pub.dartlang.org" 517 | source: hosted 518 | version: "3.5.0" 519 | yaml: 520 | dependency: transitive 521 | description: 522 | name: yaml 523 | url: "https://pub.dartlang.org" 524 | source: hosted 525 | version: "2.2.0" 526 | sdks: 527 | dart: ">=2.6.0 <3.0.0" 528 | flutter: ">=1.12.1" 529 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: multiple_base_routes 2 | description: A new Flutter application. 3 | version: 1.0.0+1 4 | 5 | environment: 6 | sdk: ">=2.6.0 <3.0.0" 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | 12 | provider: ^4.0.1 13 | deep_link_navigation: 14 | path: ../../ 15 | 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter 19 | 20 | flutter_driver: 21 | sdk: flutter 22 | 23 | test: any 24 | flutter_gherkin: ^1.1.4 25 | 26 | flutter: 27 | uses-material-design: true -------------------------------------------------------------------------------- /examples/multiple_base_routes/test_driver/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_driver/driver_extension.dart'; 2 | import 'package:multiple_base_routes/main.dart' as app; 3 | 4 | void main() { 5 | enableFlutterDriverExtension(); 6 | app.main(); 7 | } -------------------------------------------------------------------------------- /examples/multiple_base_routes/test_driver/app_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter_gherkin/flutter_gherkin.dart'; 4 | import 'package:gherkin/gherkin.dart'; 5 | import 'package:glob/glob.dart'; 6 | 7 | import 'steps/steps.dart'; 8 | 9 | Future main() { 10 | final config = FlutterTestConfiguration() 11 | ..features = [Glob(r"test_driver/features/**.feature")] 12 | ..reporters = [ 13 | ProgressReporter(), 14 | TestRunSummaryReporter(), 15 | ] 16 | ..stepDefinitions = [GoBack(), TextDisappears(), TitleIs()] 17 | ..restartAppBetweenScenarios = true 18 | ..targetAppPath = "test_driver/app.dart" 19 | ..exitAfterTestRun = true; 20 | return GherkinRunner().execute(config); 21 | } 22 | 23 | // cd examples/multiple_base_routes 24 | // ~/Dev/flutter/bin/cache/dart-sdk/bin/dart test_driver/app_test.dart 25 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/test_driver/features/bottom_navigation.feature: -------------------------------------------------------------------------------- 1 | Feature: Deep link navigator bottom navigation 2 | Bottom navigation should appear on every screen and control multiple base routes. 3 | 4 | Scenario: Navigation goes to default page once authenticated 5 | Given the title is "Login" 6 | When I tap the "login" button 7 | Then the title is "Library" 8 | 9 | Scenario: Navigating though bottom navigation 10 | Given I tap the "login" button 11 | # Navigate to each screen 12 | When I tap the "favorites" button 13 | Then the title is "My favorites (3)" 14 | When I tap the "user" button 15 | Then the title is "User" 16 | When I tap the "library" button 17 | Then the title is "Library" 18 | 19 | Scenario: Error screen goes full screen 20 | Given I tap the "login" button 21 | When I tap the "Non-existant push" button 22 | Then the title is "ERROR" 23 | # Hide bottom navigation 24 | And the text "Library" disappears 25 | And the text "Favorites" disappears 26 | And the text "User" disappears -------------------------------------------------------------------------------- /examples/multiple_base_routes/test_driver/features/promise.feature: -------------------------------------------------------------------------------- 1 | Feature: Deep link navigator push promises 2 | A push should be able to return a future result from a pop. 3 | 4 | Scenario: Cancelling (un)authentication does nothing when already authenticated 5 | Given I tap the "login" button 6 | And I tap the "user" button 7 | And I tap the "authentication chooser" button 8 | And the title is "Authentication" 9 | When I go back 1 time 10 | And I tap the "favorites" button 11 | Then the title is "My favorites (3)" 12 | 13 | Scenario: Authenticating does nothing when already authenticated 14 | Given I tap the "login" button 15 | And I tap the "user" button 16 | And I tap the "authentication chooser" button 17 | And the title is "Authentication" 18 | When I tap the "authenticate" button 19 | And I tap the "favorites" button 20 | Then the title is "My favorites (3)" 21 | 22 | Scenario: Unauthenticating redirects to login screen on navigation attempt 23 | Given I tap the "login" button 24 | And I tap the "user" button 25 | And I tap the "authentication chooser" button 26 | And the title is "Authentication" 27 | When I tap the "unauthenticate" button 28 | And I tap the "favorites" button 29 | Then the title is "Login" 30 | 31 | -------------------------------------------------------------------------------- /examples/multiple_base_routes/test_driver/steps/go_back.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_driver/flutter_driver.dart'; 2 | import 'package:flutter_gherkin/flutter_gherkin.dart'; 3 | import 'package:gherkin/gherkin.dart'; 4 | 5 | class GoBack extends When1WithWorld { 6 | @override 7 | RegExp get pattern => RegExp(r"I go back {int} time(s)"); 8 | 9 | @override 10 | Future executeStep(int times) async { 11 | for (var i = 0; i < times; i++) { 12 | await world.driver.tap(find.pageBack(), timeout: timeout); 13 | await FlutterDriverUtils.waitForFlutter(world.driver, timeout: timeout); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /examples/multiple_base_routes/test_driver/steps/steps.dart: -------------------------------------------------------------------------------- 1 | export 'go_back.dart'; 2 | export 'text_disappears.dart'; 3 | export 'title_is.dart'; -------------------------------------------------------------------------------- /examples/multiple_base_routes/test_driver/steps/text_disappears.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_gherkin/flutter_gherkin.dart'; 2 | import 'package:gherkin/gherkin.dart'; 3 | import 'package:flutter_driver/flutter_driver.dart'; 4 | 5 | class TextDisappears extends Then1WithWorld { 6 | @override 7 | RegExp get pattern => RegExp(r"the text {string} disappears"); 8 | 9 | @override 10 | Future executeStep(String input1) async { 11 | expect(await FlutterDriverUtils.isAbsent(world.driver, find.text(input1)), true); 12 | } 13 | } -------------------------------------------------------------------------------- /examples/multiple_base_routes/test_driver/steps/title_is.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_gherkin/flutter_gherkin.dart'; 2 | import 'package:gherkin/gherkin.dart'; 3 | import 'package:flutter_driver/flutter_driver.dart'; 4 | 5 | class TitleIs extends Then1WithWorld { 6 | @override 7 | RegExp get pattern => RegExp(r"the title is {string}"); 8 | 9 | @override 10 | Future executeStep(String title) async { 11 | try { 12 | final text = await FlutterDriverUtils.getText(world.driver, find.byValueKey("title"));// .catchError(onError); default reporter message thing! 13 | expect(text, title); 14 | } catch (e) { 15 | await reporter.message("Step error '${pattern.pattern}': $e", MessageLevel.error); 16 | rethrow; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /examples/single_base_route/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | /build/ 31 | 32 | # Android related 33 | **/android/**/gradle-wrapper.jar 34 | **/android/.gradle 35 | **/android/captures/ 36 | **/android/gradlew 37 | **/android/gradlew.bat 38 | **/android/local.properties 39 | **/android/**/GeneratedPluginRegistrant.java 40 | 41 | # iOS/XCode related 42 | **/ios/**/*.mode1v3 43 | **/ios/**/*.mode2v3 44 | **/ios/**/*.moved-aside 45 | **/ios/**/*.pbxuser 46 | **/ios/**/*.perspectivev3 47 | **/ios/**/*sync/ 48 | **/ios/**/.sconsign.dblite 49 | **/ios/**/.tags* 50 | **/ios/**/.vagrant/ 51 | **/ios/**/DerivedData/ 52 | **/ios/**/Icon? 53 | **/ios/**/Pods/ 54 | **/ios/**/.symlinks/ 55 | **/ios/**/profile 56 | **/ios/**/xcuserdata 57 | **/ios/.generated/ 58 | **/ios/Flutter/App.framework 59 | **/ios/Flutter/Flutter.framework 60 | **/ios/Flutter/Generated.xcconfig 61 | **/ios/Flutter/app.flx 62 | **/ios/Flutter/app.zip 63 | **/ios/Flutter/flutter_assets/ 64 | **/ios/Flutter/flutter_export_environment.sh 65 | **/ios/ServiceDefinitions.json 66 | **/ios/Runner/GeneratedPluginRegistrant.* 67 | 68 | # Exceptions to above rules. 69 | !**/ios/**/default.mode1v3 70 | !**/ios/**/default.mode2v3 71 | !**/ios/**/default.pbxuser 72 | !**/ios/**/default.perspectivev3 73 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 74 | -------------------------------------------------------------------------------- /examples/single_base_route/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: cc949a8e8b9cf394b9290a8e80f87af3e207dce5 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /examples/single_base_route/README.md: -------------------------------------------------------------------------------- 1 | # Single Base Route 2 | 3 | **This example demonstrates:** 4 | * Dispatchers with path-only deep links 5 | * Dispatchers with value deep links (ArtistDL, SongDL) 6 | * Cross-branch navigation (from favorite's song page to artist page) 7 | ![Navigation diagram for multiple base routes example](navigation.png) -------------------------------------------------------------------------------- /examples/single_base_route/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 28 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "dev.krasnov.single_base_route" 42 | minSdkVersion 16 43 | targetSdkVersion 28 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 47 | } 48 | 49 | buildTypes { 50 | release { 51 | // TODO: Add your own signing config for the release build. 52 | // Signing with the debug keys for now, so `flutter run --release` works. 53 | signingConfig signingConfigs.debug 54 | } 55 | } 56 | } 57 | 58 | flutter { 59 | source '../..' 60 | } 61 | 62 | dependencies { 63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 64 | testImplementation 'junit:junit:4.12' 65 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 66 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 67 | } 68 | -------------------------------------------------------------------------------- /examples/single_base_route/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/single_base_route/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 13 | 20 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /examples/single_base_route/android/app/src/main/kotlin/dev/krasnov/single_base_route/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package dev.krasnov.single_base_route 2 | 3 | import android.os.Bundle 4 | 5 | import io.flutter.app.FlutterActivity 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | GeneratedPluginRegistrant.registerWith(this) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/single_base_route/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /examples/single_base_route/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/single_base_route/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/single_base_route/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/single_base_route/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/single_base_route/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/single_base_route/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/single_base_route/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/single_base_route/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/single_base_route/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/single_base_route/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/single_base_route/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /examples/single_base_route/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/single_base_route/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.2.71' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.2.1' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /examples/single_base_route/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | 3 | android.enableR8=true 4 | -------------------------------------------------------------------------------- /examples/single_base_route/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip 7 | -------------------------------------------------------------------------------- /examples/single_base_route/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /examples/single_base_route/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/single_base_route/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /examples/single_base_route/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /examples/single_base_route/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/single_base_route/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /examples/single_base_route/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/single_base_route/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/single_base_route/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /examples/single_base_route/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/single_base_route/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /examples/single_base_route/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/single_base_route/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /examples/single_base_route/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/single_base_route/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /examples/single_base_route/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/single_base_route/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /examples/single_base_route/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/single_base_route/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /examples/single_base_route/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/single_base_route/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /examples/single_base_route/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/single_base_route/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /examples/single_base_route/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/single_base_route/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /examples/single_base_route/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/single_base_route/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /examples/single_base_route/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/single_base_route/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /examples/single_base_route/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/single_base_route/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /examples/single_base_route/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/single_base_route/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /examples/single_base_route/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/single_base_route/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /examples/single_base_route/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/single_base_route/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /examples/single_base_route/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/single_base_route/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /examples/single_base_route/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/single_base_route/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/single_base_route/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /examples/single_base_route/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/single_base_route/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /examples/single_base_route/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/single_base_route/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /examples/single_base_route/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /examples/single_base_route/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /examples/single_base_route/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/single_base_route/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | single_base_route 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /examples/single_base_route/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /examples/single_base_route/lib/data.dart: -------------------------------------------------------------------------------- 1 | import 'package:single_base_route/model.dart'; 2 | 3 | /// Random placeholder data meant to be replaced with a database. 4 | class Data { 5 | static final artists = { 6 | "1234": Artist( 7 | "1234", 8 | "John Lennon", 9 | [ 10 | Song( 11 | "4312", 12 | "1234", 13 | "Yesterday", 14 | ), 15 | Song( 16 | "4313", 17 | "1234", 18 | "Yellow Submarine", 19 | ), 20 | ], 21 | ), 22 | "1235": Artist( 23 | "1235", 24 | "Ludwig van beethoven", 25 | [ 26 | Song( 27 | "6363", 28 | "1235", 29 | "Symphony No. 5", 30 | ), 31 | Song( 32 | "6364", 33 | "1235", 34 | "Symphony No. 9", 35 | ), 36 | ], 37 | ), 38 | }; 39 | 40 | static final favoriteSongs = [ 41 | artists["1234"].songs[0], 42 | artists["1235"].songs[0], 43 | artists["1235"].songs[1], 44 | ]; 45 | } -------------------------------------------------------------------------------- /examples/single_base_route/lib/deep_links.dart: -------------------------------------------------------------------------------- 1 | import 'package:deep_link_navigation/deep_link_navigation.dart'; 2 | 3 | import 'package:single_base_route/model.dart'; 4 | 5 | class LibraryDL extends DeepLink { 6 | LibraryDL() : super("library"); 7 | } 8 | 9 | class FavoritesDL extends DeepLink { 10 | FavoritesDL() : super("favorites"); 11 | } 12 | 13 | class ArtistDL extends ValueDeepLink { 14 | ArtistDL(Artist artist) : super("artist", artist, toString: (artist) => artist.id); 15 | } 16 | 17 | class SongDL extends ValueDeepLink { 18 | SongDL(Song song) : super("song", song, toString: (song) => song.id); 19 | } 20 | 21 | class ErrorDL extends ValueDeepLink { 22 | ErrorDL(E e) : super("error", e); 23 | } -------------------------------------------------------------------------------- /examples/single_base_route/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:deep_link_navigation/deep_link_navigation.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'package:single_base_route/deep_links.dart'; 5 | import 'package:single_base_route/model.dart'; 6 | import 'package:single_base_route/widgets/artist_page.dart'; 7 | import 'package:single_base_route/widgets/error_page.dart'; 8 | import 'package:single_base_route/widgets/favorites_page.dart'; 9 | import 'package:single_base_route/widgets/library_page.dart'; 10 | import 'package:single_base_route/widgets/song_page.dart'; 11 | import 'package:single_base_route/widgets/splash_page.dart'; 12 | 13 | void main() => runApp(MusicApp()); 14 | 15 | class MusicApp extends StatelessWidget { 16 | @override 17 | Widget build(BuildContext context) => DeepLinkMaterialApp( 18 | // This is where the magic happens 19 | navigation: Dispatcher() 20 | ..path( 21 | (route) => LibraryPage(), 22 | subNavigation: Dispatcher() 23 | ..value( 24 | (artist, route) => ArtistPage(artist: artist), 25 | subNavigation: (artist) => Dispatcher() 26 | ..song() 27 | ) 28 | ..path( 29 | (route) => FavoritesPage(), 30 | subNavigation: Dispatcher() 31 | ..song() 32 | ) 33 | ..value>((exception, route) => ErrorPage(exception)), 34 | ) 35 | // Exception handling mappings and route dispatchers are specified independently 36 | ..exception((exception, route) => [LibraryDL(), ErrorDL(exception)]), 37 | defaultRoute: [LibraryDL()], 38 | splashScreen: SplashPage(), 39 | // Non-navigation related fields are still available 40 | themeMode: ThemeMode.light, 41 | ); 42 | } 43 | 44 | /// Reusing code through static extension methods. 45 | extension DispatcherExtensions on Dispatcher { 46 | void song() => value((song, route) => SongPage(song: song)); 47 | } -------------------------------------------------------------------------------- /examples/single_base_route/lib/model.dart: -------------------------------------------------------------------------------- 1 | import 'package:meta/meta.dart'; 2 | 3 | @immutable 4 | class Artist { 5 | final String id; 6 | 7 | final String name; 8 | 9 | final List songs; 10 | 11 | Artist(this.id, this.name, this.songs); 12 | } 13 | 14 | @immutable 15 | class Song { 16 | final String id; 17 | 18 | /// Foreign key for artist 19 | final String artistId; 20 | 21 | final String name; 22 | 23 | Song(this.id, this.artistId, this.name); 24 | } -------------------------------------------------------------------------------- /examples/single_base_route/lib/widgets/artist_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:single_base_route/model.dart'; 4 | import 'package:single_base_route/widgets/song_list_tile.dart'; 5 | 6 | class ArtistPage extends StatelessWidget { 7 | final Artist artist; 8 | 9 | const ArtistPage({Key key, this.artist}) : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Scaffold( 14 | appBar: AppBar( 15 | title: Text( 16 | artist.name, 17 | key: Key("title"), 18 | ), 19 | ), 20 | body: ListView( 21 | children: [ 22 | for (final song in artist.songs) 23 | SongListTile(song: song), 24 | ], 25 | ), 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/single_base_route/lib/widgets/error_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ErrorPage extends StatelessWidget { 4 | final Exception e; 5 | 6 | ErrorPage(this.e); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Scaffold( 11 | appBar: AppBar( 12 | title: Text( 13 | "ERROR", 14 | key: Key("title"), 15 | ) 16 | ), 17 | body: Center( 18 | child: Text("$e"), 19 | ), 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/single_base_route/lib/widgets/favorites_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:single_base_route/data.dart'; 4 | import 'package:single_base_route/widgets/song_list_tile.dart'; 5 | 6 | class FavoritesPage extends StatelessWidget { 7 | @override 8 | Widget build(BuildContext context) { 9 | return Scaffold( 10 | appBar: AppBar( 11 | title: Text( 12 | "My favorites (${Data.favoriteSongs.length})", 13 | key: Key("title"), 14 | ), 15 | ), 16 | body: ListView( 17 | children: [ 18 | for (final song in Data.favoriteSongs) 19 | SongListTile(song: song), 20 | ], 21 | ), 22 | ); 23 | } 24 | } -------------------------------------------------------------------------------- /examples/single_base_route/lib/widgets/library_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:deep_link_navigation/deep_link_navigation.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'package:single_base_route/data.dart'; 5 | import 'package:single_base_route/deep_links.dart'; 6 | 7 | class LibraryPage extends StatelessWidget { 8 | @override 9 | Widget build(BuildContext context) { 10 | return Scaffold( 11 | appBar: AppBar( 12 | title: Text( 13 | "Library", 14 | key: Key("title"), 15 | ), 16 | actions: [ 17 | IconButton( 18 | icon: Icon(Icons.favorite), 19 | onPressed: () { 20 | DeepLinkNavigator.of(context).push(FavoritesDL()); 21 | }, 22 | ) 23 | ], 24 | ), 25 | body: ListView( 26 | children: [ 27 | for (final artist in Data.artists.values) 28 | ListTile( 29 | key: Key(artist.name), 30 | leading: CircleAvatar(child: Icon(Icons.person)), 31 | title: Text(artist.name), 32 | subtitle: Text("${artist.songs.length} songs"), 33 | onTap: () => DeepLinkNavigator.of(context).push(ArtistDL(artist)), 34 | ), 35 | Divider(), 36 | ListTile( 37 | key: Key("Non-existant push"), 38 | leading: CircleAvatar(child: Icon(Icons.bug_report)), 39 | title: Text("Non-existant push"), 40 | // Route doesn't exist 41 | onTap: () => DeepLinkNavigator.of(context).push(SongDL(Data.favoriteSongs[0])), 42 | ), 43 | ListTile( 44 | key: Key("Non-existant navigate"), 45 | leading: CircleAvatar(child: Icon(Icons.bug_report)), 46 | title: Text("Non-existant navigate"), 47 | // Route doesn't exist 48 | onTap: () => DeepLinkNavigator.of(context).navigateTo([LibraryDL(), SongDL(Data.favoriteSongs[1])]), 49 | ) 50 | ], 51 | ), 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /examples/single_base_route/lib/widgets/song_list_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:deep_link_navigation/deep_link_navigation.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'package:single_base_route/deep_links.dart'; 5 | import 'package:single_base_route/model.dart'; 6 | 7 | class SongListTile extends StatelessWidget { 8 | final Song song; 9 | 10 | const SongListTile({Key key, this.song}) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return ListTile( 15 | key: Key(song.name), 16 | leading: Icon(Icons.music_note), 17 | title: Text(song.name), 18 | onTap: () => DeepLinkNavigator.of(context).push(SongDL(song)), 19 | ); 20 | } 21 | } -------------------------------------------------------------------------------- /examples/single_base_route/lib/widgets/song_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:deep_link_navigation/deep_link_navigation.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'package:single_base_route/data.dart'; 5 | import 'package:single_base_route/deep_links.dart'; 6 | import 'package:single_base_route/model.dart'; 7 | 8 | class SongPage extends StatelessWidget { 9 | final Song song; 10 | 11 | const SongPage({Key key, this.song}) : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Scaffold( 16 | appBar: AppBar( 17 | title: Text( 18 | song.name, 19 | key: Key("title") 20 | ), 21 | ), 22 | body: Center( 23 | child: Column( 24 | mainAxisAlignment: MainAxisAlignment.center, 25 | children: [ 26 | Icon( 27 | Icons.music_video, 28 | size: 200, 29 | ), 30 | RaisedButton( 31 | child: Text("Go to artist"), 32 | onPressed: () => DeepLinkNavigator.of(context).navigateTo([ 33 | LibraryDL(), 34 | ArtistDL(Data.artists[song.artistId]), 35 | ]), 36 | ), 37 | ], 38 | ), 39 | ), 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/single_base_route/lib/widgets/splash_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SplashPage extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return Scaffold( 7 | appBar: AppBar( 8 | title: Text( 9 | "splash", 10 | key: Key("title"), 11 | ) 12 | ), 13 | body: IconButton( 14 | icon: Icon(Icons.autorenew), 15 | onPressed: () => null, 16 | ), 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/single_base_route/navigation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/5010892aa229c9827514e61012245ae815c8bf7e/examples/single_base_route/navigation.png -------------------------------------------------------------------------------- /examples/single_base_route/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | after_layout: 5 | dependency: transitive 6 | description: 7 | name: after_layout 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "1.0.7+2" 11 | analyzer: 12 | dependency: transitive 13 | description: 14 | name: analyzer 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "0.36.4" 18 | archive: 19 | dependency: transitive 20 | description: 21 | name: archive 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.0.11" 25 | args: 26 | dependency: transitive 27 | description: 28 | name: args 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.5.2" 32 | async: 33 | dependency: transitive 34 | description: 35 | name: async 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "2.4.0" 39 | boolean_selector: 40 | dependency: transitive 41 | description: 42 | name: boolean_selector 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.0.5" 46 | charcode: 47 | dependency: transitive 48 | description: 49 | name: charcode 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.1.2" 53 | collection: 54 | dependency: transitive 55 | description: 56 | name: collection 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "1.14.11" 60 | convert: 61 | dependency: transitive 62 | description: 63 | name: convert 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "2.1.1" 67 | coverage: 68 | dependency: transitive 69 | description: 70 | name: coverage 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "0.13.3+3" 74 | crypto: 75 | dependency: transitive 76 | description: 77 | name: crypto 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "2.1.3" 81 | csslib: 82 | dependency: transitive 83 | description: 84 | name: csslib 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "0.16.1" 88 | deep_link_navigation: 89 | dependency: "direct main" 90 | description: 91 | path: "../.." 92 | relative: true 93 | source: path 94 | version: "1.3.1" 95 | file: 96 | dependency: transitive 97 | description: 98 | name: file 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "5.1.0" 102 | flutter: 103 | dependency: "direct main" 104 | description: flutter 105 | source: sdk 106 | version: "0.0.0" 107 | flutter_driver: 108 | dependency: "direct dev" 109 | description: flutter 110 | source: sdk 111 | version: "0.0.0" 112 | flutter_gherkin: 113 | dependency: "direct dev" 114 | description: 115 | name: flutter_gherkin 116 | url: "https://pub.dartlang.org" 117 | source: hosted 118 | version: "1.1.4" 119 | flutter_test: 120 | dependency: "direct dev" 121 | description: flutter 122 | source: sdk 123 | version: "0.0.0" 124 | front_end: 125 | dependency: transitive 126 | description: 127 | name: front_end 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "0.1.19" 131 | fuchsia_remote_debug_protocol: 132 | dependency: transitive 133 | description: flutter 134 | source: sdk 135 | version: "0.0.0" 136 | gherkin: 137 | dependency: transitive 138 | description: 139 | name: gherkin 140 | url: "https://pub.dartlang.org" 141 | source: hosted 142 | version: "1.1.3" 143 | glob: 144 | dependency: transitive 145 | description: 146 | name: glob 147 | url: "https://pub.dartlang.org" 148 | source: hosted 149 | version: "1.2.0" 150 | html: 151 | dependency: transitive 152 | description: 153 | name: html 154 | url: "https://pub.dartlang.org" 155 | source: hosted 156 | version: "0.14.0+3" 157 | http: 158 | dependency: transitive 159 | description: 160 | name: http 161 | url: "https://pub.dartlang.org" 162 | source: hosted 163 | version: "0.12.0+2" 164 | http_multi_server: 165 | dependency: transitive 166 | description: 167 | name: http_multi_server 168 | url: "https://pub.dartlang.org" 169 | source: hosted 170 | version: "2.1.0" 171 | http_parser: 172 | dependency: transitive 173 | description: 174 | name: http_parser 175 | url: "https://pub.dartlang.org" 176 | source: hosted 177 | version: "3.1.3" 178 | image: 179 | dependency: transitive 180 | description: 181 | name: image 182 | url: "https://pub.dartlang.org" 183 | source: hosted 184 | version: "2.1.4" 185 | intl: 186 | dependency: transitive 187 | description: 188 | name: intl 189 | url: "https://pub.dartlang.org" 190 | source: hosted 191 | version: "0.16.0" 192 | io: 193 | dependency: transitive 194 | description: 195 | name: io 196 | url: "https://pub.dartlang.org" 197 | source: hosted 198 | version: "0.3.3" 199 | js: 200 | dependency: transitive 201 | description: 202 | name: js 203 | url: "https://pub.dartlang.org" 204 | source: hosted 205 | version: "0.6.1+1" 206 | json_rpc_2: 207 | dependency: transitive 208 | description: 209 | name: json_rpc_2 210 | url: "https://pub.dartlang.org" 211 | source: hosted 212 | version: "2.1.0" 213 | kernel: 214 | dependency: transitive 215 | description: 216 | name: kernel 217 | url: "https://pub.dartlang.org" 218 | source: hosted 219 | version: "0.3.19" 220 | logging: 221 | dependency: transitive 222 | description: 223 | name: logging 224 | url: "https://pub.dartlang.org" 225 | source: hosted 226 | version: "0.11.3+2" 227 | matcher: 228 | dependency: transitive 229 | description: 230 | name: matcher 231 | url: "https://pub.dartlang.org" 232 | source: hosted 233 | version: "0.12.6" 234 | merge_map: 235 | dependency: transitive 236 | description: 237 | name: merge_map 238 | url: "https://pub.dartlang.org" 239 | source: hosted 240 | version: "1.0.2" 241 | meta: 242 | dependency: transitive 243 | description: 244 | name: meta 245 | url: "https://pub.dartlang.org" 246 | source: hosted 247 | version: "1.1.8" 248 | mime: 249 | dependency: transitive 250 | description: 251 | name: mime 252 | url: "https://pub.dartlang.org" 253 | source: hosted 254 | version: "0.9.6+3" 255 | multi_server_socket: 256 | dependency: transitive 257 | description: 258 | name: multi_server_socket 259 | url: "https://pub.dartlang.org" 260 | source: hosted 261 | version: "1.0.2" 262 | nested: 263 | dependency: transitive 264 | description: 265 | name: nested 266 | url: "https://pub.dartlang.org" 267 | source: hosted 268 | version: "0.0.4" 269 | node_interop: 270 | dependency: transitive 271 | description: 272 | name: node_interop 273 | url: "https://pub.dartlang.org" 274 | source: hosted 275 | version: "1.0.3" 276 | node_io: 277 | dependency: transitive 278 | description: 279 | name: node_io 280 | url: "https://pub.dartlang.org" 281 | source: hosted 282 | version: "1.0.1+2" 283 | node_preamble: 284 | dependency: transitive 285 | description: 286 | name: node_preamble 287 | url: "https://pub.dartlang.org" 288 | source: hosted 289 | version: "1.4.8" 290 | package_config: 291 | dependency: transitive 292 | description: 293 | name: package_config 294 | url: "https://pub.dartlang.org" 295 | source: hosted 296 | version: "1.1.0" 297 | package_resolver: 298 | dependency: transitive 299 | description: 300 | name: package_resolver 301 | url: "https://pub.dartlang.org" 302 | source: hosted 303 | version: "1.0.10" 304 | path: 305 | dependency: transitive 306 | description: 307 | name: path 308 | url: "https://pub.dartlang.org" 309 | source: hosted 310 | version: "1.6.4" 311 | pedantic: 312 | dependency: transitive 313 | description: 314 | name: pedantic 315 | url: "https://pub.dartlang.org" 316 | source: hosted 317 | version: "1.8.0+1" 318 | petitparser: 319 | dependency: transitive 320 | description: 321 | name: petitparser 322 | url: "https://pub.dartlang.org" 323 | source: hosted 324 | version: "2.4.0" 325 | platform: 326 | dependency: transitive 327 | description: 328 | name: platform 329 | url: "https://pub.dartlang.org" 330 | source: hosted 331 | version: "2.2.1" 332 | pool: 333 | dependency: transitive 334 | description: 335 | name: pool 336 | url: "https://pub.dartlang.org" 337 | source: hosted 338 | version: "1.4.0" 339 | process: 340 | dependency: transitive 341 | description: 342 | name: process 343 | url: "https://pub.dartlang.org" 344 | source: hosted 345 | version: "3.0.12" 346 | provider: 347 | dependency: transitive 348 | description: 349 | name: provider 350 | url: "https://pub.dartlang.org" 351 | source: hosted 352 | version: "4.0.1" 353 | pub_semver: 354 | dependency: transitive 355 | description: 356 | name: pub_semver 357 | url: "https://pub.dartlang.org" 358 | source: hosted 359 | version: "1.4.2" 360 | quiver: 361 | dependency: transitive 362 | description: 363 | name: quiver 364 | url: "https://pub.dartlang.org" 365 | source: hosted 366 | version: "2.0.5" 367 | shelf: 368 | dependency: transitive 369 | description: 370 | name: shelf 371 | url: "https://pub.dartlang.org" 372 | source: hosted 373 | version: "0.7.5" 374 | shelf_packages_handler: 375 | dependency: transitive 376 | description: 377 | name: shelf_packages_handler 378 | url: "https://pub.dartlang.org" 379 | source: hosted 380 | version: "1.0.4" 381 | shelf_static: 382 | dependency: transitive 383 | description: 384 | name: shelf_static 385 | url: "https://pub.dartlang.org" 386 | source: hosted 387 | version: "0.2.8" 388 | shelf_web_socket: 389 | dependency: transitive 390 | description: 391 | name: shelf_web_socket 392 | url: "https://pub.dartlang.org" 393 | source: hosted 394 | version: "0.2.3" 395 | sky_engine: 396 | dependency: transitive 397 | description: flutter 398 | source: sdk 399 | version: "0.0.99" 400 | source_map_stack_trace: 401 | dependency: transitive 402 | description: 403 | name: source_map_stack_trace 404 | url: "https://pub.dartlang.org" 405 | source: hosted 406 | version: "1.1.5" 407 | source_maps: 408 | dependency: transitive 409 | description: 410 | name: source_maps 411 | url: "https://pub.dartlang.org" 412 | source: hosted 413 | version: "0.10.8" 414 | source_span: 415 | dependency: transitive 416 | description: 417 | name: source_span 418 | url: "https://pub.dartlang.org" 419 | source: hosted 420 | version: "1.5.5" 421 | stack_trace: 422 | dependency: transitive 423 | description: 424 | name: stack_trace 425 | url: "https://pub.dartlang.org" 426 | source: hosted 427 | version: "1.9.3" 428 | stream_channel: 429 | dependency: transitive 430 | description: 431 | name: stream_channel 432 | url: "https://pub.dartlang.org" 433 | source: hosted 434 | version: "2.0.0" 435 | string_scanner: 436 | dependency: transitive 437 | description: 438 | name: string_scanner 439 | url: "https://pub.dartlang.org" 440 | source: hosted 441 | version: "1.0.5" 442 | term_glyph: 443 | dependency: transitive 444 | description: 445 | name: term_glyph 446 | url: "https://pub.dartlang.org" 447 | source: hosted 448 | version: "1.1.0" 449 | test: 450 | dependency: "direct dev" 451 | description: 452 | name: test 453 | url: "https://pub.dartlang.org" 454 | source: hosted 455 | version: "1.9.4" 456 | test_api: 457 | dependency: transitive 458 | description: 459 | name: test_api 460 | url: "https://pub.dartlang.org" 461 | source: hosted 462 | version: "0.2.11" 463 | test_core: 464 | dependency: transitive 465 | description: 466 | name: test_core 467 | url: "https://pub.dartlang.org" 468 | source: hosted 469 | version: "0.2.15" 470 | typed_data: 471 | dependency: transitive 472 | description: 473 | name: typed_data 474 | url: "https://pub.dartlang.org" 475 | source: hosted 476 | version: "1.1.6" 477 | vector_math: 478 | dependency: transitive 479 | description: 480 | name: vector_math 481 | url: "https://pub.dartlang.org" 482 | source: hosted 483 | version: "2.0.8" 484 | vm_service: 485 | dependency: transitive 486 | description: 487 | name: vm_service 488 | url: "https://pub.dartlang.org" 489 | source: hosted 490 | version: "2.2.0" 491 | vm_service_client: 492 | dependency: transitive 493 | description: 494 | name: vm_service_client 495 | url: "https://pub.dartlang.org" 496 | source: hosted 497 | version: "0.2.6+2" 498 | watcher: 499 | dependency: transitive 500 | description: 501 | name: watcher 502 | url: "https://pub.dartlang.org" 503 | source: hosted 504 | version: "0.9.7+12" 505 | web_socket_channel: 506 | dependency: transitive 507 | description: 508 | name: web_socket_channel 509 | url: "https://pub.dartlang.org" 510 | source: hosted 511 | version: "1.1.0" 512 | xml: 513 | dependency: transitive 514 | description: 515 | name: xml 516 | url: "https://pub.dartlang.org" 517 | source: hosted 518 | version: "3.5.0" 519 | yaml: 520 | dependency: transitive 521 | description: 522 | name: yaml 523 | url: "https://pub.dartlang.org" 524 | source: hosted 525 | version: "2.2.0" 526 | sdks: 527 | dart: ">=2.6.0 <3.0.0" 528 | flutter: ">=1.12.1" 529 | -------------------------------------------------------------------------------- /examples/single_base_route/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: single_base_route 2 | description: A new Flutter application. 3 | version: 1.0.0+1 4 | 5 | environment: 6 | sdk: ">=2.6.0 <3.0.0" 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | 12 | deep_link_navigation: 13 | path: ../../ 14 | 15 | dev_dependencies: 16 | flutter_test: 17 | sdk: flutter 18 | 19 | flutter_driver: 20 | sdk: flutter 21 | 22 | test: any 23 | flutter_gherkin: ^1.1.4 24 | 25 | flutter: 26 | uses-material-design: true -------------------------------------------------------------------------------- /examples/single_base_route/test_driver/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_driver/driver_extension.dart'; 2 | 3 | import 'package:single_base_route/main.dart' as app; 4 | 5 | void main() { 6 | enableFlutterDriverExtension(); 7 | app.main(); 8 | } -------------------------------------------------------------------------------- /examples/single_base_route/test_driver/app_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter_gherkin/flutter_gherkin.dart'; 4 | import 'package:gherkin/gherkin.dart'; 5 | import 'package:glob/glob.dart'; 6 | 7 | import 'steps/steps.dart'; 8 | 9 | Future main() { 10 | final config = FlutterTestConfiguration() 11 | ..features = [Glob(r"test_driver/features/**.feature")] 12 | ..reporters = [ 13 | ProgressReporter(), 14 | TestRunSummaryReporter(), 15 | ] 16 | ..stepDefinitions = [GoBack(), NavigateToArtist(), OpenArtist(), OpenFavorites(), OpenSong(), TextAppears(), TitleIs()] 17 | ..restartAppBetweenScenarios = true 18 | ..targetAppPath = "test_driver/app.dart" 19 | ..exitAfterTestRun = true; 20 | return GherkinRunner().execute(config); 21 | } 22 | 23 | // cd examples/single_base_route 24 | // ~/Dev/flutter/bin/cache/dart-sdk/bin/dart test_driver/app_test.dart -------------------------------------------------------------------------------- /examples/single_base_route/test_driver/features/navigate.feature: -------------------------------------------------------------------------------- 1 | Feature: Deep link navigator direct navigation 2 | Routes should be accessed from any other route. 3 | 4 | Scenario: Navigation from artists' song page to artist page 5 | Given I open the artist "John Lennon" 6 | And I open the song "Yesterday" 7 | And the title is "Yesterday" 8 | When I navigate to the song's artist 9 | Then the title is "John Lennon" 10 | # Once like normal 11 | Then I go back 1 time 12 | And the title is "Library" 13 | 14 | Scenario: Navigation from favorites' song page to artist page 15 | Given I open my favorite songs 16 | And I open the song "Yesterday" 17 | And the title is "Yesterday" 18 | When I navigate to the song's artist 19 | Then the title is "John Lennon" 20 | # Once instead of twice 21 | Then I go back 1 time 22 | And the title is "Library" 23 | 24 | Scenario: Navigation to an unknown route shows error page 25 | Given the title is "Library" 26 | When I tap the "Non-existant navigate" button 27 | Then the title is "ERROR" 28 | And the text "Route not found: [library, song/6363]" appears 29 | Then I go back 1 time 30 | And the title is "Library" -------------------------------------------------------------------------------- /examples/single_base_route/test_driver/features/push_pop.feature: -------------------------------------------------------------------------------- 1 | Feature: Deep link navigator push and pop 2 | Pages should be pushed onto and popped from the navigator stack. 3 | 4 | Scenario: Navigation starts with the default route 5 | Then the title is "Library" 6 | 7 | Scenario: Pushing to and popping from favorites page 8 | Given I open my favorite songs 9 | And the title is "My favorites (3)" 10 | When I go back 1 time 11 | Then the title is "Library" 12 | 13 | Scenario: Pushing to and popping favorites' song page 14 | Given I open my favorite songs 15 | And I open the song "Yesterday" 16 | And the title is "Yesterday" 17 | When I go back 1 time 18 | Then the title is "My favorites (3)" 19 | 20 | Scenario: Pushing to and popping artist page 21 | Given I open the artist "John Lennon" 22 | And the title is "John Lennon" 23 | When I go back 1 time 24 | Then the title is "Library" 25 | 26 | Scenario: Pushing to and popping artist's song page 27 | Given I open the artist "John Lennon" 28 | And I open the song "Yesterday" 29 | And the title is "Yesterday" 30 | When I go back 1 time 31 | Then the title is "John Lennon" 32 | 33 | Scenario: Pushing to an unknown route shows error page 34 | Given the title is "Library" 35 | When I tap the "Non-existant push" button 36 | Then the title is "ERROR" 37 | And the text "Route not found: [library, song/4312]" appears 38 | Then I go back 1 time 39 | And the title is "Library" -------------------------------------------------------------------------------- /examples/single_base_route/test_driver/steps/go_back.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_driver/flutter_driver.dart'; 2 | import 'package:flutter_gherkin/flutter_gherkin.dart'; 3 | import 'package:gherkin/gherkin.dart'; 4 | 5 | class GoBack extends When1WithWorld { 6 | @override 7 | RegExp get pattern => RegExp(r"I go back {int} time(s)"); 8 | 9 | @override 10 | Future executeStep(int times) async { 11 | for (var i = 0; i < times; i++) { 12 | await world.driver.tap(find.pageBack(), timeout: timeout); 13 | await FlutterDriverUtils.waitForFlutter(world.driver, timeout: timeout); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /examples/single_base_route/test_driver/steps/navigate_to_artist.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_gherkin/flutter_gherkin.dart'; 2 | import 'package:gherkin/gherkin.dart'; 3 | import 'package:flutter_driver/flutter_driver.dart'; 4 | 5 | class NavigateToArtist extends WhenWithWorld { 6 | @override 7 | RegExp get pattern => RegExp(r"I navigate to the song's artist"); 8 | 9 | @override 10 | Future executeStep() async { 11 | await FlutterDriverUtils.tap(world.driver, find.text("Go to artist")); 12 | } 13 | } -------------------------------------------------------------------------------- /examples/single_base_route/test_driver/steps/open_artist.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_driver/flutter_driver.dart'; 2 | import 'package:flutter_gherkin/flutter_gherkin.dart'; 3 | import 'package:gherkin/gherkin.dart'; 4 | 5 | class OpenArtist extends When1WithWorld { 6 | @override 7 | RegExp get pattern => RegExp(r"I open the artist {string}"); 8 | 9 | @override 10 | Future executeStep(String key) async => FlutterDriverUtils.tap(world.driver, find.byValueKey(key)); 11 | } -------------------------------------------------------------------------------- /examples/single_base_route/test_driver/steps/open_favorites.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_gherkin/flutter_gherkin.dart'; 2 | import 'package:gherkin/gherkin.dart'; 3 | import 'package:flutter_driver/flutter_driver.dart'; 4 | 5 | class OpenFavorites extends WhenWithWorld { 6 | @override 7 | RegExp get pattern => RegExp(r"I open my favorite songs"); 8 | 9 | @override 10 | Future executeStep() async { 11 | await FlutterDriverUtils.tap(world.driver, find.byType("IconButton")); 12 | } 13 | } -------------------------------------------------------------------------------- /examples/single_base_route/test_driver/steps/open_song.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_driver/flutter_driver.dart'; 2 | import 'package:flutter_gherkin/flutter_gherkin.dart'; 3 | import 'package:gherkin/gherkin.dart'; 4 | 5 | class OpenSong extends When1WithWorld { 6 | @override 7 | RegExp get pattern => RegExp(r"I open the song {string}"); 8 | 9 | @override 10 | Future executeStep(String key) async => FlutterDriverUtils.tap(world.driver, find.byValueKey(key)); 11 | } -------------------------------------------------------------------------------- /examples/single_base_route/test_driver/steps/steps.dart: -------------------------------------------------------------------------------- 1 | export 'go_back.dart'; 2 | export 'navigate_to_artist.dart'; 3 | export 'open_artist.dart'; 4 | export 'open_favorites.dart'; 5 | export 'open_song.dart'; 6 | export 'text_appears.dart'; 7 | export 'title_is.dart'; -------------------------------------------------------------------------------- /examples/single_base_route/test_driver/steps/text_appears.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_gherkin/flutter_gherkin.dart'; 2 | import 'package:gherkin/gherkin.dart'; 3 | import 'package:flutter_driver/flutter_driver.dart'; 4 | 5 | class TextAppears extends Then1WithWorld { 6 | @override 7 | RegExp get pattern => RegExp(r"the text {string} appears"); 8 | 9 | @override 10 | Future executeStep(String input1) async { 11 | 12 | expect(await FlutterDriverUtils.isPresent(find.text(input1), world.driver), true); 13 | } 14 | } -------------------------------------------------------------------------------- /examples/single_base_route/test_driver/steps/title_is.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_gherkin/flutter_gherkin.dart'; 2 | import 'package:gherkin/gherkin.dart'; 3 | import 'package:flutter_driver/flutter_driver.dart'; 4 | 5 | class TitleIs extends Then1WithWorld { 6 | @override 7 | RegExp get pattern => RegExp(r"the title is {string}"); 8 | 9 | @override 10 | Future executeStep(String title) async { 11 | try { 12 | final text = await FlutterDriverUtils.getText(world.driver, find.byValueKey("title"));// .catchError(onError); default reporter message thing! 13 | expect(text, title); 14 | } catch (e) { 15 | await reporter.message("Step error '${pattern.pattern}': $e", MessageLevel.error); 16 | rethrow; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /lib/deep_link_navigation.dart: -------------------------------------------------------------------------------- 1 | library deep_link_navigation; 2 | 3 | export 'src/deep_link.dart'; 4 | export 'src/deep_link_material_app.dart'; 5 | export 'src/deep_link_navigator.dart'; 6 | export 'src/dispatchers.dart'; 7 | export 'src/exceptions.dart'; -------------------------------------------------------------------------------- /lib/src/deep_link.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | /// Base class for all deep links. 4 | /// 5 | /// eg. final deepLink = XYZDeepLink(); 6 | @immutable 7 | abstract class DeepLink { 8 | /// Human-readable representation of deep link. 9 | final String path; 10 | 11 | DeepLink(this.path); 12 | 13 | /// Mixins on [DeepLink] may override [onDispatch] to perform validation that throws custom errors. 14 | /// May validate against state accessed through context, or defined as a global/static variable. 15 | /// Does nothing by default. 16 | void onDispatch(BuildContext context) {} 17 | 18 | @override 19 | String toString() => path; 20 | } 21 | 22 | /// Deep link that also stores typed data. 23 | /// May override [toString()] to provide human-readable data representation. 24 | /// 25 | /// eg. final deepLink = XYZValueDeepLink(42); 26 | /// eg. class SongDL extends ValueDeepLink { 27 | /// SongDL(Song song) : super("song", song, toString: (song) => song.id); 28 | /// } 29 | abstract class ValueDeepLink extends DeepLink { 30 | final T data; 31 | 32 | ValueDeepLink(String path, this.data, {String Function(T) toString}) 33 | : super("$path/${toString != null ? toString(data) : data}"); 34 | } 35 | 36 | /// Value deep link that base64 encodes its [toString] representation. 37 | /// Shouldn't manually override toString(). 38 | /// 39 | /// eg. final deepLink = XYZBase64DeepLink(data); 40 | /// TODO: test this deep link 41 | //import 'dart:convert'; 42 | //abstract class Base64DeepLink extends ValueDeepLink { 43 | // /// Converts [original] to base64 representation. 44 | // static String _base64Encoded(String original) { 45 | // final bytes = utf8.encode(original); 46 | // return base64.encode(bytes); 47 | // } 48 | // 49 | // Base64DeepLink(String path, String data) : super(path, data, toString: (data) => _base64Encoded(data)); 50 | //} -------------------------------------------------------------------------------- /lib/src/deep_link_material_app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | import 'package:deep_link_navigation/deep_link_navigation.dart'; 6 | import 'package:deep_link_navigation/src/default_page_loader.dart'; 7 | import 'package:deep_link_navigation/src/pop_observer.dart'; 8 | 9 | // TODO: create versions for material, ios, etc 10 | 11 | /// Extension of [MaterialApp] adds deep-linking functionality by orchestrating a native flutter navigator. 12 | class DeepLinkMaterialApp extends MaterialApp { 13 | /// Internal instance of a native flutter Navigator. 14 | static final _navigatorKey = GlobalKey(); 15 | 16 | /// Top-level dispatcher to represent deep link navigation hierarchy. 17 | final Dispatcher navigation; 18 | 19 | /// {@macro flutter.widgets.widgetsApp.builder} 20 | /// 21 | /// Material specific features such as [showDialog] and [showMenu], and widgets 22 | /// such as [Tooltip], [PopupMenuButton], also require a [Navigator] to properly 23 | /// function. 24 | /// 25 | /// Efficiently rebuilds sandwiched widgets on every meaningful change to [DeepLinkNavigator]. 26 | final Widget Function(BuildContext context, DeepLinkNavigator deepLinkNavigator, Widget child) childBuilder; 27 | 28 | /// Widget to display while waiting for first build to complete. 29 | /// Defaults to empty container. 30 | final Widget splashScreen; 31 | 32 | /// Initial route of application. 33 | /// [RouteNotFound] exception defaults back to this route. 34 | /// If null, must manually navigate from the splash screen. 35 | /// If specified, must contain at least one deep link. 36 | final List defaultRoute; 37 | 38 | /// Creates a MaterialApp. 39 | /// 40 | /// At least one of [home], [routes], [onGenerateRoute], or [builder] must be 41 | /// non-null. If only [routes] is given, it must include an entry for the 42 | /// [Navigator.defaultRouteName] (`/`), since that is the route used when the 43 | /// application is launched with an intent that specifies an otherwise 44 | /// unsupported route. 45 | /// 46 | /// This class creates an instance of [WidgetsApp]. 47 | /// 48 | /// The boolean arguments, [routes], and [navigatorObservers], must not be null. 49 | DeepLinkMaterialApp({ 50 | Key key, 51 | // GlobalKey navigatorKey, 52 | List navigatorObservers = const [], 53 | String title = '', 54 | GenerateAppTitle onGenerateTitle, 55 | Color color, 56 | ThemeData theme, 57 | ThemeData darkTheme, 58 | ThemeMode themeMode = ThemeMode.system, 59 | Locale locale, 60 | Iterable> localizationsDelegates, 61 | LocaleListResolutionCallback localeListResolutionCallback, 62 | LocaleResolutionCallback localeResolutionCallback, 63 | Iterable supportedLocales = const [Locale('en', 'US')], 64 | bool debugShowMaterialGrid = false, 65 | bool showPerformanceOverlay = false, 66 | bool checkerboardRasterCacheImages = false, 67 | bool checkerboardOffscreenLayers = false, 68 | bool showSemanticsDebugger = false, 69 | bool debugShowCheckedModeBanner = true, 70 | // Custom fields 71 | @required this.navigation, 72 | this.childBuilder, 73 | this.splashScreen, 74 | this.defaultRoute, 75 | }) : super( 76 | key: key, 77 | navigatorKey: _navigatorKey, 78 | home: splashScreen ?? Container(), 79 | navigatorObservers: [ 80 | PopObserver(), 81 | ...navigatorObservers 82 | ], 83 | // Provider for easy access through ListenableProvider.of(context) and rebuild capabilities 84 | builder: (BuildContext context, Widget child) => ListenableProvider( 85 | create: (BuildContext context) => DeepLinkNavigator( 86 | navigatorKey: _navigatorKey, 87 | navigation: navigation, 88 | defaultRoute: defaultRoute, 89 | ), 90 | dispose: (BuildContext context, DeepLinkNavigator value) => null, 91 | // Inline extraction of [DeepLinkNavigator] instance 92 | child: Consumer( 93 | builder: (BuildContext context, DeepLinkNavigator value, Widget child) => childBuilder != null 94 | ? childBuilder(context, value, DefaultRouteLoader(child: child)) 95 | : DefaultRouteLoader(child: child), 96 | child: child, 97 | ), 98 | ), 99 | title: title, 100 | onGenerateTitle: onGenerateTitle, 101 | color: color, 102 | theme: theme, 103 | darkTheme: darkTheme, 104 | themeMode: themeMode, 105 | locale: locale, 106 | localizationsDelegates: localizationsDelegates, 107 | localeListResolutionCallback: localeListResolutionCallback, 108 | localeResolutionCallback: localeResolutionCallback, 109 | supportedLocales: supportedLocales, 110 | debugShowMaterialGrid: debugShowMaterialGrid, 111 | showPerformanceOverlay: showPerformanceOverlay, 112 | checkerboardRasterCacheImages: checkerboardRasterCacheImages, 113 | checkerboardOffscreenLayers: checkerboardOffscreenLayers, 114 | showSemanticsDebugger: showSemanticsDebugger, 115 | debugShowCheckedModeBanner: debugShowCheckedModeBanner, 116 | ); 117 | } -------------------------------------------------------------------------------- /lib/src/deep_link_navigator.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:merge_map/merge_map.dart'; 5 | import 'package:provider/provider.dart' show Provider; 6 | 7 | import 'package:deep_link_navigation/deep_link_navigation.dart'; 8 | import 'package:deep_link_navigation/src/utils.dart'; 9 | import 'package:deep_link_navigation/src/dispatchers.dart'; 10 | 11 | /// Internal state which orchestrates native navigator though [navigatorKey]. 12 | class DeepLinkNavigator with ChangeNotifier { 13 | /// {@macro flutter.widgets.widgetsApp.navigatorKey} 14 | final GlobalKey navigatorKey; 15 | 16 | /// Top-level dispatcher to represent deep link navigation hierarchy. 17 | final Dispatcher navigation; 18 | 19 | /// Initial route of application. 20 | /// [RouteNotFound] exception defaults back to this route. 21 | /// If null, must manually navigate from the splash screen. 22 | /// If specified, must contain at least one deep link. 23 | final List defaultRoute; 24 | 25 | /// Current location within navigation hierarchy. 26 | List _currentRoute; 27 | List get currentRoute => _currentRoute; 28 | 29 | /// Previous location within navigation hierarchy. 30 | List _previousRoute; 31 | List get previousRoute => _previousRoute; 32 | 33 | /// Only return promises and animate transitions when pushing. 34 | bool _actionWasPush = false; 35 | bool get actionWasPush => _actionWasPush; 36 | 37 | /// Whether currently handling pops for a deep link navigation. 38 | /// Typical pop handling should be temporarily disabled. 39 | bool _poppingForDeepNavigation = false; 40 | 41 | DeepLinkNavigator({ 42 | @required this.navigatorKey, 43 | @required this.navigation, 44 | this.defaultRoute 45 | }) : 46 | assert(navigation != null), 47 | assert(defaultRoute == null || defaultRoute.isNotEmpty); 48 | 49 | /// Handle a significant change of the current route. 50 | /// Optionally handles [promise] for pushes. 51 | @optionalTypeArgs 52 | Future _handleRouteChanged([Future promise]) { 53 | handlePop(); 54 | 55 | // Mutable route built up level-by-level 56 | var accumulatedRoute = []; 57 | 58 | // Deep link dispatcher at the next level of navigation hierarchy 59 | var nextDispatcher = navigation; 60 | 61 | // Whether this is the last level of navigation hierarchy 62 | // This doesn't affect the base (first level) navigation 63 | var endOfNavigationHierarchy = false; 64 | 65 | // Mutable exception handling uses deepest level possible 66 | var errors = {}; 67 | 68 | try { 69 | // Trace each deep link to reach current route 70 | for (final deepLink in currentRoute) { 71 | // Throw if further navigation can't be found 72 | if (endOfNavigationHierarchy) { 73 | throw(RouteNotFound(currentRoute)); 74 | } 75 | 76 | // Deep link dispatcher at this level of navigation hierarchy 77 | final currentDispatcher = nextDispatcher; 78 | assert(currentDispatcher.routeBuilders.isNotEmpty); 79 | 80 | // Attempt to go deeper in navigation 81 | if (currentDispatcher.subNavigations.containsKey(deepLink.runtimeType)) { 82 | nextDispatcher = currentDispatcher.subNavigations[deepLink.runtimeType](deepLink is ValueDeepLink ? deepLink.data : null); 83 | assert(nextDispatcher != null); 84 | } 85 | else { 86 | endOfNavigationHierarchy = true; 87 | } 88 | 89 | // Merge and override error mappings 90 | errors = mergeMap([errors, currentDispatcher.errorMappers]); 91 | assert(errors.containsKey(RouteNotFound), "Must specify `exception` on base navigation builder."); 92 | 93 | // Attempt to find deep link 94 | try { 95 | if (!currentDispatcher.routeBuilders.containsKey(deepLink.runtimeType)) { 96 | throw(RouteNotFound(currentRoute)); 97 | } 98 | 99 | // Custom logic can throw custom exceptions 100 | deepLink.onDispatch(navigatorKey.currentContext); 101 | } catch (_) { 102 | // Set current route to represent real navigator state on failure 103 | // Otherwise, handleRouteChanged would pop too many times 104 | _currentRoute = accumulatedRoute; 105 | rethrow; 106 | } 107 | 108 | // Append deep link to accumulated route 109 | accumulatedRoute.add(deepLink); 110 | 111 | // Only return push promise on last deep link 112 | if (actionWasPush && deepLink == currentRoute.last) { 113 | return _pushNavigatorIfNecessary(deepLink, currentDispatcher, accumulatedRoute); 114 | } 115 | _pushNavigatorIfNecessary(deepLink, currentDispatcher, accumulatedRoute); 116 | } 117 | } on RouteNotFound catch(exception) { 118 | final route = errors[exception.runtimeType](exception, List.from(accumulatedRoute)); 119 | assert(route != null && route.isNotEmpty); 120 | 121 | navigateTo(route); 122 | } on Exception catch(exception) { 123 | if (!errors.containsKey(exception.runtimeType)) rethrow; 124 | 125 | final route = errors[exception.runtimeType](exception, List.from(accumulatedRoute)); 126 | assert(route != null && route.isNotEmpty); 127 | 128 | navigateTo(route); 129 | } 130 | return null; 131 | } 132 | 133 | /// Pops until lowest common ancestor route. 134 | void handlePop() { 135 | _poppingForDeepNavigation = true; 136 | var _timesDidNotPop = 0; 137 | for (final deepLink in previousRoute.sublist(commonElementsInRoutes).reversed) { 138 | // Allow base routes to be conceptually popped once since they're replaced instead of pushed 139 | if (navigatorKey.currentState.canPop()) { 140 | navigatorKey.currentState.pop(); 141 | } else assert(++_timesDidNotPop <= 1, "$deepLink caused a second pop to a base route"); 142 | } 143 | _poppingForDeepNavigation = false; 144 | } 145 | 146 | /// Pushes widgets for deep links for deep links that didn't exist in previous route. 147 | @optionalTypeArgs 148 | Future _pushNavigatorIfNecessary(DeepLink deepLink, Dispatcher currentDispatcher, List accumulatedRoute) { 149 | if (previousRoute.isEmpty || currentRoute.sublist(commonElementsInRoutes).contains(deepLink)) { 150 | // Widget that corresponds with deep link 151 | final widget = currentDispatcher.routeBuilders[deepLink.runtimeType]( 152 | deepLink is ValueDeepLink ? deepLink.data : null, 153 | accumulatedRoute, 154 | ); 155 | assert(widget != null); 156 | 157 | // Animate only push (and thus pop) transitions 158 | final pageRoute = actionWasPush 159 | ? MaterialPageRoute(builder: (BuildContext context) => widget) 160 | : NoAnimationPageRoute(builder: (BuildContext context) => widget); 161 | 162 | // Replace root level pages 163 | final action = accumulatedRoute.length == 1 164 | ? (pageRoute) => navigatorKey.currentState.pushReplacement(pageRoute) 165 | : (pageRoute) => navigatorKey.currentState.push(pageRoute); 166 | 167 | // Reuse native navigator's completer 168 | return action(pageRoute); 169 | } 170 | return null; 171 | } 172 | 173 | /// Number of common deep links in [previousRoute] and [currentRoute]. 174 | int get commonElementsInRoutes => indexOfLastCommonElement(previousRoute ?? [], currentRoute); 175 | 176 | /// Pushes the given [deepLink] onto the navigator. 177 | /// Optionally returns a [T] value. 178 | @optionalTypeArgs 179 | Future push(DeepLink deepLink) async { 180 | _previousRoute = List.from(currentRoute); 181 | currentRoute.add(deepLink); 182 | _actionWasPush = true; 183 | 184 | notifyListeners(); 185 | return _handleRouteChanged(); 186 | } 187 | 188 | /// Navigates to a specific route anywhere in the navigation hierarchy. 189 | void navigateTo(List route) { 190 | _previousRoute = List.from(currentRoute ?? []); 191 | _currentRoute = List.from(route); 192 | _actionWasPush = false; 193 | 194 | notifyListeners(); 195 | _handleRouteChanged(); 196 | } 197 | 198 | /// Resets navigation to user's default page. 199 | /// Does nothing if [defaultRoute] is null. 200 | void replaceWithDefault() { 201 | if (defaultRoute != null) { 202 | navigateTo(defaultRoute); 203 | } 204 | } 205 | 206 | /// Pop the top-most deep link off the navigator that most tightly encloses the given context. 207 | @optionalTypeArgs 208 | bool pop([T result]) => navigatorKey.currentState.pop(result); 209 | 210 | /// Handles deep link popping logic. 211 | /// Notified exclusively from native navigator's [PopObserver]. 212 | void notifyPopped() { 213 | // Only processes if not currently popping routes for [navigateTo]. 214 | if (!_poppingForDeepNavigation) { 215 | _previousRoute = List.from(currentRoute); 216 | currentRoute.removeLast(); 217 | 218 | notifyListeners(); 219 | } 220 | } 221 | 222 | /// Method that allows widgets to access [DeepLinkNavigator] as their `BuildContext` 223 | /// contains a [DeepLinkNavigator] instance. 224 | /// 225 | /// TODO: other examples using dart code notation 226 | /// ```dart 227 | /// DeepLinkNavigator.of(context); 228 | /// ``` 229 | static DeepLinkNavigator of(BuildContext context) { 230 | try { 231 | return Provider.of(context, listen: false); 232 | } on Object catch (_) { 233 | throw FlutterError( 234 | """ 235 | DeepLinkNavigator.of() called with a context that does not contain a DeepLinkNavigator. 236 | No ancestor could be found starting from the context that was passed to DeepLinkNavigator.of(). 237 | This can happen if the context you used comes from a widget above the DeepLinkRouter. 238 | """, 239 | ); 240 | } 241 | } 242 | 243 | /// URL-friendly representation of current route. 244 | @override 245 | String toString() => currentRoute.join("/"); 246 | } 247 | 248 | // TODO: add on google analytics listener on callback 'routeChanged' -------------------------------------------------------------------------------- /lib/src/default_page_loader.dart: -------------------------------------------------------------------------------- 1 | import 'package:after_layout/after_layout.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | 4 | import 'package:deep_link_navigation/deep_link_navigation.dart'; 5 | 6 | /// Navigates to default route after first build. 7 | class DefaultRouteLoader extends StatefulWidget { 8 | /// The widget that is below this widget in the tree. 9 | /// 10 | /// {@macro flutter.widgets.child} 11 | final Widget child; 12 | 13 | const DefaultRouteLoader({Key key, this.child}) : super(key: key); 14 | 15 | @override 16 | _DefaultRouteLoaderState createState() => _DefaultRouteLoaderState(); 17 | } 18 | 19 | class _DefaultRouteLoaderState extends State with AfterLayoutMixin { 20 | @override 21 | void afterFirstLayout(BuildContext context) => DeepLinkNavigator.of(context).replaceWithDefault(); 22 | 23 | @override 24 | Widget build(BuildContext context) => widget.child; 25 | } -------------------------------------------------------------------------------- /lib/src/dispatchers.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | import 'package:deep_link_navigation/src/deep_link.dart'; 4 | 5 | /// Widget for route. 6 | typedef PathBuilder = Widget Function(List route); 7 | 8 | /// Widget for route with [value]. 9 | typedef ValueBuilder = Widget Function(T value, List route); 10 | 11 | /// Route mapping for [exception]. 12 | typedef ErrorMapping = List Function(Exception exception, List route); 13 | 14 | /// Dispatcher for this level of navigation with [value]. 15 | typedef NavigationValueBuilder = Dispatcher Function(T value); 16 | 17 | /// A non-leaf node in the navigation hierarchy tree. 18 | class Dispatcher { 19 | /// Internal representation of widget builders. 20 | /// Values must be dynamic since function parameter types don't upcast. 21 | /// Ideally, the value type would be `ValueBuilder`. 22 | Map _routeBuilders = {}; 23 | Map get routeBuilders => _routeBuilders; 24 | 25 | /// Internal representation of error mappings. 26 | Map _errorMappers = {}; 27 | Map get errorMappers => _errorMappers; 28 | 29 | /// Internal representation of deep link navigation hierarchy. 30 | /// Ideally the value type would be `NavigationValueBuilder`. 31 | Map _subNavigations = {}; 32 | Map get subNavigations => _subNavigations; 33 | 34 | /// Add a path widget builder to this level of hierarchy. 35 | void path
( 36 | PathBuilder builder, 37 | {Dispatcher subNavigation} 38 | ) { 39 | assert(DL != dynamic, "A deep link type must be specified."); 40 | assert(!routeBuilders.containsKey(DL), "A widget builder for ${DL.runtimeType} has already beed defined."); 41 | assert(builder != null); 42 | 43 | _routeBuilders[DL] = (_, route) => builder(route); 44 | 45 | if (subNavigation != null) { 46 | _subNavigations[DL] = (_) => subNavigation; 47 | } 48 | } 49 | 50 | /// Add a value widget builder to this level of hierarchy. 51 | void value>( 52 | ValueBuilder builder, 53 | {NavigationValueBuilder subNavigation} 54 | ) { 55 | assert(T != dynamic, "Data type must be specified."); 56 | assert(DL != dynamic, "A deep link type must be specified."); 57 | assert(!routeBuilders.containsKey(DL), "A widget builder for ${DL.runtimeType} has already beed defined."); 58 | assert(builder != null); 59 | 60 | _routeBuilders[DL] = builder; 61 | 62 | if (subNavigation != null) { 63 | _subNavigations[DL] = subNavigation; 64 | } 65 | } 66 | 67 | /// Add a exception mapping to this level of hierarchy. 68 | void exception(ErrorMapping mapper) { 69 | assert(E != dynamic, "An error type must be specified."); 70 | assert(!routeBuilders.containsKey(E), "An error mapping for ${E.runtimeType} has already beed defined."); 71 | assert(mapper != null); 72 | 73 | _errorMappers[E] = mapper; 74 | } 75 | } -------------------------------------------------------------------------------- /lib/src/exceptions.dart: -------------------------------------------------------------------------------- 1 | import 'package:deep_link_navigation/deep_link_navigation.dart'; 2 | 3 | /// Reserved exception thrown when a route didn't match the given navigation hierarchy. 4 | class RouteNotFound implements Exception { 5 | List route; 6 | RouteNotFound(this.route); 7 | 8 | @override 9 | String toString() => "Route not found: $route"; 10 | } -------------------------------------------------------------------------------- /lib/src/pop_observer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:deep_link_navigation/deep_link_navigation.dart'; 4 | 5 | /// Notifies [DeepLinkNavigator] of a pop that occurred from the native Flutter navigator. 6 | /// Handles android back button, back arrow, and [DeepLinkNavigator.pop()] calls native pop. 7 | /// 8 | /// eg. Navigator(observers: [PopObserver()]) 9 | class PopObserver extends NavigatorObserver { 10 | @override 11 | void didPop(Route route, Route previousRoute) => DeepLinkNavigator.of(route.navigator.context).notifyPopped(); 12 | } -------------------------------------------------------------------------------- /lib/src/utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as math; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | /// Used to compare how many deep links in a route match another route. 6 | int indexOfLastCommonElement(Iterable a, Iterable b) { 7 | int smallerLength = math.min(a.length, b.length); 8 | for (int i = 0; i < smallerLength; i++) { 9 | if (a.elementAt(i) != b.elementAt(i)) { 10 | return i; 11 | } 12 | } 13 | return smallerLength; 14 | } 15 | 16 | /// Page route transition without an animation. 17 | class NoAnimationPageRoute extends MaterialPageRoute { 18 | NoAnimationPageRoute({ 19 | @required WidgetBuilder builder, 20 | RouteSettings settings, 21 | bool maintainState = true, 22 | bool fullscreenDialog = false, // TODO makes 'x' icon instead of '<-' 23 | }) : super( 24 | builder: builder, 25 | maintainState: maintainState, 26 | settings: settings, 27 | fullscreenDialog: fullscreenDialog 28 | ); 29 | 30 | @override 31 | Widget buildTransitions(BuildContext context, Animation animation, Animation secondaryAnimation, Widget child) => child; 32 | } -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | after_layout: 5 | dependency: "direct main" 6 | description: 7 | name: after_layout 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "1.0.7+2" 11 | archive: 12 | dependency: transitive 13 | description: 14 | name: archive 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.0.11" 18 | args: 19 | dependency: transitive 20 | description: 21 | name: args 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.5.2" 25 | async: 26 | dependency: transitive 27 | description: 28 | name: async 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "2.4.0" 32 | boolean_selector: 33 | dependency: transitive 34 | description: 35 | name: boolean_selector 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.0.5" 39 | charcode: 40 | dependency: transitive 41 | description: 42 | name: charcode 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.1.2" 46 | collection: 47 | dependency: transitive 48 | description: 49 | name: collection 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.14.11" 53 | convert: 54 | dependency: transitive 55 | description: 56 | name: convert 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "2.1.1" 60 | crypto: 61 | dependency: transitive 62 | description: 63 | name: crypto 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "2.1.3" 67 | flutter: 68 | dependency: "direct main" 69 | description: flutter 70 | source: sdk 71 | version: "0.0.0" 72 | flutter_test: 73 | dependency: "direct dev" 74 | description: flutter 75 | source: sdk 76 | version: "0.0.0" 77 | image: 78 | dependency: transitive 79 | description: 80 | name: image 81 | url: "https://pub.dartlang.org" 82 | source: hosted 83 | version: "2.1.4" 84 | matcher: 85 | dependency: transitive 86 | description: 87 | name: matcher 88 | url: "https://pub.dartlang.org" 89 | source: hosted 90 | version: "0.12.6" 91 | merge_map: 92 | dependency: "direct main" 93 | description: 94 | name: merge_map 95 | url: "https://pub.dartlang.org" 96 | source: hosted 97 | version: "1.0.2" 98 | meta: 99 | dependency: transitive 100 | description: 101 | name: meta 102 | url: "https://pub.dartlang.org" 103 | source: hosted 104 | version: "1.1.8" 105 | nested: 106 | dependency: transitive 107 | description: 108 | name: nested 109 | url: "https://pub.dartlang.org" 110 | source: hosted 111 | version: "0.0.4" 112 | path: 113 | dependency: transitive 114 | description: 115 | name: path 116 | url: "https://pub.dartlang.org" 117 | source: hosted 118 | version: "1.6.4" 119 | pedantic: 120 | dependency: transitive 121 | description: 122 | name: pedantic 123 | url: "https://pub.dartlang.org" 124 | source: hosted 125 | version: "1.8.0+1" 126 | petitparser: 127 | dependency: transitive 128 | description: 129 | name: petitparser 130 | url: "https://pub.dartlang.org" 131 | source: hosted 132 | version: "2.4.0" 133 | provider: 134 | dependency: "direct main" 135 | description: 136 | name: provider 137 | url: "https://pub.dartlang.org" 138 | source: hosted 139 | version: "4.0.1" 140 | quiver: 141 | dependency: transitive 142 | description: 143 | name: quiver 144 | url: "https://pub.dartlang.org" 145 | source: hosted 146 | version: "2.0.5" 147 | sky_engine: 148 | dependency: transitive 149 | description: flutter 150 | source: sdk 151 | version: "0.0.99" 152 | source_span: 153 | dependency: transitive 154 | description: 155 | name: source_span 156 | url: "https://pub.dartlang.org" 157 | source: hosted 158 | version: "1.5.5" 159 | stack_trace: 160 | dependency: transitive 161 | description: 162 | name: stack_trace 163 | url: "https://pub.dartlang.org" 164 | source: hosted 165 | version: "1.9.3" 166 | stream_channel: 167 | dependency: transitive 168 | description: 169 | name: stream_channel 170 | url: "https://pub.dartlang.org" 171 | source: hosted 172 | version: "2.0.0" 173 | string_scanner: 174 | dependency: transitive 175 | description: 176 | name: string_scanner 177 | url: "https://pub.dartlang.org" 178 | source: hosted 179 | version: "1.0.5" 180 | term_glyph: 181 | dependency: transitive 182 | description: 183 | name: term_glyph 184 | url: "https://pub.dartlang.org" 185 | source: hosted 186 | version: "1.1.0" 187 | test_api: 188 | dependency: transitive 189 | description: 190 | name: test_api 191 | url: "https://pub.dartlang.org" 192 | source: hosted 193 | version: "0.2.11" 194 | typed_data: 195 | dependency: transitive 196 | description: 197 | name: typed_data 198 | url: "https://pub.dartlang.org" 199 | source: hosted 200 | version: "1.1.6" 201 | vector_math: 202 | dependency: transitive 203 | description: 204 | name: vector_math 205 | url: "https://pub.dartlang.org" 206 | source: hosted 207 | version: "2.0.8" 208 | xml: 209 | dependency: transitive 210 | description: 211 | name: xml 212 | url: "https://pub.dartlang.org" 213 | source: hosted 214 | version: "3.5.0" 215 | sdks: 216 | dart: ">=2.4.0 <3.0.0" 217 | flutter: ">=1.12.1" 218 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: deep_link_navigation 2 | description: Deep link navigation for Flutter apps with an elegant configuation internally orchestrating a native Flutter navigator. 3 | version: 1.3.1 4 | author: Dennis Krasnov 5 | homepage: https://github.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation 6 | repository: https://github.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation 7 | issue_tracker: https://github.com/Dennis-Krasnov/Flutter-Deep-Link-Navigation/issues 8 | 9 | environment: 10 | sdk: ">=2.2.2 <3.0.0" 11 | 12 | dependencies: 13 | flutter: 14 | sdk: flutter 15 | 16 | provider: ^4.0.1 17 | after_layout: ^1.0.7+2 18 | merge_map: ^1.0.2 19 | 20 | dev_dependencies: 21 | flutter_test: 22 | sdk: flutter 23 | 24 | flutter: 25 | 26 | -------------------------------------------------------------------------------- /test/deep_link_navigation_test.dart: -------------------------------------------------------------------------------- 1 | // TODO: deep link navigator unit tests (mock native navigator) 2 | // navigating to same route 3 | --------------------------------------------------------------------------------