return new futureBuilder keeps requesting to the server on screen size change - flutter

I'm building an app which has a bottomBar that request data from the server, and when I change the size of the screen it request again to the server
this is my bottomBar Widget
Widget bottomNavigationBar() {
return new BottomAppBar(
color: const Color(0xFF1E90FF),
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children:[
Container(
padding: EdgeInsets.only(left: 20),
child: futureBuilderWidget(setVersion())
),
Container(
child: Text(
DateFormat('yyyy-MM-dd hh:mm:ss').format(DateTime.now()),
style: TextStyle( fontSize: 16, color: const Color(0xffebebeb))
),
),
],
),
);
}
And this is my futureBuilder widget
Widget futureBuilderWidget(_future) {
return new FutureBuilder<dynamic>(
future: _future,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
return Text(
snapshot.error.toString(),
style: TextStyle(fontSize: 16, color: const Color(0xffebebeb)),
);
} else if (snapshot.hasData) {
return Text(snapshot.data,
style: TextStyle(fontSize: 16, color: const Color(0xffebebeb)),
);
}
}
return CircularProgressIndicator();
},
);
}
Is there anyway in which I could just ask once?

Place future inside init() state. It will run only once I hope. PS: Don't put it in setState() it will run every time your screen regenerates.

I assume that the _future contains an HTTP request. As mentioned in the FutureBuilder docs, future should be obtained earlier.
The future must have been obtained earlier, e.g. during State.initState, State.didUpdateWidget, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder. If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted.
What you can do here is either run the request on initState() and update Future<dynamic> _future with a setState() to trigger a widget rebuild, or use async package to only run the request once.
final AsyncMemoizer _memoizer = AsyncMemoizer();
Future<dynamic> _future() {
return _memoizer.runOnce(() async {
// Do the request
});
}

Related

Flutter Looking up a deactivated widget's ancestor on dialog pop

I'm having an issue trying to pop a dialog that contains a circle loader. I actually pop fine once my data is loaded, but in debug mode it's showing an exception that I can't figure out how to fix.
I have a stateful screen that on init I use the following code:
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
showLoading();
});
The method showLoading is as follows:
void showLoading() {
//let's show the loading bar
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
dialogContext = context;
return AppLoader();
},
);
}
Where AppLoader simply returns:
class AppLoader extends StatelessWidget {
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
backgroundColor: Colors.transparent,
body: Center(
child: Stack(
alignment: Alignment.center,
children: <Widget>[
SizedBox(
child: new CircularProgressIndicator(),
height: 80.0,
width: 80.0,
),
],
),
),
);
}
}
dialogContent is defined in the initial of the class as:
late BuildContext dialogcontext;
The main bulk of my code looks like this:
#override
Widget build(BuildContext context) {
return Container(
color: ColorConstant.gray100,
child: Scaffold(
backgroundColor: ColorConstant.gray100,
body: Stack(
children: <Widget>[
getMainListViewUI(),
SizedBox(
height: MediaQuery.of(context).padding.bottom,
)
],
),
),
);
}
Widget getMainListViewUI() {
return FutureBuilder<bool>(
future: getData(),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
return ListView.builder(
itemCount: listViews.length,
scrollDirection: Axis.vertical,
itemBuilder: (BuildContext context, int index) {
return listViews[index];
},
);
},
);
}
Basically, the issue that I have is that when I finish getting the data from (getData()), I use:
Navigator.pop(dialogContext);
This works great: it removes the circle loader and I can see the screen behind it, no issues, no errors. However, if I run in debug mode, when I do a hotsync, it always shows me the error:
Looking up a deactivated widget's ancestor on dialog pop
I understand that this is because of the Navigator.pop that I am doing, but I don't get it. I've defined the dialogContext, which is what I am passing to the showDialog, and that's what I am popping. I've also tried setting a scheduled navigator, but again, same issue.
Any advice please?
You can check if widget is mounted or not. This problem is likely to occur when you pass a context and have async function. Can you add this before navigation and see if problem is solved
if(!mounted) return;
Problem solved. The issue is the my getData method was being called multiple times, the first time it pops the loading widget fine, after that it would throw the error which is correct since the loading widget no longer exists.

Flutter : How to show Progress indicator until the data is fetched from the server [duplicate]

