How to implement a communication chain in Flutter app with non-Flutter component - flutter

I'm trying to implement a communication chain in my app.
The app has at least to layer:
core, which is responsible for network communications. It's implemented as a Dart library
UI, which is responsible for communication with user. It's implemented as a Flutter app.
The core has a piece that handles invitations. Communication part is asynchronous and works in a way:
receive a request
handle request
send a response
void _handleMemberInviteRequest(AtNotification notification) async {
final sender = AtSignMember(atSign: notification.from);
if (await onMemberInvitation(sender)) {
send(notification.from, CommunicationVerbs.memberInviteRespond.name,
"accept");
} else {
send(notification.from, CommunicationVerbs.memberInviteRespond.name,
'reject');
}
}
onMemberInvitation is an event handler that in my understanding should be implemented in Flutter app. My problem is that I need user to accept an invitation. The whole chain of actions I see:
Request is received (core) -> onMemberInvitation is invoked (core) -> Confirmation dialog pops up (Flutter app) -> Response is returned by onMemberInvitation (core) -> Response is sent (core).
What I can't figure out is how to make Flutter to pop up the confirmation and answer with the result. I use BLoC patter for state management. So I though of having a separate Cubit that would emit a state that would be listened by a BlocListener on a top of application and pop up the dialog.
class Confirmation extends Cubit {
void askForConfirmation(sender) {
emit(ConfirmationState("InvitationConfirmation"));
}
void gotConfirmation(bool confirmation) {
emit(ConfirmationResponseState(confirmation));
}
}
and in app initialization implement an onMemberInvitation handler:
Future<bool> onMemberInvitation(sender) async {
askForConfirmation(sender);
await for (/* here somehow wait for `ConfirmationResponseState`*/) {
return confirmation;
}
}
But then I can't realise how do I wait for the response in onMemberInvitation handler.
Any ideas? Can BLoC be utilised here as well? Or because it's outside of Flutter app some custom streams have to be implemented? Or there is another way?

What you need is an async onMemberInvitation function that you can finish from outside the scope of the function itself.
You can achieve this using a Completer. This enables you to emit the result of the confirmation from anywhere while pausing the execution of onMemberInvitation until the result arrived. Check the sample below.
import 'dart:async';
Completer completer = new Completer<bool>();
void main() async {
String sender = 'test';
completer = new Completer();
if (await onMemberInvitation(sender)) {
print("accept");
} else {
print('reject');
}
}
Future<bool> onMemberInvitation(String sender) async {
askForConfirmation(sender);
print('UI event emitted');
return await completer.future;
}
void askForConfirmation(String sender) async {
// TODO: emit the state to the UI here
await Future.delayed(Duration(seconds: 3));
//TODO: call this when you get the confirmation event
gotConfirmation(true);
}
void gotConfirmation(bool confirmation) {
completer.complete(confirmation);
}

Related

Cannot navigate to a specific page when tapping a notification (when app is in background)

I am developing an app in flutter. I used awesome_notification package to show notifications. In awesome_notification, onActionReceivedMethod works fine when an app is in the foreground but does not work when an app is in the background. How to handle this?
#pragma("vm:entry-point")
static Future <void> onActionReceivedMethod(ReceivedAction receivedAction) async {
// code to navigate some page
} // not calling when user tap notification
Also, onMessageOpenedApp function is not triggered when tapping the notification. How to solve this? Kindly help me to resolve this.
FirebaseMessaging.onMessageOpenedApp.listen((message) async {
// code to navigate some page
}); // not calling when user tap notification
I encountered the same problem with local_notification, to pass an information from the vm entry point to the app.
I solved it by doing an isolate sent / receive like this:
//needed for isolate
import 'dart:isolate';
import 'dart:ui';
//outside main
const String channel = 'channel_key';
#pragma('vm:entry-point')
void onReceiveBackgroundResponse(NotificationResponse notificationResponse) async {
final sendPort = IsolateNameServer.lookupPortByName(channel);
sendPort?.send(notificationResponse.actionId);
}
...
//inside main
void listenNotification() {
final receivePort = ReceivePort();
IsolateNameServer.registerPortWithName(receivePort.sendPort, channel);
receivePort.asBroadcastStream().listen((event) {
print(event);
});
}

