Flutter Background Service - flutter

Hi I'm building a VoIP App in Flutter. I use background_fetch to run a headless task in order to work even if the app is closed. The listener is working, and a notification is sent. But, as the application is closed, the push notification with wake up the app (so home.dart for example) and I would like the push my call screen widget. I see two solution but I don't know how to do it :
the headless task from background_fetch is independent, so I can't transfer my service call data to my app (main) when the user open it, so the call is lost ...
I try to push the right widget (Router.go(/callscreen)) but it's not working.
What can I do in order to fix this ? Thank !

You are using 2 services in background, flutter-local-notification and background-fetch. It's too much. You can use flutter-local-notification in backgound only. Have a look here.
final newRouteName = "callScreen";//Future onSelectNotification(String payload) async
bool isNewRouteSameAsCurrent = false;
Navigator.popUntil(context, (route) {
if (route.settings.name == newRouteName) {
isNewRouteSameAsCurrent = true;
}
return true;
});
if (!isNewRouteSameAsCurrent) {
Navigator.of(context).push(CallScreen())
}

Related

Getting null argument while routing through firebase push notifications flutter

I am trying to route to a certain screen with some argument when clicked on push notifications. So far it is working fine when app is in foreground or in background but open. But when the app is terminated it is routing to the correct screen but there is some issue with the argument, it is null.
const payload = admin.messaging.MessagingPayload = {
data : {
'type' : 'msg',
'route' : 'chat-screen',
'argument' : sentby,
},
notification : {
title : senderData.user_name,
body: original.message,
image: notificationIcon,
android_channel_id : "Finiso",
channel_id : "Finiso",
clickAction : 'FLUTTER_NOTIFICATION_CLICK',
}
}
This is the code for the notification which I am triggering through cloud functions.
Future<void> backgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
print(message.data.toString());
print(message.notification.title);
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FirebaseMessaging.onBackgroundMessage(backgroundHandler);
added this in main.dart
FirebaseMessaging.instance.getInitialMessage().then((message) async {
if (message != null) {
print(message.data);
routeNotification(message);
}
});
listening to messages and on app open trigger.
void routeNotification(RemoteMessage message) {
final routeName = message.data['route'];
if (routeName == ActivityFeed.routeName) {
Navigator.of(context).pushNamed(ActivityFeed.routeName);
} else {
if (message.data['type'] == 'msg') {
Navigator.pushNamed(
context,
message.data['route'],
arguments: message.data['argument'],
);
}
}
}
This is the routing function I used above
Image showing console logs
It is printing the first two lines from main.dart and "null" is the argument I am trying to get.
Can anyone help me on this. I have no idea what's going on.
Thank you in advance.
I have an answer but I'm not positive on the reason why this is happening yet. Let's collaborate on this.
First of all, I think the routing is happening automatically for you. In all other scenarios except the terminal state, you are pushing the route and handling adding the arguments. In the terminal state, the app is ignoring your initialRoute and using the route from your push notification.
Usually when you setup MaterialApp your initialRoute is used:
MaterialRoute(
initialRoute: '/whatever_you_put_here'
...
)
It seems that WidgetsBinding.instance.platformDispatcher.defaultRouteName (source) is being set within the flutter code and this is causing your default route to be this route instead of whatever you are passing to MaterialApp.
Reading the flutter documentation, it looks like this property is only set when someone calls FlutterView.setInitialRoute from Android.
That is the answer to your question.
--
I don't know what is calling FlutterView.setInitialRoute. A search of FlutterFire code seems to show no instances.

How to get OneSignal playerId (userId) in Flutter?

How to get playerId of a user inside the flutter app?. Player Id can be found in One signal website but i want that inside the flutter app and want to store it to send the notification to particular user.
This is my goto function for initating onesignal
Future<void> initOneSignal(BuildContext context) async {
/// Set App Id.
await OneSignal.shared.setAppId(SahityaOneSignalCollection.appID);
/// Get the Onesignal userId and update that into the firebase.
/// So, that it can be used to send Notifications to users later.̥
final status = await OneSignal.shared.getDeviceState();
final String? osUserID = status?.userId;
// We will update this once he logged in and goes to dashboard.
////updateUserProfile(osUserID);
// Store it into shared prefs, So that later we can use it.
Preferences.setOnesignalUserId(osUserID);
// The promptForPushNotificationsWithUserResponse function will show the iOS push notification prompt. We recommend removing the following code and instead using an In-App Message to prompt for notification permission
await OneSignal.shared.promptUserForPushNotificationPermission(
fallbackToSettings: true,
);
/// Calls when foreground notification arrives.
OneSignal.shared.setNotificationWillShowInForegroundHandler(
handleForegroundNotifications,
);
/// Calls when the notification opens the app.
OneSignal.shared.setNotificationOpenedHandler(handleBackgroundNotification);
}

