Flutter:如何在Riverpod提供商中监听GoRouter的路线/位置变化?

ruarlubt  于 2023-03-13  发布在  Flutter
关注(0)|答案(3)|浏览(247)

我正在用go_router和riverpod开发一个Flutter应用程序,分别用于导航和状态管理,这个应用程序有一个小部件,可以显示实时的摄像头信息,我想“关掉它”,当其他页面堆叠在它上面时释放摄像头。
下面是GoRouter代码的一个示例,没有这样的逻辑。

GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => CameraWidget(),
      routes: [
        GoRoute(
          path: 'page1',
          builder: (context, state) => Page1Screen(),
        ),
      ],
    ),
  ],
)

我的第一个尝试是在GoRoute构建器中加入一些逻辑:

GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) {
        if (state.location == "/") {
          return CameraWidget();
        }
        return Center(child: Text("Camera not visible");
      },
      routes: [
        GoRoute(
          path: 'page1',
          builder: (context, state) => Page1Screen(),
        ),
      ],
    ),
  ],
)

但这显然不起作用,因为从“/”转到“/page 1”时,构建器不会再次调用。
然后我想到了使用一个riverpod StateProvider来保持摄像头的“开/关”状态,由GoRouter来操作。

GoRouter(
  routes: [
    GoRoute(
      path: '/',
      redirect: (context, state) {
        final cameraStateNotifier = ref.read(cameraStateNotifierProvider.notifier);
        if (state.location == "/") {
          cameraStateNotifier.state = true;
        } else {
          cameraStateNotifier.state = false;
        }
        return null;
      },
      builder: (context, state) => CameraWidget(),
      routes: [
        GoRoute(
          path: 'page1',
          builder: (context, state) => Page1Screen(),
        ),
      ],
    ),
  ],
)

但是这也不起作用,因为很明显redirect在重建小部件树时被调用,并且在这种情况下禁止更改提供者状态。
以前有人遇到过同样的问题吗?我如何让提供商监听GoRouter的位置更改?

owfi6suc

owfi6suc1#

**编辑:**此方法似乎不适用于Navigator.pop()调用和后退按钮按下。请查看当前接受的答案以获得更好的解决方案。

我相信我找到了一个很好的方法。我首先为GoRouter定义了一个提供者,然后定义了第二个提供者来监听router.routeInformationProvider。这是一个ChangeNotifier,它在每次路由信息发生变化时都会发出通知。最后,我们可以通过第三个提供者来监听特定位置。
我认为这是一个很好的变通办法,即使需要从GoRouter包中导入src/information_provider.dart,这并不是故意的。

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:go_router/src/information_provider.dart';

final routerProvider = Provider<GoRouter>((ref) => GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => CameraWidget(),
      routes: [
        GoRoute(
          path: 'page1',
          builder: (context, state) => Page1Screen(),
        ),
      ],
    ),
  ],
));

final routeInformationProvider = ChangeNotifierProvider<GoRouteInformationProvider>((ref) {
  final router = ref.watch(routerProvider);
  return router.routeInformationProvider;
});

final currentRouteProvider = Provider((ref) {
  return ref.watch(routeInformationProvider).value.location;
});
kxxlusnw

kxxlusnw2#

在对我的previous answer进行进一步测试后,我发现我的go_router方法在Navigator.pop()调用或按下后退按钮时不起作用。在深入研究go_router的代码后,我认为切换到Routemaster包会更容易,它似乎与Riverpod集成得更好。到目前为止,我对这一变化非常满意。

**编辑:**改进的方法现在使用Routemaster的可观察API。

下面是代码:

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:routemaster/routemaster.dart';

class RouteObserver extends RoutemasterObserver {
  final ProviderRef _ref;
  MyObserver(this._ref);

  @override
  void didChangeRoute(RouteData routeData, Page page) {
    _ref.invalidate(locationProvider);
  }
}

final routerProvider = Provider((ref) => RoutemasterDelegate(
  routesBuilder: (context) => RouteMap(routes: {
    '/': (_) => MaterialPage(child: CameraWidget()),
    '/page1': (_) => MaterialPage(child: Page1Screen()),
  }),
  observers: [RouteObserver(ref)],
));

final locationProvider = Provider((ref) => ref.read(routerProvider).currentConfiguration?.fullPath);
fae0ux8s

fae0ux8s3#

您可以使用GoRouter处理路由更改的一个方法是使用RouteObserver

// router.dart

final routeObserverProvider = RouteObserver<ModalRoute<void>>(); // <--

final routerProvider = Provider<GoRouter>((ref) {
  final routeObserver = ref.read(routeObserverProvider);
  
  return GoRouter(
    observers: [routeObserver], // <--
    routes: [
      GoRoute(
        path: '/',
        builder: (context, state) => CameraWidget(),
        routes: [
          GoRoute(
            path: 'page1',
            builder: (context, state) => Page1Screen(),
          ),
        ],
      ),
    ],
  );
});
// camera_widget.dart

class CameraWidget extends ConsumerStatefulWidget {
  const CameraWidget({Key? key}) : super(key: key);

  @override
  ConsumerState createState() => _CameraWidgetState();
}

class _CameraWidgetState extends ConsumerState<CameraWidget> with RouteAware { // <-- NOTE: `with RouteAware`
  late RouteObserver _routeObserver;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();

    // saving the route observer reference for later use in 
    // `dispose()`. Otherwise, calling `ref.read` there causes the
    // "Looking up a deactivated widget's ancestor is unsafe" error
    _routeObserver = ref.read(routeObserverProvider);

    _routeObserver.subscribe(this, ModalRoute.of(context)!);
  }

  @override
  void dispose() {
    _routeObserver.unsubscribe(this);

    super.dispose();
  }

    @override
  void didPush() { // <--
    // do something
  }

  @override
  void didPushNext() { // <--
    // do something
  }

  @override
  void didPop() { // <--
    // do something
  }

  @override
  void didPopNext() { // <--
    // do something
  }

  @override
  Widget build(BuildContext context) { /* blah blah */ }
}

如您所见,现在可以访问小部件上的didPush()didPushNext()didPop()didPopNext()方法。

相关问题