ListTile not getting updated with Google Places API in flutter - flutter

I have this method which gets a suggested place through the Google Places API based on the user input.
void getSuggestion(String input) async {
var googlePlace = GooglePlace(AppConstants.APIBASE_GOOGLEMAPS_KEY);
var result = await googlePlace.queryAutocomplete.get(input);
setState(() {
_placeList = result!.predictions!;
});
}
_placeList is filled properly, but it does not get updated instantly, I have to hot reload to see the changes whenever I change the query value in my TextController:
TextField(
onSubmitted: (value) {
setState(() {
getSuggestion(value);
showDialog(
context: context,
builder: ((context) {
return Card(
child: ListTile(
title: Text(_placeList[0].description),
),
);
}));
});
},
For example, if I search for "Miami" I get the recommendation on my listTile, but if I change it to "Madrid" it still appears "Miami" and I have to reload screen to see the change.
I do not understand because I am setting the state in my method.

getSuggestion is an asynchronous function. In other words, in the following code snippet:
getSuggestion(value);
showDialog(
context: context,
...
After invoking getSuggestion, it does not wait the function to finish before showing the dialog. In other words, when the dialog is shown, maybe the previous function hasn't completed yet, so that _placeList is not updated yet.
Firstly, it is a better idea to get rid of setState within getSuggestion as it is redundant to do it twice.
Secondly, in the onSubmitted lambda, make the anonymous function async (onSubmitted: (value) async { ...), then wait for getSuggestion to finish by await getSuggestion() (do not await inside setState). At this point, _placeList is updated, and you can invoke setState now, things should rebuild properly if there are no other errors.

Related

Why is my function preventing the dialog dismiss/pop in Flutter?

I am trying to execute a function after a dialog is dismissed/popped. I read this article How to run code after showDialog is dismissed in Flutter? and tried to do it as recommended but it wouldn't work for me.
This is how I call my dialog:
Future<void> onDeleteEventData(BuildContext context) async {
final title = context.messages.settings.offline.deleteEventData;
final subTitle = context.messages.settings.offline.deleteEventDataDesc;
final res = await showDeleteDialog(context,
title: title,
subTitle: subTitle);
if (res == true){
context.read<EventDownloadTileController>().deleteEventRelatedData();
}
}
The showDeleteDialog function just calls a custom Dialog which is basically just the Flutter Dialog with some style changes.
Future<bool?> showDeleteDialog(BuildContext context,
{required String title, String? subTitle}) async {
return await showDialog(
context: context,
builder: (_) => DeleteDialog(title: title,subTitle: subTitle,)
);
}
In the dialog I press on a button and do this:
onPressed: () => Navigator.of(context).pop(true),
So looking at the first function I wait for my res which evaluates to true. At this point I thought the dialog should be popped. But it is not.
The problem is this call:
context.read().deleteEventRelatedData();
Because when I replace this call with e.g. Future.delayed(duration(seconds:5)); the dialog pops right away as expected.
This is the function:
Future<void> deleteEventRelatedData() async {
_ticketLoader.stop();
_ticketStorage.deleteScanTicketsForEvent(event.eventId);
_eventStorage.deleteEventPermissions(event.eventId);
_eventStorage.deleteEventData(event.eventId);
_ticketStorage.deleteCachedTicketsForEvent(event.eventId);
_ticketStorage.deleteCachedUnknownTicketsForEvent(event.eventId);
_ticketLoader.updateLastSync(null);
_ticketLoader.reset();
checkLocalStatus();
}
A function with some async and synchronous functions. The execution takes up to 3 seconds which is the time it takes to dismiss/pop my dialog. But I want to pop the dialog right away and let it work in the back. What could my function possibly do for this behavior?
Thanks in advance
The dialog window isn't going to disappear until the app can manage to do a rebuild. If your function call takes a while, it could be hogging the main thread until it's complete, disallowing other code (including widget code) from running.
Try wrapping your function call in a microtask so it doesn't run until the next available task window which will give the app time to clean up the dialog window:
await Future.microtask(deleteEventRelatedData);
It's also worth mentioning the body of the deleteEventRelatedData is marked as async but it never awaits anything. That means all of the synchronous calls can happen in a sequence that wasn't intended and the asynchronous calls won't get executed until a later time and in no guaranteed order.

Trying to use showDialog()/show Pop up on app startup

What I want to achieve: I want to open a pop up explaining my app when it starts.
My approach: As far as I understand it from googling the issue, I should use the showDialog() method. In its most basic form:
showDialog(
context: context,
builder: (context) {
return Text('data');
});
I tried returning actual dialogs (e.g. AlertDialog) but it doesn't change the behavior so I'm just using Text() with a string as a placeholder for now.
The problem:
No matter where I place the showDialog function, it doesn't work as intended (also see scrennshots below):
Placing it in initState: I get an error message about inherited Widgets being called before the initState is done + an explanation about dependiencies I can barely follow.
Placing it in the build method: I get an error message that setState() or markNeedsBuild() gets called while the app is already buildung widgets.
Placing it in DidChangeAppLifeCycleState(): This is actually working and opening the pop when I pause the app and then resume it. It is not opening on app startup though.
Wrapping it in WidgetsBinding.instance!.addPostFrameCallback(): An idea I picked up here: How to show a popup on app start in Flutter. Doesn't change the outcome of the error messages, neither in initState nor in build.
Any ideas?
Screenshots:
From initState:
From build method:
From DidChangeAppLifecycleState (the "succesful" variant:
Will you please try below code in your init method? I hope this may work.
Future.delayed(Duration.zero, () async {
myFunction();
});
Using WidgetsBinding.instance.addPostFrameCallback inside initState perform its inner task after the 1st frame is complete.
addPostFrameCallback Schedule a callback for the end of this frame.
Next issue arise for not having material. You can directly return AlertDialog on builder or wrap with any material widget like Material, Scaffold..
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
showDialog(
context: context,
builder: (context) {
return const AlertDialog(
content: Text('data'),
);
},
);
});
}
If you are running version<3 null safety, use WidgetsBinding.instance?.addPostFrameCallback
One of the methods with WidgetsBinding.instance!.addPostFrameCallback() works fine .
If you show a normal show dialog with the press of a button too it will produce the same result.
Here, you need to wrap the text("data") in a dialog widget such as alertDialog or simpleDialog widget as needed and it will display the dialog within the current scaffold as -
WidgetsBinding.instance!.addPostFrameCallback((_) async {
return await showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text("data"),
);
});
});
I tried adding this in the init state and the dialog pops up fine when I restart the app
Thanks a lot for your answers. I ficed the issue by rewriting with your suggestions; and it works. I tihnk the issue was that I did not have _ or anything else in my WidgetsBinding code. So I did:
WidgetsBinding.instance?.addPostFrameCallback(() {})
instead of
WidgetsBinding.instance?.addPostFrameCallback((_) {})

