How to pass object to StatefulWidget while Routing in Flutter? [duplicate] - flutter

This question already has answers here:
How do I pass non-string data to a named route in Flutter?
(11 answers)
Closed 4 years ago.
I have MyApp class which is responsible for loading application. I'm loading a Class HomeWidget as home in MyApp.
void main() => runApp(new myApp());
class myApp extends StatelessWidget{
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'My App',
color: Colors.grey,
home: new HomeWidget(),
theme: new ThemeData(
primarySwatch: Colors.lightBlue,
),
routes: <String, WidgetBuilder>{
"/ItemDetailWidget": (BuildContext context) => new MovieDetailsPage(),
},
);}}
HomeWidget contains - Header, ListView, BottomNavigation.
When user taps particular item from ListView, I wanted to show new Widget/Page(ItemDetailWidget) which has all information about that particular item.
So i created ItemDetailWidget which is stateful and it accepts one parameter which is of type Object MyModel suppose. I made it stateful for purpose.
How should i add ItemDetailWidget into Routes as i'm passing
parameter to it?
I tried using
"/ItemDetailWidget": (BuildContext context) => new ItemDetailWidget(),
However, It throwing error as "The Constructor return type dynamic that isn't of expected type widget"
& Also how can i pass MyModel object to ItemDetailWidget using Navigator syntax? I'm having onTap() function in ListView.
Navigator.of(context).pushNamed('/widget1');

This depends on the data you're sending; it sounds like in your case you have a bunch of movie details in a DB (or something), and you want to be able to show details for that movie. What you can do is use a unique identifier for each movie, and put that in the request; this is described more or less in the potential duplicate mentioned in the comments. The flutter stocks example also explains this.
To summarize:
When you push, do a pushNamed("moviedetails/${movieUniqueIdentifier}").
In your MaterialApp, you can set
routes:
routes: <String, WidgetBuilder>{
'/': (BuildContext context) => new Movie(movies, _configuration),
'/settings': (BuildContext context) => new MovieSettings(_configuration)
},
and:
onGenerateRoute: (routeSettings) {
if (routeSettings.name.startsWith("movie:") {
// parse out movie, get data, etc
}
}
However, this isn't always the easiest way of doing things - say for example your database takes a while to respond and so you want to do a query before and then pass in the result (caching would be a good answer to this but let's ignore that for now =D). I'd still recommend the first method, but there are cases where it doesn't work.
To instead pass an object directly in (as the question actually asks) you can use:
Navigator.of(context).push(new PageRouteBuilder(pageBuilder:
(context, animation, secondaryAnimation) {
// directly construct the your widget here with whatever object you want
// to pass in.
})
Note that this will get quite messy if you have these Navigator.of(context).push blocks all over your code; I got away from this by using static convenience methods to it such as MyNavigator.pushMovie(context, movieObject) which would call Navigator.of(context).... under the hood. (I also subclass Navigator and so do MyNavigator.of(context) instead, but my setup is complicated as it does a bunch of additional custom navigation stuff).

Related

How can you access GoRoute state.params outside of the routing process?

I hope some simple pseudocode is enough so that both me and you can understand the question and answer.
The problem I'm facing is especially hard when using flutter-web, where the refresh restarts the whole program.
I want to use the path parameters to build objects in a child widgets build method.
GoRouter(routes:...,GoRoute(path="/categories/:category/",
builder:(context,state){
category = state.params['category];
return ParentWidget(category);}
ParentWidget extends StatelessWidget {
build(context){
return ChildWidget();}}
ChildWidget extends StatlessWidget {
build(context){
return "do something with category";}}
Now one way which I can think of and should technically work without any errors would be to pass the params first into the ParentWidget and then pass it along to the next child and so on. But if there's a long chain of child widgets it gets quite tedious and I'm guessing error prone as well. The other thing I was thinking was to use providers: pass the param once again to the parent widget and then make the parent widget send it to a provider. But then the question becomes, where do I do it? Apparently I shouldn't update a provider on build(), but if I do it on initState() it only does it bugs out if I change into a route that include the same widget tree but different path e.g. /categories/apples -> /categories/bananas.
Ps. For some reason I don't remember what the problem with refreshing was. (It has something to do with resetting the providers). But I'll update it when I remember.
go_router has it's params in its state.
Hence pass the state to the page
Router
GoRoute(
name: "test",
path: "/test/:id",
builder: (context, state) {
return SampleWidget(
goRouterState: state, 👈 Pass state here
);
},
),
Usage
context.goNamed("test", params: {"id": "123"}),
Accesing in the page
class SampleWidget extends StatelessWidget {
GoRouterState? goRouterState;
SampleWidget({super.key, this.goRouterState});
#override
Widget build(BuildContext context) {
print(goRouterState?.params.toString()); 👈 access anywhere like so
return const Scaffold(
body: ...
);
}
}

Call future provider with delay on user progress in app

my goal is to provide value of user location with a Provider in the whole app. It's important to me that is't on top of the app as I want to use the value in the routes also.
However, in my app user first needs to login. Only afterwards he gets to the map. Here is the thing. According to bussiness requirements I can't call for permissions before the user gets to the map widget.
So the provider needs to be on top o the app but the future function has to be called once the user logs in.
How can I achieve that?
Here is a sample of my MyApp widget.
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
StreamProvider<UserModel?>.value(
initialData: null,
value: AuthService().user,
),
FutureProvider<LatLng?>.value(
value: GeolocationService().getUserLocation(),
initialData: null,
)
],
child: MaterialApp(
title: '',
theme: themeData(),
onGenerateRoute: onGenerateRoute(),
builder: EasyLoading.init(),
home: AuthWrapper(),
),
);
}
}
SchedulerBinding.instances from scheduler. You can use it inside the initState of StatefulWidget
{
//inside initState method
SchedulerBinding.instances?.addPostframecallback((_){
//anything run within this function will be called just after the very first build method
// how it works?
// before the build method ran, initState will be called first synchronously
// after that, build method will be called
// then right after that build method finished the first render task,
// the post frame callback will be called, in this place we can use context
// since the UI has been built
});
}

