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.
Related
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.
I have successfully created the .pkpass file and the api successfully returns the .pkpass file. In Xamarin forms I consume the API and try to add the .pkpass file into wallet but the wallet is not launching automatically.
The file which consumed from api via Xamarin app is working fine, there is no issue with the file. I have sent is as an email attachment and downloaded the attachment - the .pkpass file automatically opens with wallet app.
public async Task DigitalMembershipCardApple()
{
string accesstoken = _dataHelper.GetAccessTokenFromDBAsync().Result;
try
{
_oHttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accesstoken);
HttpResponseMessage response = await _oHttpClient.GetAsync(new Uri(Constants.Urls.DigitalMembershipCardApple + _dataHelper.GetPersonID()));
byte[] filebytes = await response.Content.ReadAsByteArrayAsync();
string filePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), Constants.CISIMembershipCardFields.FileDownloadName);
if (File.Exists(filePath))
{
File.Delete(filePath);
File.WriteAllBytes(filePath, filebytes);
}
else
{
File.WriteAllBytes(filePath, filebytes);
}
await Launcher.OpenAsync(new OpenFileRequest
{
File = new ReadOnlyFile(filePath, Constants.CISIMembershipCardFields.MimeTypeApple)
});
}
catch (Exception ex)
{
Debug.WriteLine(ex.StackTrace);
throw ex;
}
}
I have also used the Xamarin essential launcher but that did not help.
Much appreciate a quick help
//Called from Xamarin Forms, now on iOS Project,
public async void AddToWallet(byte[] passByteArray)
{
NSData nsdata = NSData.FromArray(passByteArray);
NSError err = new NSError(new NSString("42"), -42);
PKPass newPass = new PKPass(nsdata, out err);
PKAddPassesViewController pkapvc = new PKAddPassesViewController(newPass);
await UIApplication.SharedApplication.KeyWindow.RootViewController.
PresentViewControllerAsync(pkapvc, true);
}
I don't use Apple wallet before while when I read document of PassKit in Xamarin.iOS, in the Adding Passes into Wallet section, it says:
Passes can be added to Wallet in the following ways:
Conduit Apps – These do not manipulate passes directly, they simply load pass files and present the user with the option of adding them to Wallet.
Companion Apps – These are written by providers to distribute passes and offer additional functionality to browse or edit them. Xamarin.iOS applications have complete access to the PassKit API to create and manipulate passes. Passes can then be added to Wallet using the PKAddPassesViewController. This process is described in more detail in the Companion Applications section of this document.
Mail is Conduit Application, it recognizes attachment as a Pass so the wallet app opens automatically .
Your xamarin app is Companion App, passes can then be added to Wallet using the PKAddPassesViewController. Read the file in your app won't open the wallet app.
My idea is you can download the file and follow the steps here to open the wallet app by using dependency-service in iOS project.
I used a different approach that show a popup to add multiple passes or review them.
Call this using a dependency service:
public async void AddAppleWalletPass(byte[] passByteArray)
{
if (PKPassLibrary.IsAvailable)
{
var library = new PKPassLibrary();
var passes = library.GetPasses();
NSData nsdata = NSData.FromArray(passByteArray);
NSError err = new NSError(new NSString("42"), -42);
PKPass newPass = new PKPass(nsdata, out err);
PKPass[] pKPasses = new PKPass[] { newPass };
await library.AddPassesAsync(pKPasses);
}
else
{
new UIAlertView("Alert", "Wallet is not available!", null, "Ok", null).Show();
}
}
I used these instructions to add Azure Notification Hub FCM-based Remote Notifications to my Xamarin.Forms Android app.
https://learn.microsoft.com/en-us/azure/app-service-mobile/app-service-mobile-xamarin-forms-get-started-push
I can receive Remote Notifications when the app is open or running in the background, but when I close the application or stop the application I no longer receive Remote Notifications.
My test device is running API level 22, so I'm using the following method to to build the notification on the device.
Android.Support.V4.App.NotificationCompat.Builder builder = new
Android.Support.V4.App.NotificationCompat.Builder(this)
For API level 26+, I'm using the follow method along with a channel to build the notification on the device.
var builder = new Android.App.Notification.Builder(this)
I'm thinking that I have to use a BroadcastReceiver to achieve this, but I really have no idea after reading so many comments on this subject. My app is compiled using API 27 and targets API 27.
Method 1
I'm trying to create a BroadcastReceiver that will launch MyService using an Explicit Intent when a Notification arrives. Unfortunately, this does not work on my API Level 22 test device.
//[BroadcastReceiver]
//[IntentFilter(new[] { Android.Content.Intent.ActionBootCompleted })]
[BroadcastReceiver(Enabled = true, Exported = true)]
[IntentFilter(new[] { "com.xamarin.example.TEST" })]
public class MyBroadcastReceiver : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
string message = intent.GetStringExtra("message");
string title = intent.GetStringExtra("title");
string id = intent.GetStringExtra("id");
//Explicit Intent to launch MyService
Intent intent2 = new Intent(Application.Context, typeof(MyService));
intent2.PutExtra("message", message);
intent2.PutExtra("title", title);
intent2.PutExtra("id", id);
Application.Context.StartService(intent2);
}
}
// Service is exported and given a name so other applications can use it
[Service(Exported = true, Name = "com.mycompany.myapp.MyService")]
// Intent filter only needed for Implicit Intents
//[IntentFilter(new string[] { "com.xamarin.example.TEST" })]
public class MyService : Service
{
public static string PRIMARY_NOTIF_CHANNEL = "default";
public override IBinder OnBind(Intent intent)
{
return null;
}
[return: GeneratedEnum]
public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
{
var pm = PowerManager.FromContext(this);
var wakeLock = pm.NewWakeLock(WakeLockFlags.Partial, "Notification");
wakeLock.Acquire();
string message = intent.GetStringExtra("message");
string title = intent.GetStringExtra("title");
string id = intent.GetStringExtra("id");
var intent2 = new Intent(this, typeof(MainActivity));
intent2.PutExtra("id", id);
intent2.AddFlags(ActivityFlags.ClearTop);
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, PendingIntentFlags.OneShot);
NotificationManager notificationManager = (NotificationManager)GetSystemService(Context.NotificationService);
Android.Support.V4.App.NotificationCompat.Builder builder = new Android.Support.V4.App.NotificationCompat.Builder(this)
.SetAutoCancel(true)
.SetContentIntent(pendingIntent)
.SetContentTitle(title)
.SetContentText(message)
.SetSound(RingtoneManager.GetDefaultUri(RingtoneType.Notification))
.SetVibrate(new long[1])
//1 is Hi
.SetPriority(1)
.SetLargeIcon(BitmapFactory.DecodeResource(Resources, SalesFlash.Droid.Resource.Drawable.Icon_lg))
.SetSmallIcon(MyApp.Droid.Resource.Drawable.icon)
.SetChannelId(PRIMARY_NOTIF_CHANNEL);
notificationManager = NotificationManager.FromContext(this);
notificationManager.Notify(0, builder.Build());
wakeLock.Release();
//return StartCommandResult.Sticky;
return base.OnStartCommand(intent, flags, startId);
}
public override void OnDestroy()
{
base.OnDestroy();
}
}
According to this post, a BroadCastReceiver won't work for FCM Notifications. https://stackoverflow.com/a/44287962/5360237
This post seems to show a BroadcastReceiver accepting a Notification.
https://stackoverflow.com/a/45616014/5360237
Any help is much appreciated. Thanks in advance!
Here is the best post that I've read that explains the problem. It also provides a manual solution for the affected devices. https://medium.freecodecamp.org/why-your-push-notifications-never-see-the-light-of-day-3fa297520793 I was able to get Notifications working (when app is closed, stopped or not running) on my Alcatel One Touch Pop by navigating to Settings - Apps and then swiping over to the Restricted tab. From here, I was able to uncheck my app.
NOTE: Messages are delivered to my Android app using the data payload. Even though I was receiving Notifications in the foreground and background, I went ahead and added the following permission to my AndroidManifest.
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
This permission is used for Auto Start. I also used Azure App Center to distribute a signed APK to my test device. Hope this helps someone else.
UPDATE: I removed the RECEIVE_BOOT_COMPLETE permission and did a Debug Build. I closed the app out of Debug (connected to Visual Studio) by swiping it off the screen. I then sent a Notification and it did not display. I then re-opened the app (not being debugged by Visual Studio) and closed it by swiping it off the screen, sent a Notification and it worked. So, it had nothing to with the RECEIVE_BOOT_COMPLETE permission and it doesn't have to be a Release version / signed APK.
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.
I have two IAPs set up on for my iOS app on iTunes Connect. All of the information is in there and they marry up to what I have set for Android. On iTunes, they are set as non-renewing subscriptions (mainly as this is what the review said they should be). There are no errors shown on the IAP screen and have been submitted for review (this makes no difference, the ones I had there previously didn't show either)
I'm using the Xamarin In App Purchase nuget package to get this working. When I run the app on Android, the packages show correctly. When I run the same code on iOS, nothing is showing.
I have a user set on the sandbox and have followed the instructions on the iTunes website on logging out of the store, but I'm seeing nothing - it's not even asking me to log into the store.
My code for interrogating the store is this
async Task<List<InAppBillingProduct>> GetItems()
{
var billing = CrossInAppBilling.Current;
try
{
var productIds = new string[] { "monthly_renewals", "yearly_subscriptions" };
//You must connect
var connected = await billing.ConnectAsync();
if (!connected)
{
//Couldn't connect
return new List<InAppBillingProduct>();
}
//check purchases
var items = await billing.GetProductInfoAsync(ItemType.Subscription, productIds);
return items.ToList();
}
catch (InAppBillingPurchaseException)
{
return new List<InAppBillingProduct>();
}
catch (Exception)
{
return new List<InAppBillingProduct>();
}
finally
{
await billing.DisconnectAsync();
}
}
It makes no difference if I set the ItemType to Subscription or InAppPurchase. All of the licences on iTunesConnect are correct, IAP is set to work in the provisioning profile too.
Given the store isn't asking for a login for the sandbox user, I'm wondering if something else needs to be done - I just don't know what.