Why rebuilding when TextField tapped? - flutter

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.

Related

How to use nested Consumers in Flutter?

I need to access two different view model in one page in Flutter.
How to use nested Consumers in Flutter, Will they effect each other?
How can I use it in this code for example?
class CounterDisplay extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<CounterModel>(
builder: (context, counterModel, child) {
return Text('${counterModel.count}');
},
);
}
}
you can use Consumer2<> to access two different providers like this :
class CounterDisplay extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer2<CounterModel, SecondModel>(
builder: (context, counterModel, secondModel, child) {
return Text('${counterModel.count}');
},
);
}
}
With this, your Text() widget will be rebuilt each time one the providers value is changed with notifyListener().
If your Text() widget doesn't need to be rebuilt with one of your providers, you can simply use Provider.of<MySecondProvider>(context, listen: false);.
Here for example:
class CounterDisplay extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<CounterModel>(
builder: (context, counterModel, child) {
MyThemeProvider myThemeProvider = Provider.of<MyThemeProvider>(context, listen: false);
return Text('${counterModel.count}', color: myThemeProvider.isDark ? Colors.white : Colors.dark);
},
);
}
}
I hope this helps!
You can absolutely nest consumers, but you need to understand if you really want them to nest.
Case 1: Nesting consumers:
class CounterDisplay extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<CounterModel>(
builder: (context, counterModel, child) {
return Consumer<SomeAnotherModel>(
builder: (anothercontext,anotherCounterModel, anotherChild) {
return Text('${counterModel.count}');
});
},
);
}
}
Please note that if consumer for CounterModel is rebuilt, everything will be rebuilt. If consumer for SomeAnotherModel is rebuild, only the part inside its builder would be rebuilt.
Instead of using consumers this way, I'd recommend them to be used as in case 2 below.
Case 2: Use consumers on the required components:
class CounterDisplay extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children:[
Consumer<CounterModel>(
builder: (context, counterModel, child) { return...}),
Consumer<CounterModel>(
builder: (anotherContext, anotherModel, anotherChild) { return...}),
]
);
}
}

How to modify a variable using async method before it use in widget build?

I have a variable named userName,which depends on databse query,so async is a must.
My older code can be concluded liks this
class IndexScreen extends StatefulWidget {
#override
_IndexScreenState createState() => _IndexScreenState();
}
//use database query function
Future<void> initUser() async{
UserTable().getUserInfo(curUserEmail).then((value)=>null);
}
//show page
class _IndexScreenState extends State<IndexScreen> {
#override
Widget build(BuildContext context) {
initUser().then((value){
final theme = Theme.of(context);
return WillPopScope(
onWillPop: () =>router.navigateTo(context, '/welcome'),
child: SafeArea(
child: Scaffold(
//The static global variable is used in Body in other files
body: Body()
),
),
);
});
}
}
It warns that miss return,I dont knwo how to amend my code.
Thanks!!
You can achive this by using the FutureBuilder widget. Please refer the code below.
class IndexScreen extends StatefulWidget {
#override
_IndexScreenState createState() => _IndexScreenState();
}
//use database query function
Future<Map> initUser() async {
final data =
await UserTable().getUserInfo(curUserEmail);
return data;
}
//show page
class _IndexScreenState extends State<IndexScreen> {
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: initUser(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
final theme = Theme.of(context);
return WillPopScope(
onWillPop: () => router.navigateTo(context, '/welcome'),
child: SafeArea(
child: Scaffold(
body: Body(),
),
),
);
} else {
// Returns empty container untill the data is loaded
Container();
}
},
);
}
}

Flutter Show Modal Bottom Sheet after build

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(),
);
}
}

ChangeNotifierProvider inside ListView in flutter

class EventTimeModel with ChangeNotifier {
update() {
notifyListeners();
}
}
class SingleEvent extends StatelessWidget {
...
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (BuildContext context2) => event.timeModel,
child: buildEventColumn());
}
}
class EventList extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: events.length,
itemBuilder: (context, index) {
Event event = events[index];
return SingleEvent(event: event)
}
}
EventList uses a ListView to display mutliple SingleEvents. SingleEvent uses a ChangeNotifierProvider to provide EventTimeModel. When scrolling up and down I got the message
Unhandled Exception: A EventTimeModel was used after being disposed.
E/flutter (10215): Once you have called dispose() on a EventTimeModel, it can no longer be used.
So I think an event was deleted because it has been outside the screen. When it should be displayed again the error was thrown. How can I fix this?
Since timeModel exists the create method can't be used here.
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider.value(
value: event.timeModel,
child: buildEventColumn());
}

How to build a PreferredSizeWidget, not a Widget, from a FutureBuilder or async code?

I know how to build a Widget from a FutureBuilder, so this is my future/async function, for example:
Future<Null> myFuture() async
{
// etc.
}
If I want to build an AppBar title, this works fine:
class MyStuff extends StatelessWidget {
#override
Widget build(BuildContext context) {
return AppBar(
title: FutureBuilder(
future: myFuture(),
builder: (context, snapshot) {
// etc...
return Text("blahblah");
}
));
}
}
Now, I want to build the AppBar's bottom which expects a PreferredSizeWidget, so this works, but is not async:
class MyStuff2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return AppBar(
bottom: PreferredSize(),
);
}
}
But how can I use this in a future/async way? this doesn't even compile:
class MyStuff3 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return AppBar(
bottom: FutureBuilder(, // is there a FutureBuilder for PreferredSizeWidgets?
future: myFuture(),
builder: (context, snapshot) {
// etc...
// I want to compute preferred size from async call result
return PreferredSize(); // pseudo-code ???
}
);
}
}
It's not gonna work since the FutureBuilder returns a Widget , so, what you can do is convert your stateless builder to a stateful one and create your own "PreferredSizeFutureBuilder"
import 'package:flutter/material.dart';
class PreferredSizeFutureBuilder extends StatefulWidget {
#override
_PreferredSizeFutureBuilderState createState() =>
_PreferredSizeFutureBuilderState();
}
class _PreferredSizeFutureBuilderState
extends State<PreferredSizeFutureBuilder> {
bool isCompleted = false;
#override
void initState() {
super.initState();
myFuture();
}
Future<void> myFuture() async {
//do something
setState(() {
isCompleted = true;
});
}
#override
Widget build(BuildContext context) {
return AppBar(
bottom: isCompleted //we check if the future has ended
? PreferredSize(
child: YourComponent(), //your component
preferredSize: Size.fromHeight(12.0), //the size you want
)
: null); //if the future is loading we don't return anything, you can add your loading widget here
}
}
Of course this means that the snapshot object is not gonna work , instead you use the isCompleted bool to check if your future is done, you can even use the ConnectionState instead of a boolean to have the "snapshot" behaviour
You can wrap FutureBuilder into a PreferredSize-Widget.
No need to create an own FutureBuilder-Widget that implements PreferredSize.
class MyStuff2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return AppBar(
bottom: PreferredSize(
child: FutureBuilder(future: _getData(), builder: (BuildContext context, AsyncSnapshot snapshot) {
if(snapshot.data != null) {
return Text(snapshot.data);
} else {
return Text('Loading...');
}
),
preferredSize: Size.fromHeight(50.0),
);
}
}