Skip to content

[go_router_builder][go_router] .location, .go(context) and .push(context) are only defined in the generated extension #106790

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
ValentinVignal opened this issue Jun 29, 2022 · 4 comments
Labels
c: new feature Nothing broken; request for a new capability c: proposal A detailed proposal for a change to Flutter p: go_router_builder The go_router_builder package p: go_router The go_router package P3 Issues that are less important to the Flutter project package flutter/packages repository. See also p: labels. team-go_router Owned by Go Router team triaged-go_router Triaged by Go Router team

Comments

@ValentinVignal
Copy link
Contributor

ValentinVignal commented Jun 29, 2022

Use case

Right now, the generation creates an extension on each pages:

// routes.dart
@TypedGoRoute< MyRoute1 >(path: '/my-route-1')
class MyRoute1 extends GoRouteData {
  const MyRoute1();

  @override
  Widget build(BuildContext context) => const MyScreen2();
}

@TypedGoRoute< MyRoute2 >(path: '/my-route-1')
class MyRoute2 extends GoRouteData {
  const MyRoute2();

  @override
  Widget build(BuildContext context) => const MyScreen2();
}
// routes.g.dart
extension $MyRoute1Extension on MyRoute1 {
  static MyRoute1 _fromState(GoRouterState state) => const MyRoute1();

  String get location => GoRouteData.$location(
        '/my-route-1',
      );

  void go(BuildContext context) => context.go(location, extra: this);

  void push(BuildContext context) => context.push(location, extra: this);
}

extension $MyRoute2Extension on MyRoute2 {
  static MyRoute2 _fromState(GoRouterState state) => const MyRoute2();

  String get location => GoRouteData.$location(
        '/my-route-2',
      );

  void go(BuildContext context) => context.go(location, extra: this);

  void push(BuildContext context) => context.push(location, extra: this);
}

But because of that, when manipulating a GoRouteData object, we cannot use go, push or location.

GoRouteData route;

route.go(context); // <- Error

This also prevents us from creating an extension on GoRouteData that reuses those methods:

extension on GoRouteData {
  void customGo(BuildContext context) {
    go(context); // <- Not possible
  } 
}

Proposal

It would be nice if the generated code doesn't work with extension (like what freezed does) for example

abstract class GoRouteMethods {
  void go(BuildContext context);
}

class MyRoute extends GoRouteData with _$MyRoute implements GoRouteMethods {}
@danagbemava-nc danagbemava-nc added in triage Presently being triaged by the triage team c: new feature Nothing broken; request for a new capability p: first party package flutter/packages repository. See also p: labels. c: proposal A detailed proposal for a change to Flutter p: go_router The go_router package p: go_router_builder The go_router_builder package and removed in triage Presently being triaged by the triage team labels Jun 29, 2022
@ValentinVignal
Copy link
Contributor Author

ValentinVignal commented Jul 4, 2022

I'm willing to work on this by the way, but I guess this needs to be discussed/workshopped first

@ValentinVignal
Copy link
Contributor Author

I have a proposal. Let's say a class GoRouteDataImpl was created:

abstract class GoRouteDataImpl {
  String get location;

  void go(BuildContext context);
}

It would define the methods that will be generated by the generator and it could be part of go_router.
I'm only taking the example of go (other methods like push and replace are similar).

Then let's consider this set of routes (taken from the example in the doc):

@TypedGoRoute<HomeRoute>(
  path: '/',
  routes: [
    TypedGoRoute<FamilyRoute>(
      path: 'family/:fid',
      routes: [
        TypedGoRoute<PersonRoute>(
          path: 'person/:pid',
        ),
      ],
    ),
  ],
)
class HomeRoute extends GoRouteData {
  const HomeRoute();

  @override
  Widget build(BuildContext context) => const HomeScreen();
}

class FamilyRoute extends GoRouteData {
  const FamilyRoute({
    required this.fid,
  });

  final String fid;

  @override
  Widget build(BuildContext context) => FamilyScreen(fid: fid);
}

class PersonRoute extends GoRouteData {
  const PersonRoute({
    required this.fid,
    required this.pid,
  });
  final String fid;
  final String pid;

