How do I set up navigator using Getx and Auto Route? - flutter

Problem:
I am having trouble setting up navigation using GetX and AutoRoute.
Code Setup:
According to the GetX documentation, if you want to use GetX navigation you have to replace MaterialApp() with GetMaterialApp(). You also set the routes.
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.zoom
),
],
)
);
}
The AutoRoute example uses MaterialApp.router() to set up the routerDelegate and routeInformationParser.
final _appRouter = AppRouter()
...
Widget build(BuildContext context){
return MaterialApp.router(
routerDelegate: _appRouter.delegate(...initialConfig),
routeInformationParser: _appRouter.defaultRouteParser(),
),
}
Here is how I set up the navigation according to Getx and AutoRoute:
void main() {
configureDependencies();
runApp(Portfolio());
}
class Portfolio extends StatelessWidget {
final _appRouter = AppRouter.Router();
#override
Widget build(BuildContext context) {
return GetMaterialApp.router(
routerDelegate: _appRouter.delegate(),
routeInformationParser: _appRouter.defaultRouteParser(),
builder: (context, extendedNav) => Theme(
data: ComplexReduxTheme.complexReduxLightTheme,
child: extendedNav ?? Container(color: Colors.red),
),
);
}
}
I am using GetMaterialApp.router which returns a GetMaterialApp. Despite this, I get the error "You are trying to use contextless navigation without a GetMaterialApp or Get.key.". I have tried setting up the navigator key and setting Get.testMode = true but nothing happens(no error) when I try to navigate to another screen.
Desired Result:
I should be able to navigate to the desired screen via Get.toNamed().
Current Result:
I get the following error from GetX when trying to navigate to another screen using Get.toNamed() : "You are trying to use contextless navigation without
a GetMaterialApp or Get.key.
If you are testing your app, you can use:
[Get.testMode = true], or if you are running your app on
a physical device or emulator, you must exchange your [MaterialApp]
for a [GetMaterialApp]."
AutoRoute Version: 2.2.0
Get Version: 4.1.4

You don't need external routing plugin, GetX already did that for you, and if you want to navigate, just use Get.toNamed("/some-page") and it will show you the page you wanted. Same goes to nested route.
For Example
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.zoom,
children: [
GetPage(
name: '/child-of-third',
page: () => ChildOfThird(),
),
],
),
// You access it like this
Get.toNamed("/third");
// And this one, for the nested page
Get.toNamed("/third/child-of-third");
The reason you got the error is when you use external routing plugin in GetX, it will generate their own code, with their own context in their own ecosystem. GetX doesn't know which context does the plugin use since it was outside of its lifecycle.

I was facing the same issue when combining both getx and auto router in my case i needed nested navigation as well I created a work around like this
I created initial bindings and passed appRouter to it and saved it in getx routing controller that i was using to a method like Get.toNamed because with initial appRouter you don't need context you can navigate like this
// main app widget
class _myAppState extends State<MyApp> {
final _appRouter = AppRouter();
#override
Widget build(BuildContext context) {
return GetMaterialApp.router(
routerDelegate: _appRouter.delegate(),
routeInformationParser: _appRouter.defaultRouteParser(),
initialBinding: InitialBinding(router: _appRouter,),
);
}
}
// initial binding to store to store app router
class InitialBinding extends Bindings {
AppRouter router;
InitialBinding({required this.router,});
#override
void dependencies() {
Get.put(NavRoutesController(router: router,),permanent: true);
}
}
// router controller
class NavRoutesController extends GetxController {
AppRouter router;
NavRoutesController({required this.router,});
void toNamed(String route){
router.pushNamed(route);
}
}
//to navigate use
final router = Get.find<RouterController>();
router.toNamed("/some")
//or
Get.find<RouterController>().toNamed("/some")
// you can get base context as well from AppRouter like this
Get.find<RouterController>().router.navigatorKey.currentState.context

Related

What is the best approach for giving route names in flutter and managing route?

Thanks in advance,
As we all know that we can assign names to pages for route and use Navigator.pushNamed() with this, but what is the best approach?
What I do is to put the route name as a static string in the class itself,
class Home extends StatelessWidget {
static const String id = "/home";
#override
Widget build(BuildContext context) {
return widget;
}
}
So now when I'm assign route in MaterialApp as
routes: {
Home.id: (context) => Home(),
}
and now whenever I'm routing the page, I know which page I'm routing to
Navigator.pushNamed(context, Home.id);
But I've seen people having a different file of routes as
class Routes {
static const String home = "/home";
static Map<String, Widget Function(BuildContext) routes = {
home: (context) => Home(),
}
}
//MaterialApp
routes: Routes.routes
So now can anyone tell me which is the best approach and which one should I use, which is best with performance.
And any better way to manage routing?
For me, I use getX package which can be found at:
https://pub.dev/packages/get
This is very efficient and easy to use for routing and State Management as well.
You can import getX package and use in place of material space:
GetMaterialApp( // Before: MaterialApp(
home: MyHome(),
)
Here Routes of Screens can be defined as:
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.zoom
),
],
)
);
}
Named routing can be used for this.
Get.toNamed("/NextScreen", arguments: 'Get is the best');
Routing without getting back to previous screen.
Get.offNamed("/NextScreen");

