Getting null argument while routing through firebase push notifications flutter - 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.

Related

how can I route using GoRouter in Flutter without context?

The problem I want to solve:
My app which uses GoRouter needs to be able to route to a named route from within main(). Since most routing is of the form 'context.go' I cannot do so within main.
Background
My app uses GoRouter. The ease with which GetX had let me define named routes and pass parameters from main() was perfect.
However, GetX and GoRouter eventually causes problems for me. GoRouter would eventually have no context in other parts of the app.
If there were a way to have them co-exist simply, I'd be open to it.
I had used the service locator pattern with the GetIt package to associate with a navigatorKey. It would work when I tested it -- but this involved creating two MaterialApps.
However, this app uses GoRouter which doesn't seem to use the navigatorKey.
I would like to go to a specific route from within main (). It seems like the service locator pattern could work for GoRouter as it did with Navigator 2.0 for MaterialApp -- but I can't find an example of how to do so.
More detailed context:
Here is what I have currently in main().
You can see the key challenge I have is that the listener for the data parameters being passed in lives in main (I got this from the third-party SDK -- I don't need it to be in main but it needs to listen regardless of the state of the app).
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FFAppState(); // Initialize FFAppState
GetSocial.addOnInitializedListener(() => {
// GetSocial SDK is ready to use
});
setupLocator();
runApp(MyApp());
locator<LandingPageData>().referralID = "defaultReferralID";
registerListeners();
}
void registerListeners() {
Invites.setOnReferralDataReceivedListener((received) {
globalReferralData = received;
print(globalReferralData);
print(globalReferralData.linkParams);
print("listener - socialdata");
String passedReferralID =
globalReferralData.linkParams['referralID'].toString();
String passedCreatorID =
globalReferralData.linkParams['creatorID'].toString();
String passedCampaignID =
globalReferralData.linkParams['\$campaign_id'].toString();
print(passedReferralID);
print(passedCreatorID);
print(passedCampaignID);
// How can I route to a named Route?
locator<LandingPageData>().referralID = passedReferralID;
locator<LandingPageData>().creatorID = passedCreatorID;
locator<LandingPageData>().campaignID = passedCampaignID;
});
}
Here is what the locator.service.dart looks like:
final locator = GetIt.instance;
class NavigationService {
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
// final GlobalKey<ScaffoldMessengerState> navigatorKey = GlobalKey<ScaffoldMessengerState>();
}
The above worked when I could attach to a navigatorKey and then navigate from within the listener. But that doesn't seem to work since the rest of the application uses GoRouter.
static BuildContext? get ctx => myGoRouter.routerDelegate.navigatorKey.currentContext;
you can get context in your NavigationService in this way and use it like
NavigationService.ctx?.go(...)
the problem you may face is that ctx will be null on app state till your first page starts to be built. In the case your listener has a data while ctx is still null, routing won't work. but you can handle this situation like:
define a global tempPageToGo in main func or a service and
var _ctx = NavigationService.ctx;
if(_ctx == null) {
tempPageToGo = anyPageDataYouWant;
while((await Future.delayed(Duration(seconds: 1))) == null) {
if(_ctx != null) {
_ctx!.go(...);
break;
}
}
} else _ctx!.go(...);
Unluckily, if I were you, I'd either drop the usage of GetX or of GoRouter.
Actually, I'd just drop GetX.
The reason is that GetX performs magic under the hood that lifts the developer the responsibility and usage of BuildContext, but that's clearly an anti-pattern, as the built-in navigation from Flutter clearly uses context: think of Navigator.of, for example.
GoRouter is built around context, and simplifies a lot of the implementations needed to perform "Navigator 2.0" actions.
If you're trying to implement deep linking, your MaterialApp should look like this in your root widget:
return MaterialApp.router( // Flutter's Router 2.0 usage
title: 'MyApp',
routeInformationProvider: myGoRouter.routeInformationProvider,
routeInformationParser: myGoRouter.routeInformationParser,
routerDelegate: myGoRouter.routerDelegate,
);
If GetX enables you to put myGoRouter there, then you should be good to go. But as I said before, everytime you need explicit navigation, you need context.
I'm in researching to adopt go_router in my project, and i was also stuckled for this usecase ( in my case i tried to prove that i can navigate from deferred link that callback from appsflyer SDK ).
For solution, like that go_router allows us to either navigate from context that is below the router declaration or from the redirect state. So we can wrap up all the state that effect the navigation on that.
This is how i redirect from appRouterState
redirect: (GoRouterState state) {
String? redirection(GoRouterState state) {
final appRouterState = ref.read(appRouterStateNotifierProvider);
final isAuthed = appRouterState.email != null;
if (appRouterState.deferredLink != state.location && appRouterState.deferredLink != null) {
return appRouterState.deferredLink;
}
if (state.location != '/login' && !isAuthed) return '/login';
if (state.location == '/login' && isAuthed) return '/';
return null;
}
final result = redirection(state);
return result;
},
In your case, you may implement setOnReferralDataReceivedListener in the appRouterStateProvider or something. And use it for refreshListenable param in the GoRouter constructor.
Hope this helps.

Flutter Background Service

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())
}

Problems with camera in flutter app (The selected imageFormatGroup is not supported by > Android. Defaulting to yuv420)

