I'm new of Flutter and Dart in general, I'm trying to do a expansive computation during the loading of the page but the loader is stuck when I try to do something like this:
body: Center(
child:FutureBuilder(
future: _lorem()
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done){
print("loader");
return Center(
child: CircularProgressIndicator(
backgroundColor: Theme.of(context).primaryColor)
);
}
[...]
Future<void> _lorem() async {
//there is not a request to service, there is a more than one filter on map and some lists. I set the for loop for example of a local computation
return Future(() {
for (int i = 0; i < 50000; i++){
print(i);
}
}
);
}
I think the easier way to implement this is using a field in your widget of type Completer, eg Completer calc. You can start your expensive computation in your widget initialization (never in your build function), and when the computation is done you complete that Completer by calling calc.complete().
In your widget's FutureBuilder you should then listen to calc's future by including future: calc.future instead of your future: _lorem().
See FutureBuilder for an example of this UI paradigm.
Solved with a Future and compute.
In the detail:
Future<List<CustomObject>> _retrieveCustomObjects() async {
SourceData data = SourceData(CustomSourceData());
return compute(getFilteredClients, data);
}
List<CustomObject> computeCustomObject(SourceData data) {
List<CustomObject> list = [];
// expensive logic on data, not only network call
return list;
}
class LoremIpsumClass {
// use where you need `List<CustomObject> value = await _retrieveFilteredClient();`
}
Related
I was Learning Async and Future Functions in Dart but I got confused because I still not get it like how print("object") is compiled before getData() function because traditionally next Line is read by compiler once the before line or Function is fully compiled/Executed . If I am making a Mistake Please correct me out , I am noob tbh
import 'package:flutter/material.dart';
class Loading extends StatefulWidget {
const Loading({super.key});
#override
State\<Loading\> createState() =\> \_LoadingState();
}
class \_LoadingState extends State\<Loading\> {
void getdata() async {
String parth = await Future.delayed(Duration(seconds: 3), () {
return 'parth';
});
print('Hey');
print(parth);
}
#override
int count = 0;
void initState() {
// TODO: implement initState
super.initState();
getdata();
print('object');
}
#override
Widget build(BuildContext context) {
// print(' Setstae vala + $count');
return Scaffold(
appBar: AppBar(
title: Text('Loading'),
),
body: ElevatedButton(
onPressed: () {
setState(() {
count++;
});
},
child: Text('$count')),
);
}
}
Your output should be like this:
object
Hey
parth
because traditionally next Line is read by compiler once the before
line or Function is fully compiled/Executed
Normaly, yes. But since you are using the async keyword in this case, it works a little differently.
What happens here:
You call getData first in your initState. In getData you have a future delayed in it, which you wait for with keywoard await. So it waits 3 seconds until you return 'parth' as a string. At the same time, however, it continues to run in your initState to return 'object'.
getdata is a async method, you need to return Future<void> to await.
Now the initState() cant be async, you can create another method to await and place it here.
void newMethod() async {
await getdata();
print('object');
}
And place newMethod() inside initState.
Or you can use .then
getdata().then((_) {
print('object');
});
My problem is with Futures, because they should be obtained before build() method executed, as the documentation states:
The future must be obtained earlier, because 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.
I know that Futures should be called in initstate() function before the build method executed, but my case is different.
I want to get data from api as a Future, but the request I am sending to the api needs some parameters that user should select inside the screen's build() method.
And I don't know what the parameter of the request will be until user selects in build() method, and I have to call the api in the build() method and use FutureBuilder there, but that makes FutureBuilder to get constantly called, and I don't want that.
basically, I don't want to call FutureBuilder indefinetely, and I can't put my Future inside initState() because the Future needs some parameters that user later selects when the screen is shown inside build() method.
inside the build method:
FutureBuilder<List<LatLng>>(
builder: (context, snapshot) {
if (snapshot.hasData) {
return PolylineLayer(
polylines: [
Polyline(
points: snapshot.data!,
strokeWidth: 4,
color: Colors.purple),
],
);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
} else {
return Container();
}
},
future: Provider.of<NavigationProvider>(context)
.getNavigationPoints(pointToGoTo!),
),
now if you look at the code, at the final lines, I am sending the parameter pointToGoTo to the function which calls the backend.
simply, I want to get rid of calling api and getting data back as a Future inside build method, I want to do it in initState or somewhere else that prevents the build methods calling backend indefinitely.
is there any way to fix this problem?
Thanks in advance.
Firstly, create future state variable and a nullable params and use it with conditional if while using FutureBuilder.
I will recommend checking Fixing a common FutureBuilder and StreamBuilder problem
Now you can follow this example. It is missing progressBar on API recall, StreamBuilder might be better option in cases like this.
class Foo extends StatefulWidget {
const Foo({super.key});
#override
State<Foo> createState() => _FooState();
}
class _FooState extends State<Foo> {
int? params;
Future<int> fetch(int? data) async {
await Future.delayed(Duration(seconds: 1));
return (params ?? 0) * 2;
}
late Future<int> future = fetch(params);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
DropdownButton<int?>(
value: params,
items: List.generate(
12,
(index) => DropdownMenuItem(
value: index,
child: Text("$index"),
)).toList(),
onChanged: (value) {
future =
fetch(params); // this will only call api with update data
setState(() {
params = value;
});
},
),
if (params != null)
FutureBuilder<int>(
future: future,
builder: (context, snapshot) {
if (snapshot.hasData) return Text("${snapshot.data}");
return CircularProgressIndicator();
},
)
],
),
);
}
}
class Testing extends StatefulWidget {
const Testing({super.key});
#override
State<Testing> createState() => _TestingState();
}
class _TestingState extends State<Testing> {
bool isFetched = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Consumer<SomethingProvider>(
builder: (context, prov, child) {
if (!isFetched) {
prov.getData("a", "b");
Future.delayed(const Duration(milliseconds: 200), () {
isFetched = true;
});
}
if (prov.newData.isNotEmpty) {
return Column(
// make widget tree from here
);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
),
);
}
}
class SomethingProvider extends ChangeNotifier {
List newData = [];
Future getData(param1, param2) async {
newData = ["testingdata"];
}
}
I wrote a code like this:
StreamBuilder(
stream: _firestore.collection("Products").where("Name", isGreaterThanOrEqualTo: SearchText.toLowerCase()).snapshots(),
builder: (BuildContext context, snapshot) {
if (snapshot.data == null) {
return const Text("No data");
}
return ListView.builder(
itemCount: snapshot.data.docs.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
title: Text(snapshot.data.docs[index].data()["Name"]),
),
);
},
);
}
),
I want to shrink the data from Firestore with .toLowerCase() or otherwise. In order to make a search system, I need to shrink the incoming data. How can I do that?
Thanks for help.
I don't understand what you mean by shrink. You mentioned toLowerCase() so this is what I think the problem is
You have a stream of product names from firestore and you want to be able to make them searchable. The user search query text might be lowercase so you want to run your search on the products from firestore(lowercased)
One way to do this is to modify the stream of products that you are getting from your firestore . You can run this on dartpad.dev/
Here is a simple example with a fake list of products. I have illustrated how to use something called a streamTransformer
// A mock list of products
final List<String> productList = [
"Airpods",
"Wallet",
"Glasses",
"Gatorade",
"Medicine"
];
// A stream that exposes the product list
Stream<String> productStream() async* {
for(var product in productList){
yield product;
}
}
void main() {
// Use a stream transformer to transform or modify the stream
StreamTransformer<String, dynamic> lowerCaser = StreamTransformer.fromHandlers(handleData: (data,sink)=> sink.add(data.toString().toLowerCase()));
// Transform the stream with the .transform function
productStream().transform(lowerCaser).listen(
(product)=>print(product)
);
}
I do not want to use accesstoken or refreshtoken but it is not working. Does anyone help me to handle this situation? Here is my future =>
Future<bool> autoLogin() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var user = prefs.getString('username');
var pwd = prefs.getString('password');
debugPrint(user.toString());
debugPrint(pwd.toString());
UserResponse account = await LoginApi().login(user, pwd);
// debugPrint(account.toString());
bool acc = prefs.containsKey('userInfo');
return acc;
}
This is my usage part =>
home: FutureBuilder<bool>(
future: autoLogin(),
builder: (context, snapshot){
if(snapshot.data == null){
Future.delayed(Duration(milliseconds: 1000), () {
setState(() {
snapshot.data == false;
});
});
return LoadingScreen();
}
return snapshot.data == true ? MainWidget() : LoginScreen();
},
),
When i first time to try login it stays on loading screen, snapshot.data stays null. It does not change.
Create an instance at widget that is int ctr=0;
Then you can change there like this:
home: FutureBuilder<bool>(
future: autoLogin(),
builder: (context, snapshot){
if(snapshot.data == null) {
ctr++;
if(ctr>1) {
return LoginScreen();
}
else {
return LoadingScreen();
}
}
return snapshot.data == true ? MainWidget() : LoginScreen();
Your Future.delayed() is not being awaited, so the time delay is effectively a no-op except to push that setState later.
When the layers start to be complicated with a series of async operations, it's time to look at something like RiverPod (which I prefer over Provider and BLoC) to give you wrappers around those async operations, which properly cascade the dependencies in a readable format.
I am making an bungalow reservation system with spring rest back end and flutter front end.
In this I want to get a list of bungalows.
So I decided to make a method to get the list of bungalows in a method using HttpService class that I made to handle the rest end points, That method is getBungalows() method.
Then I called this method by overriding initstate().
But the problem is that before my initstate() is completed. my build method starts.
To prove this I printed two lines 'print' and 'print build' as I thought I get 'print build' first. what am I doing wrong here. Please help.
Method to retrieve data from rest back end
When this happened I first checked this method but this works fine and return the desired result.
Future<List<Bungalow>> getBungalows() async {
Uri uri = Uri.parse('$url/bungalows/');
http.Response response = await http.get(uri);
if (response.statusCode == 200) {
List<Bungalow> bungalows = List<Bungalow>.from(
json.decode(response.body).map((x) => Bungalow.fromJson(x)));
// print(bungalows.first.address + 'asafafasfafdfgfgarfgargafvfrvaerg');
return bungalows;
} else {
throw 'Unable to retrieve data';
}
}
Code of the HomeScreen
class _HomeScreenState extends State<HomeScreen> {
HttpService httpService = HttpService();
late List<Bungalow> bungalows;
bool isLoggedIn = false;
User? user;
void getBungalows() async {
bungalows = await httpService.getBungalows();
print('done');
}
#override
Widget build(BuildContext context) {
if (widget.user != null) {
isLoggedIn = true;
user = widget.user;
}
print('done build');
return Scaffold(
backgroundColor: Colors.white,
body: Column(
children: [
Text(isLoggedIn ? user!.userDetail.username : 'No login'),
// TextButton(
// onPressed: () {
// setState(() {
// getBungalows();
// print(bungalows.first.address);
// });
// },
// child: Text('click'))
],
),
);
}
#override
void initState() {
getBungalows();
}
}
Console Output
I/flutter (22248): done build
I/flutter (22248): done
It is behaving correctly, initState function is not async and method getBungalows() is called in parallel.
You should either use setState in getBungalows, or add a listener, or use the then keyword, or use StreamBuilder.
Check this: https://stackoverflow.com/a/54647682/305135