I am trying to simple connect to Google play billing & access details such as the title & the pricing but I've been unable to do so.
What I've done so far (step-by-step):
(Added the IAP details on Google play console as well as published the app)
Added the Billing permission in the Manifest file -> <uses-permission android:name="com.android.vending.BILLING" />
Added the Billing library (v.4.0) -> implementation 'com.android.billingclient:billing:4.0.0'
Initialized the BillingClient in MainActivity & on trying to connect to it, I get no response from the onBillingSetupFinished callback method
private void initializeBillingClient(Context context) {
billingClient = BillingClient.newBuilder(context)
.enablePendingPurchases()
.setListener(this)
.build();
connectToGooglePlayBilling(context);
}
private void connectToGooglePlayBilling(Context context) {
billingClient.startConnection(new BillingClientStateListener() {
#Override
public void onBillingServiceDisconnected() {
connectToGooglePlayBilling(context);
}
#Override
public void onBillingSetupFinished(#NonNull BillingResult billingResult) {
Toast.makeText(context, "connected to google play billing", Toast.LENGTH_SHORT).show();
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
//I should then fetch & display premium content details
}
}
});
}
Weird enough I noticed that when I enable the Battery Saving Mode (I'm using a Samsung device) the onBillingSetupFinished method gets called as the toast is shown.
What am I doing incorrect & also if anyone can explain why enabling Battery save mode suddenly makes it work?
Make sure that onBillingSetupFinished is called in UI thread.
Try to use runOnUiThread to show the message:
runOnUiThread(() ->
Toast.makeText(context, "connected to google play billing", Toast.LENGTH_SHORT).show());
Related
I've integrated UnityAds on my Android app (that is not published yet).
I get app id and placement id from database on my server.
App id and placement id are correct, I've copied and pasted about 30 times for be sure of it.
So, when I try to get an ad in test mode, it give me the INVALID_ARGUMENT error.
Here an explaination of the error code by Unity, but as you can see it is a little generic.
I have an object that simply represents an ad service (like admob, FAN, inmobi etc)
In this case the object is called advert, and here it's how I show an ad with Unity:
protected void showUnity(){
UnityAds.initialize(this, advert.getApiKey(), true); //advert.getApiKey() returns the app id
UnityAds.addListener(new IUnityAdsListener() {
#Override
public void onUnityAdsReady(String s) {
Log.i(TAG, "onUnityAdsReady "+s);
if(s.equals(advert.getUnitId()) && !unityReady)
UnityAds.show(ActivityAd.this, advert.getUnitId()); //advert.getUnitId() returns the placement id
}
#Override
public void onUnityAdsStart(String s) {
Log.i(TAG, "onUnityAdsStart "+s);
unityReady = true;
}
#Override
public void onUnityAdsFinish(String s, UnityAds.FinishState finishState) {
if (finishState.compareTo(UnityAds.FinishState.COMPLETED) == 0) {
onAdReward(); //my callback for reward
} else if (finishState.compareTo(UnityAds.FinishState.SKIPPED) == 0) {
onAdClosed(); //my callback for ad close
} else if (finishState.compareTo(UnityAds.FinishState.ERROR) == 0) {
onAdError(finishState.toString()); //my callback for errors
}
}
#Override
public void onUnityAdsError(UnityAds.UnityAdsError unityAdsError, String s) {
onAdError(unityAdsError.toString()); //my callback for errors, here results INVALID_ARGUMENT error
}
});
}
Does anyone know what is wrong? Thanks in advance
If you check the callback closely the onUnityAdsError has 2 params, first provides the error code and the second param provides you information about what went wrong.
#Override
public void onUnityAdsError(UnityAds.UnityAdsError unityAdsError, String reason) {
onAdError(unityAdsError.toString()); //my callback for errors, here results INVALID_ARGUMENT error
}
So just check the reason and you should be able to find out what is going wrong in your integration.
Here are some methods which you can follow to solve this INVALID_ARGUMENT problem
1. Make sure you are implementing the right Initialization code in your app. There are 2 types of Initialization.
Only Unity ads Initialization
Mediation Initialization
and both methods have their own banner, interstitial, and rewarded ad code.
2. Make sure you enable test mode as Boolean. (i.e: private Boolean testMode = true;) (make sure to do false this before publish on store)
3. You can add your mobile phone as a test device to get test ads on your phone forcefully. for this, you have to first copy the Ad ID of your device. For that, go to your mobile settings > Google > Ads > This device's advertising ID. copy that ID and go to unity dashboard > Monetization > Testing > Add Test Device. Add your device Ads ID here with any name, and now you will be able to see test ads on the device.
Facebook sdk can only login either with read permission or publish permission, but what if I want both? I only need read permission when logging in to register a user or to authenticate it.
So I thought the right way was to first signIn with SignInMode.READ and then signIn again but now with SignInMode.PUBLISH
The problem is when I try to sign in the second time, it just load the accessToken, I tried to put facebook.signOut() in between but nothing...
facebook.signIn(SignInMode.READ, readPermissions, new GDXFacebookCallback<SignInResult>() {
#Override
public void onSuccess(SignInResult result) {
// Login successful
clientManager.login(result.getAccessToken().getToken());
}
#Override
public void onError(GDXFacebookError error) {
// Error handling
}
#Override
public void onCancel() {
}
#Override
public void onFail(Throwable t) {
}
});
//after the user log ins, the app ask for publish permissions, and I
// try to get it by logging in again but now wit `SignInMode.PUBLISH` like so....
facebook.signIn(SignInMode.PUBLISH, publishPermissions, new GDXFacebookCallback<SignInResult>() {
#Override
public void onSuccess(SignInResult result) {
// Login successful
clientManager.login(result.getAccessToken().getToken());
}
#Override
public void onError(GDXFacebookError error) {
// Error handling
}
#Override
public void onCancel() {
}
#Override
public void onFail(Throwable t) {
}
});
What im trying to ask here is how I get an accessToken capable of publishing after I singin with SignInMode.READ?
I am the developer of gdx-facebook extension.
You are doing it correctly, sign in with read permissions first and then follow with publish permission.
There are 2 cases:
You did not authorize the app yet or did authorize the app with less permissions than the ones you request.
-> you get a new token, which contains all existing and newly requested permissions. All existing tokens become invalid.
You already have granted all the requested permissions.
-> you get the latest token.
What im trying to ask here is how I get an accessToken capable of
publishing after I singin with SignInMode.READ?
Just request the permission you need, you get either a new upgraded token which contains old and new permissions or an old one which already contains the permissions.
It is likely that you already granted the permissions and thats why the token does not change. Look in your FB settings -> apps to see which permissions you granted.
Tokens often look similar the first 10-20 characters. Don't get confused by that.
I press a custom button to let the user give publish permissions to my Android app(Facebook SDK 4.18.0). It works. It goes to the facebook fragment in which prompts the user to give publish permissions to the app and then it comes back to the activity in which the button was pressed. The thing is that I've run the debugger and it never goes through the callback functions which I need to do what I need to do after the user has accepted.
The listener:
permissionButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
permissionButton.startAnimation(rotation);
JSONObject userCredentials = new JSONObject();
//ask for facebook publishing permissions
if(facebook_connected) {
CallbackManager callbackManager = CallbackManager.Factory.create();
facebookLoginWithPublishPermissions(callbackManager);
}
}
});
And my custom function 'facebookLoginWithPublishPermissions' which intends to get a token with publish permissions(do I need a new one or the old one works now that I've been granted the "publish_actions" permissions for this user, cause I'm already logged in and have the access_token but only with the basic permissions using firebase ui auth module for android)? If I go to the facebook page of the user I can see that the proper permissions has been set so everything OK regarding that. The code:
private void facebookLoginWithPublishPermissions(CallbackManager callbackManager) {
LoginManager.getInstance().registerCallback(callbackManager, new FacebookCallback<LoginResult>() {
#Override
public void onSuccess(LoginResult loginResult) {
Log.d("FACEBOOK_LOGIN_RESULT", "facebook login success");
Log.d("LOGIN_RESULT", loginResult.getAccessToken().getToken());
}
#Override
public void onCancel() {
Log.d("FACEBOOK_LOGIN_RESULT", "facebook login canceled");
}
#Override
public void onError(FacebookException exception) {
Log.e("FACEBOOK_LOGIN_RESULT", "facebook login error");
exception.printStackTrace();
}
});
LoginManager.getInstance().logInWithPublishPermissions((Activity) mContext, Arrays.asList("publish_actions"));
}
1-How can I get a new token(in case is need to renew the token if you ask for additional permissions)?
2-How can I make the callback function work to perform actions after the publish_actions permission has been granted??
Thank you very much for your help!
EDIT: For future reference regarding Facebook API/Permission politics, Facebook provides a DIFFERENT Token from the one you are first provided when you login with the basic permissions. They have to review your app to see if you are using the permissions ONLY when you need it and also to check if you are not asking those permissions "just in case" to avoid unnecessary requests to their server. Also they want to make sure that you let the user know what's happening, they must accept the publishing permission, that's why you have to do 2 requests, first the read permissions the first time you connect to facebook, and a second request for writing(publish_actions) permissions. You CANNOT ask for publishing permissions unless you already asked for reading permissions first....
Are you adding the following method to your code ?
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
callbackManager.onActivityResult(requestCode, resultCode, data);
Log.d("TAG: 5. ", "I´m at onActivity result");
}
I am successfully integrating pushwoosh in my android application.I am using GCM.here is my code:
oncreate:
PushManager pushManager = new PushManager(this,
App_id, Sender_id);
pushManager.onStartup(savedInstanceState, this);
checkMessage(getIntent());
#Override
protected void onNewIntent(Intent intent)
{
super.onNewIntent(intent);
setIntent(intent);
checkMessage(intent);
setIntent(new Intent());
}
private void checkMessage(Intent intent)
{
if (null != intent)
{
if (intent.hasExtra(PushManager.PUSH_RECEIVE_EVENT))
{
showMessage("push message is " + intent.getExtras().getString(PushManager.PUSH_RECEIVE_EVENT));
}
else if (intent.hasExtra(PushManager.REGISTER_EVENT))
{
showMessage("register");
}
else if (intent.hasExtra(PushManager.UNREGISTER_EVENT))
{
showMessage("unregister");
}
else if (intent.hasExtra(PushManager.REGISTER_ERROR_EVENT))
{
showMessage("register error");
}
else if (intent.hasExtra(PushManager.UNREGISTER_ERROR_EVENT))
{
showMessage("unregister error");
}
}
}
private void showMessage(String message)
{
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
}
But when i am running my app its showing register error.I can not find what's the problem?In logcat it shows Messaging registration error:Account Missing.Please Help
Account Missing error occurred when there is no Google account on the phone. Here is the official explanation from GCM page.
There is no Google account on the phone. The Android application
should ask the user to open the account manager and add a Google
account. Fix on the device side.
For more information check here.
If you have "account missing" error you need to configure your Google Account on the device.
Open the account manager and add a Google account there.
Also make sure you can to login to the Android Marketplace from the device.
I am also facing the problem, but as far I understood that the appid and senderId is wrong here, try with the new one.
Lets say my Android App version 0.1 is installed currently on the User's phone. Everytime they launch my App I want to check if there is a different version available in the Android Market let's say this version is 0.2. If there is a mismatch between these two version I want to show a dialog box prompting the user to Upgrade the App.
I totally understand there exists a notification procedure from Android Market itself to the users but as far as my Analytics data is concerned it is not very effective in reminding users to upgrade to the new version of the App.
Any insight would be very helpful. Thanks StackOverflowers, you guys rock!
As of 2019 the best way for updating your app is to use In-app updates provided by Play Core library (1.5.0+). It works for Lollipop and newer, but let's be fair, Kit-Kat is less than 7% as of today and soon will be gone forever. You can safely run this code on Kit-Kat without version checks, it won't crash.
Official documentation: https://developer.android.com/guide/app-bundle/in-app-updates
There are two types of In-app updates: Flexible and Immediate
Flexible will ask you nicely in a dialog window:
whereas Immediate will require you to update the app in order to continue using it with full-screen message (this page can be dismissed):
Important: for now, you can't choose which type of update to roll out in your App Release section on Developer Play Console. But apparently, they will give us that option soon.
From what I've tested, currently, we're getting both types available in onSuccessListener.
So let's implement both types in our code.
In module build.gradle add the following dependency:
dependencies {
implementation 'com.google.android.play:core:1.6.1'//for new version updater
}
In MainActivity.class:
private static final int REQ_CODE_VERSION_UPDATE = 530;
private AppUpdateManager appUpdateManager;
private InstallStateUpdatedListener installStateUpdatedListener;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
checkForAppUpdate();
}
#Override
protected void onResume() {
super.onResume();
checkNewAppVersionState();
}
#Override
public void onActivityResult(int requestCode, final int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
switch (requestCode) {
case REQ_CODE_VERSION_UPDATE:
if (resultCode != RESULT_OK) { //RESULT_OK / RESULT_CANCELED / RESULT_IN_APP_UPDATE_FAILED
L.d("Update flow failed! Result code: " + resultCode);
// If the update is cancelled or fails,
// you can request to start the update again.
unregisterInstallStateUpdListener();
}
break;
}
}
#Override
protected void onDestroy() {
unregisterInstallStateUpdListener();
super.onDestroy();
}
private void checkForAppUpdate() {
// Creates instance of the manager.
appUpdateManager = AppUpdateManagerFactory.create(AppCustom.getAppContext());
// Returns an intent object that you use to check for an update.
Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();
// Create a listener to track request state updates.
installStateUpdatedListener = new InstallStateUpdatedListener() {
#Override
public void onStateUpdate(InstallState installState) {
// Show module progress, log state, or install the update.
if (installState.installStatus() == InstallStatus.DOWNLOADED)
// After the update is downloaded, show a notification
// and request user confirmation to restart the app.
popupSnackbarForCompleteUpdateAndUnregister();
}
};
// Checks that the platform will allow the specified type of update.
appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) {
// Request the update.
if (appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
// Before starting an update, register a listener for updates.
appUpdateManager.registerListener(installStateUpdatedListener);
// Start an update.
startAppUpdateFlexible(appUpdateInfo);
} else if (appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE) ) {
// Start an update.
startAppUpdateImmediate(appUpdateInfo);
}
}
});
}
private void startAppUpdateImmediate(AppUpdateInfo appUpdateInfo) {
try {
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo,
AppUpdateType.IMMEDIATE,
// The current activity making the update request.
this,
// Include a request code to later monitor this update request.
MainActivity.REQ_CODE_VERSION_UPDATE);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
}
private void startAppUpdateFlexible(AppUpdateInfo appUpdateInfo) {
try {
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo,
AppUpdateType.FLEXIBLE,
// The current activity making the update request.
this,
// Include a request code to later monitor this update request.
MainActivity.REQ_CODE_VERSION_UPDATE);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
unregisterInstallStateUpdListener();
}
}
/**
* Displays the snackbar notification and call to action.
* Needed only for Flexible app update
*/
private void popupSnackbarForCompleteUpdateAndUnregister() {
Snackbar snackbar =
Snackbar.make(drawerLayout, getString(R.string.update_downloaded), Snackbar.LENGTH_INDEFINITE);
snackbar.setAction(R.string.restart, new View.OnClickListener() {
#Override
public void onClick(View view) {
appUpdateManager.completeUpdate();
}
});
snackbar.setActionTextColor(getResources().getColor(R.color.action_color));
snackbar.show();
unregisterInstallStateUpdListener();
}
/**
* Checks that the update is not stalled during 'onResume()'.
* However, you should execute this check at all app entry points.
*/
private void checkNewAppVersionState() {
appUpdateManager
.getAppUpdateInfo()
.addOnSuccessListener(
appUpdateInfo -> {
//FLEXIBLE:
// If the update is downloaded but not installed,
// notify the user to complete the update.
if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) {
popupSnackbarForCompleteUpdateAndUnregister();
}
//IMMEDIATE:
if (appUpdateInfo.updateAvailability()
== UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
// If an in-app update is already running, resume the update.
startAppUpdateImmediate(appUpdateInfo);
}
});
}
/**
* Needed only for FLEXIBLE update
*/
private void unregisterInstallStateUpdListener() {
if (appUpdateManager != null && installStateUpdatedListener != null)
appUpdateManager.unregisterListener(installStateUpdatedListener);
}
And we're done!
Testing.
Please read the docs so you will know how to test it properly with test tracks on Google Play.
Long story short:
Sign your app with the release certificate and upload it to the one of publishing tracks in Developer Play Console under App Releases (alpha/beta/other custom closed track).
In your release track page in the Manage Testers section create and add a list of testers and make sure you checked the checkbox! - this step is optional since your developer account email is also a testers account and you can use it for testing.
Under the list of testers you will find "Opt-in URL" - copy this url and give it to your testers or open it yourself. Go to that page and accept proposition for testing. There will be a link to the app. (You won't be able to search for the app in Play Store so bookmark it)
Install the app on your device by that link.
In build.gradle increment the version of defaultConfig { versionCode k+1 } and build another signed apk Build > Generate Signed Bundle / APK... and upload it to your publishing track.
Wait for... 1 hour? 2 hours? or more before it will be published on the track.
CLEAR THE CACHE of Play Store app on your device. The problem is that Play app caches details about installed apps and their available updates so you need to clear the cache. In order to do that take two steps:
7.1. Go to Settings > App > Google PLay Store > Storage > Clear Cache.
7.2. Open the Play Store app > open main menu > My apps & games > and there you should see that your app has a new update.
If you don't see it make sure that your new update is already released on the track (go to your bookmarked page and use it to open your apps listing on the Play Store to see what version is shown there). Also, when your update will be live you'll see a notification on the top right of your Developer Play Console (a bell icon will have a red dot).
Hope it helps.
The Android Market is a closed system and has only an unofficial api that might break at any point of time.
Your best bet is simply to host a file(xml, json or simple text) on a web server of yours in which you just have to update the current version of your app when you post it on the Market.
Your app will then only have to fetch that file at startup, checks wether currently installed app has a lower version number and displays a dialog to warn the user he is lagging.
Another option you can use, if you want to avoid having your backend server to store your current app version like it's suggested in the accepted answer, is to use Google Tag Manager (GTM).
If you're already using the Google Analytics SDK, you have the GTM in it also.
In GTM you can define a value in the container for your app that specifies your latest released version. For example:
{
"latestAppVersion": 14,
...
}
Then you can query that value when your app starts and show the user update dialog reminder if there's a newer version.
Container container = TagManager.getInstance(context).openContainer(myContainerId);
long latestVersionCode = container.getLong("latestAppVersion");
// get currently running app version code
PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
long versionCode = pInfo.versionCode;
// check if update is needed
if(versionCode < latestVersionCode) {
// remind user to update his version
}
Take a look at this library that you can use to query the Android Market API
http://code.google.com/p/android-market-api/
You can use this Android Library: https://github.com/danielemaddaluno/Android-Update-Checker. It aims to provide a reusable instrument to check asynchronously if exists any newer released update of your app on the Store.
It is based on the use of Jsoup (http://jsoup.org/) to test if a new update really exists parsing the app page on the Google Play Store:
private boolean web_update(){
try {
String curVersion = applicationContext.getPackageManager().getPackageInfo(package_name, 0).versionName;
String newVersion = curVersion;
newVersion = Jsoup.connect("https://play.google.com/store/apps/details?id=" + package_name + "&hl=en")
.timeout(30000)
.userAgent("Mozilla/5.0 (Windows; U; WindowsNT 5.1; en-US; rv1.8.1.6) Gecko/20070725 Firefox/2.0.0.6")
.referrer("http://www.google.com")
.get()
.select("div[itemprop=softwareVersion]")
.first()
.ownText();
return (value(curVersion) < value(newVersion)) ? true : false;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
And as "value" function the following (works if values are beetween 0-99):
private long value(String string) {
string = string.trim();
if( string.contains( "." )){
final int index = string.lastIndexOf( "." );
return value( string.substring( 0, index ))* 100 + value( string.substring( index + 1 ));
}
else {
return Long.valueOf( string );
}
}
If you want only to verify a mismatch beetween versions, you can change:
"value(curVersion) < value(newVersion)" with "value(curVersion) != value(newVersion)"
For prompting Android App User to Update App if current version is not equal to market version, you should first check the app version on the market and compare it with the version of the app on the device. If they are different, it may be an update available. In this post I wrote down the code for getting the current version of market and current version on the device and compare them together. I also showed how to show the update dialog and redirect the user to the update page. Please visit this link: https://stackoverflow.com/a/33925032/5475941
My working Kotlin code for force App update:
const val FLEXIABLE_UPADTE: Int = 101
const val FORCE_UPDATE: Int = 102
const val APP_UPDATE_CODE: Int = 500
override fun onCreate {
// Get updateType from Webservice.
updateApp(updateType)
}
private fun updateApp(statusCode: Int) {
appUpdateManager = AppUpdateManagerFactory.create(this #MainActivity)
val appUpdateInfoTask = appUpdateManager ? .appUpdateInfo
appUpdateInfoTask ? .addOnSuccessListener {
appUpdateInfo - >
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) {
if ((statusCode == Constants.FORCE_UPDATE))
appUpdateManager ? .startUpdateFlowForResult(
appUpdateInfo, AppUpdateType.IMMEDIATE, this, Constants.APP_UPDATE_CODE
)
else if (statusCode == Constants.FLEXIABLE_UPADTE)
appUpdateManager ? .startUpdateFlowForResult(
appUpdateInfo, AppUpdateType.FLEXIBLE, this, Constants.FLEXIABLE_UPADTE
)
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent ? ) {
try {
if (requestCode == Constants.APP_UPDATE_CODE && resultCode == Activity.RESULT_OK) {
if (resultCode != RESULT_OK) {
appUpdateCompleted()
}
}
} catch (e: java.lang.Exception) {
}
}
private fun appUpdateCompleted() {
Snackbar.make(
findViewById(R.id.activity_main_layout),
"An update has just been downloaded.",
Snackbar.LENGTH_INDEFINITE
).apply {
setAction("RESTART") {
appUpdateManager.completeUpdate()
}
setActionTextColor(resources.getColor(R.color.snackbar_action_text_color))
show()
}
}