I need to update all the widgets on the screen, for this I decided to just make navigation to the screen I'm currently on. But unfortunately nothing happens. Tell me, what's the problem?
I am using the go_router package.
routes -
final GoRouter _router = GoRouter(
routes: <GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) => Splash() ,
),
GoRoute(
path: '/home',
builder: (BuildContext context, GoRouterState state) => const Home() ,
),
GoRoute(
path: '/login',
builder: (BuildContext context, GoRouterState state) => const Login() ,
),
GoRoute(
path: '/createPinCode',
builder: (BuildContext context, GoRouterState state) => CreatePinCode() ,
),
],
button -
onCompleted: (value) async {
pinCode = int.parse(value);
sharedPrefsSet(pinCode);
context.go('/createPinCode');
},
If you just want to clear your form textFields than rather than re-build entire page you can user clear() property of TextEditingController.
TextEditingController nameController = TextEditingController();
func clearAllData() {
nameController.clear();
emailController.clear();
}
Call this clearAllData() when user tap on submit form.
Related
In my Flutter app, I use go_router to navigate between pages.
Here are the current pages in my app:
accounts_page
add_account_page
import_accounts_page
Now, I would like to implement nested navigation inside add_account_page, so I can add a new account using multiple steps, let's say:
account_info_step
account_type_step
account_detail_step
Here is what I tried:
final _navigatorKey = GlobalKey<NavigatorState>();
final _addAccountNavigatorKey = GlobalKey<NavigatorState>();
late final router = GoRouter(
navigatorKey: _navigatorKey,
initialLocation: "/accounts_page",
routes: [
ShellRoute(
navigatorKey: _addAccountNavigatorKey,
builder: (context, state, child) => AddAccountPage(child: child),
routes: [
GoRoute(
parentNavigatorKey: _addAccountNavigatorKey,
name: "account_info_step",
path: "/account_info_step",
builder: (context, state) => const AccountInfoStep(),
),
GoRoute(
parentNavigatorKey: _addAccountNavigatorKey,
name: "account_type_step",
path: "/account_type_step",
builder: (context, state) => const AccountTypeStep(),
),
GoRoute(
parentNavigatorKey: _addAccountNavigatorKey,
name: "account_detail_step",
path: "/account_detail_step",
builder: (context, state) => const AccountDetailStep(),
),
],
),
GoRoute(
name: "accounts_page",
path: "/accounts_page",
pageBuilder: (context, state) => const AccountsPage(),
),
GoRoute(
name: "import_accounts_page",
path: "/import_accounts_page",
pageBuilder: (context, state) => const ImportAccountsPage(),
),
],
);
And then I call context.pushNamed("account_info_step"), but nothing happens.
Is it possible then to use go_router to implement nested navigation inside add_account_page and if yes, how?
Thanks.
If your intention is to get to AccountInfoStep() full path leading to this builder should be added in your case:
context.push("/accounts_page/account_info_step")
I assume you are missing name for your top routes to represent proper nesting to use the context.pushNamed()
I think the way you are trying to create nested navigation is not right. Also you don't need to use ShellRoute, you can use shell route if you want to create something like a persistent bottom navigation bar and changing only the child while keeping the bottom nav bar at the bottom.
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:untitled/homepage.dart';
import 'package:untitled/test_page.dart';
final _navigatorKey = GlobalKey<NavigatorState>();
// final _addAccountNavigatorKey = GlobalKey<NavigatorState>();
class MyRouter{
static final router = GoRouter(
navigatorKey: _navigatorKey,
initialLocation: "/accounts_page",
routes: [
GoRoute(
name: "accounts_page",
path: "/accounts_page",
builder: (context, state) => const MyHomePage(),
routes: <RouteBase>[
GoRoute(
name: "account_info_step",
path: "account_info_step",
builder: (context, state) => const TestPage(),
),
GoRoute(
name: "account_type_step",
path: "account_type_step",
builder: (context, state) => const TestPage(),
),
GoRoute(
name: "account_detail_step",
path: "account_detail_step",
builder: (context, state) => const TestPage(),
),
]
),
],
);
}
I think this is what you are looking for. Also, note that you don't need to add a '/' in path for nested screens.
And to navigate to those nested screens, you can do something
like this:-
context.go('/accounts_page/account_info_step');
I want to pass object from the listview to the sub route. It seems no way to pass the object.
Is there any how to do it?
GoRouter routes(AuthBloc bloc) {
return GoRouter(
navigatorKey: _rootNavigatorKey,
routes: <RouteBase>[
GoRoute(
routes: <RouteBase>[
GoRoute(
path: loginURLPagePath,
builder: (BuildContext context, GoRouterState state) {
return const LoginPage();
},
),
GoRoute(
path: homeURLPagePath,
builder: (BuildContext context, GoRouterState state) =>
const HomePage(),
routes: <RouteBase>[
GoRoute(
path: feeURLPagePath,
name: 'a',
builder: (context, state) => FeePage(),
routes: [
/// Fee Details page
GoRoute(
name: 'b',
path: feeDetailsURLPagePath,
builder: (BuildContext context, GoRouterState state) =>
const FeeDetails(),
),
]),
],
),
],
path: welcomeURLPagePath,
builder: (BuildContext context, GoRouterState state) =>
const SplashPage(),
),
],
refreshListenable: GoRouterRefreshStream(bloc.stream),
debugLogDiagnostics: true,
initialLocation: welcomeURLPagePath,
},
);
}
The error says no initial match found for feeDetails!
Use extra parameter in context.goNamed()
Example:
Object:
class Sample {
String attributeA;
String attributeB;
bool boolValue;
Sample(
{required this.attributeA,
required this.attributeB,
required this.boolValue});}
Define GoRoute as
GoRoute(
path: '/sample',
name: 'sample',
builder: (context, state) {
Sample sample = state.extra as Sample; // -> casting is important
return GoToScreen(object: sample);
},
),
Call it as:
Sample sample = Sample(attributeA: "True",attributeB: "False",boolValue: false)
context.goNamed("sample",extra:sample );
Receive it as:
class GoToScreen extends StatelessWidget {
Sample? object;
GoToScreen({super.key, this.object});
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text(
object.toString(),
style: const TextStyle(fontSize: 24),
)),
);
}
}
I configured Navigator through GoRouter and I want to know how to receive a parameter through a deep link.
Instead of configuring arguments in Widget corresponding to the destination,
For iOS simulators, on the connected terminal,
xcrun simctl openurl booted "customScheme://myHost.name.com/login?p=0"
If I enter , I want to enter the login screen and use the parameter p=0 in Login Widget at the same time. Is there any other way?
I considered using uni_links, but I do not want to use it because it interferes with Flutter's basic Deeplinking activities.
(The reason is that if you run the command from the terminal as shown above, if you have uni_links, you will only move to the root and not reach the sub-destination.)
Below is the routerConfig of GoRouter.
final _router = GoRouter(
initialLocation: '/home',
navigatorKey: _rootNavigatorKey,
observers: [
GoRouterObserver(),
routeObserver
],
routes: [
GoRoute(
path: "/",
builder: (context, state) {
return const Root();
},
routes: [
ShellRoute(
navigatorKey: _shellNavigatorKey,
builder: (context, state, child) {
return ScaffoldWithNavBar(child: child);
},
routes: [
GoRoute(
path: 'home',
builder: (context, state) {
return const Home();
},
),
GoRoute(
path: 'discover',
builder: (context, state) {
return const Discover();
},
),
GoRoute(
path: 'shop',
builder: (context, state) {
return const Shop();
}),
],
),
GoRoute(
path: 'login',
parentNavigatorKey: _rootNavigatorKey,
builder: (context, state) {
return const Login();
},
routes: loginRouter),
],
)
],
);
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?
EDIT: This approach doesn't seem to work with Navigator.pop() calls and back button presses. Check out the currently accepted answer for a better solution.
I believe I found a good way to do so. I defined a provider for GoRouter first, then a second one to listen to router.routeInformationProvider. This is a ChangeNotifier which notifies everytime the route information changes. Finally we can listen to this through a third provider for the specific location.
I think this is a good workaround, even though requires importing src/information_provider.dart from the GoRouter package which is not meant to.
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;
});
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);
I am using the go_router package because I need the deep linking it provides. I applied animation transitions to some routes but they are static, so every time I go to that route, the same animation is going to trigger. I would like to change the animation when I do GoRouter.of(context).go('/inbox')
This is what I have right now:
final router = GoRouter(
initialLocation: '/inbox',
routes: <GoRoute>[
GoRoute(
path: '/inbox',
pageBuilder: (BuildContext context, GoRouterState state) {
return PageTransition.slideFromRight(
myChildWidget: Layout(
context: context,
state: state,
child: EmailPage(),
),
state: state,
);
},
),
GoRoute(
path: '/email/inbox/:id',
pageBuilder: (BuildContext context, GoRouterState state) {
return PageTransition.slideFromLeft(
myChildWidget: Layout(
context: context,
state: state,
child: const EmailDetailsPage(),
),
state: state,
);
},
),
GoRoute(
path: '/menu',
pageBuilder: (BuildContext context, GoRouterState state) {
return PageTransition.slideFromRight(
myChildWidget: Layout(
context: context,
state: state,
child: const MenuPage(),
),
state: state,
);
},
)
],
);
PageTransition is just a custom transition Widget I build.
So, in this case, if I do GoRouter.of(context).go('/inbox') it will play the slideFromRight transition, if I do GoRouter.of(context).go('/email/inbox/:id') it will play the slideFromLeft and I can't change that. I would like for this to be dynamic and choose what animation it is going to play.
go_router provides CustomTransitionPage for transition animations.
Code:
GoRoute(
path: 'details',
pageBuilder: (context, state) {
return CustomTransitionPage(
key: state.pageKey,
child: DetailsScreen(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
// Change the opacity of the screen using a Curve based on the the animation's
// value
return FadeTransition(
opacity:
CurveTween(curve: Curves.easeInOutCirc).animate(animation),
child: child,
);
},
);
},
),
Reference: https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/transition_animations.dart