As the title says, I have a String parameter and when I load the Home Stateful Widget I would like to open this bottom sheet if the parameter is not null.
As I understood I can't call showModalBottomSheet() in the build function of the Home widget because it can't start building the bottom sheet while building the Home Widget, so, is there a way to call this immediately after the Home Widget is built?
One of the solutions might be using addPostFrameCallback function of the SchedulerBinding instance. This way you could call showModalBottomSheet after the Home widget is built.
import 'package:flutter/scheduler.dart';
...
#override
Widget build(BuildContext context) {
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
//Your builder code
},
);
});
//Return widgets tree for Home
}
Here's one way:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return Container(
child: Text('heyooo'),
);
}
);
});
return Scaffold(
appBar: AppBar(),
body: Container(),
);
}
}
Related
I have created a widget with a scaffold and called a widget in it as
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text("Everything Store"),
),
body: Register(),
);
}
}
the Register component doesn't have a scaffold in it, but when I try to navigate from the Register widget to another one that also doesn't have a scaffold too, so I used
onPressed: () => {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => login_page()))
},
I got an error as " No Material widget found. "
So is there a way to have one scaffold or should I make a scaffold for each widget?
The scaffold is one of the main widgets that helps you build up the UI screen on a device but a screen doesn't necessarily need it. The error that you are getting it might be because you are not passing the Widget class correctly.
Try to replace login_page() with the class name like so Register().
If remove the MediaQuery.of(context).size code, it will not rebuild.
this is my code.
class ExamplePage extends StatelessWidget {
Future<Size> init(BuildContext context) async {
print("init");
return MediaQuery.of(context).size;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: init(context),
builder: (BuildContext context, AsyncSnapshot<Size> snapshot) {
return Center(child: TextField());
}));
}
}
future: init(context),
This will probably cause init(context) to be called each time build() is called for this widget. Instead, something like this should be done in the initState() for this widget, so that it is done only once at widget creation.
I can't access a provider defined above a Scaffold from showModalBottomSheet in the FloatingActionButton.
I've defined a HomePage like so:
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => MyProvider(),
builder: (context, _) {
return Scaffold(
body: Consumer<MyProvider>(
builder: (context, provider, _) {
return Text(provider.mytext); // this works fine
}
),
floatingActionButton: MyFAB(), // here is the problem
);
}
)
}
}
And this is MyFAB:
class MyFAB extends StatefulWidget {
#override
_MyFABState createState() => _MyFABState();
}
class _MyFABState extends State<MyFAB> {
#override
Widget build(BuildContext context) {
return FloatingActionButton(
...
onPressed: () => show(),
);
}
void show() {
showModalBottomSheet(
...
context: context,
builder: (BuildContext context) {
return Wrap(
children: [
...
FlatButton(
onPressed: Provider.of<MyProvider>(context, listen: false).doSomething(); //Can't do this
Navigator.pop(context);
)
],
);
}
);
}
}
Error: Could not find the correct Provider<MyProvider above this BottomSheet Widget.
Fixed by placing the provider above MaterialApp, as described here.
Bottom sheets are created at the root of the material app. If a prodiver is declared below the material app, a bottom sheet cannot access it because the provider is not an ancestor of the bottom sheet in the widget tree.
The screenshot below shows a widget tree: the whole app is inside Wrapper and the bottom sheet is not created inside Wrapper. It is created as another child of MaterialApp (with a root element Container in this case).
For your case:
// main.dart
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => MyProvider(),
builder: (context, _) {
return MaterialApp(
home: HomePage(),
);
},
);
}
}
// home_page.dart
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: MyFAB()
);
}
}
This is caused by passing it the wrong context. Wrap your FAB to a Builder widget and pass it as builder property. This will take a new context and pass it to showModalBottomSheet. Also, you can do onPressed: show, it's more concise.
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => MyProvider(),
builder: (context, _) {
return Scaffold(
body: Consumer<MyProvider>(
builder: (context, provider, _) {
return Text(provider.mytext); // this works fine
}
),
floatingActionButton: MyFAB(context), // here is the problem
);
}
)
}
}
class MyFAB extends StatefulWidget {
#override
_MyFABState createState() => _MyFABState();
}
class _MyFABState extends State<MyFAB> {
#override
Widget build(BuildContext context) {
return FloatingActionButton(
...
onPressed: (context) => show(context),
);
}
void show(ctx) {
showModalBottomSheet(
...
context: ctx,
builder: (BuildContext context) {
return Wrap(
children: [
...
FlatButton(
onPressed: () {
Provider.of<MyProvider>(ctx, listen: false).doSomething(); //Can't do this
Navigator.pop(ctx)
};
)
],
);
}
);
}
}
SOLUTION
HomePage:
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => MyProvider(),
builder: (context, _) {
return Scaffold(
body: Consumer<MyProvider>(
builder: (context, provider, _) {
return Text(provider.mytext); // this works fine
}
),
floatingActionButton: MyFAB(context), // here is the problem
);
}
)
}
}
MyFAB:
class MyFAB extends StatefulWidget {
final BuildContext ctx;
MyFAB(this.ctx)
#override
_MyFABState createState() => _MyFABState();
}
class _MyFABState extends State<MyFAB> {
#override
Widget build(BuildContext context) {
return FloatingActionButton(
...
onPressed: () => show(),
);
}
void show() {
showModalBottomSheet(
...
context: context,
builder: (BuildContext context) {
return Wrap(
children: [
...
FlatButton(
onPressed: Provider.of<MyProvider>(widget.ctx, listen: false).doSomething(); //Can't do this
Navigator.pop(context);
)
],
);
}
);
}
}
In my opinion: showModalBottomSheet builds a bottom sheet with context which comes from Material App
1st image
so when we return any Widget to show in the Bottom sheet it uses that Material app context as we can see in the builder property in the:1st image.
2ng Image: your code
so in your code, when you are writing: Provider.of(context, listen: false).doSomething(); it is using context from the builder: (BuildContext context) which is the context of Material App. we have to change this context in order to use this Provider without having to uplift the position of our Provider above the Material App.
Now if we want to keep using that context to get the benefits of that overlay and automatic detection of suitable themes and still want to use the context of a widget that does have access to our provider:
we can pass the context of the Widget which does have Provider access to the FAB, but we will have to keep passing that context through widgets till we need to use that Provider in our FAB or till we go to a different route: in which case we can start from a new context and provider as Providers are scoped in mature.
so in your HomePage either you can wrap your scaffold inside a Builder or you can create a new widget like this:"
3rd image
so that it will have its own context which does have access to the provider we need inside our FAB as shown below in 4th image:
4th image
and then in the builder property of showModalBottomSheet change the name of the parameter in an anonymous function so that it won't be confused with the MAterial App context and context we will be passing in (Builder context or IdeaScreen context in my case image 4th)
5th image
I am creating a new widget but you do not have need to do so you can directly write your Fab code inside the anonymous function:
and can use context(not newContext which is related to Material App context) while calling the Provider as you are already doing.
But I will show in my case What I am doing in my AddTask Widget in case anyone's use case is similar to mine:
6th image
expect a context, which does have a provider access, I my case its context of IdeaScreen.
and then use it just like this:
7th image
I have a parent widget "BookmarkedShows" and child widget "ListOfShows". From child widget, when user taps on list item, it opens details page. When the user removes the show from bookmark from details page, on pressing back button, the show is not removed from the listing page. ie the parent is not refreshed. I'm using BlocBuilder.
There are some options mentioned in other question to add .then() to Navigator.push() method. However Navigator.push() happens in children component. How would I force refresh parent BlocBuilder during Navigation.pop()?
Parent "BookmarkedShows":
class BookmarkedShows extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => BookmarkShowsBloc()..add(LoadBookmarkedShows()),
child: BlocBuilder<BookmarkShowsBloc, BookmarkedShowsState>(
builder: (BuildContext context, BookmarkedShowsState state) {
return ShowList("Bookmarked shows", state.shows)
}),
);
}
}
Child "ListOfShows":
class ListOfShows extends StatelessWidget {
final String listName;
final List<Show> shows;
const ListOfShows(this.listName, this.shows);
#override
Widget build(BuildContext context) {
return Wrap(children: shows.map((show) => showItem(show, context)).toList());
}
InkWell showItem(Show show, BuildContext context) {
return InkWell(
onTap: () async {
await Navigator.of(context).push(MaterialPageRoute(
builder: (context) => showDetails(show)));
},
child: Container(
CachedNetworkImage(
imageUrl: show.portraitPoster
),
));
}
}
The question stated is a bit unclear, but I'm going to answer it the best I can.
If you want your widget to be able to update you need to make it Stateful.
Make your BookmarkedShows Widget Stateful:
class BookmarkedShows extends StatefulWidget {
BookmarkedShows ({Key key}) : super(key: key); //Can also work without this line
#override
StatefulBookmarkedShows createState() => StatefulBookmarkedShows();
}
class StatefulBookmarkedShows extends State<BookmarkedShows> {
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => BookmarkShowsBloc()..add(LoadBookmarkedShows()),
child: BlocBuilder<BookmarkShowsBloc, BookmarkedShowsState>(
builder: (BuildContext context, BookmarkedShowsState state) {
return ShowList("Bookmarked shows", state.shows)
}),
);
}
}
On returning back to parent you could implement something like in this Flutter docs example which might help to update the parent when navigating back. The async method awaits a response back from the child(Navigator).
When returning back to the Stateful parent you can call this like in the above mentioned async method:
LoadBookmarkedShows();
setState(() { });
I hope it works. Goodluck.
I've being studying Flutter for about 4 days, it's becoming tedius, now I want put something together. I want to show a different AppBar content at each screen. But it seems my Scaffold's AppBar is begin ignored.
Here's my code:
class Login extends StatelessWidget{
Widget build(BuildContext context){
return Scaffold(appBar: AppBar(title:Text( 'Identification') ), body: Stack( children: [RaisedButton( child: Text("logar"), onPressed: () => {
Navigator.pushNamed(context, '/other-screen')
})]));
}
}
The app bar above it seems invisible, doesn't show
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'title placedholder',
initialRoute: '/',
routes: {
'/' : (BuildContext context) => Login(),
'/other-screen' : (BuildContext context) => Scaffold(body: Row(children: <Widget>[],))
},
I have copied your code into DartPad and the first AppBar seems to be Ok.
The problem is with your second page.
Every time you create a new Scaffold, you need to add an AppBar to it.
Try running flutter clean and then running it again.
I find this fixes most issues when the code is correct but the application doesn't reflect the intended behaviour.