Xamarin android: Async await calls not working when app open by clicking push notification - firebase

When I open app by tapping on FCM push notification, The API service calls I am making by using await keyword those are not working. Entire app not returning data.
Code for API calling
var result = await objHomework.GetHomeWorksForStudentPagesAsync(studentId.ToString());
result returning null. if app already open, everything working fine. See the Image below screenshot of app

Notification messages are delivered to OnMessageReceived callback only when the app is in the foreground.
Override the HandleIntent Method of the FirebaseMessageService to work for background as well
public override void HandleIntent(Intent intent)
{
try
{
if (intent.Extras != null)
{
var builder = new RemoteMessage.Builder("MyFirebaseMessagingService");
foreach (string key in intent.Extras.KeySet())
{
builder.AddData(key, intent.Extras.Get(key).ToString());
}
this.OnMessageReceived(builder.Build());
}
else
{
base.HandleIntent(intent);
}
}
catch (Exception)
{
base.HandleIntent(intent);
}
}

Actually, I was missing some keys which is necessary for service call authentication in my project. I am getting those keys in MaiActivity but notification click even starting app from somewhere else therefore keys values was null and service calls was not happening.

Related

iOS Push Notifications with Azure Notification Hub

I am having absolutely no luck getting push notifications to work in iOS in a Xamarin Forms project.
In AppDelegate.cs, I am calling the following in the FinishedLaunching override:
MSNotificationHub.Start("Endpoint=sb://[redacted].servicebus.windows.net/;SharedAccessKeyName=DefaultListenSharedAccessSignature;SharedAccessKey=[redacted]",
"[redacted]");
After the user logs in further in the app lifecycle, I also register the user with their user tag as follows:
public async Task UpdateTags(string token)
{
await Task.Run(() =>
{
try
{
// No point registering tags until the user has signed in and we have a device token
if (CurrentAccount == null)
{
Console.WriteLine($"UpdateTags cancelled: Account is null");
return;
}
var tag = $"user:{CurrentAccount.UserName}";
Console.WriteLine($"Registering tag: {tag}");
MSNotificationHub.AddTag(tag);
}
catch (Exception e)
{
Console.WriteLine($"Error registering tag: {e.ToString()}");
}
});
}
I have properly configured the Apple (APNS) settings in the notification hub, using the Token authentication mode (verified the four fields several times). The certificate (signing identity) is "iOS Distribution", the identifier bundle matches exactly what I have in the configuration (not using wildcard), the key has Apple Push Notifications service (APNs) enabled, and the provisioning profile has Platform: iOS and Type: App Store.
I pushed the application to TestFlight, as I don't have access to a physical Mac (we use a Cloud mac for development). When I view the device logs from my personal iPhone with the app installed, I see the following when I run it:
<Notice>: Registered for push notifications with token: [redacted]
<Notice>: Registering tag: user:[redacted]
There are no instances of "Error registering tag" or "UpdateTags cancelled" in the logs at all, which tells me that the method calls are succeeding without an exception. However, when I attempt to send a test notification to either a blank/empty tag, or the specific tag for my test user, no notifications are received and the messaging simply shows "Message was successfully sent, but there were no matching targets."
Also, when I pull all of the registrations with var registrations = await hub.GetAllRegistrationsAsync(0);, I only see the FCM (Firebase/Android) registrations from my successful testing on the Android side of things.
I am at a complete loss and have hit a wall, as there are no exceptions being thrown, and seemingly no way to troubleshoot what is going on behind the scenes.
This is also my 2nd attempt - I was using a more complex SBNotificationHub implementation and had the same results - no exceptions and everything looked fine at face value.
Thanks to a comment pointing to another question, I have determined that all I needed to do was to ensure that my tag registration ran on the main UI thread. My updated code below is working:
public async Task UpdateTags(string token)
{
await Task.Run(() =>
{
Device.BeginInvokeOnMainThread(() =>
{
try
{
// No point registering tags until the user has signed in and we have a device token
if (CurrentAccount == null)
{
Console.WriteLine($"UpdateTags cancelled: Account: {Trico.OrbitalApp.App.CurrentAccount};");
return;
}
var tag = $"user:{CurrentAccount.UserName}";
Console.WriteLine($"Registering tag: {tag}");
MSNotificationHub.AddTag(tag);
}
catch (Exception e)
{
Console.WriteLine($"Error registering device: {e.ToString()}");
}
});
});
}
You can try implementing the MSInstallationLifecycleDelegate interface which will allow you to check and see if the installation is being saved on the back end with either success or failure.
// Set a listener for lifecycle management
MSNotificationHub.SetLifecycleDelegate(new InstallationLifecycleDelegate());
// Implementation of the lifecycle listener.
public class InstallationLifecycleDelegate : MSInstallationLifecycleDelegate
{
public InstallationLifecycleDelegate()
{
}
public override void DidFailToSaveInstallation(MSNotificationHub notificationHub, MSInstallation installation, NSError error)
{
Console.WriteLine($"Save installation failed with exception: {error.LocalizedDescription}");
}
public override void DidSaveInstallation(MSNotificationHub notificationHub, MSInstallation installation)
{
Console.WriteLine($"Installation successfully saved with Installation ID: {installation.InstallationId}");
}
}