Box not found. Did you forget to call Hive.openBox()? - does not detect the box even though I have opened it

I am trying to open my box after getting some data on a particular page and moving to another page. However, it keeps saying that I did not open it. Why?
GestureDetector(
onTap: () async{
final data = Hive.openBox('${setTask.getAt(index)}');
setState(() {
Navigator.push(
context,
MaterialPageRoute(builder: (context) =>
Tasks(setTask.getAt(index), data)));
}
);
},
);
The next page
final opendata;
Tasks(#required this.opendata);
Also..I added a line in my Stateful Widget when the widget builds
final openBox = Hive.openBox('${widget.hiveName}');
Putting it in initState(){} and using async and await did not work either.
To fix you issue, you have to add await, i.e,
final data = await Hive.openBox('box');
instead of,
final data = Hive.openBox('box');
The problem here is, Flutter is rebuilding the state without waiting for Hive to actually open the box, and hence the error. Adding an await would tell Flutter to keep track of Hive opening the box and work accordingly (i.e, refresh state or whatever you want to do).

FLUTTER : Displaying a loading indicator while listview.builder is building

When clicking on an OPEN LIST button, the app creates a series of lists needed for future display. I found out that when the number of items in the list increases, these calculations can take a little time (2, 3 seconds). So for better UX, I would like to add something similar to a loading indicator telling the user the "lists are being prepared".
In my app, I use the package Loading Indicator : it works fine.
So I wanted to use it for this situation.
Here's what I did :
I transformed my "void" list creating functions into "Future Void".
I added the async keyword to the function plugged to my "OPEN LIST BUTTON".
But... for some reason, it never displays the loading indicator....
Here's the code (UI part) :
onMenuOuvrir: () async {
DialogBuilder(context).showLoadingIndicator(
text: 'Ouverture de la liste', color: Colors.black45);
uD.setSelectedCarnetList(
index, uD.userInfo!.carnetList![index].ref!);
await uD.getListReady();
DialogBuilder(context).hideOpenDialog();
Navigator.pushReplacementNamed(
context, EditCarnetScreen.id);
},
Here's the code (Provider / back end part) :
Future<void> getListReady() async {
await createBufferCarnetWordBank();
await createBufferCarnetList();
await createBufferGrammarList();
await createBufferLevelList();
setEditMode(false);
clearSearchList();
}
The functions "createBuffer...List" are all of type Future .
What am I doing wrong ?
I actually found out that the problem was elsewhere... not in the creation of the lists, but in the building of the "listview.builder" in the EditCarnetScreen.
So here's the question now... how can we display some kind of indicator while this task is being processed.... it seems to be "in between screens"...
Create a widget in a separate dart file :
import 'package:flutter/material.dart';
Loading(BuildContext context) {
return showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
),
);
});
}
Then use it anywhere in your code:
Loading(context)
Dismiss in whenever you want:
Navigator.of(context).pop()
You can use FutureBuilder widget for that, e.g.
FutureBuilder(future: Provider.of<//your provider>(context)getListReady(), builder: (context, snapshot) => snapshot.connectionState == ConnectionState.waiting ? Center(child: CircularProgressIndicator()) : //your widget)
If you process the calculation in the main thread, even if the indicator shows up the UI freezes and the spinning animation would be laggy.
You may process the heavy lifting tasks by Isolates. Search for Isolate or compute method of dart.
I have used the fluttertoast: ^8.0.8 package as shown below. You can set the toast length to a longer duration if needed. FutureBuilder is not useful in the case, where your widget themselves take a long time render. FutureBuilder is useful while fetching the data required for your widgets.
#override
Widget build(BuildContext context) {
Fluttertoast.showToast(
msg: 'Loading...'
);
//Code for your complex widget here
}

Flutter: Send data back to specific list item

I am trying to send data to a specific list item on the screen.
The logic is that you click on the specific card in the list it opens a second screen with an input field (see images below). You then submit your input and it changes the value of that specific card on the first screen.
What is the best way to achieve this?
You can do it like this:
Instead of only pushing to another page, await for a return
final data = await Navigator.push(context,
MaterialPageRoute(builder: (context) => InputScreen()));
setState(()
myList.add(data); //do whatever you want with the return here
});
And in your InputScreen you do this:
Navigator.of(context).pop(data);
Also, if your user press the back button of their phone, it will return null, so you will need to handle that.
You can achieve this by doing the steps below:
1) In the onTap function of the card, await the result by adding the code below:
// on tap function of your card
onTap: () async {
// navigate to the second screen and wait for input user enters
final result = await Navigator.push(context
MaterialPageRoute(builder: (context) => SecondScreen()));
// call setstate to see your changes
setState(() {
// add the input to your list
myList.add(result);
);
},
1) In the onTap function of your submit button send back the result by adding the code below:
// ontap function of your submit button
onTap: () {
// value is what the user has inputted in the text field
Navigator.pop(context, value);
},