riverpod_navigator 0.5.0 copy "riverpod_navigator: ^0.5.0" to clipboard
riverpod_navigator: ^0.5.0 copied to clipboard

outdated

Simple but powerfull Flutter navigation with riverpod, freezed and Navigator 2.0.

Navigator for Riverpod #

Simple but powerfull navigation library (based on Flutter Navigator 2.0, Riverpod, and Freezed) that solves the following problems: #

  • Strictly typed navigation:
    You can use navigate([Home(), Books(), Book(id: bookId)]); instead of navigate('home/books/$bookId'); in your code.
  • Easier coding:
    The problem of navigation is reduced to manipulation an immutable collection.
  • Better separation of concerns: UI x Model (thanks to riverpod 👍):
    Navigation logic can be developed and tested without typing a single flutter widget.
  • Asynchronous navigation:
    Prepare all necessary asynchronous operations before the navigation starts, e.g.
    • save data from the previous screen
    • loading data for new screen
  • Dependence on external providers:
    The navigation state may also depend on external providers, e.g. user login status
  • Possibility to configure many navigation parameters (Page builder, Navigator builder, Splash screen)

The mission #

Take a look at the following terms:

  • string path: stringPath = 'home/books/book;id=2';
  • string segment - the string path consists of three string segments: 'home', 'books', 'book;id=2'
  • typed path: typedPath = <TypedSegment>[HomeSegment(), BooksSegment(), BookSegment(id:2)];
  • typed segment - the typed path consists of three instances of [TypedSegment]'s: [HomeSegment], [BooksSegment], [BookSegment]
  • navigation stack of Flutter Navigator 2.0: HomeScreen(HomeSegment())) => BooksScreen(BooksSegment()) => BookScreen(BookSegment(id:3))

The mission of navigation is to keep string path <= typed path => navigation stack always in sync. With typed path as the source of the truth.

How does it work #

How to use it #

Lesson01 #

  • simple example

See lesson01.dart source code

1. classes for typed path segments (aka TypedSegment) #

From the following definition, freezed package generates three typed segment classes: HomeSegment, BooksSegment and BookSegment.

@freezed
class AppSegments with _$AppSegments, TypedSegment {
  AppSegments._();
  factory AppSegments.home() = HomeSegment;
  factory AppSegments.books() = BooksSegment;
  factory AppSegments.book({required int id}) = BookSegment;

  factory AppSegments.fromJson(Map<String, dynamic> json) => _$AppSegmentsFromJson(json);
}

2. App-specific navigator #

  • contains actions related to navigation. The actions are then used in the screen widgets.
  • configures various navigation parameters
class AppNavigator extends RiverpodNavigator {
  AppNavigator(Ref ref)
      : super(
          ref,
  //*** parameters common to all examples
          /// home (initial) navigation path
          initPath: [HomeSegment()],
          /// how to decode JSON to AppSegments
          json2Segment: (jsonMap, _) => AppSegments.fromJson(jsonMap),
          /// map TypedSegment's to Screens
          screenBuilder: appSegmentsScreenBuilder,
        );

Common navigation actions #

Future<void> toHome() => navigate([HomeSegment()]);
  Future<void> toBooks() => navigate([HomeSegment(), BooksSegment()]);
  Future<void> toBook({required int id}) => navigate([HomeSegment(), BooksSegment(), BookSegment(id: id)]);
  Future<void> bookNextPrevButton({bool? isPrev}) {
    assert(currentTypedPath.last is BookSegment);
    var id = (currentTypedPath.last as BookSegment).id;
    if (isPrev == true)
      id = id == 0 ? booksLen - 1 : id - 1;
    else
      id = booksLen - 1 > id ? id + 1 : 0;
    return toBook(id: id);
  }

3. Root widget #

Note: To make it less verbose, we use the functional_widget package to generate widgets. See generated "lesson??.g.dart"" file for details.

@cwidget
Widget booksExampleApp(WidgetRef ref) {
  final navigator = ref.read(riverpodNavigatorProvider);
  return MaterialApp.router(
    title: 'Books App',
    routerDelegate: navigator.routerDelegate as RiverpodRouterDelegate,
    routeInformationParser: RouteInformationParserImpl(navigator.pathParser),
    debugShowCheckedModeBanner: false,
  );
}

4. App entry point #

app entry point with ProviderScope's override

void runMain() => runApp(
    ProviderScope(
      overrides: [
        riverpodNavigatorCreatorProvider.overrideWithValue(AppNavigator.new /*See Constructor tear-offs in Dart ^2.15*/),
      ],
      child: const BooksExampleApp(),
    ),
  );
const booksLen = 5;

5. Map TypedSegment's to Screens #

Only the TypedSegment => Screen mapping is displayed.. You can view all application widgets here: screen.dart source code

final ScreenBuilder appSegmentsScreenBuilder = (segment) => (segment as AppSegments).map(
  // See Constructor tear-offs in Dart ^2.15, "HomeScreen.new" is equivalent to "(segment) => HomeScreen(segment)"
      home: HomeScreen.new,
      books: BooksScreen.new,
      book: BookScreen.new,
    );

Other lessons: #

Lesson02 #

It enriches lesson01 by:

