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.
Related
I'm a new intern at this small tech company that uses appwrite as a database for developing mobile applications using flutter. The task was to check if there are duplicate registration IDs in Appwrite database and, if there are, notify users that the ID already exists and ask them to enter a different registration ID when completing their user profile. The query function for checking duplicate IDs is proving to be a challenge for me because I'm a newbie to flutter and appwrite.
It first checks whether the registryID parameter is successfully received, and returns the registration ID under the registryID column in the Appwrite document, but when printing out the result, it returns an empty map. So I believe I somehow wrote the function incorrectly.
Future<dynamic> checkDuplicateID(String registerID) async{
try {
dynamic res = await db.listDocuments(
collectionId: kycCollectionId,
queries: [
Query.equal('registryId', registerID),
]
);
} on AppwriteException catch(e) {
print(e.toString());
}
}
here is the appwrite image that contains document information and registryID row
In submit button section where the user submits her information, I used a provider package and called the checkDuplicateID method and passed the id "UKH00250238", which is repeated twice in the database.
onTap: () {
dynamic result = state.checkDuplicateID('UKH00250238');
}
If the above function is incorrect, how do I write a function in which I can pass a registerID as a parameter and check if the id is already repeated? If my implementation is incorrect, what are the other ways to check duplicate IDs in the Appwrite?
Your checkDuplicateID() function should probably return something to indicate whether there's a duplicate or not. Otherwise, the function seems fine, assuming the user has access to the data.
Your next step is probably to have some sort of UI to collect input from the user so that you can pass it into your checkDuplicateID() function. The Flutter Docs have plenty of resources you can use, like this.
If you still need help from the Appwrite size, feel free to join the Appwrite Discord server.
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.
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
I'm looking for suggestions on how to handle loading things after login, and combine data from multiple endpoints without mixing too much stuff, specifically the following two things:
1) Login flow
After I get a successful login response with userId, I need to push HomeScreen() and load some initial data, from various providers.
Example:
// home_screen.dart
initState() {
super.initState();
initializeData();
}
Future <void> initializeData() {
var authenticationProvider = Provider.of<AuthenticationProvider>(context);
var accountProvider = Provider.of<AccountProvider>(context);
var albumProvider = Provider.of<AlbumProvider>(context);
var songProvider = Provider.of<SongProvider>(context);
await accountProvider.loadAccount(authenticationProvider.getLoggedInUser().id);
await albumProvider.loadAlbums();
await songProvider.loadSongsForAlbums(albumProvider.getAlbums())
}
This works, but feels ugly?
2) Cleaner data sharing
Imagine the API like this:
api/albums (model contains info about album)
api/albumpurchases (model contains which albumId and userId)
What would be the best way to get purchased albums of the logged in user?
I can think of 3 different ways, none of which seem good:
AlbumProvider having two arrays, albums[], and purchases[], and a method getAlbumByPurchase (String purchaseId) or getPurchaseForAlbum(String albumId) which then does .where() by Id and returns the item.
Having AlbumProvider and AlbumPurchaseProvider, then using ProxyProvider to combine the two.
I'm not sure how exactly would that be implemented, an example would be very appreciated!
Adding purchase property to an Album, then manually mapping it similarly to way #1
.
I've used this so far, and it seems great in the beginning but gets very ugly very quick since your subsequent Album HTTP responses everywhere would be lacking that purchase property, so I either need to get the purchase again, or I need to get it an Album object with the purchase from AlbumProvider based on an Id in response I got.
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