BuildContext: the meaning of nearest enclosing?

I'm reading the docs: https://docs.flutter.io/flutter/widgets/BuildContext-class.html
This can lead to some tricky cases. For example, Theme.of(context) looks for the nearest enclosing Theme of the given build context. ...
Does enclosing includes the Theme of current context? I don't understand what the tricky cases the author indicated.
The tricky situation they mentioned becomes more clear if you understand the previous statement:
In particular, this means that within a build method, the build context of the widget of the build method is not the same as the build context of the widgets returned by that build method.
Ok, not a very helpful language. Imagine:
FooWidgetA:
attributes: context, height, width
methods: build
FooWidgetB:
attributes: context, theme
methods: build
FooWidgetB gets built inside FooWidgetA build method. If you try to find theme using the context of FooWidgetA it won't find because FooWidgetA is one level above in the widget tree.
So, exemplifying their tricky situation, it looks like this:
class Foo extends StatelessWidget {
#override
Widget build(BuildContext buildMethodContext) {
return MaterialApp(
// Here we create the [ThemeData] that our method below will try to find
theme: ThemeData(primaryColor: Colors.orange),
builder: (BuildContext materialAppContext, _) {
return RaisedButton(
child: const Text('Get ThemeData'),
onPressed: () {
getThemeData(buildMethodContext); // unsucessful
getThemeData(materialAppContext); // sucessful
},
);
},
);
}
ThemeData getThemeData(BuildContext context) => Theme.of(context);
}
It's tricky because, since the two contexts are way to close, it's easy to forget that buildMethodContext is actually from the parent (Foo), so it cannot see materialAppContext.

Future inside MaterialPageRoute builder returns null