  • screens require some asynchronous actions (when creating, deactivating or merging)
  • the splash screen appears before the HomeScreen is displayed

See lesson02 documentation

Lesson03 #

It enriches lesson02 by:

  • login application logic (where some pages are not available without a logged in user)
  • more TypedPath roots (AppSegments and LoginSegments)
  • navigation state also depends on another provider (userIsLoggedProvider)

See lesson03 documentation

Lesson04 #

It modified lesson03 by:

  • introduction of the route concept

See lesson04 documentation

Lesson05 #

Test for lesson03

See lesson05 documentation

Todo## Examples #

Lesson01 #

  • simple example

See lesson01.dart source code

1. classes for typed path segments (aka TypedSegment) #

From the following definition, freezed package generates three typed segment classes: HomeSegment, BooksSegment and BookSegment.

@freezed
class AppSegments with _$AppSegments, TypedSegment {
  AppSegments._();
  factory AppSegments.home() = HomeSegment;
  factory AppSegments.books() = BooksSegment;
  factory AppSegments.book({required int id}) = BookSegment;

  factory AppSegments.fromJson(Map<String, dynamic> json) => _$AppSegmentsFromJson(json);
}

2. App-specific navigator #

  • contains actions related to navigation. The actions are then used in the screen widgets.
  • configures various navigation parameters
class AppNavigator extends RiverpodNavigator {
  AppNavigator(Ref ref)
      : super(
          ref,
  //*** parameters common to all examples
          /// home (initial) navigation path
          initPath: [HomeSegment()],
          /// how to decode JSON to AppSegments
          json2Segment: (jsonMap, _) => AppSegments.fromJson(jsonMap),
          /// map TypedSegment's to Screens
          screenBuilder: appSegmentsScreenBuilder,
        );

Common navigation actions #

Future<void> toHome() => navigate([HomeSegment()]);
  Future<void> toBooks() => navigate([HomeSegment(), BooksSegment()]);
  Future<void> toBook({required int id}) => navigate([HomeSegment(), BooksSegment(), BookSegment(id: id)]);
  Future<void> bookNextPrevButton({bool? isPrev}) {
    assert(currentTypedPath.last is BookSegment);
    var id = (currentTypedPath.last as BookSegment).id;
    if (isPrev == true)
      id = id == 0 ? booksLen - 1 : id - 1;
    else
      id = booksLen - 1 > id ? id + 1 : 0;
    return toBook(id: id);
  }

3. Root widget #

Note: To make it less verbose, we use the functional_widget package to generate widgets. See generated "lesson??.g.dart"" file for details.

@cwidget
Widget booksExampleApp(WidgetRef ref) {
  final navigator = ref.read(riverpodNavigatorProvider);
  return MaterialApp.router(
    title: 'Books App',
    routerDelegate: navigator.routerDelegate as RiverpodRouterDelegate,
    routeInformationParser: RouteInformationParserImpl(navigator.pathParser),
    debugShowCheckedModeBanner: false,
  );
}

4. App entry point #

app entry point with ProviderScope's override

void runMain() => runApp(
    ProviderScope(
      overrides: [
        riverpodNavigatorCreatorProvider.overrideWithValue(AppNavigator.new /*See Constructor tear-offs in Dart ^2.15*/),
      ],
      child: const BooksExampleApp(),
    ),
  );
const booksLen = 5;

5. Map TypedSegment's to Screens #

Note: *Only the "TypedSegment => Screen" mapping is displayed. You can view all application screens and widgets here: screen.dart source code

final ScreenBuilder appSegmentsScreenBuilder = (segment) => (segment as AppSegments).map(
  // See Constructor tear-offs in Dart ^2.15, "HomeScreen.new" is equivalent to "(segment) => HomeScreen(segment)"
      home: HomeScreen.new,
      books: BooksScreen.new,
      book: BookScreen.new,
    );

Other lessons: #

Lesson02 #

Lesson02 is lesson01 enhanced with:

  • asynchronous navigation when screens require some asynchronous actions (when creating, deactivating, or merging)
  • the splash screen appears before the HomeScreen is displayed

See lesson02 documentation

Lesson03 #

Lesson03 is lesson02 extended by:

  • login application logic (where some pages are not available without a logged in user)
  • more TypedPath roots (AppSegments and LoginSegments)
  • navigation state also depends on another provider (userIsLoggedProvider)

See lesson03 documentation

Lesson04 #

Lesson04 is lesson03 prepared using the router concept.

See lesson04 documentation

Lesson05 #

Lesson05 includes a test for lesson03.

See lesson05 documentation

25
likes
0
pub points
57%
popularity

Publisher

unverified uploader

Simple but powerfull Flutter navigation with riverpod, freezed and Navigator 2.0.

Repository (GitHub)
View/report issues

License

unknown (LICENSE)

Dependencies

flutter, freezed_annotation, hooks_riverpod, json_annotation, meta, riverpod, tuple

More

Packages that depend on riverpod_navigator