Firebase Cloud Messaging onLaunch callback

My app structure is a little bit mess, but I have to add this patch first and then I'll restructure the entire logic. The thing is I first check if there's a firebase user, then if there is one I use StreamBuilder to get the current user profile from Firestore, then I have the _firebaseMessaging.configure method because onLaunch and onResume I use this callback:
void _navigateToGestorResevas(Map<String, dynamic> message, User currentUser) {
Navigator.push(context,
MaterialPageRoute(builder: (context) =>
GestorScreen(user: currentUser)));
}
Because I need to send the User to this screen where he fetch the message from firebase.
onResume this works fine, but onLaunch it goes to the screen and fetch the data but there are like 20 seconds where there are some kind of glitch. It switch like 20-30 times between two states where I have and no have snapshot data in this _initState func:
final snapshot = await _dbRef.child('mensajes').child(widget.user.id).once();
if (snapshot.value != null) {
setState(() {
hayMensajes = true;
});
final data = snapshot.value;
for (var entry in data.entries) {
Message message = Message.fromJson(entry.value);
setState(() {
message.add(message);
});
}
} else {
setState(() {
hayMensajes = false;
});
}
Anyone have an idea what am I doing wrong?
If I am not mistaken, there are some active issues about FCM onLaunch callback with flutter. Some of them are still not fixed. One of the problems most people had to face was that onLaunch callback being called multiple times. I don't know why it happened, but as in your case, you can possibly get rid of the issue by some temporary fixes.
If the same screen is getting pushed over and over again, and glitching, you can pop the stack until it reaches the one you meant to open and set a condition to push navigator only if the new route is different from the old one. Using the named routes,
Navigator.popUntil(context, ModalRoute.withName(routeName));
if (ModalRoute.of(context).settings.name != routeName) {
Navigator.pushNamed(context, routeName);
}
I am not sure if that was the problem you asked, but I hope at least my answer helps somehow.

How to send app in background when back button is pressed

I'm trying to avoid the default behaviour of Flutter, instead of closing app when the back button (of the smartophone of course) is pressed I will send the app in background, so when I try to reopen the app it returns on the last screen.
I also tried a solution that I have found here, but didn't work.
This is the code:
return WillPopScope(
onWillPop: () async =>
await SystemChannels.platform.invokeMethod('SystemNavigator.pop'),
child: Scaffold(
It also closes the app and don't send it in background.
The screen that contains the code showed above is the last route.
You can use MethodChannel for this, invoke a method from Flutter that will trigger a method in Java.
// in flutter use something like this
methodChannel.invokeMethod("homeButton");
And in Java, you can create a method like:
public void homeButton() {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
More info on how to write platform specific code.
Flutter plugin for sending mobile applications to the background. Supports iOS and Android.
move_to_background: ^1.0.2
WillPopScope(
child: MaterialApp(...),
onWillPop: () async {
MoveToBackground.moveTaskToBack();
return false;
},
);

firebase_database online / offline status

How can we determine whether our flutter app is online or offline when using firebase and the flutter-fire firebase_database plugin?
This blog post (https://firebase.googleblog.com/2013/06/how-to-build-presence-system.html) shows using the '.info/connected' child to get an Event when the app goes online/offline. However it only seems to trigger on app startup once and thats it.
I'm using this:
void initState() {
super.initState();
print('Setting up the connected handler');
final amOnline = FirebaseDatabase.instance.reference().child('.info/connected');
_amOnlineSubscription = amOnline.onValue.listen((Event event) {
print('EVENT has occured');
});
}
Maybe there is a better way to determine online/offline status? What I'm trying to do is avoid a sign-in page when the device is offline. Yet force a sign-in once it becomes connected to firebase again...
I use connectivity_plus plugin on my app to check if the app has active network connection. This way, you won't need to use firebase_database just to check the device's connectivity.
You can add this on the app's initState()
#override
initState() {
super.initState();
subscription = Connectivity().onConnectivityChanged.listen((ConnectivityResult result) {
// Check connectivity status
})
}
and make sure to cancel the subscription once done.
#override
dispose() {
super.dispose();
subscription.cancel();
}