Firebase gets logged out after long time in Flutter Web

I'm developing a web app and I use Firebase Authentication for the authentication service.
The project seems to store the authentication, since if I refresh the page, or close the browser, the user is still logged in.
However I noticed that if I don't access the app for a long time (more than 1 hour, after the night for example), the authentication gets lost.
I don't know how to debug this and how to solve this.
Following some snippets of code to better understand my implementation:
This is the function I have in my startup view to redirect the user to the right page based on auth status.
bool isUserLoggedIn() {
var user = _firebaseAuth.currentUser;
return user != null;
}
void handleStartupBasedOnAuthStatus() {
Future.delayed(const Duration(milliseconds: 1000), () async {
bool loggedInShared =
await sharedPreferences.getBoolSharedPreferences("loggedIn");
if (isUserLoggedIn() || loggedInShared) {
String ruoloValue =
await sharedPreferences.getSharedPreferences('ruolo');
(ruoloValue == Ruolo.ADMIN)
? navigationService.replaceWith(Routes.admin)
: navigationService.replaceWith(Routes.messages);
} else {
navigationService.replaceWith(Routes.login);
}
});
}
In the following function I call the onAuthStateChange to set sharedpreferences accordingly. I have the check on the timestamp because I noticed that it is triggered more time once the page is refreshed.
void listenToAuthChangesSharedPref() {
FirebaseAuth.instance.authStateChanges().listen((firebaseUser) async {
var datetimeNow = (DateTime.now().millisecondsSinceEpoch);
String oldDatetimeString =
await sharedPreferences.getSharedPreferences('previous_timestamp');
if (oldDatetimeString != null) {
var oldDatetime = (new DateTime.fromMillisecondsSinceEpoch(
int.parse(oldDatetimeString)))
.millisecondsSinceEpoch;
if (datetimeNow - oldDatetime > 1000) {
if (firebaseUser == null) {
await sharedPreferences.setBoolSharedPreferences('loggedIn', false);
} else {
await sharedPreferences.setBoolSharedPreferences('loggedIn', true);
}
await sharedPreferences.setSharedPreferences(
'previous_timestamp', datetimeNow.toString());
}
} else {
if (firebaseUser == null) {
await sharedPreferences.setBoolSharedPreferences('loggedIn', false);
} else {
await sharedPreferences.setBoolSharedPreferences('loggedIn', true);
}
await sharedPreferences.setSharedPreferences(
'previous_timestamp', datetimeNow.toString());
}
});
}
My question is: is possible that after long time currentUser and also the onAuthStateChanges gets called and the user is not logged in?
Persisting authentication state#
The Firebase SDKs for all platforms provide out of the box support for ensuring that your user's authentication state is persisted across app restarts or page reloads.
On native platforms such as Android & iOS, this behaviour is not configurable and the user's authentication state will be persisted on-device between app restarts. The user can clear the apps cached data via the device settings which will wipe any existing state being stored.
On web platforms, the user's authentication state is stored in local storage. If required, you can change this default behaviour to only persist authentication state for the current session, or not at all. To configure these settings, call the setPersistence() method (note; on native platforms an UnimplementedError will be thrown):
// Disable persistence on web platforms
await FirebaseAuth.instance.setPersistence(Persistence.NONE);
for more info:
for more info:

Xamarin Forms iOS - Saving a user tag in Azure Notification Hubs works in AppDelegate but not in a service

