flutter firestore stream mapping to another stream - flutter

I have a menus collection on firestore and I want to perform a map operation on each document and return a new stream. So, instead of the Stream<QuerySnapShop>, I wanted Stream<VendorMenuItem>
Stream<VendorMenuItem> getAllVendorMenuItems(String vendorId) async* {
var collectionReference = fs.collection('restaurants').doc('$vendorId').collection("menus").snapshots();
collectionReference.map((event) {
print("mapping");
event.docs.forEach((element) {
return VendorMenuItem.fromMap(element.data());
});
});
}
and I am calling it within a build method just to test my approach, and I got nothing printed on the console, here is how I called it
#override
Widget build(BuildContext context) {
var fs = Provider.of<FireStoreDatabaseRoute>(context);
fs.getAllVendorMenuItems("ewP3B6XWNyqjM98GYYaq").listen((event) {
print("printing final result");
print(event.name);
});
Any clues? thank you
UPDATE:
I wasn't yielding anything, however the yield keyword didnt help
Stream<VendorMenuItem> getAllVendorMenuItems(String vendorId) async* {
var collectionReference = FirebaseFirestore.instance.collection('restaurants').doc('$vendorId').collection("menus").snapshots();
yield* collectionReference.map((event) => event.docs.map((e) => VendorMenuItem.fromMap(e.data())));
}

This is how you transform stream using the method you use.
Stream<List<VendorMenuItem>> getAllVendorMenuItems(String vendorId) async* {
var collectionReference =
FirebaseFirestore.instance.collection('Files').snapshots();
yield* collectionReference.map(
(event) => event.docs
.map(
(e) => VendorMenuItem.fromMap(e.data()),
)
.toList(), //Added to list to Match the type, other wise dart will throw an error something Like MappedList is not a sub type of List
);
}
This is a second way to achieve the same task using a stream controller.
Stream<List<VendorMenuItem>> getAllVendorMenuItems2(String vendorId) {
StreamController<List<VendorMenuItem>> controller =
StreamController<List<VendorMenuItem>>();
FirebaseFirestore.instance.collection("Files").snapshots().listen((event) {
controller.add(event.docs
.map(
(e) => VendorMenuItem.fromMap(e.data()),
)
.toList() //ToList To Match type with List
);
});
return controller.stream;
}

So the reason why it didn't work was I didnt realize the map function is only a middleware and therefore the async* is not required; here is an alternative to #Taha's solution
(without the use of a stream controller)
Stream<List<VendorMenuItem>> getAllVendorMenuItems(String vendorId) {
var snapshot = fs.collection('restaurants').doc(vendorId).collection('menus').snapshots();
return snapshot.map<List<VendorMenuItem>>((event) {
return event.docs.map((e) {
return VendorMenuItem.fromMap(e.data());
}).toList();
});
}

Related

I can't list database data in Flutter

I have a problem, my database has data, but I can't list this data in the application, can you see where the problem is?
Here the database query is being implemented
#override
Stream<Either<TodoFailures, List<Todo>>> watchAll() async* {
//yield left(const InsufficientPermissions());
// users/{user ID}/notes/{todo ID}
final userDoc = await firestore.userDocument();
yield* userDoc.todoCollection
.snapshots()
.map((snapshot) => right<TodoFailures, List<Todo>>(snapshot.docs
.map((doc) => TodoModel.fromFirestore(doc).toDomain()).toList()))
.handleError((e) {
if (e is FirebaseException) {
if (e.code.contains('permission-denied') || e.code.contains("PERMISSION_DENIED")) {
return left(InsufficientPermisssons());
} else {
return left(UnexpectedFailure());
}
} else {
// ? check for the unauthenticated error
// ! log.e(e.toString()); // we can log unexpected exceptions
return left(UnexpectedFailure());
}
});
}
Below is where I capture the integrated query through the BloC
#injectable
class ObserverBloc extends Bloc<ObserverEvent, ObserverState> {
final TodoRepository todoRepository;
StreamSubscription<Either<TodoFailures, List<Todo>>>? todoStreamSubscription;
ObserverBloc({required this.todoRepository}) : super(ObserverInitial()) {
on<ObserverEvent>((event, emit) async {
emit(ObserverLoading());
await todoStreamSubscription?.cancel();
todoStreamSubscription = todoRepository
.watchAll()
.listen((failureOrTodos) => add(TodosUpdatedEvent(failureOrTodos: failureOrTodos)));
});
on<TodosUpdatedEvent>((event, emit) {
event.failureOrTodos.fold((failures) => emit(ObserverFailure(todoFailure: failures)),
(todos) => emit(ObserverSuccess(todos: todos)));
});
}
#override
Future<void> close() async {
await todoStreamSubscription?.cancel();
return super.close();
}
}
Even containing data in the database it comes empty, I need help to know where the problem is.

Listen to document changes on Firestore and create a stream of objects for StreamBuilder

I have the following function which gets a product document from Firestore, to be used with a FutureBuilder:
Future<Product> getProduct(String productId) async {
var ref = _db.collection('products').doc(productId);
var snapshot = await ref.get();
return Product.fromJson(snapshot.data() ?? {});
}
I can I achieve the same functionality, but with a StreamBuilder so that the build method will be called any time there's a change to the document?
Stream<Product> listenToProduct(String productId) {
?
};
Well, this seems to work:
Stream<Product> listenToProduct(String productId) {
return _db.collection('products')
.doc(productId)
.snapshots()
.map((snapshot) => Product.fromJson(snapshot.data()!));
}
Stream<Product> listenToProduct(String productId) {
return _db.collection('products').doc(productId).snapshots()
.listen((event) => Product.fromJson(event.data() ?? {});
}
Something similar to that.

how to create a stream in flutter that return a bool in every second

i am making a app. And i want to check my server state every minite and give user information
about the server. How do i do it. is stream good for it. Can some provide me a code for that.
just follow this guide
suppose your bool return value function is
Future<bool> isGpsOn() async {
return await Geolocator().isLocationServiceEnabled();
}
and this is create stream from bool value
Stream futureToStream(fn, defaultValue, Duration duration) async* {
var result;
while (true) {
try {
result = await fn();
}
catch (error) {
result = defaultValue;
}
finally {
yield result;
}
await Future.delayed(duration);
}
}
final gpsStatusStream = futureToStream(isGpsOn, false, Duration(seconds: 5));
gpsStatusStream.listen((enabled) {
print(enabled ? 'enabled' : 'disabled');
});
Use asyncMap
Stream<String> checkConnectionStream() async* {
yield* Stream.periodic(Duration(seconds: 1), (_) {
return //your function
}).asyncMap((event) async => await event);
}

How to convert a stream to another type without using map?

I've this code:
Stream<int> get fooStream async* {
barStream.listen((_) async* {
int baz = await getBaz();
yield baz; // Does not work
});
}
How can I return Stream<int> from another stream?
Note: If I use map to transform the stream, then I'll have to return Stream<Future<int>>, but I want to return Stream<int>. I also don't feel like using rxdart pacakge for this tiny thing.
Use asyncMap.
barStream.asyncMap((e) => getBaz())
Use await for
Stream<int> get fooStream async* {
await for (final item in barStream) {
yield await getBaz();
}
}

Is it possible to filter a List with a function that returns Future?

I have a list List<Item> list and a function Future<bool> myFilter(Item).
Is there a way to filter my list using the Future returning function myFilter()?
The idea is to be able to do something like this:
final result = list.where((item) => myFilter(item)).toList();
But this is not possible since where expects bool and not Future<bool>
Since the iteration involves async operation, you need to use a Future to perform the iteration.
final result = <Item>[];
await Future.forEach(list, (Item item) async {
if (await myFilter(item)) {
result.add(item);
}
});
You can iterate over your collection and asynchronously map your value to the nullable version of itself. In asyncMap method of Stream class you can call async methods and get an unwrapped Future value downstream.
final filteredList = await Stream.fromIterable(list).asyncMap((item) async {
if (await myFilter(item)) {
return item;
} else {
return null;
}
}).where((item) => item != null).toList()
You can try bellow:
1, Convert List => Stream:
example:
Stream.fromIterable([12, 23, 45, 40])
2, Create Future List with this function
Future<List<int>> whereAsync(Stream<int> stream) async {
List<int> results = [];
await for (var data in stream) {
bool valid = await myFilter(data);
if (valid) {
results.add(data);
}
}
return results;
}
Here's a complete solution to create a whereAsync() extension function using ideas from the accepted answer above. No need to convert to streams.
extension IterableExtension<E> on Iterable<E> {
Future<Iterable<E>> whereAsync(Future<bool> Function(E element) test) async {
final result = <E>[];
await Future.forEach(this, (E item) async {
if (await test(item)) {
result.add(item);
}
});
return result;
}
}
You can now use it in fluent-style on any iterable type. (Assume the function validate() is an async function defined elsewhere):
final validItems = await [1, 2, 3]
.map((i) => 'Test $i')
.whereAsync((s) async => await validate(s));
Try this:
final result = turnOffTime.map((item) {
if(myFilter(item)) {
return item;
}
}).toList();