Need to query server for an object before pushing the ProfileWidget route.
Not sure if error message has anything to do with the Future call inside the MaterialPageRoute's builder (see routes function)?
ERROR
flutter: Another exception was thrown: The builder for route "null" returned null.
..
#override
Widget build(BuildContext context) {
return new MaterialApp(
onGenerateRoute: routes,
debugShowCheckedModeBanner: false,
theme: new ThemeData(
primarySwatch: Colors.blueGrey,
textTheme: TextTheme(
)
),
home: ...,
)
;
}
Route routes(RouteSettings settings) {
if (settings.name.startsWith("/profile")) {
String username = settings.name.replaceFirst("/profile/", '');
return MaterialPageRoute(
builder: (context) {
api.fetchProfile(username)
.then((profile) {
return ProfileWidget(profile);
});
}
);
}
}
Yes, that's exactly where the issue is. Async methods don't work properly within a build function as the build function is expecting a widget to be returned right away - by using a future you're actually just returning null.
Another issue is that you're calling your api in the build function - think about it this way... the build function is called any time anything is changed in your widget. Do you really want to fetch the profile every time that happens?
Instead, I'd recommend using a FutureBuilder and starting the future in initState, or starting the future in initState and using setState(() ...) after completion. Either way, if you do it this way you'll have to deal with the case where there's a brief period of time before the profile is created, unless you think about doing something like moving the profile loading out of the widget into the main function (I don't know if that's really recommended but it seems to work for me).
You might even think about putting the profile into an InheritedWidget or ScopedModel so that if you change the logged-in user it propagates automatically.

InheritedWidget with Scaffold as child doesn't seem to be working

I was hoping to use InheritedWidget at the root level of my Flutter application to ensure that an authenticated user's details are available to all child widgets. Essentially making the Scaffold the child of the IW like this:
#override
Widget build(BuildContext context) {
return new AuthenticatedWidget(
user: _user,
child: new Scaffold(
appBar: new AppBar(
title: 'My App',
),
body: new MyHome(),
drawer: new MyDrawer(),
));
}
This works as expected on app start so on the surface it seems that I have implemented the InheritedWidget pattern correctly in my AuthenticatedWidget, but when I return back to the home page (MyHome) from elsewhere like this:
Navigator.popAndPushNamed(context, '/home');
This call-in the build method of MyHome (which worked previously) then results in authWidget being null:
final authWidget = AuthenticatedWidget.of(context);
Entirely possible I'm missing some nuances of how to properly implement an IW but again, it does work initially and I also see others raising the same question (i.e. here under the 'Inherited Widgets' heading).
Is it therefore not possible to use a Scaffold or a MaterialApp as the child of an InheritedWidget? Or is this maybe a bug to be raised? Thanks in advance!
MyInherited.of(context) will basically look into the parent of the current context to see if there's a MyInherited instantiated.
The problem is : Your inherited widget is instantiated within the current context.
=> No MyInherited as parent
=> crash
The trick is to use a different context.
There are many solutions there. You could instantiate MyInherited in another widget, so that the context of your build method will have a MyInherited as parent.
Or you could potentially use a Builder to introduce a fake widget that will pass you it's context.
Example of builder :
return new MyInheritedWidget(
child: new Builder(
builder: (context) => new Scaffold(),
),
);
Another problem, for the same reasons, is that if you insert an inheritedWidget inside a route, it will not be available outside of this route.
The solution is simple here !
Put your MyInheritedWidget above MaterialApp.
above material :
new MyInherited(
child: new MaterialApp(
// ...
),
)
Is it therefore not possible to use a Scaffold or a MaterialApp as the
child of an InheritedWidget?
It is very possible to do this. I was struggling with this earlier and posted some details and sample code here.
You might want to make your App-level InheritedWidget the parent of the MaterialApp rather than the Scaffold widget.
I think this has more to do with how you are setting up your MaterialWidget, but I can't quite tell from the code snippets you have provided.
If you can add some more context, I will see if I can provide more.