I'm currently trying to get push notifications working for my mobile app using Azure Notification Hubs. Android is working fine and the initial iOS set up in AppDelegate works ok with a sample tag.
public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
if (deviceToken == null)
{
return;
}
SBNotificationHub hub = new SBNotificationHub(CommonConstants.LISTEN_CONNECTION_STRING, CommonConstants.NOTIFICATION_HUB_NAME);
// update registration with Azure Notification Hub
hub.UnregisterAll(deviceToken, async (error) =>
{
if (error != null)
{
System.Diagnostics.Debug.WriteLine($"Unable to call unregister {error}");
return;
}
string[] tags = new[] { "iostestpush" };
NSSet userTags = new NSSet(tags);
hub.RegisterNative(deviceToken, userTags, (error) =>
{
if (error != null)
{
System.Diagnostics.Debug.WriteLine($"Unable to call register {error}");
return;
}
});
var templateExpiration = DateTime.Now.AddDays(120).ToString(System.Globalization.CultureInfo.CreateSpecificCulture("en-US"));
hub.RegisterTemplate(deviceToken, "defaultTemplate", CommonConstants.APN_TEMPLATE_BODY, templateExpiration, userTags, (errorCallback) =>
{
if (errorCallback != null)
{
System.Diagnostics.Debug.WriteLine($"RegisterTemplateAsync error: {errorCallback}");
}
});
});
}
The issue I'm having is I need to register the UserId after a successful login. So I set up a service with the above code, saved the token to the device as string so it can be retrieved in the service and turned back into an NSData token
NSData deviceToken = new NSData(token, NSDataBase64DecodingOptions.None);
After a successful login I send the token string and the tag array to my service.
string[] userTag = new[] { loginResponse.UserId.ToString() };
await this._azureReg.SendRegistrationToServer(deviceToken, userTag);
Which, other than turning the token back into NSData and the user tag into an NSSet, is the same as above other than the name change. But Azure is claiming there is no registration even though my output shows
Registered for push notifications with token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
I thought it was the string conversion back and forth, so tested that in the AppDelegate and it worked fine.
So, I'm at a loss at how to register the UserId after a successful login and why it works in one place but not the other.
I hope that's clear and thanks for any advice in advance.
You probably ran into the same bug as me and several others.
Basically SBNotificationHub method overloads like UnregisterAll and RegisterTemplate with the callback signature do not work when you use them off the main thread, using the libraries to date. I was also using a Service for the same purpose (to handle push across platforms with different tags, especially for user id) but my implementation involved switching off the main thread for this.
The bug we logged and is now being addressed is here: https://github.com/Azure/azure-notificationhubs-ios/issues/95
The solution, for now, is to ditch SBNotificationHub completely. The Xamarin / Azure documentation is out of date, and SBNOtificationHub is legacy code. The recommended library is MSNotificationHub. https://github.com/azure/azure-notificationhubs-xamarin
As workarounds you can use the SBNotificationHub method overloads that do not involve callbacks (they return an error message instead) or the workaround in the 95 issue above.

How to disable Firebase push notifications in Unity from client side application?

I have integrated Firebase into Unity project and it all works. I would like to integrate disabling of push notifications if user wants to disable them in his app. I haven't found solution in code for this, also there is one unanswered question same as mine, so I am posting new one just in case someone has come with a solution for this.
I managed to do this by using Firebase Messaging Topic and calling SubscribeAsync() and UnsubscribeAsync().
private void Start()
{
if ( notificationsAreOn )
InitFirebaseMessaging();
else
DisableFirebase();
}
private void DisableFirebase()
{
Firebase.FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith( task =>
{
var dependencyStatus = task.Result;
if ( dependencyStatus == Firebase.DependencyStatus.Available )
{
FirebaseAnalytics.SetAnalyticsCollectionEnabled( false );
Firebase.Messaging.FirebaseMessaging.UnsubscribeAsync("VeryCoolTopic");
Firebase.Messaging.FirebaseMessaging.TokenReceived += null;
Firebase.Messaging.FirebaseMessaging.MessageReceived += null;
}
else
{
UnityEngine.Debug.LogError(
System.String.Format("Could not resolve all Firebase dependencies: {0}", dependencyStatus)
);
}
});
}
private void InitFirebaseMessaging()
{
Firebase.FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith( task =>
{
var dependencyStatus = task.Result;
if ( dependencyStatus == Firebase.DependencyStatus.Available )
{
FirebaseAnalytics.SetAnalyticsCollectionEnabled( true );
Firebase.Messaging.FirebaseMessaging.SubscribeAsync("VeryCoolTopic");
Firebase.Messaging.FirebaseMessaging.TokenReceived += OnTokenReceived;
Firebase.Messaging.FirebaseMessaging.MessageReceived += OnMessageReceived;
}
else
{
UnityEngine.Debug.LogError(
System.String.Format("Could not resolve all Firebase dependencies: {0}", dependencyStatus)
);
}
});
}
Then in the Firebase console when creating a message, use Topic as a target instead of User Segment.
You could also use disable whole Firebase by deleting the Firebase token using DeleteTokenAsync() but I haven't tested this because the the method with using subscribe and unsubscribe worked for me.
There are two types of messages that you can send with FCM: notifications messages, and data messages.
If your app is active, notification messages are delivered to your application code, which can decide what to do with it. When the app is not active, notification messages are automatically displayed by the system. There is not way to suppress this behavior.
Data messages are always delivered to your application code, which can decide what to do with them.
So if you want to allow the user to suppress the display of messages, you'll want to only send data messages, and then display them from within your application code.
Note that alternatively, you can find a way to not deliver messages to a user who has disabled notifications. How exactly to do this depends on your implementation. For example: if you're sending directly to FCM Instance ID tokens, you can skip the tokens of users who have disabled push notifications. And if you're using topic subscription to send messages, you can create a topic that users subscribe to to disable notifications, and then create conditions to exclude delivery to that topic.