This question already has answers here:
Flutter how to user setState()
(2 answers)
Closed 8 months ago.
I want to show a progress indicator until the required data is fetched from the server. Currently what I am doing is made a function getQuotes() that will fetch the data into a variable using setState(). And the Used the FutureBuilder where its future parameter is set to getQuotes(). But this approach gives me a non-ending CircularProgressIndicator. I don't why it is happening. Is ther any problem with the combination of FutureBuilder() and setState() ? Can Some one help ?
Here is my code,
Map<String, dynamic> userInfo = {};
Future<void> getQoutes() async {
var data = await FirebaseFirestore.instance.collection('user').doc(auth.currentUser!.uid).get();
setState(() {
userInfo = data.data() as Map<String, dynamic>;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'Some Quotes',
),
backgroundColor: Colors.deepOrange,
),
body: FutureBuilder (
future: getQoutes(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
if (snapshot.hasError) {
return Text('Error : ${snapshot.error}');
}
return SingleChildScrollView(
child: Container(
height: MediaQuery.of(context).size.height,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: ListView.builder(
itemCount: 3,
scrollDirection: Axis.vertical,
itemBuilder: (context, index) {
return Card_fun(userInfo['quotes'][index]);
}
)
)
],
),
)
);
default:
return const CircularProgressIndicator();
}
}
),
);
}
Another solution would be to make your getQuotes() function returning a Future<String> instead of a Future<void> and then access the data via the snapshot instead of accessing the state.
The Flutter docs of the FutureBuilder Flutter Docs are also doing it that way in the demo. As long as you don't need the state of userInfo in other places this should be an acceptable solution and you could also remove userInfo as variable. If you want to maintain or manipulate it later you could try to put the setState({}) statement in the ConnectionState.done switch case within an if(snapshot.hasData){} block.

How to display a Cubit state modified in a Widget into another Widget?