Flutter on web, using query params in url redirects to inital route

I'm having a pretty weird issue with Flutter when using it for a web page. I need to process a query param from an external source, which hits my website with e.g /page?param=1
I have a super simple flutter project setup:
import 'package:client/screens/screens.dart';
import 'package:flutter/material.dart';
import 'package:url_strategy/url_strategy.dart';
void main() {
setPathUrlStrategy();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: {
"/": (context) => HomeScreen(),
"/page": (context) => PageScreen(),
},
);
}
}
When going to "/" or "/page" it works fine. But as soon as I go to "/page?param=1". I get the following error:
The following message was thrown:
Could not navigate to initial route.
The requested route name was: "/page?param=1"
There was no corresponding route in the app, and therefore the initial route specified will be
ignored and "/" will be used instead.
Is Flutter not able to see what a query param is? It's web 101 doing something like this, I must be doing something wrong, I just can't find the answer.
Try using onGenerateRoute callback in MaterialApp, for eg:
onGenerateRoute: (RouteSettings settings) {
Widget? pageView;
if (settings.name != null) {
var uriData = Uri.parse(settings.name!);
//uriData.path will be your path and uriData.queryParameters will hold query-params values
switch (uriData.path) {
case '/page':
pageView = PageScreen();
break;
//....
}
}
if (pageView != null) {
return MaterialPageRoute(
builder: (BuildContext context) => pageView!);
}
},

Is there any way to insert a query parameters to a named route in flutter web?

I want to insert query parameters to a named route.
I have this code on my MaterialApp
Widget build(BuildContext context) {
return MaterialApp(
title: 'Web',
theme: ThemeData(
primarySwatch: Colors.amber,
),
// Start the app with the "/" named route. In this case, the app starts
// on the FirstScreen widget.
initialRoute: '/login',
routes: {
'/login': (context) => LoginPage(),
'/mainmenu': (context) => MainMenu(),
},
);
}
Now I want to insert query parameters (for example id) to '/mainmenu' so when I want to navigate to the main menu page, the URL becomes for example: http://localhost:57430/#/mainmenu/?id=1234. Is there any way to do that? Thanks
You can pass Data through Navigator in Flutter by,
Navigator.pushReplacementNamed(context, '/home', arguments: {
'id': 1234
});
In the above code you will be pass data as a map to the next screen using arguments.
You can decode the map by these steps:
Declaring a Map variable in the next screen:
Map data = {}
Then decoding it by:
data = ModalRoute.of(context).settings.arguments;
print(data);
It's recommended to create a class to specify the arguments that need to be passed to the route, for example:
class MainMenuArguments {
final int id;
MainMenuArguments(this.id);
}
That can be passed to a Navigator:
Navigator.pushNamed(context, MainMenuScreen.routeName, arguments: MainMenuArguments(1234)); // id = 1234
And can be then accessed from the MainMenu Widget:
class MainMenuScreen extends StatelessWidget {
static const routeName = '/mainMenu';
#override
Widget build(BuildContext context) {
final MainMenuArguments args = ModalRoute.of(context).settings.arguments;
return Scaffold(
body: Center(
child: Text(args.id.toString()), // displays 1234
),
);
}
}
In order to do so, you'd need to register the route inside the MaterialApp constructor:
MaterialApp(
routes: {
MainMenuArgumentsScreen.routeName: (context) => MainMenuArgumentsScreen(),
},
);
Flutter has a cookbook specially for this situation. Link here

Provider ValueNotifier listen not working on some Pages on Flutter

