Does compute support asnc request? - flutter

I have a list of String address like:
List<String> addressStrings = [....];
I am using geocoding plugin to get the address data and marker for these address strings like:
//This is a class-level function
Future<List<MarkerData>> getMarkerDataList() async {
List<MarkerData> list = [];
addressStrings.forEach((element) async {
final result = await locationFromAddress(element);
final markerData = MarkerData(element, result.first);
list.add(markerData);
});
return list;
}
But it freezes the UI as expected. I tried to use compute to perform the operation in another isolate like:
//This is a top-level function
Future<List<MarkerData>> getMarkerDataList(List<String> addressStrings) async {
List<MarkerData> list = [];
addressStrings.forEach((element) async {
final result = await locationFromAddress(element);
final markerData = MarkerData(element, result.first);
list.add(markerData);
});
return list;
}
//This is a class-level function
Future<List<MarkerData>> getMarkerData()async{
final result = await compute(getMarkerDataList, addressStrings);
return result;
}
But it doesn't work and shows Unhandled exception in the console.
I guess final result = await locationFromAddress(element); request is the problem here. Because it do pass before that statement but doesn't this one.
So, my question is: does compute support async? If yes, what I am doing wrong here? If no, how can I do asynchronous performance intensive tasks like this efficiently without blocking the UI?

Yes, as far as I know async does support compute - here's an article that should help out:
https://medium.com/flutterdevs/flutter-performance-optimization-17c99bb31553

Related

How make async api calls inside loop and complete loop with all data from api call. Due to async I'm losing that part of data

I'm reading json List from device memory and want to perform some operations on it's components.
When I load that list I start loop where I check each item of that list.
While in loop I add each item to new List to have updated List after loop ends so I could save it on device memory.
If some conditions are true then I use future async http call to get updated data
then theoretically I update that item of the List while staying inside loop. And thus after loop ends I must have updated Json List ready to be saved on device memory.
Problem is that While I http call inside loop, the answer delays, loop ends and new Json List is being constructed and saved on memory without the component that was supposed to be updated.
Is there any way to force wait the whole loop or something else ?
Here is the code
Future<void> readStoredData() async {
try {
final prefs = await SharedPreferences.getInstance();
_rawJsonListE = prefs.getStringList('storedData');
List<String> rawJsonListNEW = [];
bool _isNeedUpdate = false;
_rawJsonListE!.forEach((item) async {
if (someCondition with item Data) {
_isNeedUpdate = true;
await makeHttpCallFutureAwaitFunction(item).then((_) {
rawJsonListNEW.add(updatedItem);
});
} else {
rawJsonListNEW.add(item);
}
});
if (_isNeedUpdate) prefs.setStringList('storedData', rawJsonListNEW);
}
notifyListeners();
} catch (error) {
print('Error : ${error}');
throw error;
}
You can separate the refreshing data part to another function.
// Just need to check _rawJsonListE is empty or not
_isNeedUpdate = _rawJsonListE.isNotEmpty();
Create a new function.
Future<List<String>> checkDataAndRefresh(List<String> _rawJsonListE) async {
List<String> rawJsonListNEW = [];
_rawJsonListE!.forEach((item) async {
if (someCondition with item Data) {
final String newString = await makeHttpCallFutureAwaitFunction(item);
rawJsonListNEW.add(newString);
} else {
rawJsonListNEW.add(item);
}
});
return rawJsonListNEW;
}
And if _isNeedUpdate is true, do work.
if (_isNeedUpdate)
final List<String> newData = await checkDataAndRefresh(_rawJsonListE);
prefs.setStringList('storedData', newData);

Most elegant way to wait for async initialization in Dart

