I have two drop downs, and I want to do things when they get values selected. One of those is to change the second buttondrop items based on what's selected in the first dropdown.
For example:
Dropdown1 is a list of car manufactuers
Dropdown2 is a list of their models
Dropdown1 selects mercedes
Dropdown2 gets "E Class, S Class" etc
Dropdown1 selects lexus
Dropdown2 gets "ES, LS", etc
(Eventually the second drop down will update a listview as well, but haven't gotten to that yet.)
Data wise, it works, I update the list. The problem is the UI won't update unless I do a hot reload
Currently I am just having the dropdowns fetch their data and using Future builders
Future? data1;
Future? data2;
void initState(){
super.initState();
data1 = _data1AsyncMethod();
data2 = _data2AsyncMethod();
}
_data2AsyncMethod([int? item1_id]) async{
if(item1_id == null){
item2Classes = await DefaultItems().getAllItem2Classes();
listOfItem2ClassNames = DefaultItems().returnListOfItemClassNames(item2Classes);
}
else{
// The methods below calls the DefaultItems methods which have Futures all in them.
// The getAllItems calls a network file with GET methods of future type to get data and decodes them, etc.
// They build a list of the object type, ex List<Item2>
item2Classes = await DefaultItems().getAllItem2Classes(item1_id);
listOfItem2ClassNames = DefaultItems().returnListOfItemClassNames(item2Classes);
}
}
I have this Future Builder nested in some containers and paddings
FutureBuilder{
future: data2,
builder: (context, snapshot){
if(snapshot.connectionState != done...)
// return a circle progress indictator here
else{
return CustomDropDown{
hintText: 'example hint'
dropDownType: 'name'
dropDownList: listOfItem2ClassNames
dropDownCallback: whichDropDown,
}
The onChanged in CustomDropDown passes the dropDownType and the dropDownValue
The callback
whichDropDown(String dropDownType, String dropDownValue){
if(dropDownType == 'item1'){
//so if the first dropdown was used
// some code to get item_1's id and I call the data2 method
_data2AsyncMethod(item1_id);
}
Again the data updates (listOfItem2ClassNames) BUT the UI won't update unless I hot reload. I've even called just setState without any inputs to refresh but doesn't work
So how do I get the UI to update with the data, and is my solution too convoluted in the first place? How should I solve? StreamBuilders? I was having trouble using them.
Thanks
If you do a setState in the whichDropDown function, it will rebuild the UI. Although I'm not exactly sure what you want, your question is really ambiguous.
whichDropDown(String dropDownType, String dropDownValue){
if(dropDownType == 'item1'){
//so if the first dropdown was used
// some code to get item_1's id and I call the data2 method
_data2AsyncMethod(item1_id).then((_) {
setState(() {});
});
}
}
I notice a couple things:
nothing is causing the state to update, which is what causes a rebuild. Usually this is done explicitly with a call to setState()
in whichDropdown(), you call _data2AsyncMethod(item1_id), but that is returning a new Future, not updating data2, which means your FutureBuilder has no reason to update. Future's only go from un-completed to completed once, so once the Future in the FutureBuilder has been completed, there's no reason the widget will update again.
You may want to think about redesigning this widget a bit, perhaps rather than relying on FutureBuilder, instead call setState to react to the completion of the Futures (which can be done repeatedly, as opposed to how FutureBuilder works)
I get the following error:
A value of type 'Future<int>' can't be assigned to a variable of type 'int'
It might be another type instead of int, but basically the pattern is:
A value of type 'Future<T>' can't be assigned to a variable of type 'T'
So:
What exactly is a Future?
How do I get the actual value I want to get?
What widget do I use to display my value when all I have is a Future<T>?
In case you are familiar with Task<T> or Promise<T> and the async/ await pattern, then you can skip right to the "How to use a Future with the widgets in Flutter" section.
What is a Future and how do I use it?
Well, the documentation says:
An object representing a delayed computation.
That is correct. It's also a little abstract and dry. Normally, a function returns a result. Sequentially. The function is called, runs and returns it's result. Until then, the caller waits. Some functions, especially when they access resources like hardware or network, take a little time to do so. Imagine an avatar picture being loaded from a web server, a user's data being loaded from a database or just the texts of the app in multiple languages being loaded from device memory. That might be slow.
Most applications by default have a single flow of control. When this flow is blocked, for example by waiting for a computation or resource access that takes time, the application just freezes. You may remember this as standard if you are old enough, but in today's world that would be seen as a bug. Even if something takes time, we get a little animation. A spinner, an hourglass, maybe a progress bar. But how can an application run and show an animation and yet still wait for the result? The answer is: asynchronous operations. Operations that still run while your code waits for something. Now how does the compiler know, whether it should actually stop everything and wait for a result or continue with all the background work and wait only in this instance? Well, it cannot figure that out on it's own. We have to tell it.
This is achieved through a pattern known as async and await. It's not specific to flutter or dart, it exists under the same name in many other languages. You can find the documentation for Dart here.
Since a method that takes some time cannot return immediately, it will return the promise of delivering a value when it's done.
That is called a Future. So the promise to load a number from the database would return a Future<int> while the promise to return a list of movies from an internet search might return a Future<List<Movie>>. A Future<T> is something that in the future will give you a T.
Lets try a different explanation:
A future represents the result of an asynchronous operation, and can have two states: uncompleted or completed.
Most likely, as you aren't doing this just for fun, you actually need the results of that Future<T> to progress in your application. You need to display the number from the database or the list of movies found. So you want to wait, until the result is there. This is where await comes in:
Future<List<Movie>> result = loadMoviesFromSearch(input);
// right here, you need the result. So you wait for it:
List<Movie> movies = await result;
But wait, haven't we come full circle? Aren't we waiting on the result again? Yes, indeed we are. Programs would be utterly chaotic if they did not have some resemblence of sequential flow. But the point is that using the keyword await we have told the compiler, that at this point, while we want to wait for the result, we do not want our application to just freeze. We want all the other running operations like for example animations to continue.
However, you can only use the await keyword in functions that themselves are marked as async and return a Future<T>. Because when you await something, then the function that is awaiting can no longer return their result immediately. You can only return what you have, if you have to wait for it, you have to return a promise to deliver it later.
Future<Pizza> getPizza() async {
Future<PizzaBox> delivery = orderPizza();
var pizzaBox = await delivery;
var pizza = pizzaBox.unwrap();
return pizza;
}
Our getPizza function has to wait for the pizza, so instead of returning Pizza immediately, it has to return the promise that a pizza will be there in the future. Now you can, in turn, await the getPizza function somewhere.
How to use a Future with the widgets in Flutter?
All the widgets in flutter expect real values. Not some promise of a value to come at a later time. When a button needs a text, it cannot use a promise that text will come later. It needs to display the button now, so it needs the text now.
But sometimes, all you have is a Future<T>. That is where FutureBuilder comes in. You can use it when you have a future, to display one thing while you are waiting for it (for example a progress indicator) and another thing when it's done (for example the result).
Let's take a look at our pizza example. You want to order pizza, you want a progress indicator while you wait for it, you want to see the result once it's delivered, and maybe show an error message when there is an error:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
/// ordering a pizza takes 5 seconds
/// and then gives you a pizza salami with extra cheese
Future<String> orderPizza() {
return Future<String>.delayed(
const Duration(seconds: 5),
() async => 'Pizza Salami, Extra Cheese');
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark(),
home: Scaffold(
body: Center(
child: PizzaOrder(),
),
),
);
}
}
class PizzaOrder extends StatefulWidget {
#override
_PizzaOrderState createState() => _PizzaOrderState();
}
class _PizzaOrderState extends State<PizzaOrder> {
Future<String>? delivery;
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: delivery != null
? null
: () => setState(() {
delivery = orderPizza();
}),
child: const Text('Order Pizza Now')
),
delivery == null
? const Text('No delivery scheduled')
: FutureBuilder(
future: delivery,
builder: (context, snapshot) {
if(snapshot.hasData) {
return Text('Delivery done: ${snapshot.data}');
} else if(snapshot.hasError) {
return Text('Delivery error: ${snapshot.error.toString()}');
} else {
return const CircularProgressIndicator();
}
})
]);
}
}
This is how you use a FutureBuilder to display the result of your future once you have it.
Here's a list of analogies to Dart's Future from other languages:
JS: Promise
Java: Future
Python: Future
C#: Task
Just like in other languages Future is a special type of object which allows to use async/await syntax sugar, write asynchronous code in synchronous/linear way. You return Future from an async method rather than accept a callback as a parameter and avoid the callback hell - both Futures and callbacks solve same problems (firing some code at a latter time) but in a different way.
Future<T> returning the potential value which will be done by async work
Eg:
Future<int> getValue() async {
return Future.value(5);
}
Above code is returning Future.value(5) which is of int type, but while receiving the value from method we can't use type Future<int> i.e
Future<int> value = await getValue(); // Not Allowed
// Error
A value of type 'Future<int>' can't be assigned to a variable of type 'int'
To solve above getValue() should be received under int type
int value = await getValue(); // right way as it returning the potential value.
I hope this key point will be informative, I show it in two different Async methods:
Note the following method where showLoading(), getAllCarsFromApi() and hideLoading() are inner Async methods.
If I put the await keyword before showLoading(), the Operation waits until it's done then goes to the next line but I intentionally removed the await because I need my Loading dialog be displayed simultaneously with getAllCarsFromApi() is being processed, so it means showLoading() and getAllCarsFromApi() methods are processed on different Threads. Finally hideLoading() hides the loading dialog.
Future<List<Car>> getData() async{
showLoading();
final List<Car> cars = await getAllCarsFromApi();
hideLoading();
return cars;
}
Now look at this another Async method, here the getCarByIdFromApi() method needs an id which is calculated from the getCarIdFromDatabase(), so there must be an await keyword before the first method to make the Operation wait until id is calculated and passed to the second method. So here two methods are processed one after another and in a single Thread.
Future<Car> getCar() async{
int id = await getCarIdFromDatabase();
final Car car = await getCarByIdFromApi(id);
return car;
}
A simple answer is that if a function returns its value with a delay of some time, Future is used to get its value.
Future<int> calculate({required int val1, required int val2}) async {
await Future.delayed(const Duration(seconds: 2));
return val1 + val2;
}
if we call the above function as
getTotal() async {
int result = calculate(val1: 5, val2: 5);
print(result);
}
we will get the following error:
A value of type 'Future<int>' can't be assigned to a variable of type 'int'
but if we use await before function call it will give the actual returned value from the function after a delay
getTotal() async {
int result = await calculate(val1: 5, val2: 5);
print(result);
}
the keyword async is required to use await for the Future to get returned value
I am trying to give very simple example. Suppose you have ordered something online, let it be a shirt. then you have to wait until the order is dispatched and delivered to your home. In the meanwhile you will not stop working your daily activities/work anything you do and after a day if it delivered to your home you will collect it and wear it. Now, look at the following example.
Ok, now let's make a function which handles our order delivery.(Read Comments Also)
//order function which will book our order and return our order(which is our shirt). don't focus on Order object type just focus on how this function work and you will get to know about future definitely.
Future<Order> orderSomething(){
//here our order processing and it will return our order after 24 hrs :)
await Future.delayed(const Duration(hours: 24),() => Order('data'));
}
Now
void main() {
//now here you have called orderSomething() and you dont want to wait for it to be delivered
//you are not dependent on your order to do your other activities
// so when your order arrives you will get to know
orderSomething()
wearSomething()
goingCollege()
}
Now if you are dependent on your order then you have to add await async ( i will show you where)
void main() async{
//now you're dependent on your order you want to wait for your order
await orderSomething()
wearOrderedShirt() // :)
goingCollege()
}
Now most of the times in flutter applications you will have to await for your API calls(Network), for background task for downloading/uploading, for database calls etc.
What I wanted to achieve, was to retrieve a document from firestore, and provide it down the widget tree, and be able to make changes on the data of the document itself.
show something like "No data available/selected" when there's nothing to display,
show loading-screen/widget if the document is loading,
show data in the UI when document is ready
be able to make changes to the data of the document itself (in firestore) AND reflect those changes in the UI, WITHOUT reading the document again from firestore
be able to reload the document/load another document from firestore, show a loading screen while waiting for document, then show data again
The whole purpose of this, is to avoid too many firestore read operations. I update the document data on the server (firestore), then make the same updates in the frontend (an ugly alternative would be to retrieve the document from firestore each time I make a change).
If the changes are too complex though, or if it is an operation that is rarely executed, it might just be a better idea to read the whole document again.
"Answer your own question – share your knowledge, Q&A-style" -> see my solution to this problem in my answer below
The above described functionalities are NOT POSSIBLE with:
FutureProvider: you can use this to retrieve a document from firestore and show a loading-screen while waiting, but can't make changes to it afterwards, that will also show in the UI
FutureBuilder: same as above, even worse, this can't provide data down the widget-tree
It was however possible with ChangeNotifierProvider (& ChangeNotifier), and this is how I did it:
enum DocumentDataStatus { noData, loadingData, dataAvailable }
...
class QuestionModel extends ChangeNotifier {
DocumentSnapshot _questionDoc; //the document object, as retrieved from firestore
dynamic _data; //the data of the document, think of it as Map<String, dynamic>
DocumentDataStatus _documentDataStatus;
DocumentDataStatus get status => _documentDataStatus;
//constructors
QuestionModel.example1NoInitialData() {
_documentDataStatus = DocumentDataStatus.noData; //no question selected at first
}
QuestionModel.example2WithInitialData() {
_documentDataStatus = DocumentDataStatus.loadingData; //waiting for default document or something...
//can't use async/await syntax in a dart constructor
FirebaseFirestore.instance.collection('quiz').doc('defaultQuestion').get().then((doc) {
_questionDoc = doc;
_data = _questionDoc.data();
_documentDataStatus = DocumentDataStatus.dataAvailable;
notifyListeners(); //now UI will show document data
});
}
//all kinds of getters for specific data of the firestore document
dynamic get questionText => _data['question'];
dynamic get answerText => _data['answer'];
// dynamic get ...
///if operation too complex to update in frontend (or if lazy dev), just reload question-document
Future<void> loadQuestionFromFirestore(String questionID) async {
_data = null;
_documentDataStatus = DocumentDataStatus.loadingData;
notifyListeners(); //now UI will show loading-screen while waiting for document
_questionDoc = await FirebaseFirestore.instance.collection('quiz').doc(questionID).get();
_data = _questionDoc.data();
_documentDataStatus = DocumentDataStatus.dataAvailable;
notifyListeners(); //now UI will show document data
}
///instantly update data in the UI
void updateQuestionTextInUI(String newText) {
_data['question'] = newText; //to show new data in UI (this line does nothing on the backend)
notifyListeners(); //UI will instantly update with new data
}
}
...
Widget build(BuildContext context) {
QuestionModel questionModel = context.watch<QuestionModel>();
return [
Text('No question to show'),
Loading(),
Text(questionModel.questionText),
][questionModel.status.index]; //display ONE widget of the list depending on the value of the enum
}
The code in my example is simplified for explanation. I have implemented it in a larger scale, and everything works just as expected.
i need to remove listview items on button click
local_data_source
Future<void> deleteTask(int index) async {
final box = await asyncBox;
await box.deleteAt(index);
}
tasks_repository
Future<void> deleteLocalTask(int index) async {
await tasksLocalDataSource.deleteTask(index);
}
tasks_provider
Future<void> deleteTasks(int index) async {
await tasksRepository.deleteLocalTask(index);
}
button that removes element
return GestureDetector(
onTap: () {
final delete = context
.read(tasksProvider.notifier)
.deleteTasks(tasks.length);
delete;
log('$delete');
},
You did not post enough code to actually see if this is the only mistake.
But in the code you posted, you are passing tasks.length as the index of the task to delete. While I don't know which task you want to delete, tasks.length is definetely not the index of one of your tasks, it's practically guaranteed to not be an index of your tasks at all.
Your methods all seem to deal with the async/ await pattern just fine and then in your onTap method it reads as if you never heard of Future<> before. If those methods were written by different persons, maybe contact the first one and ask for advice. If all of it was written by you, you need a break and a coffee or something.
I have a widget (called RecordsListWidget) that load a list of records from a database using sqflite.
From this widget (or a third widget) is possible to open another widget (called NewRecordFormWidget) with Navigator.pushNamed(context, NewRecordFormWidget.ROUTE);
In NewRecordFormWidget the user can enter some data, and save a new record in the database.
I would like to display the new record in RecordsListWidget after the user left the NewRecordFormWidget. What is the best approach?
I'd like to keep using sqflite, and not migrate to another library if possible.
When you pop back to RecordsListWidget from NewRecordFormWidget, you can send back a boolean as a check for if the UI is to be updated or not.
For example (loose code) -
class ClassOne {
Button(
onPressed () async {
var res = await Navigator.push(ClassTwo);
if(res)
//run query on database again and update UI
else
//do nothing
}
)
}
class ClassTwo {
Button(
onPressed () {
Navigator.pop(context, true); //send true for updating the UI, or false for not updating
}
)
}