I have a future builder, and Im using two future variables:
Future<List<Notifications>>? notificationsOfTheDay; //get saved notifications from db
Future<List<NotificationCategory>>? notificationsByCat; // sort/modify "notificationsOfTheDay"
I'm sending notificationsByCat to Future Builder.
The issues:
While the app starts, the future builder is able to receive all the notifications and manipulate the data with some asynchronous operations.
But sometimes the Future Builder displays blank.
I'm also appending received notifications to the existing Future Variable notificationsOfTheDay, when sometimes the view does not update.
Code snippets are listed below:
Here is my initState
void initState() {
super.initState();
initPlatformState(); // Notification Listener Service
notificationsOfTheDay = initializeData(isToday);
}
initilizeData Method
Future<List<Notifications>> initializeData(bool istoday) async {
notificationsOfTheDay = initializeNotifications(istoday);
if (notifications!.length > 0) {
notificationsByCat = notificationsByCategory(notificationsOfTheDay); //sorting/manipulation of existing future
}
return notifications!;
}
notificationsByCategory
Future<List<NotificationCategory>> notificationsByCategory(
List<Notifications> notificationsFuture) async {
return await NotificationsHelper.getCategoryListFuture(
isToday ? 0 : 1, notificationsFuture);
}
When any new notifications are received, it is inserted into the db and the exising future is appended with the new notification;
setState(() {
notificationsOfTheDay =
appendElements(notificationsOfTheDay!, _currentNotification!);
notificationsByCat = notificationsByCategory(notifications!);
});
Future<List<Notifications>> appendElements(
Future<List<Notifications>> listFuture,
Notifications elementToAdd) async {
final list = await listFuture;
list.add(elementToAdd);
return list;
}
Can anyone please guide me to a solution? Tried many combinations. If I'm directly showing the data without modifying it according to category, it works fine.
Where am I going wrong?
Related
I am only familiar with HTML/CSS/JS, and basics of dart/flutter
Developer Level: Beginner
Project type & language: I am developing a notes app for myself, using flutter.
My aim is to save my note, as soon as I update the text... for which I need to use a dart Function to run on every 'text-change' event..
How do I use the Text-Changes event of Quill Editor to detect changes in the Content
THE EQUIVALENT OF THIS IN JAVASCRIPT IS GIVEN BELOW , BUT I DON'T KNOW HOW TO DO IT DART & FLUTTER.
quill.on('text-change', function(delta, oldDelta, source) {
if (source == 'api') {
console.log("An API call triggered this change.");
} else if (source == 'user') {
console.log("A user action triggered this change.");
}
});
You can listen to quill document changes stream and handle it accordingly.
_controller.document.changes.listen((event) {
print(event.item1); //Delta
print(event.item2); //Delta
print(event.item3); //ChangeSource
});
I am also run into this issue. After some hours of research, I found a solution. You habe to add a listener to your QuillController, which will be called on each editor event, like pressed keys or toolbar actions.
Use the initState() method in your State class for adding a listener.
class _TextEditorState extends State<TextEditor> {
final QuillController _controller = QuillController.basic();
#override
void initState() {
_controller.addListener(() {
print('Here I am, rock me like a hurricane!!!');
});
super.initState();
}
#override
Widget build(BuildContext context) {
// Build your Widget with QuillToolbar and QuillEditor here...
}
});
Here's how it's done:
await for (final change in _quillEditorController.changes) {
final oldDelta = change.toList()[0];
final changeDelta = change.toList()[1];
final changeSource = change.toList()[2];
if (changeSource == ChangeSource.REMOTE) {
console.log("An API call triggered this change.");
} else if (changeSource == ChangeSource.LOCAL) {
console.log("A user action triggered this change.");
}
}
Note that flutter-quill's api doesn't exactly match quill.js. And since the documentation is lacking, your best hope in understanding what's available is by digging into the code-base using your editor (eg. using "go to definition").
I'm writing a flutter app and and using the bloc library. I have a bloc and a cubit, within the state of each is a list of ids of some other documents I need to fetch from firestore. There can be some overlap and some docs are already fetched so I want to get the list of ids from both states, compare them, and then only go to firestore for ones that exist in one but no the other.
I set a new cubit for this:
class CircleRecipesCubit extends Cubit<CircleRecipesState> {
CircleRecipesCubit({
#required RecipesBloc recipesBloc,
#required CirclesCubit circlesCubit,
}) : assert(
recipesBloc != null,
circlesCubit != null,
),
_recipesBloc = recipesBloc,
_circlesCubit = circlesCubit,
super(CircleRecipesInitial());
final RecipesBloc _recipesBloc;
final CirclesCubit _circlesCubit;
StreamSubscription _recipesSubscription;
StreamSubscription _circlesSubscription;
Future<void> getCircleRecipes() async {
// get a list of recipes the user already has loaded
List<String> userRecipesIds;
_recipesSubscription = _recipesBloc.stream.listen((RecipesState event) {
if (event is RecipesLoaded) {
userRecipesIds = event.recipes.map((e) => e.id).toList();
print('*');
print(userRecipesIds);
print('*');
}
});
// get a list of recipes in the circles
List<String> circleRecipeIds;
_circlesSubscription = _circlesCubit.stream.listen((CirclesState event) {
if (event is CirclesLoaded) {
circleRecipeIds = event.circles.fold([],
(previousValue, element) => [...previousValue, ...element.recipes]);
print('|');
print(circleRecipeIds);
print('|');
// List<String> circleOnlyRecipeIds = circleRecipeIds;
// circleRecipeIds.removeWhere((e) => userRecipesIds.contains(e));
// print(circleOnlyRecipeIds);
}
});
// reduce the list of recipes to a set of only circle recipes
//TODO
//------- Try with RX dart
Stream<RecipesState> recipesStream = _recipesBloc.stream;
Stream<CirclesState> circlesStream = _circlesCubit.stream;
Rx.combineLatest2(recipesStream, circlesStream, (
RecipesState recipesState,
CirclesState circlesState,
) {
print("This doesn't print!");
print(recipesState);
print(circlesState);
if (recipesState is RecipesLoaded) {
userRecipesIds = recipesState.recipes.map((e) => e.id).toList();
print('*');
print(userRecipesIds);
print('*');
}
if (circlesState is CirclesLoaded) {
circleRecipeIds = circlesState.circles.fold([],
(previousValue, element) => [...previousValue, ...element.recipes]);
print('|');
print(circleRecipeIds);
print('|');
// List<String> circleOnlyRecipeIds = circleRecipeIds;
// circleRecipeIds.removeWhere((e) => userRecipesIds.contains(e));
// print(circleOnlyRecipeIds);
}
// fetch the set of recipes
});
}
#override
Future<void> close() {
_recipesSubscription.cancel();
_circlesSubscription.cancel();
return super.close();
}
}
So above is my cubit - it listens to the recipesBloc and the circlesCubit. The first two expressions in the getCiricleRecipes() function are only there to prove that its hooked up correctly - when it runs those print statement print the ids I want it to from both the other bloc and the other cubit.
I need the latest values from both though at the same time to compare them - so I thought rx.combinelatest2 would be good. I give it the stream from the bloc and the cubit. But the combiner function doesn't even run even though things seem 'wired up' correctly.
Any help greatly appreciated.
Make sure both streams have already emitted at least one item.
combineLatest documentation states:
The Stream will not emit until all streams have emitted at least one item.
Since the first block (where you subscribe to _circlesCubit) prints, then most likely _recipesBloc is the culprit here.
I use this package https://pub.dev/packages/stop_watch_timer in my app to keep track of the music that is playing. However if I want to change the song by changing the time on the stopwatch it says that I have to reset the timer first which I have already done. If I press the button for the second time it works. This is the code:
final StopWatchTimer _stopWatchTimer = StopWatchTimer(
mode: StopWatchMode.countUp,
onChangeRawSecond: (value) => print('onChangeRawSecond $value'),
);
void change_timer_value(int song_index) {
int new_time = TimerState(
song_index: song_index,
record_side: current_side_list(
record_sides[selectedValue], widget.album_data))
.get_start_value();
print(new_time);
_stopWatchTimer.onExecute.add(StopWatchExecute.reset);
_stopWatchTimer.setPresetSecondTime(new_time); // this is where I set new time
}
I don't know how to get around this. I have already created an issue on the creators GitHub but no response. So there's somebody who can help me here
As you mentioned in the github issue, it looks like the root cause of your issue is that the reset action takes place asynchronously, and so hasn't gone through yet by the time you try to set the time.
One way to get around this is to define your own async function which resets the stopwatch, then waits for the action to complete before returning:
Future<void> _resetTimer() {
final completer = Completer<void>();
// Create a listener that will trigger the completer when
// it detects a reset event.
void listener(StopWatchExecute event) {
if (event == StopWatchExecute.reset) {
completer.complete();
}
}
// Add the listener to the timer's execution stream, saving
// the sub for cancellation
final sub = _stopWatchTimer.execute.listen(listener);
// Send the 'reset' action
_stopWatchTimer.onExecute.add(StopWatchExecute.reset);
// Cancel the sub after the future is fulfilled.
return completer.future.whenComplete(sub.cancel);
}
Usage:
void change_timer_value(int song_index) {
int new_time = TimerState(
song_index: song_index,
record_side: current_side_list(
record_sides[selectedValue], widget.album_data))
.get_start_value();
print(new_time);
_resetTimer().then(() {
_stopWatchTimer.setPresetSecondTime(new_time);
});
}
Or (with async/await):
void change_timer_value(int song_index) async {
int new_time = TimerState(
song_index: song_index,
record_side: current_side_list(
record_sides[selectedValue], widget.album_data))
.get_start_value();
print(new_time);
await _resetTimer();
_stopWatchTimer.setPresetSecondTime(new_time);
}
Solved
I've solved this problem using a Future function and using Completer inside the function.
I am getting some Images from my server with websockets.
I have a function that emits an event with the name of the image that I need and sends an event with the Image, that is working fine, but I need my App to wait for the return of my function because my app is calling the function and trying to go to the next page without the image.
Can someone explain to me how can I make my app wait for my function to return ?
Update
I am using Stream Builder now but I can't return the data from my function.
Stream<List> getFile(List imageL) async*{
if(imageL.isNotEmpty){
List downloadedData = List();
socket.emit("PostsImagem", (imageL));
socket.on("ImagemPost", (dados) {
downloadedData = dados;
imageL = List();
});
//I can't return the downloadedData because is inside the //socket.on()
}
}
I'm still quite new to Flutter and I use flutter_bloc for state management.
In bloc I'm listening to a repository method Stream<List<UserAlert>> alerts(). In this method I listen both to .onChildAdded and .onChildRemoved events on a Firebase real time database node and update a local List<UserAlert> alerts, but I'm not succeeding in returning it once it updates so I get null value on bloc listener. NoSuchMethodError: The method 'listen' was called on null. I chose to manually manage it as I don't want to download the whole node at every change as it happen when listening to .onValue. I tried return alerts as Stream<List<UserAlert>> inside both firebase snapshot scopes but I get a type cast error.
I tried Stream.value(alerts) both inside each scope and as the very last thing in the method but still returns null.
How can I get alerts to be returned as a stream?
Thank you very much for your help.
Repository method:
Stream<List<UserAlert>> alerts() {
print('alerts() called');
Stream<List<UserAlert>> alertStream;
List<UserAlert> alerts = [];
// Option 2 return a list manually updated by two different events: More complicated but very efficient(bandwidth), lowest data consuming = Lowest Firebase bill.
// returns one event per node record at app start and only new afterwards
_alertSubscription = _databaseReference.onChildAdded.listen((Event event) {
print(' observer .childAdded: added alert is : ${event.snapshot.value}');
UserAlert userAlert = UserAlert.fromSnapshot(event.snapshot);
alerts.add(userAlert);
// alertStream = Stream.value(alerts); // alerts as Stream<List<UserAlert>>;
// return alerts;
print(
'observer .childAdded: alerts are: $alerts and total alerts are ${alerts.length}');
});
// returns one event per node removed record
_alertSubscription =
_databaseReference.onChildRemoved.listen((Event event) {
print(
'observer .childRemoved: removed alert is : ${event.snapshot.value}');
int index = alerts.indexWhere(
(userAlert) => userAlert.id.startsWith(event.snapshot.value['Id']));
print('index to remove is: $index');
alerts.removeAt(index);
// return alerts;
print(
'observer .childRemoved: alerts after removing are: $alerts and total alerts are ${alerts.length}');
// return alerts;
});
// yield* alertStream;
Stream.value(alerts);
}
Bloc listener:
Stream<AlertState> _mapLoadAlertToState() async* {
_alertSubscription?.cancel();
_alertSubscription = _alertRepository
.alerts()
.listen((alerts) => add(AlertsUpdated(alerts)));
//// (List<UserAlert> alerts) {
//// print('_mapLoadAlertToState() userAlterts are: $alerts');
//// add(AlertsUpdated(alerts));
// });
}
You can use the yield statement to return a Stream:
Stream<AlertState> _mapLoadAlertToState() async* {
Stream<List<UserAlert>> _stream = _alertRepository.alerts();
await for(List<UserAlert> alerts in _stream){
yield AlertsUpdated(alerts);
}
}
I changed approach and now I get data from firebase as I was expecting with previous approach.
I decided to split Repository method into two different methods that transform the streams into Stream<UserAlert :
Stream<UserAlert> addedAlert() {
print('addedAlert() called');
handleData(Event event, EventSink<UserAlert> sink) =>
sink.add(UserAlert.fromSnapshot(event.snapshot));
final transformer = StreamTransformer<Event, UserAlert>.fromHandlers(
handleData: handleData);
return _databaseReference.onChildAdded.transform(transformer);
}
Stream<UserAlert> removedAlert() {
print('removedAlert() called');
handleData(Event event, EventSink<UserAlert> sink) =>
sink.add(UserAlert.fromSnapshot(event.snapshot));
final transformer = StreamTransformer<Event, UserAlert>.fromHandlers(
handleData: handleData);
return _databaseReference.onChildRemoved.transform(transformer);
}
and handle the adding to and removing from List<UserAlert> inside the bloc method:
Stream<AlertState> _mapLoadAlertToState() async* {
_addedAlertStreamSubcription =
_alertRepository.addedAlert().listen((UserAlert alert) {
print('received snapshot is:$alert.'); // prints null
alerts.add(alert);
print(
'observer .childAdded: added alert is :$alert, we have ${alerts.length} active alerts, active alerts are: $alerts');
add(AlertsUpdated(alerts));
});
_removedAlertStreamSubscription =
_alertRepository.removedAlert().listen((UserAlert alert) {
int index =
alerts.indexWhere((userAlert) => userAlert.id.startsWith(alert.id));
print('index to remove is: $index');
alerts.removeAt(index);
print(
'observer .childRemoved: removed alert is :$alert, we have ${alerts.length} active alerts, active alerts are: $alerts');
add(AlertsUpdated(alerts));
});
}
AlertUpdated will then trigger:
Stream<AlertState> _mapAlertsUpdatedToState(AlertsUpdated alert) async* {
print(
'_mapAlertsUpdatedToState() called, alerts are: ${alert.alerts} ');
yield AlertLoaded(alert.alerts);
}
_mapAlertsUpdatedToState prints are showing the correct List, but prints from BlocListeneronly show up once with one value in the list.
BlocListener<AlertBloc, AlertState>(
listener: (BuildContext context, AlertState state) {
if (state is AlertLoaded) {
List<UserAlert> userAlerts = (state).alerts;
print(
'AlertListener userAlerts are: $userAlerts and total alerts are : ${userAlerts.length}');
}
}),
This is solved by making AlertState classes not extending Equatable as it would compare previous and new state and find them to be the same.
Thank you very much.