I have problems with integration of a photography function in my app.
I get asked if I permit the access to the camera, but after that nothing happens exept this errror:
W/Camera (26849): The selected imageFormatGroup is not supported by
Android. Defaulting to yuv420
I/CameraManagerGlobal(26849): Camera 0 facing CAMERA_FACING_BACK state
now CAMERA_STATE_OPEN for client...
This is my code:
class FaultReporting extends StatefulWidget {
#override
_FaultReportingState createState()=> _FaultReportingState();
}
class _FaultReportingState extends State<FaultReporting>{
bool isReady=false;
List<CameraDescription> cameras;
CameraController camController;
#override
void initState() {
super.initState();
setupCameras();
}
Future<void> setupCameras() async {
try {
cameras = await availableCameras();
camController = new CameraController(cameras[0], ResolutionPreset.medium);
await camController.initialize();
} on CameraException catch (_) {
setState(() {
isReady = false;
});
}
setState(() {
isReady = true;
});
}
...
child: ElevatedButton(
onPressed: (){
if(!isReady && !camController.value.isInitialized)
{
return Container();
}
return AspectRatio(
aspectRatio: camController.value.aspectRatio,
child: CameraPreview(camController),
);
},
...
I had the exact same error when I used the camera on flutter. The message is just informing you that the imageFormatGroup parameter must be ImageFormatGroup.yuv420.
So try this:
camController = new CameraController(
cameras[0],
ResolutionPreset.medium,
imageFormatGroup: ImageFormatGroup.yuv420,
);
I had the same issue. However, the abovementioned solution (adding imageFormatGroup: ImageFormatGroup.yuv420) did not solve the problem.
I found out what was the real problem. Turns out Flutter Navigation requires pushed routes to be popped before another one pushed into navigator.
Otherwise, it will cause big issues with all camera using packages (CameraController from camera, QRViewController? controller from qr_code_scanner, MobileScannerController cameraController from mobile_scanner all affected) using packages (Both Android and iOS), specially if you use your own custom internal navigation without using Flutter Navigator.
Because, that way you use Navigator partially and in some situations you push several different routes without popping them or even pushing same route several times.
The camera will come either black or keep loading forever. Only thing will solve the issue is to kill/force stop the app and open again as it clears Flutter Navigator.
In Android you will see this error: "MessageQueue: java.lang.IllegalStateException: Handler sending message to a Handler on a dead thread"
In iOS you will see this: "The selected imageFormatGroup is not supported by iOS. Defaulting to brga8888"
Another related error from mobile_scanner package: flutter: MobileScanner: Called start() while already started!
Realted error from qr_code_scanner package: Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Scan rect cannot be set when not (yet) scanning. You may want to set it within didStartScanningBlock.
Here is the solution which may help you to prevent it. What we need to do is to make sure that when we push new route to the Navigator we are clearing other previously pushed routes from it:
For undefined routes:
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (context) => YourPage()),
(Route<dynamic> route) => false);
For defined routes:
Navigator.of(context)
.pushNamedAndRemoveUntil('/yourdefinedroute', (Route<dynamic> route) => false);

Flutter Google Calendar API - clientViaServiceAccount not responding

I know that there are already answers to this question but I couldn't solve my issue looking at them, so I hope you will be able to help me or at least suggest me what to try because I am not sure what to do or how to debug in order to solve my issue.
I am trying to list all the events in my company calendar using Google Calendar Api.
I used the same code on this question: Using dart and flutter with google calendar api to get a list of events on the a user's calendar
And I followed all the 6 steps of the answer in the question above.
For some reason, the call clientViaServiceAccount do not return any result and any errors.
It seems like it is executing an infinity loop.
This is the code I am using, and I can only see printed "getCalendarEvents". I cannot see the msg "HERE" or any error printed. So The issue is for sure in clientViaServiceAccount.
I edited the code taking into account Iamblichus suggestion, but the issue is that I cannot even reach the CalendarApi. The code is stacked on the clientViaServiceAccount.
import 'package:flutter/material.dart';
import 'package:googleapis_auth/auth_io.dart';
import 'package:googleapis/calendar/v3.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
CalendarAPI.getCalendarEvents();
return MaterialApp(
title: 'Flutter Demo',
home: Container(),
);
}
}
class CalendarAPI {
static final _accountCredentials = new ServiceAccountCredentials.fromJson(r'''
{
"private_key_id": myPrivatekeyId,
"private_key": myPrivateKey,
"client_email": myClientEmail,
"client_id": myClientId,
"type": "service_account"
}
''');
static final _scopes = [CalendarApi.CalendarScope];
static void getCalendarEvents() {
print('getCalendarEvents');
clientViaServiceAccount(_accountCredentials, _scopes).then((client) {
print('HERE');
var calendar = new CalendarApi(client);
print(calendar);
// Added iamblichus answer
var calendarListEntry = CalendarListEntry();
calendarListEntry.id = calendarId;
calendar.calendarList.insert(calendarListEntry).then((_) {
print('CALENDAR ADDED');
var calEvents = calendar.events.list(calendarId);
calEvents.then((Events events) {
events.items.forEach((Event event) {
print(event.summary);
});
}).catchError((e) => print(e));
}).catchError((e) => print(e));
}).catchError((e) => print(e));
}
}
EDIT FOR ANSWERING iamblichus COMMENT on Jul 15 at 12:31
I created the Service account Credentials on the page of the image shown below.
As soon as I created the key, I file .json was downloaded with the credentials.
I copied the content of that file and paste into ServiceAccountCredentials.fromJson function, so the credentials cannot be wrong.
And even if the credentials were wrong, why cannot I see an error that clientViaServiceAccount call is failing?
I am catching any error and print them in the screen with the last }).catchError((e) => print(e));.
For some reason the call clientViaServiceAccount is not doing anything and I cannot understand how to find the reason for that.
This method works in the cloud. If you want to use it in the flutter app, you have to get the user authenticated using the following plugin,
extension_google_sign_in_as_googleapis_auth

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.