Im developing a Shopping cart using the BLoC pattern and I got stuck trying to learn the subset Cubit. My main question is how can I display the state of a previously updated Cubit? My flow is the next...
On the Product Screen I increase/decrease the items I want to use.
To push to change the state, I click a button and send the items as a parameter to the Cubit function.
The item list gets updated and I want to get it into another widget that is outside of the Product Screen.
Here is the code:
main.dart
void main() {HttpOverrides.global = new MyHttpOverrides();
runApp(
RepositoryProvider<AuthenticationService>(
create: (context) {
return AuthService();
},
child: MultiBlocProvider(
providers: [
BlocProvider<AuthenticationBloc>(
create: (context) {
final authService = RepositoryProvider.of<AuthenticationService>(context);
return AuthenticationBloc(authService)..add(AppLoaded());
}
),
BlocProvider<CartCubit>(create: (context) => CartCubit())
],
child: MyApp(),
),
)
);
}
product_screen.dart
BlocBuilder<CartCubit, List<Item>>(
builder: (context, state) {
return Row(
children: [
_shoppingItem(0),
SizedBox(
width: SizeConfig.blockSizeHorizontal * 2,
),
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
color: Color(0xFF48AD71),
),
child: IconButton(
onPressed: () {
for (int i = 1; i <= counter; i++) {
items.add(widget.item);
print('something');
}
context.read<CartCubit>().addToList(items);
},
icon: Icon(
Icons.shopping_bag),
color: Colors.white,
),
),
],
);
},
)
cart_cubit.dart
class CartCubit extends Cubit<List<Item>> {
CartCubit() : super([]);
void addToList(List<Item> items) {
state.addAll(items);
emit(state);
print(state);
}
}
What I should add on my Cart Screen so I can get the value of the Cubit State? Also, do this should be better handled by using a bloc instead of cubit?
Edit: Based on the comment of Loren.A I removed the BlocBuilder of my ProductScreen and I added it to my CartScreen.
class _CartScreenState extends State<CartScreen> {
#override
Widget build(BuildContext context) {
SizeConfig().init(context);
return SingleChildScrollView(
child: BlocBuilder<CartCubit, List<Item>>(
builder: (context, state) {
return Column()
...
...
Align(
alignment: Alignment.topRight,
child: Text(
state.length.toString(), // this is not updating
textAlign: TextAlign.end,
style: TextStyle(
fontFamily: 'SinkinSans',
fontSize: 12.0,
fontWeight: FontWeight.w400,
color: Color(0xFFC9C9C9)),
),
),
Cubit is fine for this. You can update widgets anywhere in your app based on that list just by adding another BlocBuilder<CartCubit, List<Item>>. So just add another one to your Cart page and do what ya gotta do inside of it. It will reflect the previous changes.
Edit: Just noticed that it doesn't appear that you're actually rebuilding any widgets in your product screen so that BlocBuilder you have is not really doing anything. You can remove that and just use it where you need to reflect that value of the list. You can still fire that addToList method without being inside a BlocBuilder.

Flutter async method keeps running even after the corresponding widget is removed

I have a list of image paths and I am using the List.generate method to display images and a cross icon to remove image from list. Upload method is called on each image and when I remove the image from the list the method still keeps running until the image is uploaded. The behavior I am expecting is when I remove the image from the list the method should also stop running. I am using a future builder to display the circular progress bar and error icons while uploading an image.
What should I be doing to make sure the future method associated to the current widget also stops when I remove the widget from the list?
This is the code where I am creating a list
List.generate(
files.length,
(index) {
var image = files[index];
return Container(
height: itemSize,
width: itemSize,
child: Stack(
children: <Widget>[
Container(
getImagePreview(image, itemSize)
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
uploadHandler(image, field),
InkWell(
onTap: () => removeFileAtIndex(index, field),
child: Container(
margin: EdgeInsets.all(3),
decoration: BoxDecoration(
color: Colors.grey.withOpacity(.7),
shape: BoxShape.circle,
),
alignment: Alignment.center,
height: 22,
width: 22,
child: Icon(Icons.close, size: 18, color: Colors.white),
),
),
],
),
],
),
);
},
)
This is Upload Handler method.
Widget uploadHandler(file, field) {
return FutureBuilder(
future: upload(file),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data.statusCode == 201) {
return doneUpload();
} else {
logger.d(snapshot.error);
return error();
}
} else {
return uploading();
}
},
);
}
The lifecycle of the widget isn't attached to the async functions invoked by the widget.
You can check the mounted variable to check if the widget still mounted from your async function.

How to update a Widget inside a List

I have a list of chats, and I want to show on each chat card, if there's a new message that the user hasn't read.
The list is in a StatefulWidget, the list contains refactored cards that are also StatefulWidgets, I also made the code to work with Firestore to check if the user has read the message, but still I don't know what's happening, because it doesn't update the icon of unread messages.
The data changes in the database, but it doesn't in the chat card. If I reload the app, because the cards are rebuilt, then it does change.
Here's the chat card code:
bool hasUnreadMessages = false;
void unreadMessagesVerifier() {
setState(() {
_firestore.collection('chatRoom').document(_chatRoomID).get().then((data) async {
hasUnreadMessages = await data['hasUnreadMessages'];
});
});
}
#override
Widget build(BuildContext context) {
unreadMessagesVerifier();
return GestureDetector(
child: Stack(
children: <Widget>[
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Flexible(
child: Container(
width: double.infinity,
child: Text(
widget.lastMessage,
),
),
),
hasUnreadMessages
? Container(
margin: EdgeInsets.fromLTRB(10, 0, 5, 0),
child: CircleAvatar(
radius: 7,
backgroundColor: Colors.blue,
),
)
: SizedBox(),
],
),
),
],
),
onTap: widget.onTap,
); // ChatCard
}
If more info is needed, do let me know!
========================================================================
EDIT:
Fixed thanks to #Pedro R.
I just had to move the SetState() and check the mounted
void unreadMessagesVerifier() {
_firestore.collection('chatRoom').document(_chatRoomID).get().then((data) async {
if (mounted) {
setState(() {
hasUnreadMessages = data['hasUnreadMessages'];
});
}
});
}
I think your problem lies in the way you are calling setState.
Try calling it after the future finishes.
Like this:
void unreadMessagesVerifier() {
_firestore.collection('chatRoom').document(_chatRoomID).get().then((data) =>
data['hasUnreadMessages'].then(result){
setState((){
hasUnreadMessages = result;
});
});
}
Sorry for the formatting by the way.
Consider using StatefulBuilder class, it rebuilds the particular Widget which it wraps based upon the value getting updated
So, hasUnreadMessage will be used to update the Container(). Do something like this
StatefulBuilder(
builder: (BuildContext context, StateSetter setState){
// here you return the data based upon your bool value
return hasUnreadMessages ? Container(
margin: EdgeInsets.fromLTRB(10, 0, 5, 0),
child: CircleAvatar(
radius: 7,
backgroundColor: Colors.blue,
)
) : SizedBox();
}
)