I am wondering why my Observable does not produce any element when i am not awaiting its source which is an IAsyncEnumerable.I am using a Select over the Observable, and nothing comes out of it , my code does not execute.
public async IAsyncEnumerable<int> GetAsyncEnumerable()
{
int i=0;
while(true)
{
await Task.Delay(1000);
yield return i++;
}
}
public async Task Run()
{
IAsyncEnumerable<int> enumerable=GetAsyncEnumerable().ToObservable();
await foreach(var elem in enumerable)
{
Console.WriteLine(elem);
}
}
In the above example , the code works and is called while below there is nothing coming out.
public async Task RunObs()
{
IAsyncEnumerable<int> enumerable=GetAsyncEnumerable().ToObservable();
IObservable<int> inputObs = enumerable.ToObservable();
var outputObs = inputObs
.Select(elem=>
{
Console.WriteLine(elem); //does not execute
return elem;
});
}
Do I need to store somewhere the reference to this observable, or how should one do this?
The observable does not produce any elements, because you need to subscribe to it to it to get them.
When you await an observable you implicitly subscribe to the observable, the method will return when the observable completes or errors. If it completes you will receive the last value produced.
var outputObs = await GetAsyncEnumerable()
.ToObservable()
.Select(elem =>
{
Console.WriteLine(elem);
return elem;
})
.Take(5);
Console.WriteLine(outputObs);
will output
0, 1, 2, 3, 4, 4 on the console(each number on different lines of course)
This will also cause the observable to produce output:
GetAsyncEnumerable()
.ToObservable()
.Take(5)
.Subscribe(elem =>
{
Console.WriteLine(elem);
});
0, 1, 2, 3, 4
Because now you also subsribed to the observable.
Hope it makes sense?
Related
firebase method is working on worker thread automatically. but I have used coroutine and callbackflow to implement firebase listener code synchronously or get return from the listener.
below is my code that I explained
coroutine await with firebase for one shot
override suspend fun checkNickName(nickName: String): Results<Int> {
lateinit var result : Results<Int>
fireStore.collection("database")
.document("user")
.get()
.addOnCompleteListener { document ->
if (document.isSuccessful) {
val list = document.result.data?.get("nickNameList") as List<String>
if (list.contains(nickName))
result = Results.Exist(1)
else
result = Results.No(0)
//document.getResult().get("nickNameList")
}
else {
}
}.await()
return result
}
callbackflow with firebase listener
override fun getOwnUser(): Flow<UserEntity> = callbackFlow{
val document = fireStore.collection("database/user/userList/")
.document("test!!!!!")
val subscription = document.addSnapshotListener { snapshot,_ ->
if (snapshot!!.exists()) {
val ownUser = snapshot.toObject<UserEntity>()
if (ownUser != null) {
trySend(ownUser)
}
}
}
awaitClose { subscription.remove() }
}
so I really wonder these way is good or bad practice and its reason
Do not combine addOnCompleteListener with coroutines await(). There is no guarantee that the listener gets called before or after await(), so it is possible the code in the listener won't be called until after the whole suspend function returns. Also, one of the major reasons to use coroutines in the first place is to avoid using callbacks. So your first function should look like:
override suspend fun checkNickName(nickName: String): Results<Int> {
try {
val userList = fireStore.collection("database")
.document("user")
.get()
.await()
.get("nickNameList") as List<String>
return if (userList.contains(nickName)) Results.Exist(1) else Results.No(0)
} catch (e: Exception) {
// return a failure result here
}
}
Your use of callbackFlow looks fine, except you should add a buffer() call to the flow you're returning so you can specify how to handle backpressure. However, it's possible you will want to handle that downstream instead.
override fun getOwnUser(): Flow<UserEntity> = callbackFlow {
//...
}.buffer(/* Customize backpressure behavior here */)
I am trying to create a method to convert a List to Future<List>.
This is the method I created.
static Future<List<Product?>> fromProductRefList(
List<DocumentReference> ref) async {
List<Product> shopProductList = [];
ref.forEach((productRef) async {
final productDoc = productRef.get();
final product = await Product.fromDocument(await productDoc);
shopProductList.add(product!);
});
print('shopProductList: $shopProductList');
return shopProductList;
}
and called it in cubit,
void mapProductToState() async {
emit(state.copyWith(status: MyProductStatus.loadding));
final shop = _shopBloc.state.shop;
List<Product?> productList = [];
if (shop.shopProductRef.isNotEmpty) {
final productList = Product.fromProductRefList(shop.shopProductRef);
}
emit(state.copyWith(
shop: shop,
productList: productList,
status: MyProductStatus.loaded,
));
}
VScode shows no error but when I run the code, fromProductRefList return empty list. Seems like fromProductRefList did not wait for the Document actually get() from the database and just return.
When I add a second delay in fromProductRefList before returning the shopProductList, everything works as expected.
I have read another question on stackoverflow suggest using asyncMap() but I am not sure how to apply it in my case.
Edit:
When I add a delay, the method return without any issue. If not, it will return a empty list
static Future<List<Product?>> fromProductRefList(
List<DocumentReference> ref) async {
List<Product> shopProductList = [];
ref.forEach((productRef) async {
final productDoc = productRef.get();
final product = await Product.fromDocument(await productDoc);
shopProductList.add(product!);
});
await Future.delayed(const Duration(milliseconds: 500));
print('shopProductList: $shopProductList');
return shopProductList;
}
Thank you.
You are missing the await keyword, to actually wait for the call. It only compiles, because you also declare a new variable of name productList, shadowing the already existing one.
So this line:
final productList = Product.fromProductRefList(shop.shopProductRef);
should read:
productList = await Product.fromProductRefList(shop.shopProductRef);
In addition, this does not do what you think it does:
ref.forEach((productRef) async {
It does not wait for each call. Please use a normal for flow control structure and await the async call, not the forEach method. The forEach method will no wait for the Futures returned from the methods.
I've narrowed my problem down to the below code. I'm new to Dart, and I can't seem to figure out why the code won't move on to printing "done" before print the numbers 1-10.
import 'dart:async';
Future<bool> wait() async {
for(int i = 0; i < 10; i++){
print('$i');
}
return true;
}
Future testAsync() async {
print('starting');
wait();
print('done');
}
main(List<String> arguments){
testAsync();
}
Since there is no await keyword in front of wait(); shouldn't the program execute print('done'); and then print out the numbers? For some reason, it is waiting for wait(); to finish anyway.
This is expected behavior since Dart 2.0.0:
(Breaking) Functions marked async now run synchronously until the first await statement. Previously, they would return to the event loop once at the top of the function body before any code runs (issue 30345).
https://github.com/dart-lang/sdk/blob/stable/CHANGELOG.md
Since you async method does not actually wait on anything, the entire method is therefore executed synchronously.
You can change you code into something like this to get the behavior your want:
import 'dart:async';
Future<bool> wait() async {
await Future.microtask(() {
for (int i = 0; i < 10; i++) {
print('$i');
}
});
return true;
}
Future testAsync() async {
print('starting');
wait();
print('done');
}
main(List<String> arguments) {
testAsync();
}
I have a collection, and now I need to iterate through the data in it, and then modify one of the fields, but this modification process is time consuming and requires asynchronous, I do not know how to write this code correctly.
Here are my fake code:
Stream<int> asynchronousNaturalsTo(int n) async* {
yield calculate(n).asStream();
}
Future<int> calculate(int i) async {
// async calculate result. maybe there is some long-running operate.
return Future.value(i + 10);
}
main() async {
// 1. I have a List that contains data needs be calculated by async.
for (int i = 0; i < 3; i++) {
// 2. calculate
var list = await asynchronousNaturalsTo(i);
// 3. finally, use the final result.
print(list);
}
}
How do I write the code?
thx...
I am having problems with futures in a Flutter app.
void saveCats() async {
var cats = await convertToCats(_rawData);
await DatabaseProvider.db.addCats(cats);
}
Future<List<Cat>> convertToCats(CatList catData) async {
var cats = <Cat>[];
await catData.forEach(key, value) async {
var pos = await calculatePos();
print('This should come first');
cats.add(Cat(name: key, pos: pos);
}
}
Future<int> calculatePos() async {
return await DatabaseProvider.db.calculatePos();
}
database.dart:
Future<void> addCats(List<Cat> cats) async {
print('This should come second');
// Do adding stuff here
}
Future<int> calculatePos() async {
// Some code to calculate the position
return pos;
}
In the above code, the saveCats function is called when a button is tapped. This function converts some raw data to a list of Cat models, and adds them to the database. As part of this conversion process, it calculates the pos of the cat in a list. My problem is that I would expect that, of my two print statements, the one in the forEach loop should come before the one in the addCats database function. But instead they appear in reverse order. Where am I going wrong?
You can't async/await in List.forEach() or Map.forEach() as both of them return void.
Either use
await Future.forEach([1, 2, 3], (num) async {
await asyncMethod(num);
});
or something similar;
https://api.flutter.dev/flutter/dart-async/Future/forEach.html
forEach often doesn't do what you expect, because the provided function runs as a closure.
It's more natural when you want to iterate over a list doing something to each element to use for (or one of the more functional type methods like map).
It's not clear what type CatList is, so this is approximate, but you'll want something more like:
Future<List<Cat>> convertToCats(CatList catData) async {
var cats = <Cat>[];
for (var i = 0; i < catData.length; i++) {
var key = catData[i].key;
var pos = await calculatePos();
print('This should come first');
cats.add(Cat(name: key, pos: pos));
}
return cats;
}
or
Future<List<Cat>> convertToCats(CatList catData) async {
return catData.map(...some mapping function...).toList();
}