I am developing a Flutter application with go_router and riverpod for navigation and state management respectively. The app has a widget which displays a live camera feed, and I'd like to "switch it off" and free the camera when other pages are stacked on top of it.
Here's a sample of the GoRouter code WITHOUT such logic.
GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => CameraWidget(),
routes: [
GoRoute(
path: 'page1',
builder: (context, state) => Page1Screen(),
),
],
),
],
)
My first attempt has been to put some logic in the GoRoute builder:
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(),
),
],
),
],
)
But this apparently does not work as the builder is not called again when going from "/" to "/page1".
I then thought of using a riverpod StateProvider to hold a camera "on/off" state, to be manipulated by GoRouter. This is what I tried:
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(),
),
],
),
],
)
But this also does not work as apparently redirect
gets called while rebuilding the widget tree, and it is forbidden to change a provider state while that happens.
Has anyone encountered the same issue before? How can I have a provider listen to GoRouter's location changes?
After further testing of my previous answer, I found that my approach with go_router does not work on Navigator.pop()
calls or back button presses. After some more digging in go_router's code, I figured it'd be easier to switch to the Routemaster package, which seems to integrate much better with Riverpod. So far I am very happy with the change.
EDIT: Improved approach now using Routemaster's observable API.
Here's the code:
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);