Flutter_bloc get updated data from firestore without UI event - flutter

I am using flutter to exchange firestore data from few devices.
If I use StreamBuilder everything works fine, but I do not like mixing business logic with UI. I would prefer using BLOC as pattern using flutter_bloc plugin.
But flutter_bloc works in this way:
Steps:
Event ------------------------> New data BUT NO NEW UI EVENT
Async Request
Async Response
State (mapEventToState)-------> ¿How do I get the new state?
As far as I do not have "UI Event" because firestore data is being updated from another device, I can not update State.
I could use something like this on the bloc constructor:
Stream<QuerySnapshot> query;
QuedadaBloc(){
query = Firestore.instance.collection('my_collection').snapshots();
query.listen((datos){
dispatch(Fetch()); // send fictitious UI event
});
}
But I think this is not the right way.
¿Any suggestion?
Many thanks.
J. Pablo.

The recommended way while using Flutter, Bloc and Firestore is to have the repository layer provide a stream of data from Firestore which can be subscribed by the Bloc in Bloc Constructor (or any other function; see this example).
Then, based on the changes in the stream, dispatch events when you receive new data from Firestore in the stream. The Bloc can handle the triggered dispatch event to change the State of the Application in a similar way when the changes in UI trigger the state change.
class SampleBloc extends Bloc<SampleEvent, SampleState> {
final FirestoreRepo _firestoreRepo;
StreamSubscription<?> _firestoreStreamSubscription;
SampleBloc({#required FirestoreData firestoreData})
: assert(firestoreRepo != null),
_firestoreRepo = firestoreRepo;
// instead of '_mapXEventToState', the subscription can also be wired in 'SampleBloc' constructor function.
Stream<TimerState> _mapXEventToState(XEvent xEvent) async* {
// Runs a dispatch event on every data change in Firestore
_firestoreStreamSubscription = _firestoreRepo.getDataStream().listen(
(data) {
dispatch(YEvent(data: data));
},
);
}
References:
Comment 1 and Comment 2 by Felix Angelov (felangel), flutter_bloc library creator in Bloc Gitter Chat

Related

Best way to run background logic with Riverpod providers?

I have a a StreamProvider that exposes the User. I want to run logic in the background based on the provider. I don't want to consume the providers in my Widget tree. I'm interested in:
Where to initialise the logic - is main.dart appropriate?
If the logic isn't related to a ConsumerWidget, where would I access my WidgetRef?
How should I encapsulate my logic? Ie: Which classes or methods should I use?
final currentUserProvider = StreamProvider<User?>((ref){
return CurrentUserStream(); // The details don't matter
});
Below I have a couple of simplified use-cases.
Use-Case 1: Apply my userId somewhere
I want to make sure the userId is used for my analytics service
final User? user = ref.watch(currentUserProvider);
if (user != null) {
// Update analytics to track the userId
}
Use-Case 2: Manage Streams for the user
In this case, it's critical that I have a dispose method to tidy up after the user changes. This would be triggered if the user logs out / switches account.
final User? user = ref.watch(currentUserProvider);
/// Streams related to the user
List<StreamSubscription> userStreamSubscriptions;
if (user != null) {
// The details don't matter
}
/// Cancels the user Streams when the user changes
#override
void dispose() {
for (var subscription in userStreamSubscriptions) {
subscription.cancel();
}
super.dispose();
}
I don`t know can that help you and will actual for you, but anyway after little research I understand one way. For background logic I was used ConsumerStatefulWidget with his state. In state class we need declare login in initState method, because the state class have a ref field.
My example app (plus flutter_hooks, but not a lot) https://github.com/nicourrrn/flutter-state-managment

How to add new data into Future<dynamic> in dart / flutter?

I am developing a chat system in mobile application using Flutter / Dart.
I have fetched a user's message records from server by API and received result as Future<dynamic>. This result has list of chat data. If I pass it to FutureBuilder widget. It works and lists chat records in listTile. Everything is working well.
When user adds a new chat message then I post the that text message to server by API to store into database. It works and response status is 200 which means message has been added on server's database.
I have instance of newly added chat message, I want to append / add it to previously fetched Future<dynamic> result.
Kindly suggest me how can I do this? Can we update Future<dynamic> typed data? Thanks.
Future can emit only one item by design. If you want to emit multiple items, use Stream: https://api.flutter.dev/flutter/dart-async/Stream-class.html
To get to know how to generate streams have a look on this: https://dart.dev/articles/libraries/creating-streams
Most likely what you want to do is use rxdart's BehaviorSubject or dart's StreamController (they share api, so just substitute the name, except for ValueStream, which is specific to rxdart, this one will have to be replaced with just Stream):
class Api {
final _subject = BehaviorSubject<DataToDisplay>();
ValueStream<DataToDisplay> get data => _subject.stream;
void fetchData() {
final data = downloadDataFromSomewhere();
_subject.add(data);
}
}
Then just create a StreamBuilder similarly to FutureBuilder.

Flutter: BLoC emit states by listening to a stream of data

I have a flutter app, which fetches a list of products from the server (product are related to the user, so the list doesn't change that much) so I'm saving the product lists locally. I'm using moor as a local database inside the application.
when the user opens the products list page:
I get the latest product creation date.
Request data created after that date from the server async.
I open a stream from the database to load the local data.
When the the server response arrives with new data, I save it to the database, then the database stream will provide that data.
the code:
Stream<List<Product>> getAllProducts() async* {
this
.fetchProductsOnline() // request data from server
.then((value) => this.insertMultipleProducts(value)); // then save the received data locally
yield* getLocalProductsList(); // meanwhile get the local data, and listen to new changes.
}
then inside my BLoC I have to listen to that stream of data, and then emit states containing the data.
but I don't seem to find the best/proper way to do that.
what I tried:
yield the stream of data after mapping it, and wrapping its items inside the state:
Stream<ProductState> mapProductListPageOpenedEventToState() async* {
yield ProductListLoading();
inProgress(); // enters the ui into a progress state
yield* this
.productRepository
.getAllProducts()
.map((event) => ProductListLoadedSuccess(event));
await outProgress(); // exists the ui from the progress state
}
The problems I faced here are:
- the code right under the stream yield, never gets executed (idk why).
- it only work once, means if I exit that page and reopen it (trigger the load product even again), nothing would happen (no state change, no event triggered).
listen to the stream of data, and use the emit() method to emit new states.
Stream<ProductState> mapProductListPageOpenedEventToState() async* {
yield ProductListLoading();
inProgress(); // enters the ui into a progress state
this.productRepository.getAllProducts().listen((event) {
if (event.isNotEmpty) {
emit(ProductListLoadedSuccess(event));
outProgress();
}
});
}
this approach worked properly, I achieved what I wanted, but the docs say that the emit() method, should be used for test purposes only, and that states from bloc should be yield only.
I'd appreciate any other solution that satisfies my needs, and that doesn't break any rules set by the docs.

Unhandled Exception: Bad State: Stream has already been listened to

I have the following code to navigate the user to the Home Screen upon successful authentication
Future navigateToHomeScreen(
StreamedResponse value,
BuildContext context,
) async {
print('Navigating to Home Screen');
String userString = await value.stream.bytesToString();
Map<String, dynamic> logInResponseData = jsonDecode(userString)['data'];
UserManager.persistUser(logInResponseData);
Navigator.of(context).pushReplacementNamed(HomeWidget.routePath);
}
After a successful sign up or sign in the above function is called but I keep getting:
Unhandled Exception: Bad state: Stream has already been listened to.
How do I fix this?
You can't add multiple listeners to a regular stream. In your code, if your navigateToHomeScreen function is getting called multiple times, you are basically adding that many listeners to the stream. You have two ways to fix it.
Check if the stream is already having any listener or not. If it has, remove it before adding a new one.
This is a bit easier one but not the best solution. You can convert your stream to a BroadcastStream by doing : value.stream.asBroadcastStream().bytesToString(); This will convert your regular stream to broadcast stream. By doing this you can add multiple listeners to your stream by why it's not the best option becuase if your previous stream listeners are not killed, they will keep getting notified and will keep consuming your reads. If you are using a service which charge you based on read writes(like cloud firestore) then this might not be a good idea.
https://medium.com/flutter-community/flutter-stream-basics-for-beginners-eda23e44e32f Here is a link to gain better understanding of Streams in Dart and Flutter

Creating Firestore Listeners in a mobx store (flutter)

I am currently working on a flutter app using firestore and mobx. I use mobx to keep the UI up to date, and make api calls to firestore (so the data flow is firestore -> mobx store -> UI). I want to set up listeners to listen for real-time changes in a firestore collection. Ideally, I would like to set up this listener in mobx, but I'm not sure how this would work - is a mobx store the right place to listen for firestore changes? One thing I'm worried about is that there is no dispose method in the mobx store in which I can detach listeners. I am wondering if this is an acceptable way to update variables in my store (and thus the UI, indirectly), or if I need to switch to BLoC/stream model. Any general advice about this issue (i.e. the best way to listen for real-time firestore updates and propagate the changes to the UI) would be appreciated!
I am not using flutter but I guess it shouldn't be very different.
Here is an example of how I listen to a user profile changes in my app.
class UserModel {
#observable id = ''
updateDetails (userUpdate) {
// update observable properties
}
destroy () {
// Call destroy to remove listener
if (this.stopWatch) {
this.stopWatch()
}
}
init () {
// firestore onSnapshot returns a disposer, keep it on the instance
this.stopWatch = fdb.collection('users').doc(this.id).onSnapshot((doc) => {
if (doc.exists) {
this.updateMyDetails(doc.data())
}
})
}
constructor ({id}) {
// ...
this.id = id
}
}
const user = new UserModel({id: 'firestoreId')})
user.init()
// then any observer, like your UI, is listening to changes of the userModel data
//...
user.destroy() // for example when the user signs out.
Note that you could listen to changes outside of the model if you want to keep these concerns separated, instead of having this init function.
If you are wondering why I check if (doc.exists) it's because Firestore won't send you any error if the document does not exist. (like an http 404). You need to handle it yourself.