riverpod_navigator_core 0.26.0 copy "riverpod_navigator_core: ^0.26.0" to clipboard
riverpod_navigator_core: ^0.26.0 copied to clipboard

outdated

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

Riverpod navigation #

Simple but powerful Flutter navigation with riverpod, freezed and Navigator 2.0 that solves the following: #

  • Strictly typed navigation:
    you can use navigate([HomeSegment(),BookSegment(id: 2)]); instead of navigate('home/book;id:2'); in your code
  • asynchronous navigation
    is the case when changing the navigation state requires asynchronous actions (such as loading or saving data from the Internet)
  • multiple providers
    is the case when the navigation state depends on multiple riverpod providers
  • 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
  • nested navigation
    just use the nested riverpod ProviderScope() and Flutter Router widget

Terminology used #

Take a look at the following terms related to URL path home/book;id=2

  • string-path: final stringPath = 'home/book;id=2';
  • string-segment: the string-path consists of two slash-delimited string-segments (home and book;id=2)
  • typed-segment: (class TypedSegment {}'s descendant) describes coresponding string-segment's (HomeSegment() and BookSegment(id:2))
  • typed-path: (typedef TypedPath = List<TypedSegment>) describes coresponding string-path ([HomeSegment(), BookSegment(id:2)];)
  • Flutter Navigator 2.0 navigation-stack is uniquely determined by the TypedPath (where each TypedSegment instance corresponds to a screen and page instance):
    [MaterialPage (child: HomeScreen(HomeSegment())), MaterialPage (child: BookScreen(BookSegment(id:2)))]

riverpod_navigator

As you can see, changing the Input state starts the async calculation. The result of the calculations is Output state which can have app-specific Side effects. Navigator 2.0 RouterDelegate is then synchronized with navigationStackProvider

Simple example #

Create an application using these simple steps.

Step1 - define imutable classes for the typed-segment #

We use freezed-package to generate immutable TypedSegment descendant classes.

It's a good idea to be familiar with the freezed-package (including support for JSON serialization).

From the following Segments class declaration, the freezed generates two classes: HomeSegment and PageSegment.

@freezed
class Segments with _$Segments, TypedSegment {
  Segments._();
  /// Segments.home() means that the string 'home' appears in the web URL, e.g. '/home'
  factory Segments.home() = HomeSegment;
  /// the Segments.page() means that the string 'page' appeares in web url, e.g. '/page;title=title'
  factory Segments.page({required String title}) = PageSegment;

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

Step2 - extend the RNavigator class #

Configure RNavigator:

class AppNavigator extends RNavigator {
  AppNavigator(Ref ref)
      : super(
          ref,
          [
            RRoutes<Segments>(Segments.fromJson, [ // deserialize HomeSegment or PageSegment
              RRoute<HomeSegment>(HomeScreen.new), // assign HomeScreen builder for HomeSegment
              RRoute<PageSegment>(PageScreen.new), // assign PageScreen builder for PageSegment
            ])
          ],
        );
}

Step3 - use the AppNavigator in MaterialApp.router #

If you are familiar with the Flutter Navigator 2.0 and the riverpod, the following code is clear:

class App extends ConsumerWidget {
  const App({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final navigator = ref.read(navigatorProvider) as AppNavigator;
    return MaterialApp.router(
      title: 'Riverpod Navigator Example',
      routerDelegate: navigator.routerDelegate,
      routeInformationParser: navigator.routeInformationParser,
      debugShowCheckedModeBanner: false,
    );
  }
}

Step4 - main entry point #

Place and configure ProviderScope:

void main() => runApp(
      ProviderScope(
        // home-path and navigator constructor are required
        overrides: RNavigatorCore.providerOverrides([HomeSegment()], AppNavigator.new),
        child: const App(),
      ),
    );

And that's all #

Navigation to a specific screen is performed as follows:

// navigation to PageScreen
ElevatedButton(
  onPressed: () => ref.read(navigatorProvider).navigate([HomeSegment(), PageSegment(title: 'Page')]),

// navigation to HomeScreen
ElevatedButton(
  onPressed: () => ref.read(navigatorProvider).navigate([HomeSegment()]),

Whole source code and test see:

Testing #

Before developing a GUI, it is good practice to develop and test the invisible part of the application (app model and state). It is advantageous to use a dart test environment, see:

  test('navigation test', () async {
    final container = ProviderContainer(overrides: RNavigatorCore.providerOverrides([HomeSegment()], AppNavigator.new));
    final navigator = container.read(navigatorProvider);

    Future navigTest(Future action(), String expected) async {
      await action();
      await container.pump();
      expect(navigator.navigationStack2Url, expected);
    }

    await navigTest(() => navigator.navigate([HomeSegment()]), 'home');

    await navigTest(() => navigator.navigate([HomeSegment(), PageSegment(title: 'Page')]), 'home/page;title=Page');

    await navigTest(() => navigator.pop(), 'home');

    await navigTest(() => navigator.push(PageSegment(title: 'Page2')), 'home/page;title=Page2');

    await navigTest(() => navigator.replaceLast<PageSegment>((old) => PageSegment(title: 'X${old.title}')), 'home/page;title=XPage2');
  });

Other features and examples #

Note: The examples are prepared using a functional_widget package that simplifies writing widgets. The use of functional_widget is optional.

Installation of examples #

After cloning the riverpod_navigator repository, go to examples/doc subdirectory and execute:

  • flutter create .
  • flutter pub get

See the /lib subdirectory for examples.

Roadmap #

I prepared this package for my new project. Its further development depends on whether the community will use it.

  • proofreading because my English is not good. Community help is warmly welcomed.
  • parameterization allowing Cupertino
7
likes
0
pub points
18%
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

json_annotation, meta, riverpod, tuple

More

Packages that depend on riverpod_navigator_core