IAP items not showing using the Xamarin IAP nuget package - xamarin.forms

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.

Related

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.

Xamarin Forms In App Billing plugin does not work

I installed the in app billing plugin in my Xamarin Forms project: Plugin.
First I want to use it on iOS but the plugin doesn't work. It shows following error: Plugin.InAppBilling.Abstractions.InAppBillingPurchaseException: Cannot connect to iTunes Store
at Plugin.InAppBilling.InAppBillingImplementation.PurchaseAsync (System.String productId, Plugin.InAppBilling.Abstractions.ItemType itemType, System.String payload, Plugin.InAppBilling.Abstractions.IInAppBillingVerifyPurchase verifyPurchase) ..
I think my productId or the payload is wrong, what should I put in there?
Here is my Code:
try
{
var productId = "mySKU";
var connected = await CrossInAppBilling.Current.ConnectAsync();
if (!connected)
{
//Couldn't connect to billing, could be offline, alert user
return;
}
//try to purchase item
var purchase = await CrossInAppBilling.Current.PurchaseAsync(productId, ItemType.Subscription, "payload");
if (purchase == null)
{
//Not purchased, alert the user
}
else
{
//Purchased, save this information
var id = purchase.Id;
var token = purchase.PurchaseToken;
var state = purchase.State;
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
//Something bad has occurred, alert user
}
finally
{
//Disconnect, it is okay if we never connected
await CrossInAppBilling.Current.DisconnectAsync();
}
I hope anybody can help me. Thanks in advance.
I fixed it. For all those who have the same problem: My problem was, that I created a bundle id. Then I activated the automatic signing in Visual Studio and it created another bundle id automatically. I selected it and I had to give it a name. After I clicked save, it changed back to the other id. The problem was that the ids had the same name. So I renamed the bundle id in Visual Studio and it works like a charm.
You must setup ios settings before using that nuget:
https://help.apple.com/xcode/mac/current/#/dev88ff319e7

How to deal with device settings in Xamarin.Forms?

I have a Xamarin.Forms application supporting Android, iOS, and UWP. I need the application at the starting point to check if location feature is enabled, and if not, suggest the user to do so, and let him open device Settings. But if the user agrees to open the Settings, I would need the application to wait for the results, and then continue based on the newly changed settings. But how can I make the execution wait for the user to finish working with the settings?
Here is the code I have for now:
Task<bool> task = Application.Current?.MainPage?.DisplayAlert("Location service is disabled on this device",
"MyCompany Mobile uses your location to provide you with the correct product mix and other information for your market. Please go into Settings and turn on Location for the device.",
"Settings",
"Maybe Later");
if (task == null)
{
return bu;
}
bool result = await task;
if (result)
{
IDeviceService deviceService = DependencyService.Get<IDeviceService>();
if (deviceService != null)
{
bool openedSuccessfully = await deviceService.OpenDeviceSettingsAsync();
}
}

Weird login behaviour using Azure AD B2C with Xamarin iOS

We have a Xamarin Forms app that uses Azure AD B2C for login. I have followed this example code https://github.com/Azure-Samples/active-directory-b2c-xamarin-native
The Android version works perfectly. The workflow is seamless and works as expected.
On the iOS version we are getting some weird behaviour though.
Here is the flow on the iOS version of the app.
User launches the app and is presented with the Azure AD B2C login screen (correct)
User enters their credentials (correct)
The app redirects the user back to the login screen (with the username and password fields blank) (wrong)
After a few seconds the app then logs them in and navigates them to the main screen
After entering their credentials the user should be immediately navigated to the app main screen, NOT back to the login screen.
Once they are logged in, subsequently launching the app correctly logs them in. The problem is only on first login.
I can't seem to find any help on this anywhere. Is it the Safari browser, the token cache, something else? The code and workflow are correct as the Android version works perfectly, so it's something specific to the iOS app.
UPDATE
After some investigation it seems that this method may be causing the issue.
public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
{
AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs(url);
return true;
}
According to the docs
this logic is meant to ensure that once the interactive portion of the
authentication flow is concluded, the flow goes back to MSAL
But it doesn't. It redirects the user back to the OnAppearing() method of the login page where it attempts (again) to acquire a token (which it does).
Is there any way to fix the behaviour of this method?
AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs(url)
UPDATE2
Here is the login code (simplified for clarity)
protected override async void OnAppearing()
{
base.OnAppearing();
// Check to see if we have a User in the cache already.
try
{
IEnumerable<IAccount> accounts = await AuthenticationService.PCA().GetAccountsAsync();
var account = this.GetAccountByPolicy(accounts, ApplicationConstants.SignUpSignInPolicy);
AuthenticationResult ar = await AuthenticationService.PCA().AcquireTokenSilentAsync(ApplicationConstants.Scopes, account, ApplicationConstants.Authority, false);
}
catch (Exception)
{
// Doesn't matter, we go in interactive mode
this.OnSignIn();
}
}
private async void OnSignIn()
{
try
{
IEnumerable<IAccount> accounts = await AuthenticationService.PCA().GetAccountsAsync();
var account = this.GetAccountByPolicy(accounts, ApplicationConstants.SignUpSignInPolicy);
AuthenticationResult ar = await AuthenticationService.PCA().AcquireTokenAsync(ApplicationConstants.Scopes, account, App.UiParent);
}
catch (Exception ex)
{
InstanceManager.LoggingHelper().TrackError(ex);
// Checking the exception message
// should ONLY be done for B2C
// reset and not any other error.
if (ex.Message.Contains("AADB2C90118"))
{
this.OnPasswordReset();
}
else
{
await DisplayAlert(StringConstants.ExceptionText, StringConstants.AuthenticationError, StringConstants.CloseDialog);
this.OnSignIn();
}
}
}

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