FutureBuilder - setState() or markNeedsBuild() called during build error - flutter

I have the below FutureBuilder entry.
FutureBuilder(
future: _checkConn,
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState){
case ConnectionState.none:
Navigator.pushReplacementNamed(context, NoConnView);
break;
case ConnectionState.active:
case ConnectionState.waiting:
case ConnectionState.done:
if(snapshot.data=='OK'){
initialfbget();
break;
} else {
Navigator.pushReplacementNamed(context, NoConnView);
break;
}
}
return SizedBox.expand(
child: FittedBox(
fit: BoxFit.fill,
child: SizedBox(
width: _vcontroller.value.size?.width ?? (MediaQuery.of(context).size.width),
height: _vcontroller.value.size?.height ?? (MediaQuery.of(context).size.height),
child: VideoPlayer(_vcontroller),
),
),
);
}
),
The below is the complete initstate section:
void initState() {
super.initState ();
_vcontroller = VideoPlayerController.asset("assets/testimages/sukiitestanimation.mp4")
..initialize().then((_) {
// Once the video has been loaded we play the video and set looping to true.
_vcontroller.play();
_vcontroller.setLooping(false);
// Ensure the first frame is shown after the video is initialized.
});
_checkConn = checkConn();
Firebase.initializeApp();
}
Below is the checkconn segment:
Future<String> checkConn() async {
var connresponse = await http.get(connurl).timeout(const Duration(seconds: 10));
log('connresponse is: ${connresponse.statusCode}');
if(connresponse.statusCode!=200) {
return "BAD";
} else {
return "OK";
}
}
Kept on receiving the below error.
setState() or markNeedsBuild() called during build.
Would appreciate any assistance on this.
Thanks in advance.

Actually, I don't know what is _checkConn = checkConn(); in your initState() used for, because you've declare your checkConn() at your futureBuilder.
I just can suggest you to solve your problem with these 3 option that you can pick bellow:
1. Remove your await in initState
just delete your _checkConn = checkConn();
2. Remove your setState() during your build Widget
from my experience, the error like I write below can happen when you call setState() during the building widget.
setState() or markNeedsBuild() called during build.
so, I assume that you have some setState() inside your build without any event.
3. Use if mounted
await syntax can make some error when you build it in initState(), you can use 1 of some StatefulWidget() lifecycle to build it, that is mounted.

Related

how to call setState inside FutureBuilder in Flutter

I'm trying to set State based on result received by FutureBuilder, looks like it is not possible since FutureBuild is still building, any ideas please ? error :
The following assertion was thrown building FutureBuilder<String>(dirty, state: _FutureBuilderState<String>#db9f1):
setState() or markNeedsBuild() called during build.
This StatefulBuilder widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was: StatefulBuilder
my code :
FutureBuilder<String>(
future: fetchIdPlayer(idPlayer, bouquet),
builder: (context, snapid) {
if (!snapid.hasData)
return Container(
height: mobileHeight * 0.05,
child: Center(
child: CircularProgressIndicator(),
),
);
else if (snapid.data == "Error_ID") {
setState(() {
have_ID = true;
resultName = "رقم تعريف اللاعب خاطئ";
});
}
})
You can workaround the error you are getting by scheduling the setState to be executed in the next frame and not potentially during build.
else if (snapid.data == "Error_ID") {
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
///This schedules the callback to be executed in the next frame
/// thus avoiding calling setState during build
setState(() {
have_ID = true;
resultName = "رقم تعريف اللاعب خاطئ";
});
});
...
You can just wrap the widget who will use resultName and have_ID with FutureBuilder .So there is no need to setState.you can also handle error as well .If you want to setState then use a asyn function and after result is fetched you can just call setState
You could use the property connectionState of snapid.
This should generally work as connectionState is set to ConnectionState.done whenever the future is terminated.
FutureBuilder<String>(
future: fetchIdPlayer(idPlayer, bouquet),
builder: (context, snapid) {
if (snapshot.connectionState == ConnectionState.done) {
setState((){
//...
})
}
if (!snapid.hasData)
//...
else if (snapid.data == "Error_ID") {
//...
}
})
The key bit of this error message is, "called during build". You need to wait until after build. This can be accomplished with either:
WidgetsBinding.instance?.addPostFrameCallback()
https://api.flutter.dev/flutter/widgets/WidgetsBinding-mixin.html
or
SchedulerBinding.instance?.addPostFrameCallback()
https://api.flutter.dev/flutter/scheduler/SchedulerBinding-mixin.html
Flutter prohibit this kind of situation because setting a state in side a future widget will cause to rebuild the future widget again and this will become an infinite loop. Try to separate logic from UI. Example credits goes to #pskink and #Mofidul Islam
In your state class create a wrapper
Future<String> wrapper(idPlayer, bouquet) async {
final foo = await fetchIdPlayer(idPlayer, bouquet);
//handle errors
if(foo.error){
have_ID = true;
resultName = "رقم تعريف اللاعب خاطئ";
}
return foo;
}
call the wrapper in your widget
FutureBuilder<String>(
future: wrapper(idPlayer, bouquet),
builder: (context, snapid) {
if (!snapid.hasData)
return Container(
height: mobileHeight * 0.05,
child: Center(
child: CircularProgressIndicator(),
),
);
else {
//UI to show error
}
})