  @override
  Widget build(BuildContext context) => PersonScreen(fid: fid, pid: pid);
}

The generated file could look like this:

final $appRoutes = <GoRoute>[
  $homeRoute,
];

GoRoute get $homeRoute => GoRouteData.$route(
      path: '/',
      factory: GHomeRoute._fromState,
      routes: [
        GoRouteData.$route(
          path: 'family/:fid',
          factory: GFamilyRoute._fromState,
          routes: [
            GoRouteData.$route(
              path: 'person/:pid',
              factory: GPersonRoute._fromState,
            ),
          ],
        ),
      ],
    );

class GHomeRoute extends HomeRoute implements GoRouteDataImpl {
  const GHomeRoute();

  factory GHomeRoute._fromState(GoRouterState state) => const GHomeRoute();

  @override
  String get location => GoRouteData.$location(
        '/',
      );

  @override
  void go(BuildContext context) {
    GoRouter.of(context).go(location);
  }
}

class GFamilyRoute extends FamilyRoute implements GoRouteDataImpl {
  const GFamilyRoute({
    required super.fid,
  });

  factory GFamilyRoute._fromState(GoRouterState state) => GFamilyRoute(
        fid: state.params['fid']!,
      );

  @override
  String get location => GoRouteData.$location(
        '/family/${Uri.encodeComponent(fid)}',
      );

  @override
  void go(BuildContext context) {
    GoRouter.of(context).go(location);
  }
}

class GPersonRoute extends PersonRoute implements GoRouteDataImpl {
  const GPersonRoute({
    required super.fid,
    required super.pid,
  });

  factory GPersonRoute._fromState(GoRouterState state) => GPersonRoute(
        fid: state.params['fid']!,
        pid: state.params['pid']!,
      );

  @override
  String get location => GoRouteData.$location(
        '/family/${Uri.encodeComponent(fid)}/person/${Uri.encodeComponent(pid)}',
      );

  @override
  void go(BuildContext context) {
    GoRouter.of(context).go(location);
  }
}

The user would manipulate GXxxRoute that all implement GoRouteDataImpl:

GFamilyRoute(fid: 'family0').go(context);

And he could be able to implement some logic on GoRouteDataImpl:

void prindLocationAndGo(BuildContext context, GoRouteDataImpl route) {
  print(route.location);
  route.go(context);
}

I have made an example of the generated file (written by hand) here:

https://github.com/ValentinVignal/flutter_app_stable/tree/go-router/route-generation


What do you think?

@om-ha
Copy link

om-ha commented Sep 6, 2022

It would be nice if the generated code doesn't work with extension (like what freezed does) for example

abstract class GoRouteMethods {
  void go(BuildContext context);
}

class MyRoute extends GoRouteData with _$MyRoute implements GoRouteMethods {}

Yes please, this proposal is actually important. Right now we cannot depend on GoRouteData. As the methods exist on the generated code only and not on abstract interface like this proposal suggests (similar to Freezed)

Our use-case is that, we have a modular method that determines the route name to use as navigation destination. This method is used across multiple views:

  • With normal navigator that's easy because this modular method just returns routing name/settings. View uses the route name/settings to perform the navigation.
  • With [go_router_builder] as a workaround the modular method returns a navigation callback currently void Function(BuildContext), since the View cannot operate on GoRouteData. As the go method exists on the generated code only.
  • With this proposal, it's possible for this modular method to return GoRouteData instead of the navigation callback above. And in this case the View can operate on it to call go(context).

@silentlie
Copy link

Is there a workaround yet?
I'm trying to loop through a list of GoRouteData and encounter this problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c: new feature Nothing broken; request for a new capability c: proposal A detailed proposal for a change to Flutter p: go_router_builder The go_router_builder package p: go_router The go_router package P3 Issues that are less important to the Flutter project package flutter/packages repository. See also p: labels. team-go_router Owned by Go Router team triaged-go_router Triaged by Go Router team
Projects
No open projects
Status: No status
Development

Successfully merging a pull request may close this issue.

6 participants