csells / go_router

The purpose of the go_router for Flutter is to use declarative routes to reduce complexity, regardless of the platform you're targeting (mobile, web, desktop), handling deep linking from Android, iOS and the web while still allowing an easy-to-use developer experience.
https://gorouter.dev
441 stars 96 forks source link

add events to routing to take actions #297

Closed subzero911 closed 2 years ago

subzero911 commented 2 years ago

I'd like to wrap a branch of widget tree into provider. But if I'll try to resolve a provided class from the nested route, it will tell there's no such a class:

final _router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => Provider(create: (context) => ProfileController(), child: HomeScreen()),
      routes: [
        GoRoute(
          path: '/reg',
          builder: (context, state) => RegistrationScreen(profile: context.read<ProfileController>()),  // throws an error
        ),
      ],
    ),
  ],
);

The reason is these routes actually are spawned as neighbours, not in the parent-child relations in the widget tree.

|
|- Provider<ProfileController>
|-- HomeScreen
|- RegistationScreen // cannot get ProfileController from here

The only routing package where it is solved is GetX. It used its own Binding system, allowing to bind controller to the routes, and get them in the nested routes, and automatically dispose it when route is closed (refer to: https://github.com/jonataslaw/getx/blob/master/documentation/en_US/dependency_management.md#bindings)

csells commented 2 years ago

I'm afraid there's nothing I can do about this. However, you may want to look into the navigatorBuilder parameter of the GoRouter constructor to be able to add providers on a per-route basis: https://gorouter.dev/navigator-builder

subzero911 commented 2 years ago

And if you add Bindings? A callback for each GoRoute where we have a place to register some dependencies, and unregister them on route pop. We can use get_it or kiwi or other IoC manager for that. I want a GetX-like solution, but without GetX (as it is unstable, poorly mantained and contradictory package).

csells commented 2 years ago

I'm not sure what problem you're attempting to solve. Does registering your dependencies in the widget tree above your MaterialApp or above the navigator via navigatorBuilder fail to solve the problem for you?

subzero911 commented 2 years ago

I want to register local dependencies, which are visible only in the certain screen and its sub-screens (and disposed after exiting this screen to save some RAM).

csells commented 2 years ago

That sounds very MVVM. I'm a fan of that myself. Is there anything stopping you from doing that in the builder?

subzero911 commented 2 years ago

Like this?

GoRoute(
        path: '/profile',
        builder: (context, state) {
          GetIt.I.registerSingleton<IProfileController>(ProfileController());
          return const ProfileScreen();
        } ,
      ),

But there's no place to unregister and dispose it, when I close the ProfileScreen(). There probably should be some GoRoute lifecycle callbacks.

csells commented 2 years ago

Ah. Normally that kind of thing is handled automatically when something is removed from the widget tree via context. I don't know how to use GetIt to do something like that. You could hook into the normal StatefulWidget lifecycle via a callback that you pass to the ProfileScreen constructor.

subzero911 commented 2 years ago

Got it.

GoRoute(
        path: '/profile',
        builder: (context, state) {
          GetIt.I.registerSingleton<IProfileController>(ProfileController());
          return ProfileScreen(onDispose: () {
             GetIt.I.unregister<IProfileController>(disposingFunction: (controller) => controller.dispose());
             }
           ),
        },
      ),

Slightly cumbersome. You could add onInit and onDispose callbacks for better management

subzero911 commented 2 years ago

That would be much cleaner:

GoRoute(
        path: '/profile',
        builder: (context, state) => const ProfileScreen(),
        onInit: () => GetIt.I.registerSingleton<IProfileController>(ProfileController());
        onDispose: () => GetIt.I.unregister<IProfileController>(disposingFunction: (controller) => controller.dispose());
      ),  

And this controller could be read from all the child routes.