We have an XF Forms app that we are adding subscription support for. We implemented Plugin.InAppBilling. Works as expected on iOS. We followed JM's docs for configuring on Android - but getting "This app not configured for subscriptions" error.
This is the purchase method we are using:
public async Task<InAppBillingPurchase> MakePurchase()
{
if (!CrossInAppBilling.IsSupported)
return null;
var currentPage = GetCurrentPage();
if(currentPage != null)
{
var confirm = await currentPage.DisplayAlert("Q5id subscription", "Become a subscriber to create an alert to find your loved one.","Confirm", "Cancel");
if (!confirm) return null;
}
var billing = CrossInAppBilling.Current;
try
{
var connected = await CrossInAppBilling.Current.ConnectAsync();
if (!connected)
{
//Couldn't connect to billing, could be offline, alert user
return null;
}
//try to purchase item
var purchase = await CrossInAppBilling.Current.PurchaseAsync(SUBSCRIPTION_PRODUCT_ID, ItemType.Subscription);
deviceService.DeviceLog("PurchaseAsync purchase: ", purchase);
var isFinish = await CrossInAppBilling.Current.FinishTransaction(purchase);
deviceService.DeviceLog("PurchaseAsync isFinish: ", isFinish);
return purchase;
}
catch (Exception ex)
{
Debug.WriteLine("Error MakePurchase: " + ex.Message);
deviceService.DeviceLog("Error MakePurchase: ", ex);
return null;
}
finally
{
await billing.DisconnectAsync();
}
}
We have verified bundleid, Play Console looks right. Any ideas?
Related
maybe my code will mess here beacuse I'm new into firebase stuff and there is lack doc for newbie like me, so I want to make save and load data from firebase, there is 2 scene the first for main menu and second one the main game,so when firstime login or auto login, user will load current user data from realtime database (I'm using firebase) also applies to user when back to main menu.
in the second scene itself, user load current user highscore data at first start then when game over if the score > lasthighscore that will save/update latest highscore to realtime database.
here the preview this is when I auto-login then playgame until game over:
it's supposed to be updated but not when back to main menu, I already try with player prefs since i erase all data when sign out so the highscore always be 0 and if login with other account that highscore is not belong to other account.
here my firebase script on main menu:
private void Start()
{
Time.timeScale = 1;
mainPanel.SetActive(true);
InitializeFirebase();
}
void InitializeFirebase()
{
auth = FirebaseAuth.DefaultInstance;
DBreference = FirebaseDatabase.DefaultInstance.RootReference;
auth.StateChanged += AuthStateChanged;
AuthStateChanged(this, null);
}
void AuthStateChanged(object sender, System.EventArgs eventArgs)
{
//This checks if the user (your local user) is the same as the one from the auth
if (auth.CurrentUser != User)
{
//this seems the same, but user could have been null before
bool signedIn = User != auth.CurrentUser && auth.CurrentUser != null;
if (!signedIn && User != null)
{
Debug.Log("Signed out " + User.UserId);
loginPanel.SetActive(true);
}
//this is important step, this user is the one you should be working with
User = auth.CurrentUser;
if (signedIn)
{
Debug.Log("Signed in " + User.UserId);
userNameShowText.text = User.DisplayName;
StartCoroutine(LoadUserData());
loginPanel.SetActive(false);
// //highScoreMainMenu.text = PlayerPrefs.GetInt("highscore").ToString("0000000");
}
else
{
loginPanel.SetActive(true);
}
}
}
public IEnumerator Login(string _email, string _password)
{
//Call the Firebase auth signin function passing the email and password
var LoginTask = auth.SignInWithEmailAndPasswordAsync(_email, _password);
//Wait until the task completes
yield return new WaitUntil(predicate: () => LoginTask.IsCompleted);
if (LoginTask.Exception != null)
{
//If there are errors handle them
Debug.LogWarning(message: $"Failed to register task with {LoginTask.Exception}");
FirebaseException firebaseEx = LoginTask.Exception.GetBaseException() as FirebaseException;
AuthError errorCode = (AuthError)firebaseEx.ErrorCode;
string message = "Login Failed!";
switch (errorCode)
{
case AuthError.MissingEmail:
message = "Missing Email";
break;
case AuthError.MissingPassword:
message = "Missing Password";
break;
case AuthError.WrongPassword:
message = "Wrong Password";
break;
case AuthError.InvalidEmail:
message = "Invalid Email";
break;
case AuthError.UserNotFound:
message = "Account does not exist";
break;
}
warningLoginText.text = message;
}
else
{
//User is now logged in
//Now get the result
User = LoginTask.Result;
Debug.LogFormat("User signed in successfully: {0} ({1})", User.DisplayName, User.Email);
warningLoginText.text = "";
confirmLoginText.text = "Logged In";
StartCoroutine(LoadUserData());
yield return new WaitForSeconds(2);
userNameShowText.text = User.DisplayName;
UserDataScreen(); // Change to user data UI
confirmLoginText.text = "";
ClearLoginFeilds();
ClearRegisterFeilds();
}
}
public IEnumerator UpdateHighScore(int _highScore)
{
var DBTask = DBreference.Child("users").Child(auth.CurrentUser.UserId).Child("highscore").SetValueAsync(_highScore);
yield return new WaitUntil(predicate: () => DBTask.IsCompleted);
if (DBTask.Exception != null)
{
Debug.LogWarning(message: $"Failed to register task with {DBTask.Exception}");
}
else
{
//giscrore now updated
}
}
and this is for the game scene also same function for update higscore like on main menu.
void Start()
{
InitializeFirebase();
YetGameOver();
//GetScore
score = 0;
scoreText.text = score.ToString("00000");
//highScoreText.text = "HI :" + PlayerPrefs.GetInt("highscore", 0).ToString("00000");
maxTime = .1f;
}
void InitializeFirebase()
{
auth = FirebaseAuth.DefaultInstance;
DBreference = FirebaseDatabase.DefaultInstance.RootReference;
auth.StateChanged += AuthStateChanged;
AuthStateChanged(this, null);
}
void AuthStateChanged(object sender, System.EventArgs eventArgs)
{
//This checks if the user (your local user) is the same as the one from the auth
if (auth.CurrentUser != User)
{
//this seems the same, but user could have been null before
bool signedIn = User != auth.CurrentUser && auth.CurrentUser != null;
if (!signedIn && User != null)
{
Debug.Log("Signed out " + User.UserId);
}
//this is important step, this user is the one you should be working with
User = auth.CurrentUser;
if (signedIn)
{
Debug.Log("Signed in " + User.UserId);
StartCoroutine(LoadUserData());
//highScoreMainMenu.text = PlayerPrefs.GetInt("highscore").ToString("0000000");
}
}
}
public void GameOver()
{
SaveData();
StartCoroutine(WaitToDeath());
}
public void SaveData()
{
Debug.Log("Saved");
StartCoroutine(UpdateHighScore(PlayerPrefs.GetInt("highscore", 0)));
}
public IEnumerator WaitToDeath()
{
_CamShake.instance.shouldShake = true;
DeathSound();
gameOverPanel.SetActive(true);
endHighScoreText.text = "HI :" + PlayerPrefs.GetInt("highscore").ToString("0000000");
scoreText.gameObject.SetActive(false);
highScoreText.gameObject.SetActive(false);
yield return new WaitForSeconds(.1f);
//_AdmobAds.instance.ShowInterstitialAd();
isStarted = false;
isGameOver = true;
Time.timeScale = 0;
}
//same function like main menu
public IEnumerator UpdateHighScore(int _highScore)
{
var DBTask = DBreference.Child("users").Child(auth.CurrentUser.UserId).Child("highscore").SetValueAsync(_highScore);
yield return new WaitUntil(predicate: () => DBTask.IsCompleted);
if (DBTask.Exception != null)
{
Debug.LogWarning(message: $"Failed to register task with {DBTask.Exception}");
}
else
{
//highscore are now updated
}
}
public IEnumerator LoadUserData()
{
//Get the currently logged in user data
var DBTask = DBreference.Child("users").Child(User.UserId).GetValueAsync();
yield return new WaitUntil(predicate: () => DBTask.IsCompleted);
if (DBTask.Exception != null)
{
Debug.LogWarning(message: $"Failed to register task with {DBTask.Exception}");
}
else if (DBTask.Result.Value == null)
{
//No data exists yet
highScoreText.text = "0";
endHighScoreText.text = "0";
}
else
{
//Data has been retrieved
DataSnapshot snapshot = DBTask.Result;
highScoreText.text = endHighScoreText.text = snapshot.Child("highscore").Value.ToString();
}
}
You don't capture child in this method. You can only capture all the data from the root. So, simply remove the child. It will work.
my solution to firebase realtime database not working was that my google-services.json wasnt updated after adding the google-services.json from firebase auth. firebase, project overview cogwheel, project settings, json on that page somewhere.
and my problem with firebase auth before that was that i didnt set the Client ID in unity, window, google play games, setup, android setup.. get the id from google play console, play games services, setup & management, configuration, and figure out how to add game server credentials
i'm trying to get the current location of the phone and then set the google maps view accordingly but if i have the gps off, it will just thrown an error instead of asking me to turn it on.
i have the access fine and coarse location permissions on android.
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
var location = await Geolocation.GetLocationAsync(new GeolocationRequest(GeolocationAccuracy.Best));
I suggest you can detect whether GPS Location enable before you use it. I do one sample to check GPS isenbale or disable on Android platform for Xamarin.forms, the setting GPS location will open if GPS location disable.
Creating interface IGpsDependencyService in shared code.
public interface IGpsDependencyService
{
void OpenSettings();
bool IsGpsEnable();
}
Implement this interface on Android Platform, the platform implementations must be registered with the DependencyService, so that Xamarin.Forms can locate them at runtime.
[assembly: Dependency(typeof(GpsDependencyService))]
namespace FormsSample.Droid
{
public class GpsDependencyService : IGpsDependencyService
{
public bool IsGpsEnable()
{
LocationManager locationManager = (LocationManager)Android.App.Application.Context.GetSystemService(Context.LocationService);
return locationManager.IsProviderEnabled(LocationManager.GpsProvider);
}
public void OpenSettings()
{
Intent intent = new Intent(Android.Provider.Settings.ActionLocat‌​ionSourceSettings);
intent.SetFlags(ActivityFlags.ClearTop | ActivityFlags.NewTask);
try
{
Android.App.Application.Context.StartActivity(intent);
}
catch (ActivityNotFoundException activityNotFoundException)
{
System.Diagnostics.Debug.WriteLine(activityNotFoundException.Message);
Android.Widget.Toast.MakeText(Android.App.Application.Context, "Error: Gps Activity", Android.Widget.ToastLength.Short).Show();
}
}
}
}
To check GPS location, I use Plugin.Permissions to check location permission.
private async void Button_OnClicked(object sender, EventArgs e)
{
//var results = await CrossGeolocator.Current.GetPositionAsync(TimeSpan.FromSeconds(10));
//LabelGeolocation.Text = "Lat: " + results.Latitude + " Long: " + results.Longitude;
bool IsGpsEnable = Xamarin.Forms.DependencyService.Get<IGpsDependencyService>().IsGpsEnable();
if (!IsGpsEnable)
{
DisplayAlert("Location service Denied", "Can not continue,please go to setting to open location service and try again.", "OK");
Xamarin.Forms.DependencyService.Get<IGpsDependencyService>().OpenSettings();
}
else
{
try
{
var status = await CrossPermissions.Current.CheckPermissionStatusAsync(Permission.Location);
if (status != PermissionStatus.Granted)
{
if (await CrossPermissions.Current.ShouldShowRequestPermissionRationaleAsync(Permission.Location))
{
await DisplayAlert("Need location", "Gunna need that location", "OK");
}
var results = await CrossPermissions.Current.RequestPermissionsAsync(Permission.Location);
status = results[Permission.Location];
}
if (status == PermissionStatus.Granted)
{
var results = await CrossGeolocator.Current.GetPositionAsync(TimeSpan.FromSeconds(10));
LabelGeolocation.Text = "Lat: " + results.Latitude + " Long: " + results.Longitude;
}
else if (status != PermissionStatus.Unknown)
{
await DisplayAlert("Location Denied", "Can not continue, try again.", "OK");
}
}
catch (Exception ex)
{
LabelGeolocation.Text = "Error: " + ex;
}
}
}
About checking GPS location on ios platform, you can take a look:
https://github.com/xamarin/Essentials/issues/1257
I have a Xamarin.Forms application with a few renewable subscriptions. I am using the InAppBilling plugin to purchase these subscriptions. Now my questions is: (which I already asked in this post) How can I check if the renewable subscription is active? Thanks in advance.
Through the document, you can check the status by
var connected = await billing.ConnectAsync(ItemType.Subscription);:
Here is the example:
public async Task<bool> PurchaseItem(string productId, string payload)
{
var billing = CrossInAppBilling.Current;
try
{
var connected = await billing.ConnectAsync(ItemType.InAppPurchase);
if (!connected)
{
//we are offline or can't connect, don't try to purchase
return false;
}
//check purchases
var purchase = await billing.PurchaseAsync(productId, ItemType.InAppPurchase, payload);
//possibility that a null came through.
if(purchase == null)
{
//did not purchase
}
else if(purchase.State == PurchaseState.Purchased)
{
//purchased!
}
}
catch (InAppBillingPurchaseException purchaseEx)
{
//Billing Exception handle this based on the type
Debug.WriteLine("Error: " + purchaseEx);
}
catch (Exception ex)
{
//Something else has gone wrong, log it
Debug.WriteLine("Issue connecting: " + ex);
}
finally
{
await billing.DisconnectAsync();
}
enter image description hereHi Thanks in advance i have facing a problem in my xamarin forms ios. Problem is that when i want to purchase product it thrown an exception that cannot to connect to itune store my same code in working fine on xamarin forms android.My code for restore purchases is working fine.
Here is my code for make purchases
private async Task<bool> MakePurchase(string ProductId, string Payload)
{
if (!CrossInAppBilling.IsSupported)
{
return false;
}
var billing = CrossInAppBilling.Current;
try
{
var connected = await billing.ConnectAsync();
if (!connected)//Couldn't connect to billing, could be offline,
alert user
{
DependencyService.Get<IToast>().Show("Something went
wrong or you may not connected with the internet!");
return false;
}
//try to purchase item
var purchase = await billing.PurchaseAsync(ProductId,
ItemType.InAppPurchase, Payload);
if (purchase == null)
{
return false;
}
else
{
//Purchased, save this information
var responseId = purchase.Id;
var responseToken = purchase.PurchaseToken;
var state = purchase.State;
return true;
}
}
catch (InAppBillingPurchaseException ex)
{
if (ex.PurchaseError == PurchaseError.DeveloperError)
{
DependencyService.Get<IToast>().Show("DeveloperError");
ex.Message.ToString();
}
else if (ex.PurchaseError == PurchaseError.AlreadyOwned)
{
DependencyService.Get<IToast>().Show("AlreadyOwned");
return true;
}
else if(ex.PurchaseError == PurchaseError.BillingUnavailable)
{
DependencyService.Get<IToast>
().Show("BillingUnavailable");
return false;
}
else if(ex.PurchaseError == PurchaseError.InvalidProduct)
{
DependencyService.Get<IToast>().Show("InvalidProduct");
return false;
}
else if(ex.PurchaseError == PurchaseError.ItemUnavailable)
{
DependencyService.Get<IToast>().Show("ItemUnavailable");
return false;
}
else if(ex.PurchaseError == PurchaseError.GeneralError)
{
DependencyService.Get<IToast>().Show("General Error");
return false;
}
//Something bad has occurred, alert user
}
finally
{
//Disconnect, it is okay if we never connected
await billing.DisconnectAsync();
}
return false;
}
Complete Scenario
I have an add icon on one page, it will show camera and gallery options when tap. If choose the camera, I will open another content page and open camera there. But the captured picture is not showing in the UI. Same for the gallery, selected image from the gallery is not showing in UI. This feature is working fine in android and not working in IOS.
Codes
When click add icon
string action = await DisplayActionSheet(null, "Cancel", null, "Camera", "Gallery");
if (action == "Camera")
{
await Navigation.PushModalAsync(new NewTweetPage("Camera"));
}
else if (action == "Gallery")
{
await Navigation.PushModalAsync(new NewTweetPage("Gallery"));
}
When entering next page
public NewTweetPage(String medium)
{
InitializeComponent();
if (medium == "Camera" )
{
OpenMyCamera();
}
else if(medium == "Gallery")
{
OpenMygallery();
}
}
public async void OpenMyCamera()
{
try
{
await CrossMedia.Current.Initialize();
if (!CrossMedia.Current.IsCameraAvailable || !CrossMedia.Current.IsTakePhotoSupported)
{
await DisplayAlert("Camera", "No camera available.", "OK");
return;
}
_mediaFile = await CrossMedia.Current.TakePhotoAsync(new Plugin.Media.Abstractions.StoreCameraMediaOptions
{
Directory = "Sample",
Name = "test.jpg",
AllowCropping = true
});
if (_mediaFile == null)
return;
tweetPicture.Source = ImageSource.FromStream(() =>
{
isPicture = true;
return _mediaFile.GetStream();
});
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("Exception:>" + ex);
}
}
public async void OpenMygallery()
{
try
{
await CrossMedia.Current.Initialize();
if (!CrossMedia.Current.IsPickPhotoSupported)
{
await DisplayAlert("Gallery", ":( No photos available.", "OK");
return;
}
_mediaFile = await CrossMedia.Current.PickPhotoAsync();
if (_mediaFile == null)
return;
tweetPicture.Source = ImageSource.FromStream(() =>
{
isPicture = true;
return _mediaFile.GetStream();
});
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("Exception:>" + ex);
}
}
The same code is working fine in profile page part, but in that case, there is no page navigation, everything is happening on the same page.
Don't know what is the problem with the current code, please help me to solve this issue.
Putting navigation commands in the constructor can cause issues. I would recommend putting them in the OnAppearing override. Also, instead of having a try...catch around a large section of code, you should handle null-checks or similar in code.