How can I use Future builder with provider?

My main objective is to show a CircularProgressIndicator before I get a location address placename but I keep getting this error The getter 'placeName' was called on null..I did try to check the null value in futureBuilder but I believe my implementation is wrong. Could you please take a look ?
This is my AppData class
class AppData extends ChangeNotifier {
Address pickUpLocation;
void updatePickUpLocationAddress(Address pickUpAddress) {
pickUpLocation = pickUpAddress;
notifyListeners();
}
}
and this is the Address class
class Address {
String placeFormattedAddress;
dynamic placeName;
String placeId;
double latitude;
double longitude;
Address(
{this.placeFormattedAddress,
this.placeName,
this.placeId,
this.latitude,
this.longitude});
}
Now in my MainScreen I am using it like this but the error persisting.
#override
Widget build(BuildContext context) {
\\\
body: Stack(
\\\
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FutureBuilder (
future: Provider.of < AppData > (context)
.pickUpLocation
.placeName,
builder: (context, snapshot) {
if (snapshot.data == null) {
return CircularProgressIndicator();
} else {
return Text(
Provider.of < AppData > (context)
.pickUpLocation
.placeName,
style: TextStyle(fontSize: 12.0),
overflow: TextOverflow.ellipsis,
);
}
}),
],
),
)
There are a few things to be aware of.
Your Future... isn't really a Future. You're just evaluating a synchronous property of your AppData Object provided by your Provider... and that's really it. Therefore the FutureBuilder should immediately evaluate its expression without giving that "loading" experience (it should render the "loaded" widget after a first rebuild).
Say you're using a Provider to return the Future you're looking for (and that's ok), which is something close to your implementation: something like that is erroneous since it would "stress" your widget tree with handling two state changes (the provider changing and/or the future being completed). Furthermore, your future gets fired every time you rebuild this Widget: that's not good (usually, firing a Future multiple times means asking your backend for the same data multiple times).
To fix your situation you have to:
Let your getter properly return and handle a Future;
Initialize the call to your getter (i.e. the Future call) before you call the build method.
Here's how I'd change your provider model/class:
class AppData extends ChangeNotifier {
Address? pickUpLocation;
Future<void> updatePickUpLocationAddress() async {
// TODO: Maybe add error handling?
var pickUpLocation = await Future.delayed(Duration(seconds: 5)); // Request mock
notifyListeners();
}
}
Now, to initialize the Future you either do so in the above Provider (in the Constructor of AppData), or you have to change your Widget to be Stateful so that we can access to the initState() method, in which we can initialize the Future without worrying about multiple calls. In your (now Stateful) Widget:
var myFuture;
// ...
void initState() {
myFuture = Provider.of<AppData>(context).updatePickUpLocationAddress();
}
// ...
Widget build (BuildContext context) {
return // ...
FutureBuilder(
future: myFuture, // already initialized, won't re-initalize when build() is called
builder: (ctx, snapshot) => // ...
}
That should be it for this Provider + Future pattern, although a final consideration should be mentioned.
Take this made up example and look what happens (i.e. read the console) when you fire the tap some times: if ChildWidget1 is disposed, then myFuture will be fired again when it gets re-loaded in the tree. This happens because ChildWidget1 is entirely removed from the tree and then re-added again to it, i.e. it's disposed and re-initialized.
This is useful to remember because having a StatefulWidget doesn't mean we are "immune" from side effects like this one.
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (Provider.of < AppData > (context)
.pickUpLocation ==
null)
CircularProgressIndicator(),
if (Provider.of < AppData > (context)
.pickUpLocation !=
null)
Text(
Provider.of < AppData > (context)
.pickUpLocation
.placeName,
style: TextStyle(fontSize: 12.0),
overflow: TextOverflow.ellipsis,
),
),
)
For anyone who is facing this problem
You can easily click on Options+enter on Mac and click on "wrap with Builder"
then just pass the context to your future function
child: Builder(
builder: (context) {
return FutureBuilder(
future: futureFunctionHere(context),
builder: (context, AsyncSnapshot snapshot) {

setState() or markNeedsBuild() called during build when usnig Get.toNamed() inside FutureBuilder

Using flutter 2.x and Get package version ^4.1.2.
i have a widget like so:
class InitializationScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future:
// this is just for testing purposes
Future.delayed(const Duration(seconds: 4)).then((value) => "done"),
builder: (context, snapshot) {
if (shouldProceed(snapshot)) {
Get.toNamed("/login");
}
if (snapshot.hasError) {
// handle error case ...
}
return const Center(child: CircularProgressIndicator());
},
),
);
}
bool shouldProceed(AsyncSnapshot snapshot) =>
snapshot.hasData && snapshot.connectionState == ConnectionState.done;
}
Get.toNamed("/login"); used inside FutureBuilder leads to this error:
The following assertion was thrown building FutureBuilder(dirty, state: _FutureBuilderState#b510d):
setState() or markNeedsBuild() called during build.
I tried to check the connectionStatus (based on a SO answer) but it didn't work.
any help?
build method is intended for rendering UI. You logic is not connected to rendering at all, so even without an error it doesn't make sense to put it into build method.
It's better to convert this method to StatefulWidget and put the logic in initState, e.g.:
Future.delayed(const Duration(seconds: 4))
.then((value) => "done")
.then((_) => Get.toNamed("/login"));
try this
Future.delayed(Duration.zero, () async {
your set state
});
This error happens when the setState() is called immediately during the screen's initial build(). In order to avoid this, you can do a work around by putting the setState() within a Future.delayed, something like:
Future.delayed(Duration(milliseconds: 100), () => Get.toNamed("/login"));

Flutter: Image.memory() fails with bytes != null': is not true -- when there IS data

I'm trying to display an image based on (base64) data coming from a backend, but I keep getting the error bytes != null': is not true.
Here's my code:
class _FuncState extends State<Func> {
Uint8List userIconData;
void initState() {
super.initState();
updateUI();
}
void updateUI() async {
await getUserIconData(1, 2, 3).then((value) => userIconData = value);
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
Container(
child: CircleAvatar(
backgroundImage: Image.memory(userIconData).image, // <--- problem here
maxRadius: 20,
),
),
),
);
}
}
Helper code:
Future<Uint8List> getUserIconData(
role,
id,
session,
) async {
var url = Uri.https(kMobileAppAPIURL, kMobileAppAPIFolder);
var response = await http.post(url, body: {
'method': 'getUserProfilePic',
'a': id.toString(),
'b': role.toString(),
'c': session.toString(),
});
if (response.statusCode == 200) {
Map data = jsonDecode(response.body);
return base64Decode(data['img']);
}
return null;
}
I have stepped through the code with a debugger and I have confirmed the helper function is returning the correct series of bytes for the image.
I'd appreciate any pointers.
Further note. The error also says:
Either the assertion indicates an error in the framework itself, or we
should provide substantially more information in this error message to
help you determine and fix the underlying cause. In either case,
please report this assertion by filing a bug on GitHub
This is quite simple; if you take a look at your code you should be able to follow through this sequence of operations.
The widget is created. No action. At this point userIconData is null.
initState is called. async http call is initiated. userIconData == null
build is called. build occurs, throws error. userIconData == null
http call returns. userIconData is set. userIconData == your image
Due to not calling setState, your build function won't run again. If you did, this would happen (but you'd still have had the exception earlier).
build is called. userIconData is set. userIconData == your image
The key here is understanding that asynchronous calls (anything that returns a future and optionally uses async and await) do not return immediately, but rather at some later point, and that you can't rely on them having set what you need in the meantime. If you had previously tried doing this with an image loaded from disk and it worked, that's only because flutter does some tricks that are only possible because loading from disk is synchronous.
Here are two options for how you can write your code instead.
class _FuncState extends State<Func> {
Uint8List? userIconData;
// if you're using any data from the `func` widget, use this instead
// of initState in case the widget changes.
// You could also check the old vs new and if there has been no change
// that would need a reload, not do the reload.
#override
void didUpdateWidget(Func oldWidget) {
super.didUpdateWidget(oldWidget);
updateUI();
}
void updateUI() async {
await getUserIconData(widget.role, widget.id, widget.session).then((value){
// this ensures that a rebuild happens
setState(() => userIconData = value);
});
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Container(
// this only uses your circle avatar if the image is loaded, otherwise
// show a loading indicator.
child: userIconData != null ? CircleAvatar(
backgroundImage: Image.memory(userIconData!).image,
maxRadius: 20,
) : CircularProgressIndicator(),
),
),
);
}
}
Another way to do the same thing is to use a FutureBuilder.
class _FuncState extends State<Func> {
// using late isn't entirely safe, but we can trust
// flutter to always call didUpdateWidget before
// build so this will work.
late Future<Uint8List> userIconDataFuture;
#override
void didUpdateWidget(Func oldWidget) {
super.didUpdateWidget(oldWidget);
userIconDataFuture =
getUserIconData(widget.role, widget.id, widget.session);
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Container(
child: FutureBuilder(
future: userIconDataFuture,
builder: (BuildContext context, AsyncSnapshot<Uint8List> snapshot) {
if (snapshot.hasData) {
return CircleAvatar(
backgroundImage: Image.memory(snapshot.data!).image,
maxRadius: 20);
} else {
return CircularProgressIndicator();
}
},
),
),
),
);
}
}
Note that the loading indicator is just one option; I'd actually recommend having a hard-coded default for your avatar (i.e. a grey 'user' image) that gets switched out when the image is loaded.
Note that I've used null-safe code here as that will make this answer have better longevity, but to switch back to non-null-safe code you can just remove the extraneous ?, ! and late in the code.
The error message is pretty clear to me. userIconData is null when you pass it to the Image.memory constructor.
Either use FutureBuilder or a condition to check if userIconData is null before rendering image, and manually show a loading indicator if it is, or something along these lines. Also you'd need to actually set the state to trigger a re-render. I'd go with the former, though.

