Flutter initiating a provider and changing its value / NoSuchMethodError - flutter

I struggle changing the values of my providers.
They are at the top of my widget tree :
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => User(),
),
ChangeNotifierProvider(
create: (context) => DisplayModel(),
),
],
child: MyApp(),
),
);
Here is the class where I try to build function to change the provider value. DisplayChange() should allow user to set his own values for DisplayModel.
class DisplayModel extends ChangeNotifier {
String blogId;
String title;
String subtitle;
String desc;
String author;
String publisherId;
String imageUrl;
DisplayModel(
{this.blogId,
this.title,
this.subtitle,
this.desc,
this.author,
this.publisherId,
this.imageUrl});
factory DisplayModel.fromDocument(doc) {
return DisplayModel(
blogId: doc['blogId'],
title: doc['title'],
subtitle: doc['subtitle'],
desc: doc['desc'],
author: doc['author'],
publisherId: doc['publisherId'],
imageUrl: doc['imageUrl'],
);
}
DisplayChange(DisplayModel) {
DisplayModel (DisplayModel);
notifyListeners();
return DisplayModel();
}
}
I call the provider in MyApp() and I want the user to be able to change the instance of DisplayModel with the values of loadebBlog (it's built with the same constructors as DisplayModel) and be redirected to home of the app
child: GestureDetector(
onTap: () {
Provider.of<DisplayModel>(
context,
listen: false,
).DisplayChange(loadedBlog);
print ("ok");
Navigator.of(this.context).pushReplacementNamed('home');
},
child: Icon(Icons.ios_share),),
But when the button is tapped, it returns an error
The following NoSuchMethodError was thrown while handling a gesture:
Class 'BlogModel' has no instance method 'call'.
Receiver: Instance of 'BlogModel'
Tried calling: call(Instance of 'BlogModel')
Is the logic good ? What am I missing ?
Thank you for your time.
Best regards,
Julien

Just came across this, so I don't know if it's helpful after 4 months but..
This syntax seems weird:
DisplayChange(DisplayModel) {
DisplayModel (DisplayModel);
notifyListeners();
return DisplayModel();
}
Should be something like:
DisplayChange(DisplayModel model) {
// I'm not sure what this is supposed to do
// but it's no-op code
// DisplayModel (DisplayModel);
notifyListeners();
return DisplayModel();
}

Related

Is there a way to trigger a BlocListener just after its initialization?

I'm working with Bloc and Hydrated Bloc and at some point in my app I want to store a boolean variable "firstTime" in a Hydrated Bloc to know if it's the first time my user is using the app. If it is the case, I redirect the user to a on-boarding page (called IntroPage), and if not, the login screen is displayed.
I use a BlocListener to listen to the changes of "firstTime", so once my user has finished navigating the on-boarding page, it redirects to the login screen.
#override
Widget build(BuildContext context) {
return MaterialApp(
...
builder: (context, child) {
return BlocListener<UserPreferencesBloc, UserPreferencesState>(
listener: (context, state) {
if (state.firstTime) {
_navigator.pushAndRemoveUntil<void>(
IntroPage.route(),
(route) => false,
);
}
},
child: child,
);
},
onGenerateRoute: (_) => SplashPage.route(),
);
}
The main problem is that if there's no change in the state of the Bloc, it does not fire the BlocListener part. The user never access the IntroPage.
Is there a way to make it so I can get into that listener just after its initialization, even without any change in the state of the Bloc ? Or is there another way to do that (that doesn't involve the use of Shared Preferences or other packages) ?
Edit : Here is the code for the Bloc :
class UserPreferencesBloc
extends HydratedBloc<UserPreferencesEvent, UserPreferencesState> {
UserPreferencesBloc() : super(const UserPreferencesState()) {
on<UserPreferencesFirstTimed>(_onFirstTime);
}
void _onFirstTime(
UserPreferencesFirstTimed event,
Emitter<UserPreferencesState> emit,
) async {
emit(state.copyWith(firstTime: event.firstTime));
}
#override
UserPreferencesState? fromJson(Map<String, dynamic> json) {
return UserPreferencesState(firstTime: json['firstTime'] as bool);
}
#override
Map<String, dynamic>? toJson(UserPreferencesState state) => {
'firstTime': state.firstTime,
};
}
And here is the state :
part of 'user_preferences_bloc.dart';
class UserPreferencesState extends Equatable {
const UserPreferencesState({
this.firstTime = true,
});
final bool firstTime;
UserPreferencesState copyWith({
bool? firstTime,
}) {
return UserPreferencesState(
firstTime: firstTime ?? this.firstTime,
);
}
#override
List<Object> get props => [firstTime];
}
And the Bloc is initialized in the app.dart file, at the start of the application :
class App extends StatelessWidget {
const App({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return MultiRepositoryProvider(
providers: ... //not shown in this piece of code
child: MultiBlocProvider(
providers: [
...
BlocProvider(create: (_) => UserPreferencesBloc())
],
child: AppView(),
),
);
}
}
It is by design so that BlocListener is only triggered once per state change.
But there are of course ways to do what you are after. If you'd show how you provide/create the bloc and also the definition of the state it could help...
But you could for instance let firstTime be nullable and use the cascade notion operator (..) when creating the bloc to immediately call a method in the bloc that sets the value of firstTime to true/false after initialization.
Edit:
Obviously hard from here to write all the changes you'd have to make, but here is the main idea:
Change: final bool firstTime; to bool? firstTime; and handle the null cases where applicable.
On creation, change:
BlocProvider(create: (_) => UserPreferencesBloc())
to:
BlocProvider(create: (_) => UserPreferencesBloc()..onFirstTime())
Write the method onFirstTime() something like this:
void onFirstTime() async {
emit(state.copyWith(firstTime: state.firstTime ?? true));
}
And remove the on<UserPreferencesFirstTimed>(_onFirstTime); part as well as this.firstTime = true,

The argument type Set<Map<String, Widget Function(BuildContext)>> can't be assigned to the parameter type 'Map<String, Widget Function(BuildContext)>

I just started learning flutter and I don't quite understand how to fix this error.
I wanted to make my own factory of screens and use them in persistent_bottom_nav_bar package.
The code with the error location (error in the routes field):
class _BottomNavigationBarItemFactory {
final String iconName;
final String label;
_BottomNavigationBarItemFactory(this.iconName, this.label);
PersistentBottomNavBarItem build(
int index,
int currentIndex,
BottomNavigationBarThemeData theme,
) {
final color = index == currentIndex
? theme.selectedItemColor
: theme.unselectedItemColor;
return PersistentBottomNavBarItem(
routeAndNavigatorSettings: RouteAndNavigatorSettings(
initialRoute: Screens.main,
routes: {
MainNavigation.routers
},
),
title: label,
icon: Image.asset(
iconName,
color: color,
),
);
}
}
class MainNavigation:
abstract class Screens {
static const main = "/";
static const notification = "/notification_screen";
}
class MainNavigation {
final _screenFactoty = ScreenFactory();
Map<String, WidgetBuilder> get routers => <String, WidgetBuilder>{
Screens.main: (_) => _screenFactoty.makeMainTabs(),
Screens.notification: (_) => _screenFactoty.makeNotificationScreen(),
};
Route<dynamic>? onGenerateRoute(RouteSettings settings) {
return null;
}
}
class ScreenFactory:
class ScreenFactory {
Widget makeMainTabs() => ChangeNotifierProvider(
child: MainTabsScreen(),
create: (_) => MainTabsViewModel(),
);
Widget makeNotificationScreen() => const NotificationScreen();
}
Try removing the {} you have around MainNavigation.routers
you need to set up the method "get routers" as non-static method, you are using it without ans instance so you if you want to use it without an instance declare it as static

How to pass in arguments in flutter when user input URL address (using auto_route)

My app uses flutter with auto_router 0.6.9 package, and I want user to be able to type in a web address http://localhost/#/book/123 and be able to access book 123 and display book123, their name, and email if they are logged in. I had figured out the routing part with auto_router but when the user put the address directly into the browser, the arguments should be passed into the page are null.
I was wondering is there a way to parse the url and pass in the arguments required before displaying the page. Thanks for any help or hints!
My router.dart page:
#MaterialAutoRouter(
routes: <AutoRoute>[
// some other pages...
MaterialRoute(path: "/book/:id", page: MultiProvider),
],
)
class $ModularRouter {}
The router.gr.dart generated for multiprovider arguments class is:
/// MultiProvider arguments holder class
class MultiProviderArguments {
final Key key;
final List<SingleChildWidget> providers;
final Widget child;
final Widget Function(BuildContext, Widget) builder;
MultiProviderArguments(
{this.key, #required this.providers, this.child, this.builder});
}
My book menu page contains a navigation:
(this is an example of how I normally call the BookPage in another page)
child: GestureDetector(
onTap: () => ExtendedNavigator.of(context).push(
Routes.multiProvider(id: book[index].bookID),
arguments: MultiProviderArguments(
providers: [
Provider<UserData>.value(
value: userdata,
),
Provider<List<BookInfo>>.value(
value: book,
),
],
child: BookPage(
bookId: book[index].bookID,
name: userdata.userName,
email: userdata.email,
),
),
),
...
And BookPage takes in 3 arguments bookId, name and email:
class BookPage extends StatefulWidget {
final String bookId;
final String name;
final String email;
BookPage({
this.bookId,
this.name,
this.email,
});
...
}
...
class _BookPageState extends State<BookPage> {
...
Widget build(BuildContext context) {
final currentUser = Provider.of<UserData>(context, listen: false);
final currentBook = Provider.of<List<BookInfo>>(context, listen: false);
...
}
}
I had figured out a Band-Aid solution (which I don't consider to be permanent):
I had abandoned using auto-route which I have no idea how to parse the url and use onGenerateRoute instead:
onGenerateRoute: (settings) {
final settingsUri = Uri.parse(settings.name);
final user = Provider.of<User>(context);
if (user == null) {
return MaterialPageRoute(
builder: (context) {
return LogInPage();
}
);
} else {
// Handle '/'
if (settingsUri.pathSegments.length == 0) {
return MaterialPageRoute(
builder: (context) {
return PageWrapper(false, bookid);
}
);
}
// Handle '/book/:id'
if (settingsUri.pathSegments.length == 2) {
if (settingsUri.pathSegments.first != 'book') {
return MaterialPageRoute(
builder: (context) {
return UnknownPage();
}
);
}
final bookid = settingsUri.pathSegments.elementAt(1);
if (bookid == null) {
return MaterialPageRoute(
builder: (context) {
return UnknownPage();
}
);
}
return MaterialPageRoute(
builder: (context) {
return PageWrapper(false, bookid);
}
);
}
}
//Handle other unknown Routes
return MaterialPageRoute(
builder: (context) {
return UnknownPage();
}
);
}
The PageWrapper authenticates user, build providers, and call BookPage class, I put the code in a place where userData providers had already been initialized:
if (this.needRedirect == true) {
return Scaffold(
...
body: FutureBuilder(
future: Future.wait([userData]),
builder: (context, snapshot) {
return MultiProvider(
providers: [
//initialize using streamproviders
...
],
child: BookPage(
bookId: book[index].bookID,
name: userdata.userName,
email: userdata.email,
),
);
}),
);
}
else {...}
The short answer is I had called the WrapperPage in onGenerateRoutes because the providers are not initialized in main.dart, but they are initialized in WrapperPage. Then I put the redirect code after provider initialization in WrapperPage and called BookPage.
This method had caused other bugs, but it had achieved its purpose.

Flutter: Access SharedPreferences Provider / ChangeNotifier in a Stream Class

I've looked around in StackoverFlow and was not able to find myself a solution to this.
Scenario:
I have a Flutter SharedPreferences Provider with ChangeNotifier Class, that will get updated with the current Logged In User info.
Simplified content:
class SharedPreferences {
final String userId;
final String userName;
SharedPreferences({
#required this.userId,
#required this.userName,
});
}
class SharedPreferencesData with ChangeNotifier {
var _sharedPreferencesData = SharedPreferences(
userId: 'testUserId',
userName: 'testUserName',
);}
And a database.dart file with Class containing DataBaseServices to get FireStore Streams from Snapshots:
class DatabaseService {
final CollectionReference companiesProfilesCollection =
Firestore.instance.collection('companiesProfiles');
List<CompanyProfile> _companiesProfilesFromSnapshot(QuerySnapshot snapshot) {
return snapshot.documents.map((doc) {
return CompanyProfile(
docId: doc.documentID,
companyName: doc.data['companyName'] ?? '',
maxLocationsNumber: doc.data['maxLocationsNumber'] ?? 0,
maxUsersNumber: doc.data['maxUsersNumber'] ?? 0,
);
}).toList();
}
Stream<List<CompanyProfile>> get getCompaniesProfiles {
return companiesProfilesCollection
.where('userId', isEqualTo: _userIdFromProvider)
// My problem is above -----
.snapshots()
.map(_companiesProfilesFromSnapshot);
}
}
I Don't want to fetch the entire Stream data as it could be massive for other Streams, I just want to pass the userID under .where('userId', isEqualTo:_userIdFromProvider).
I couldn't access the context in this class to get the data from the Provider
Couldn't send the userId to getCompaniesProfiles getter, as getter don't take parameters
And if I convert this getter to a regular method, I wont be able to send the userID to it, as this has to run under void main() {runApp(MyApp());} / return MultiProvider(providers: [ and By then I cannot call fetch the sharedPreferences with a context that does not contain the provider info ...
Couldn't figure out how to receive the context as a constructor in this class, when I did, I got the following Only static members can accessed in initializers in class DatabaseService.
I'm still a beginner, so I would appreciate if you could share with me the best approach to handle this.
Thank you!
*********** Re-Edited by adding the below: **************
I'm trying to implement the same scenario, here is my code:
Main file:
return MultiProvider(
providers: [
ChangeNotifierProvider<SpData>(
create: (context) => SpData(),
),
ProxyProvider<SpData, DS>(
create: (context) => DS(),
update: (ctx, spData, previousDS) {
print('ChangeNotifierProxyProvider RAN');
previousDS.dbData = spData;
return previousDS;
},
),
],
SP File:
class SP {
final String companyId;
SP({
#required this.companyId,
});
}
class SpData with ChangeNotifier {
var _sPData = SP(
companyId: '',
);
void setCompanyId(String cpID) {
final newSharedPreferences = SP(
companyId: cpID,
);
_sPData = newSharedPreferences;
print('_spData companyId:${_sPData.companyId}');
notifyListeners();
}
String get getCompanyId {
return _sPData.companyId;
}
}
DS file:
class DS with ChangeNotifier {
SpData dbData;
void printCompanyId() {
var companyId = dbData.getCompanyId;
print('companyId from DataBase: $companyId');
}
}
The SpData dbData; inside Class DS does not update. I've added the prints to figure out what is running and what is not. When I run my code, the print function in main.dart file print('ChangeNotifierProxyProvider RAN'); does not run.
What am I missing? Why ChangeNotifierProxyProvider is not being triggered, to update dbData inside DS file? Thanks!
You can use ProxyProvider for this purpose.
ProxyProvider is a provider that builds a value based on other providers.
You said you have a MultiProvider, so I guess you have SharedPreferencesData provider in this MultiProvider and then DatabaseService provider. What you need to do is use ProxyProvider for DatabaseService instead of a regular provider and base it on the SharedPreferencesData provider.
Here is an example:
MultiProvider(
providers: [
ChangeNotifierProvider<SharedPreferencesData>(
create: (context) => SharedPreferencesData(),
),
ProxyProvider<SharedPreferencesData, DatabaseService>(
create: (context) => DatabaseService(),
update: (context, sharedPreferencesData, databaseService) {
databaseService.sharedPreferencesData = sharedPreferencesData;
return databaseService;
},
dispose: (context, databaseService) => databaseService.dispose(),
),
],
child: ...
Here is what happens in the code snippet above:
ProxyProvider calls update everytime SharedPreferencesData changes.
DatabaseService gets its sharedPreferencesData variable set inside update.
Now that you have access to sharedPreferencesData inside the DatabaseService instance, you can do what you want easily.

Navigator pass arguments with pushNamed

Might have been asked before but I can't find it but how do you pass a arguments to a named route?
This is how I build my routes
Widget build(BuildContext context) {
return new Navigator(
initialRoute: 'home/chooseroom',
onGenerateRoute: (RouteSettings settings) {
WidgetBuilder builder;
switch (settings.name) {
case 'home/chooseroom':
// navigates to 'signup/choose_credentials'.
builder = (BuildContext _) => new ChoosePage();
break;
case 'home/createpage':
builder = (BuildContext _) => new CreateRoomPage();
break;
case 'home/presentation':
builder = (BuildContext _) => new Presentation();
break;
default:
throw new Exception('Invalid route: ${settings.name}');
}
return new MaterialPageRoute(builder: builder, settings: settings);
},
);
This is how you call it
Navigator.of(context).pushNamed('home/presentation')
But what if my widget is new Presentation(arg1, arg2, arg3)?
No need for onGenerateRoute. Simply use
var exampleArgument = 'example string';
Navigator.pushNamed(
context,
'/otherscreen',
arguments: {'exampleArgument': exampleArgument},
);
and extract the arguments as follows:
#override
Widget build(BuildContext context) {
final arguments = (ModalRoute.of(context)?.settings.arguments ?? <String, dynamic>{}) as Map;
print(arguments['exampleArgument']);
return Scaffold(...);
}
pushNamed() now supports arguments as of this merged pull request. If you can't wait, switch to channel master (flutter channel master and probably followed by flutter upgrade).
How to send:
Navigator.pushNamed(ctx, '/foo', arguments: someObject);
How to receive:
...
return MaterialApp(
...
onGenerateRoute: _getRoute,
...
);
...
Route<dynamic> _getRoute(RouteSettings settings) {
if (settings.name == '/foo') {
// FooRoute constructor expects SomeObject
return _buildRoute(settings, new FooRoute(settings.arguments));
}
return null;
}
MaterialPageRoute _buildRoute(RouteSettings settings, Widget builder) {
return new MaterialPageRoute(
settings: settings,
builder: (ctx) => builder,
);
}
The "arguments" can be any object, e.g. a map.
It took me a while to notice this, as I'm a newbie to Flutter. But the arguments you add using Navigator.pushNamed get sent directly to the widget you pushed NOT the MaterialApp for routing.
So in widget you push a new screen from you'll have:
Navigator.pushNamed(
context,
SomePage.routeName,
arguments: {
'v1': 'data1',
'v2': 'data2',
'v3': 'data3',
},
)
You won't need those arguments in your constructor at all. Instead your pull them out in the SomePage widget like the others are saying; namely via:
final arg = ModalRoute.of(context)!.settings.arguments as Map;
and can assign them within SomePage build like:
randomVar1 = arg['v1'];
randomVar2 = arg['v2'];
randomVar3 = arg['v3'];
using whatever keys you put in.
if you want MaterialApp to handle it then you use the onGenerateRoute method. This took me forever to notice that the arguments go directly to the widget pushed. For me it was counter-intuitive.
Arguments can be any object, you can make an array as you can see:
Navigator.of(context).pushNamed('/upload', arguments: {'_imagePath':_imagePath,
'num_docfiscal':num_docfiscal,'dta_docfiscal':dta_docfiscal});
and access to the router class.
Basically you have 2 options :
Use some 3rd party package for routing - I think the best is Fluro .
exploit onGenerateRoute . This option is limited to args you can pass (string/numbers)
To use second option, assuming you want to pass three arguments: Navigator.of(context).pushNamed('home/presentation:arg1:1337:hello')
MaterialApp (
... ,
onGenerateRoute: handleRoute,
routes:... , )
Route<dynamic> handleRoute(RouteSettings settings) {
WidgetBuilder builder;
final List<String> uri = settings.name.split('/');
if (uri[0].startsWith('home')) {
// handle all home routes:
if(uri[1].startsWith('presentation:'){
// cut slice by slice
final String allArgs =
uri[1].substring('presentation:'.length);
final List<String> args = allArgs.split(':');
// use your string args
print(args[0]); // prints "arg1"
int x = int.parse(args[1]); // becomes 1337
print(args[2]); // prints "hello"
builder = (ctx)=> Presentation(args[0],args[1],args[2]);
...
For simple navigation with arguments from WidgetA to WidgetB
Define routes in MaterialApp main widget:
return MaterialApp(
routes: {
'/routeAB': (context) => WidgetB(),
},
In WidgetA use pushNamed method to navigate to WidgetB:
onTap: () {
Navigator.pushNamed(context, '/routeAB',
arguments: {
'arg1': val1,
'arg2': val2,
...
Get arguments in WidgetB:
Map args = ModalRoute.of(context).settings.arguments;
Note: new routing options may be available soon.
Could this be a possible option?
Use push with a RouteSettings argument specifying the named route.
This way you can directly pass arguments of any type (including objects) to your destination Widget in a type safe manner and skip using arguments. You won't need to create a single-use throwaway arguments class nor a Map.
RouteSettings in push can supply a named Route to your Navigation stack which you can search for / use in future routing decisions, just the same as if you had used pushNamed.
Push + RouteSettings
To use push with a named route, use RouteSettings argument with the route name.
Example: a user logs in on Page1 and now you want push them from Page1 to Page2
Directly inside Page1 pass the User object (loggedInUser) to Page2 within a Navigator.push call and use a RouteSettings arg with your route name (/page2).
Navigator.push(context, MaterialPageRoute(
builder: (context) => Page2(user: loggedInUser),
settings: RouteSettings(name: '/page2')
));
And in Page2 widget, you can expect and use the User object directly.
class Page2 extends StatefulWidget {
final User loggedInUser;
Page2(this.loggedInUser);
#override
_Page2State createState() => _Page2State();
}
class _Page2State extends State<Page2> {
User loggedInUser;
#override
void initState() {
super.initState();
loggedInUser = widget.loggedInUser;
print(loggedInUser.name);
}
#override
Widget build(BuildContext context) {
}
}
Later on you can use /page2 route name. For example if you're at /page3 and you want to popUntil(context, ModalRoute.withName('/page2')), this allows that.
Under the Hood
Flutter's Navigator class shows pushNamed uses push + routeNamed and routeNamed uses RouteSettings.
Future<T> pushNamed<T extends Object>(
String routeName, {
Object arguments,
}) {
return push<T>(_routeNamed<T>(routeName, arguments: arguments));
}
Route<T> _routeNamed<T>(String name, { #required Object arguments, bool allowNull = false }) {
final RouteSettings settings = RouteSettings(
name: name,
arguments: arguments,
);
Route<T> route = widget.onGenerateRoute(settings) as Route<T>;
return route;
Pass arguments:
Navigator.pushNamed(YourScreen.routeName, arguments: {"title":myTitle, "user_name":userName});
Extract arguments:
Map<String, dynamic> arguments = new Map<String, dynamic>.from(settings.arguments);
page = MyRecordingScreen(title: arguments["title"], tags: arguments["user_name"], );
For named Routes with multiple arguments or dynamic object you need to do as follow(this is MVVM pattern example):
navigator:
void navigateEditParty(int index) {
_navigationService.navigateTo(PartyEditRoute,
arguments: {"hunter": hunter, "index": index});
}
router:
case PartyEditRoute:
Map args = settings.arguments;
return _getPageRoute(
routeName: settings.name,
viewToShow: PartyEditView(
hunter: args["hunter"],
index: args["index"],
),
);
class:
PartyEditView({Key key, this.hunter, this.index}) : super(key: key);
It is always best to wrap your arguments in an object to avoid error prone code.
Below are the working example on how you can achieve it in flutter dart.
To send
Navigator.of(context).pushNamed(Routes.submitSuccess, arguments: successModel);
To Receive
case Routes.submitSuccess:
if (settings.arguments is SubmitSuccessModel) {//Check for instance here
return CupertinoPageRoute(
builder: (_) =>
SubmitSuccessPage(successModel: settings.arguments));
}
Model Object
class SubmitSuccessModel {
SubmitSuccessModel(
{this.title, this.desc, this.actionButtonName, this.widget});
String title;
String desc;
String actionButtonName;
Widget widget;
}
final SubmitSuccessModel successModel = SubmitSuccessModel(
title: 'Title',
desc: 'Desc',
actionButtonName: 'Done',
);
It took me a while to notice this, as I'm a newbie to Flutter. But the arguments you add using Navigator.pushNamed get sent directly to the widget you pushed NOT the MaterialApp for routing.
You don't need those arguments in your constructor at all. Instead your pull them out in the widget like the others are saying; namely via:
final arg = ModalRoute.of(context)!.settings.arguments as Map;
and can assign them like:
randomVar1 = arg['v1'];
randomVar2 = arg['v2'];
randomVar3 = arg['v3'];
using whatever keys you put in.
if you want MaterialApp to handle it then you use the onGenerateRoute method. This took me forever to notice that the arguments go directly to the widget pushed. For me it was counter-intuitive.
screeen_1.dart
Navigator.of(context).pushNamed('/screen2',arguments: {'var1': val1 ,'var2': val2, "var3": val3 ,"var4" : val4}); //sending of the values to route_generator.dart
route_generator.dart
class RouteGenerator {
static Route<dynamic> generateRoute(RouteSettings settings) //from screeen_1.dart
{
// Getting arguments passed in while calling Navigator.pushNamed
Map args = settings.arguments;
switch (settings.name) {
case '/screen2':
if (args is Map) {
return MaterialPageRoute(
builder: (_) => screen_2(args),
);
}
return _errorRoute(); //optional written in a scaffold when any error arrises
default:
return _errorRoute(); //optional written in a scaffold when any error arrises
}
screen_2.dart
screen_2(Map args) //follow the params from the route_generator.dart
{
return Scaffold(
body: FutureBuilder(
future: _screen_2EndpointProvider.fetchScreen_2(args['var1'], args["var2"], args['var3'], args['var4']), //API call inside which it called the args
builder: (context, snapshot) {
return snapshot.hasData
? screen_2(param: snapshot.data)
: Center(child: CircularProgressIndicator());
}),
);
}
I tried to provide a simple example, if it could be helpful to anyone, because i was facing the same error after a lots of trials it got solved.
After a long day fighting with the routes and onGeneratedRoutes on how i should pass the data and retrieve i finally understand it here is what i learned about
routes: - the property of MaterialApp
routes: {'/profile': (context) => const Profile()}
onTap () {
Navigator.pushNamed(context, '/profile', arguments: {'name': 'someone'});
};
On the profile Screen
final args = ModalRoute.of(context)!.settings.arguments as Map;
don't forget do Downcast to the right data type using the as keyword.
useGeneratedRoute: - the property of MaterialApp for dynamic routes
onGenerateRoute: Routes.RouteGenerator,
on Routes class
static Route<dynamic> generateR(RouteSettings settings) {
switch (settings.name) {
final args = settings.arguments;
case 'profile':
if (args is Map) {
return MaterialPageRoute(
builder: (context) => Profile(
title: args,
));
}
default:
return MaterialPageRoute(
builder: ((context) => const NotFound()));
}
}
on class Profile create a constructor and a field of type Map or whatever you what to pass as data.
final Map<String> data;
const Profile({
super.key,
required this.data,
});
and you can now use the data as you wish.
child: const Text(data[name]);
So use the best option for your context i struggle so much to understand because i was get the null error. hope it's help you need
That's how to send parameters / arguments-
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NewPresentation(
id: '101',
name: 'Unnamed',
)));
},
child: const Text('Submit'))
And that's how to receive -
class NewPresentation extends StatefulWidget {
final String id, name;
const NewPresentation({Key? key, required this.id, required this.name}) : super(key: key);
#override
State<NewPresentation> createState() => _NewPresentationState();
}
class _NewPresentationState extends State<NewPresentation> {
Widget build(BuildContext context) {
return Text(widget.id + widget.name);
}
}
Screen 1 :
Navigator.push(
context,
MaterialPageRoute(
settings: RouteSettings(arguments: e),
builder: (context) =>
const AnnouncementDetail()),
);
screen 2 :
//get parameters
final arg = ModalRoute.of(context)!.settings.arguments as Announcement;
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Anang', style: TextStyle(fontWeight: FontWeight.bold),),
Text('Diposting pada ${DateFormat("d MMMM yyyy", "id_ID")
.format(DateTime.parse('${arg.announcementDate!} 00:00:00.000'))}', style: const TextStyle(fontWeight: FontWeight.normal)),
const SizedBox(height: 16),
Text(arg.announcementTitle!, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 20),),
const SizedBox(height: 16),
Html(
style : {
"body": Style(
padding: EdgeInsets.zero,
margin: Margins(
bottom: Margin.zero(),
left: Margin.zero(),
top: Margin.zero(),
right: Margin.zero(),
)
)
},
data: arg.announcement)
],
)
Announcement.dart
class Announcement {
Announcement({
this.id,
this.announcementDate,
this.announcementTitle,
this.announcement,
this.month,
this.createdBy,
this.createdAt,
this.updatedAt,});
Announcement.fromJson(dynamic json) {
id = json['id'];
announcementDate = json['announcement_date'];
announcementTitle = json['announcement_title'];
announcement = json['announcement'];
month = json['month'];
createdBy = json['created_by'];
createdAt = json['created_at'];
updatedAt = json['updated_at'];
}
int? id;
String? announcementDate;
String? announcementTitle;
String? announcement;
String? month;
int? createdBy;
String? createdAt;
String? updatedAt;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['id'] = id;
map['announcement_date'] = announcementDate;
map['announcement_title'] = announcementTitle;
map['announcement'] = announcement;
map['month'] = month;
map['created_by'] = createdBy;
map['created_at'] = createdAt;
map['updated_at'] = updatedAt;
return map;
}
}
// Main Screen from where we want to Navigate
Navigator.pushNamed(
context,
"/ScreenName",
arguments: {
'id': "20"
});
I also faced the same issue I was using NavigationRoutes
class NavigationRoutes {
static Route<dynamic> generateScreenRoute(RouteSettings settings) {
// TODO below we are able to get Arguments list
Map args = settings.arguments;
switch (settings.name) {
case "Screen Name":
return MaterialPageRoute(builder: (context) => ScreenName(args));
// TODO pass above argument here and get in the form of Constructor in Screen
// Name Class
}
}