I have a class that is responsible for all my API/Database queries. All the calls as well as the initialization of the class are async methods.
The contract I'd like to offer is that the caller has to call [initialize] as early as possible, but they don't have to await for it, and then they can call any of the API methods whenever they need later.
What I have looks roughly like this:
class MyApi {
late final ApiConnection _connection;
late final Future<void> _initialized;
void initialize(...) async {
_initialized = Future<void>(() async {
// expensive initialization that sets _connection
});
await _initialized;
}
Future<bool> someQuery(...) async {
await _initialized;
// expensive async query that uses _connection
}
Future<int> someOtherQuery(...) async {
await _initialized;
// expensive async query that uses _connection
}
}
This satisfies the nice contract I want for the caller, but in the implementation having those repeated await _initialized; lines at the start of every method feel very boilerplate-y. Is there a more elegant way to achieve the same result?
Short of using code-generation, I don't think there's a good way to automatically add boilerplate to all of your methods.
However, depending on how _connection is initialized, you perhaps instead could change:
late final ApiConnection _connection;
late final Future<void> _initialized;
to something like:
late final Future<ApiConnection> _connection = _initializeConnection(...);
and get rid of the _initialized flag. That way, your boilerplate would change from:
Future<bool> someQuery(...) async {
await _initialized;
// expensive async query that uses `_connection`
to:
Future<bool> someQuery(...) async {
var connection = await _connection;
// expensive async query that uses `connection`
This might not look like much of an improvement, but it is significantly less error-prone. With your current approach of using await _initialized;, any method that accidentally omits that could fail at runtime with a LateInitializationError when accessing _connection prematurely. Such a failure also could easily go unnoticed since the failure would depend on the order in which your methods are called. For example, if you had:
Future<bool> goodQuery() async {
await _initialized;
return _connection.doSomething();
}
Future<bool> badQuery() async {
// Oops, forgot `await _initialized;`.
return _connection.doSomething();
}
then calling
var result1 = await goodQuery();
var result2 = await badQuery();
would succeed, but
var result2 = await badQuery();
var result1 = await goodQuery();
would fail.
In contrast, if you can use var connection = await _connection; instead, then callers would be naturally forced to include that boilerplate. Any caller that accidentally omits the boilerplate and attempts to use _connection directly would fail at compilation time by trying to use a Future<ApiConnection> as an ApiConnection.

how do i force Flutter to run all the lines inside the function?

I have a this function that I need to run during initstate() in its entirety before building the widget. I have used this kind of code before in some parts of my app and it works there, but in this case, flutter jumps out before executing .then , goes to build the widget tree, and then returns back to the function but skips the remaining lines. I'm confused where I should properly put async-awaits to force it to finish the block. Also, can I ask where I can read an explanation of the proper flow of execution for flutter so that I can understand it more?
Future <bool> checkVendorStatus (buyerId) async {
var _result;
var vendorDocRef = await buyersInfoColl.doc(buyerId)
.collection("vendorsCalled")
.doc(auth.currentUser!.uid)
.get()
.then((value) async {
return await value.exists ? _result = true : _result = false;
}
);
return _result;
await is meant to interrupt the process flow until the async method has finished. then however does not interrupt the process flow (meaning the next instructions will be executed) but enables you to run code when the async method is finished.
you can write your code like this-
Future <bool> checkVendorStatus (buyerId) async {
var _result;
var vendorDocRef = await buyersInfoColl.doc(buyerId)
.collection("vendorsCalled")
.doc(auth.currentUser!.uid)
.get();
vendorDocRef.exists ? _result = true : _result = false;
return _result;
}

Flutter pagination with firestore stream

How to properly implement pagination with firestore stream on flutter (in this case flutter web) ?
my current approach with bloc which is most likely wrong is like this
function called on bloc when load next page, notice that i increased the lastPage variable of the state by 1 each time the function is called:
Stream<JobPostingState> _loadNextPage() async* {
yield state.copyWith(isLoading: true);
try {
service
.getAllDataByClassPage(state.lastPage+1)
.listen((List<Future<DataJob>> listDataJob) async {
List<DataJob?> listData = [];
await Future.forEach(listDataJob, (dynamic element) async {
DataJob data= await element;
listData.add(data);
});
bool isHasMoreData = state.listJobPostBlock.length!=listData.length;
//Update data on state here
});
} on Exception catch (e, s) {
yield StateFailure(error: e.toString());
}}
function called to get the stream data
Stream<List<Future<DataJob>>> getAllDataByClassPage(
String className, int page) {
Stream<QuerySnapshot> stream;
if (className.isNotEmpty)
stream = collection
.orderBy('timestamp', "desc")
.where('class', "==", className).limit(page*20)
.onSnapshot;
else
stream = collection.onSnapshot;
return stream.map((QuerySnapshot query) {
return query.docs.map((e) async {
return DataJob.fromMap(e.data());
}).toList();
});
}
With this approach it works as intended where the data loaded increased when i load next page and still listening to the stream, but i dont know if this is proper approach since it replace the stream could it possibly read the data twice and end up making my read count on firestore much more than without using pagination. Any advice is really appreciated, thanks.
Your approach is not very the best possible indeed, and as you scale you going to be more costly. What I would do in your shoes would be to create a global variable that represents your stream so you can manipulate it. I can't see all of your code so I am going to be as generic as possible so you can apply this to your code.
First let's declare the stream controller as a global variable that can hold the value of your stream:
StreamController<List<DocumentSnapshot>> streamController =
StreamController<List<DocumentSnapshot>>();
After that we need to change your getAllDataByClassPage function to the following:
async getAllDataByClassPage(String className) {
Stream stream = streamController.stream;
//taking out of the code your className logic
...
if(stream.isEmpty){
QuerySnapshot snap = await collection.orderBy('timestamp', "desc")
.where('class', "==", className)
.limit(20)
.onSnapshot
streamController.add(snap.docs);
}else{
DocumentSnapshot lastDoc = stream.last;
QuerySnapshot snap = await collection.orderBy('timestamp', "desc")
.where('class', "==", className)
.startAfterDocument(lastDoc)
.limit(20)
.onSnapshot;
streamController.add(snap.docs);
}
}
After that all you need to do in order to get the stream is invoke streamController.stream;
NOTE: I did not test this code but this is the general ideal of what you should try to do.
You can keep track of last document and if has more data on the list using startAfterDocument method. something like this
final data = await db
.collection(collection)
.where(field, arrayContains: value)
.limit(limit)
.startAfterDocument(lastDoc)
.get()
.then((snapshots) => {
'lastDoc': snapshots.docs[snapshots.size - 1],
'docs': snapshots.docs.map((e) => e.data()).toList(),
'hasMore': snapshots.docs.length == limit,
});

How to convert Future List instance to List String in flutter

I am saving strings list in shared procedure and fetching that like below
Future<List<String>> getList() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getStringList("key");
}
Issue is that I need to send that list to server but facing issue as I need to convert that future list to simple List
How can I do that? or is there any other way as I need to send list of ids save by user to server.
When you mark a function as async it will return a future.
If you dont wait for the future you will get 'Future instance' this means your future(data) is not available yet.
If you want to wait for the future(data) to be resolved you need to use the await keyword.
So in your case you can create a List<String> myList; then create a function to wait for the future and assign the data to the previous List.
List<String> myList;
void getStringList() async {
var tempList = await getList();
// Or use setState to assign the tempList to myList
myList = tempList;
}
Or use Then:
getList().then(List<String> myList {
// TODO: Send myList to server.
});
Hope this helpe!!
When you work with async data you should "wait" while data will not completely loaded. You can use await word in async methods like that:
foo() async {
final Future<List<dynamic>> futureList = fetchSomeFutureList();
final list = await futureList;
}
or use Future's then() method to delegate some work.
You also can wait for futures in widget tree using FutureBuilder.
Check Dart Docs page for see details.