SharedPreferences empty after restarting service / device

As I am working with Google Firebase for Push Notifications, I want to save the Instance Token to the SharedPreferences. Unfortunately, whenever the token gets refreshed and want to check the previous one from SharedPreferences, they are empty...
Is it because I am using a Service here?
public class MyFirebaseIIDService : FirebaseInstanceIdService
{
public override void OnTokenRefresh()
{
var sharedPreferences = PreferenceManager.GetDefaultSharedPreferences(this);
var sharedPreferencesEditor = sharedPreferences.Edit();
// Get Firebase Instance Token
var refreshedToken = FirebaseInstanceId.Instance.Token;
// Check if a Firebase Instance Token has been registered before and unregister it
var oldToken = sharedPreferences.GetString("FirebaseInstanceToken", null);
if (oldToken != null) // is ALWAYS null :(
{
// Unregister old token...
}
// Save the Firebase Instance Token locally
sharedPreferencesEditor.PutString("FirebaseInstanceToken", refreshedToken);
sharedPreferencesEditor.Apply();
// At this point, the SharedPreferences have to token saved.
// Next time, the app reaches this point, it is gone...
}
}
Sorry for the syntax confusion, I use Xamarin, so this is C# but it should not make any difference.
I don't know how it work in xamarin but in native android getSharedPreferences from service context maybe wrong. You should use only applicationContext or MODE_MULTI_PROCESS when open shared preferences.
You can see similar question here.
Xamarin and C# also have this mode when your open some file, so i think exactly the same with preferences. Try some like this instead of using GetDefaultSharedPreferences:
ISharedPreferences prefs = Application.Context.GetSharedPreferences ("PREF_NAME", FileCreationMode.MultiProcess);
Did you uninstall your app to refresh you token? If yes I think you can not get the old token because the SharedPreferences is cleared when you uninstall your app.
I have tried java code to save the token :
public void onTokenRefresh() {
// Get updated InstanceID token.
String refreshedToken = FirebaseInstanceId.getInstance().getToken();
Log.d("Mike", "Refreshed token: " + refreshedToken);
SharedPreferences mSharedPreferences = getSharedPreferences("hello",0);
String oldToken = mSharedPreferences.getString("Token",null);
if(oldToken == null)
{
Log.d("Mike", "oldToken: " + null);
}
SharedPreferences.Editor mEditor = mSharedPreferences.edit();
mEditor.putString("Token", refreshedToken);
mEditor.commit();
// TODO: Implement this method to send any registration to your app's servers.
//sendRegistrationToServer(refreshedToken);
}
At first time you install your app you will get the token and save it in the SharedPreferences, next time you open your app and show the old token in the textview then you can find your token has been saved , And do not uninstall your app:
TextView tv1 = (TextView)findViewById(R.id.tv1);
SharedPreferences mSharedPreferences = getSharedPreferences("hello",0);
String oldToken = mSharedPreferences.getString("Token",null);
tv1.setText(oldToken);
It works. But when you uninstall your app the textview shows null.
I solved the problem and it turned out, that my code was working correctly but behaved strange on my test device.
After re-building the application, SharedPreferences have been cleared, although I checked the Preserve application data/cache on device between deploys option in Visual Studio. That was because my physical testing device was rooted and did not accept this option.
When trying on an unrooted device, everything worked as expected.

Resources