listen is not working. when hot reload value is updated.
Page A
#override
Widget build(BuildContext context) {
ValueNotifier<List<ProductModel>> selectNotifier = Provider.of<ValueNotifier<List<ProductModel>>>(context, listen: true);
Widget
Text('${selectNotifier.value.length}'),
Page B
InkWell(
onTap: () {
selectNotifier.value.add(selectProduct);
},
main.dart
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<ValueNotifier<List<ProductModel>>>(
create: (_) => ValueNotifier<List<ProductModel>>([]),
),
],
child: MaterialApp(
theme: CustomTheme.themeData,
onGenerateRoute: Router.generateRoute,
debugShowCheckedModeBanner: false,
),
);
}
Version
provider: ^4.1.2
Flutter Version
1.17.2
I tried below ways to fix this issue. But I don't know what is right way(best way).
1st way
After downgrade Flutter and Provider, now is working. why is that?
provider: 3.2.0
git checkout v1.12.13-hotfixes
2nd way
Or it is working this way too.//but warning on the IDE
onTap: () {
selectNotifier.value.add(selectProduct);
selectNotifier.notifyListeners(); //info: The member 'notifyListeners' can only be used within instance members of subclasses of 'package:flutter/src/foundation/change_notifier.dart'.
},
But warning disappear, after adding this ChangeNotifier,
class _viewState extends State<View> with ChangeNotifier{
and also getting error after adding ChangeNotifier
The following assertion was thrown while finalizing the widget tree:
_CartItemViewState.dispose failed to call super.dispose.
dispose() implementations must always call their superclass dispose()
method, to ensure that all the resources used by the widget are fully
released.
3rd way
I don't get any issue on this way, but I used so many ValueNotifier in my production app, so, others are not a List. I don't know how to change other types.
onTap: () {
selectNotifier.value = List.from(selectNotifier.value)..add(widget.productModel);
}
In this question, 3rd way is the correct way.
onTap: () {
selectNotifier.value = List.from(selectNotifier.value)..add(widget.productModel);
}

How to navigate without context in flutter app?

I have an app that recieves push notification using OneSignal. I have made a notification opened handler that should open specific screen on click of the notification. How can i navigate to a screen without context. or how can I open specific screen on app startup. My code:
OneSignal.shared.setNotificationOpenedHandler((notification) {
var notify = notification.notification.payload.additionalData;
if (notify["type"] == "message") {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => DM(user: notify['id']),
),
);
}
if (notify["type"] == "user") {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => Profileo(notify["id"]),
),
);
}
if (notify["type"] == "post") {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ViewPost(notify["id"]),
),
);
}
});
I am able to achieve this when the app is opened for the first time but It only opens the homepage If i close the app and even if I re-open it. I guess that is because the context is changed.
Please Help!!
Look at this here:
https://github.com/brianegan/flutter_redux/issues/5#issuecomment-361215074
You can set a global key for your navigation:
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
Pass it to MaterialApp:
new MaterialApp(
title: 'MyApp',
onGenerateRoute: generateRoute,
navigatorKey: navigatorKey,
);
Push routes:
navigatorKey.currentState.pushNamed('/someRoute');
You can use this wonderful plugin:
https://pub.dev/packages/get
Description from the package: A consistent navigation library that lets you navigate between screens, open dialogs, and display snackbars from anywhere in your code without context.
Get.to(NextScreen()); // look at this simplicity :)
Get.back(); // pop()
Get.off(NextScreen()); // clears the previous routes and opens a new screen.
This solution is general if you want to navigate or to show dialog without context using globalKey especially with Bloc or when your logic is separated from your UI part.
Firstly install this package:
Not: I'm using null safety version
get_it: ^7.2.0
Then create a separate file for your service locator:
service_location.dart
import 'package:get_it/get_it.dart';
GetIt locator = GetIt.instance;
class NavigationService {
final GlobalKey<NavigatorState> navigatorKey =
new GlobalKey<NavigatorState>();
Future<dynamic> navigateTo(String routeName) {
return navigatorKey.currentState!.pushNamed(routeName);
}
void setupLocator() {
locator.registerLazySingleton(() => NavigationService());
}
void showMyDialog() {
showDialog(
context: navigatorKey.currentContext!,
builder: (context) => Center(
child: Material(
color: Colors.transparent,
child: Text('Hello'),
),
));
}
}
on main.dart:
void main() {
WidgetsFlutterBinding.ensureInitialized();
NavigationService().setupLocator();
runApp(MyApp());
}
// add navigatorKey for MaterialApp
MaterialApp(
navigatorKey: locator<NavigationService>().navigatorKey,
),
at your business logic file bloc.dart
define this inside the bloc class or at whatever class you want to use navigation inside
Then start to navigate inside any function inside.
class Cubit extends Cubit<CubitState> {
final NavigationService _navigationService = locator<NavigationService>();
void sampleFunction(){
_navigationService.navigateTo('/home_screen'); // to navigate
_navigationService.showMyDialog(); // to show dialog
}
}
Not: I'm using generateRoute for routing.
Quickest fix is above using global navigatorKey (like #tsdevelopment answered).
To fix undefined navigatorKey, it must be imported from where it is instantiated (for this example in main.dart).
Your main.dart
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
void main() {
runApp(CupertinoApp(
title: 'Navigate without context',
initialRoute: '/',
navigatorKey: navigatorKey, // important
onGenerateRoute: ...
));
}
For example you are in your lib/utils/api.dart
import 'package:your_package_name/main.dart'; // important
abstract class API {
static Future<dynamic> get() async {
// call some api
...
// then you want to navigate to specific screen like login
navigatorKey.currentState?.pushNamed('/login'); // navigate to login, with null-aware check
}
}
Also have a gist example if you prefer in a service approach.
Check this: https://gist.github.com/josephdicdican/81e59fad70530eac251ad6c28e2dcd4b
I know this is an old post, but there is a package that handles navigation without the build context (Using a navigator key) called flutter_navigator: https://pub.dev/packages/flutter_navigator
It allows you to navigate something like this:
_flutterNavigation.push(//YourRoute);
Everything seems to be mapped 1:1 with Flutter's Navigator API, so there is no worries there!
You can use this no_context_navigation package
as the name suggests, we can navigate without context
navService.pushNamed('/detail_screen', args: 'From Home Screen');