'emit was called after an event handler completed normally' issue inside a periodic Timer

Im using Flutter and flutter_bloc to make an app where it periodically sends an API request to my server and requests data.
It was working perfectly at first before i implemented the periodic functionality using Timer, this is my bloc file:
class HomeBeehivesBloc extends Bloc<HomeBeehivesEvent, HomeBeehivesState> {
HomeBeehivesBloc() : super(BeehivesInitial()) {
on<LoadBeehives>((event, emit) => _onBeehivesLoaded(emit));
}
Future<void> _onBeehivesLoaded(Emitter<HomeBeehivesState> emit) async {
emit(BeehivesLoading());
final repository = BeehivesRepository();
const duration = Duration(seconds: 5);
Timer.periodic(duration, (timer) async {
try {
await repository.getHomeBeehives().then((beehives) async {
emit(BeehivesLoadedSuccessfully(beehives: beehives));
});
} catch (exception) {
log(exception.toString());
}
});
}
}
But im getting this exception:
'package:bloc/src/emitter.dart': Failed assertion: line 114 pos 7: '!_isCompleted':
emit was called after an event handler completed normally.
This is usually due to an unawaited future in an event handler.
Please make sure to await all asynchronous operations with event handlers
and use emit.isDone after asynchronous operations before calling emit() to
ensure the event handler has not completed.
**BAD**
on<Event>((event, emit) {
future.whenComplete(() => emit(...));
});
**GOOD**
on<Event>((event, emit) async {
await future.whenComplete(() => emit(...));
});
Ive tried searching everywhere for a solution, but honestly this is the first time i used the new version of the bloc package (never used emit before), and i would like some suggestion as to how to solve this issue.
Side question: Is it a good idea to implement the periodic timer there ? because i have seen some people implement it within the frontend (Stateful widget for example), i would also love any suggestions about this.
Thank you very much!
Your code does not wait for the callback inside Timer.periodic to complete - the _onBeehivesLoaded method finishes executing, hence when the callback tries to emit a new state (BeehivesLoadedSuccessfully), you get this error.
To resolve this, instead of emitting a new state inside the callback, you should add a new event to the BLoC and handle it later as any other BLoC event.
First of all, create a new event, like HomeBeehivesLoaded:
class HomeBeehivesLoaded extends HomeBeehivesEvent {
final List<Beehive> beehives; // <-- Not sure if Beehive is the right type, just an assumption
const HomeBeehivesLoaded(this.beehives);
}
Register a new event handler in your BLoC that will update the state based on the loaded behives:
class HomeBeehivesBloc extends Bloc<HomeBeehivesEvent, HomeBeehivesState> {
HomeBeehivesBloc() : super(BeehivesInitial()) {
<...>
on<HomeBeehivesLoaded>(_onHomeBeehivesLoaded);
}
void _onHomeBeehivesLoaded(HomeBeehivesLoaded event, Emitter<HomeBeehivesState> emit) {
emit(BeehivesLoadedSuccessfully(beehives: event.beehives));
}
<...>
}
Inside the Timer.periodic callback, add this event once you get the response from the repository instead of emitting a state:
class HomeBeehivesBloc extends Bloc<HomeBeehivesEvent, HomeBeehivesState> {
<...>
Future<void> _onBeehivesLoaded(Emitter<HomeBeehivesState> emit) async {
<...>
Timer.periodic(duration, (timer) async {
try {
await repository.getHomeBeehives().then((beehives) async {
add(HomeBeehivesLoaded(beehives: beehives));
});
} catch (exception) {
log(exception.toString());
}
});
}
}

What is the difference between "return <Stream>" and "yield* <Stream>" in flutter?