Fixing Issues with FutureBuilder

In my Flutter project, I am trying to implement a button click event by using FutureBuilder. Basically when the button clicked, it supposed to get the data and display in a table. So my button onPressed event handling is as below:
onPressed: () async{
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
var p = double.parse(loanAmount);
var r = double.parse(interestRate);
var n = int.parse(monthes);
Api api = new Api();
new FutureBuilder<List>(
future: api.calculateEmi(p, r, n),
builder: (BuildContext buildContext, AsyncSnapshot<List> snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
} else {
print( snapshot.data);
return new SingleChildScrollView(
padding: const EdgeInsets.all(8.0),
child: DataTableWidget(listOfColumns: snapshot.data.map(
(e)=>{
'Month': e['Month'],
'Principal': e['Principal'],
'InterestP': e['InterestP'],
'PrinciplaP': e['PrinciplaP'],
'RemainP': e['RemainP']
}).toList()
),
);
}
}
);
}
}
The Api call is working and the method calculateEmi is called and get data returned ( a List of Map), but the view just not updated and no table appeared at all, and I use breakpoint at the builder portion but it never go into it, where did I do wrong, can anyone help? thanks.
The FutureBuilder needs to be inserted somewhere in the flutter widget tree. Simply creating a new FutureBuilder doesn't tell flutter what to do with it.
I'm guessing you instead want to put the FutureBuilder you created somewhere in the parent widget of the onPressed method. If you need it to only show when the button is pressed you can do that with a bool that determines whether to show the FutureBuilder or not.
Ex.
Widget build(context) {
if(buttonPressed) {
return FutureBuilder(
...
);
}
else {
return Container();
}
}