I noticed some strange behaviour of streams when working with Streams in Flutter.
Setup
An EventChannel provides a stream of events.
In my Cubit I listen on that stream and cancel the StreamSubscription in the close method of the Cubit.
The next screen also uses the same EventChannel to listen for events.
When I enter the second screen, the onCancel method in Android was called twice and therefore no events where passed through to the second Cubit.
The Scanner is a singleton, so both Cubits use the same instance.
Function for the Stream
class Scanner {
final eventChannel = EventChannel("events");
Stream<ScanEvent> getScanEvent() {
return _scanEvents.receiveBroadcastStream().map((event) => ScanEvent.fromJson(jsonDecode(event)));
}
}
Code in the Cubits
Scanner scanner = get<Scanner>();
Future<void> listenForScan() async {
_streamSubscription = _scanner.getScanEvent().listen((event) => submitSerialText(event.scanData));
}
#override
Future<void> close() {
_streamSubscription?.cancel();
return super.close();
}
Fix
When I use async* with yield* like so it works:
Fixed Function for the Stream
class Scanner {
final eventChannel = EventChannel("events");
Stream<ScanEvent> getScanEvent() async* {
yield* _scanEvents.receiveBroadcastStream().map((event) => ScanEvent.fromJson(jsonDecode(event)));
}
}
Question
Why is the stream in the first approach canceled twice?

Flutter Riverpod: Refresh Page Offscreen using State Notifier

I'm using StateNotifier and Riverpod.
I have a notification page, which contains a list of notification. When a notification arrives, I trigger a refresh for notification. However, when I navigate to notification page, it still using the old (cached?) list.
How do I refresh a page offscreen?
Foreground Message
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
_localNotification.showLocalNotification(message);
ProviderContainer().read(notificationProvider).getNotification();
});
Background Message
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
final LocalNotification localNotification = LocalNotification();
await localNotification.init();
localNotification.showLocalNotification(message);
ProviderContainer().read(notificationProvider).getNotification();
}
NotificationProvider
Future<void> getNotification() async {
try {
state = const NotificationLoadingState();
_notificationList = await _notificationRepository.getNotification();
state = NotificationLoadedState(_notificationList); // ==> I get a new list here
} catch (e, s) {
state = const NotificationErrorState('error fetching notification');
}
}
UI
final state = watch(notificationProvider.state);
if (state is NotificationLoadingState) {
return _buildLoading();
} else if (state is NotificationLoadedState) {
return _buildLoaded(state.notificationList); // ==> I still get the old list here
} else if (state is NotificationErrorState) {
return _buildError(state.message);
}
Edit:
I managed to solve the foreground message handler by using navigatorKey.currentContext.
However, I still haven't solved background message handler.
I've tried changing my main.dart to use UncontrolledProviderScope with a global ProviderContainer, which then was called from background message handler. It's still not refreshing.
With this line you are creating a new instance for your providers :
ProviderContainer().read(notificationProvider).getNotification();
you need to use a context to get the existing instance of ProviderContainer:
context.read(notificationProvider).getNotification();
or if you are not inside ui make dependencies between your providers

Flutter BloC flush streams on logout

I am working with blocs and rxdart. There are some blocs that I don't instantiate "per-screen/widget" but globally in the app, since I need the same instance for the entire life of the app session. Therefore, I won't dispose them by closing the streams.
When I log my user out, I would like to make sure that all the streams/subjects are reset.
How can I achieve this?
Here is a simple Bloc I have:
class FriendsBloc {
final FriendsRepository _friendsRepository = FriendsRepository();
final BehaviorSubject<FriendsResponse> _friendsSubject = BehaviorSubject<FriendsResponse>();
BehaviorSubject<FriendsResponse> get friends => _friendsSubject;
Future<void> getFriends() async {
try {
FriendsResponse response = await _friendsRepository.getFriends();
_friendsSubject.sink.add(response);
} catch (e) {
_friendsSubject.sink.addError(e);
}
}
dispose() {
_friendsSubject.close();
}
}
final friendsBloc = FriendsBloc();