I implemented in app update from google services
protected void checkUpdate() {
AppUpdateManager appUpdateManager = AppUpdateManagerFactory.create(this);
Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();
appUpdateInfoTask.addOnFailureListener(err -> onUpdateError());
appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) {
try {
appUpdateManager.startUpdateFlowForResult(appUpdateInfo, AppUpdateType.IMMEDIATE, this, UPDATE_REQUEST_CODE);
} catch (IntentSender.SendIntentException e) {
onUpdateError();
}
} else {
onNoUpdate();
}
});
}
... implementation 'com.google.android.play:core:1.10.0'
It works just fine on most of the phones. But i had some where it does not trigger any callback (addOnFailureListener,addOnSuccessListener or addOnCompleteListener)
First 2 lines are executed then it jump over callbacks
One of the phone :
play store 27.1.14-21
android 9
Thx
Related
so I'm working on a In-Apps Update for my application that already published in Play Store. I follow the documentation and try to test it by lowering my version code then run it, but after finish downloading the app, it supposed to re-open the new version app from but it still re-open my current app which is the lower version. So it's like download for nothing.
I search and got some solution to try like :
installing using apk-release (didn't work)
trying another code for in-app update (still didn't work)
Here's my code :
I put it in my Login page
private val appUpdateManager: AppUpdateManager by lazy { AppUpdateManagerFactory.create(this) }
private val appUpdatedListener: InstallStateUpdatedListener by lazy {
object : InstallStateUpdatedListener {
override fun onStateUpdate(installState: InstallState) {
when {
installState.installStatus() == InstallStatus.DOWNLOADED -> popupSnackbarForCompleteUpdate()
installState.installStatus() == InstallStatus.INSTALLED -> appUpdateManager.unregisterListener(this)
else -> Log.i("TAG", "onStateUpdate: InstallStateUpdatedListener: state: %s"+installState.installStatus())
}
}
}
}
private fun checkForAppUpdate() {
// Returns an intent object that you use to check for an update.
val appUpdateInfoTask = appUpdateManager.appUpdateInfo
// Checks that the platform will allow the specified type of update.
appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
// This example applies an immediate update. To apply a flexible update
// instead, pass in AppUpdateType.FLEXIBLE
&& appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)
) {
// Request the update.
appUpdateManager.startUpdateFlowForResult(
// Pass the intent that is returned by 'getAppUpdateInfo()'.
appUpdateInfo,
// Or 'AppUpdateType.FLEXIBLE' for flexible updates.
AppUpdateType.IMMEDIATE,
// The current activity making the update request.
this,
// Include a request code to later monitor this update request.
APP_UPDATE_REQUEST_CODE)
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == APP_UPDATE_REQUEST_CODE) {
if (resultCode != RESULT_OK) {
Toast.makeText(this,
"App Update failed, please try again on the next app launch.",
Toast.LENGTH_SHORT)
.show()
}
}
}
private fun popupSnackbarForCompleteUpdate() {
val snackbar = Snackbar.make(
findViewById(R.id.drawer_layout),
"An update has just been downloaded.",
Snackbar.LENGTH_INDEFINITE)
snackbar.setAction("RESTART") { appUpdateManager.completeUpdate() }
snackbar.setActionTextColor(ContextCompat.getColor(this, R.color.accent))
snackbar.show()
}
override fun onResume() {
super.onResume()
appUpdateManager
.appUpdateInfo
.addOnSuccessListener { appUpdateInfo ->
// If the update is downloaded but not installed,
// notify the user to complete the update.
if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) {
popupSnackbarForCompleteUpdate()
}
//Check if Immediate update is required
try {
if (appUpdateInfo.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
// If an in-app update is already running, resume the update.
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo,
AppUpdateType.IMMEDIATE,
this,
APP_UPDATE_REQUEST_CODE)
}
} catch (e: IntentSender.SendIntentException) {
e.printStackTrace()
}
}
}
Please help me, I already tried to fixing it for 3 days but still have none of the solution are work for me.
In my scenario, some user using Xiaomi Chinese MIUI ROMs device does not include PlayServices. And user installed the specific working GooglePlayServices manually from a third party Installer.
After Upgrade to the new MIUI version, that GooglePlayServices is not working anymore, but still exist in a user device.
In that case googleApiAvailability.isGooglePlayServicesAvailable(activity) still returns SUCCESS;
How can I check that installed GooglePlayServices is working properly or not?
private fun isGooglePlayServicesAvailable(activity: Activity): Boolean {
val googleApiAvailability = GoogleApiAvailability.getInstance()
val status = googleApiAvailability.isGooglePlayServicesAvailable(activity)
when (status) {
ConnectionResult.SUCCESS -> return true
ConnectionResult.SERVICE_DISABLED,
ConnectionResult.SERVICE_INVALID,
ConnectionResult.SERVICE_MISSING,
ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED -> return false
}
return true
}
Request to FusedLocationProviderClient or LocationManager
if (isGooglePlayServicesAvailable(this)) {
val task: Task<LocationSettingsResponse> =
settingsClient.checkLocationSettings(mLocationSettingsRequest)
task.addOnSuccessListener(this) { response ->
val states = response.locationSettingsStates
if (states.isLocationPresent) {
//Do something
startFusedLocationProviderClientService()
} else {
Log.d(TAG, "startLocationUpdates: ${states.toString()}")
}
}
task.addOnFailureListener(this, OnFailureListener { e ->
when ((e as ApiException).statusCode) {
LocationSettingsStatusCodes.RESOLUTION_REQUIRED -> {
Log.i(TAG, "Location settings are not satisfied. Attempting to upgrade " + "location settings ")
try {
val rae = e as ResolvableApiException
startIntentSenderForResult(rae.resolution.intentSender, REQUEST_CHECK_SETTINGS, null, 0, 0, 0, null)
} catch (sie: IntentSender.SendIntentException) {
Log.i(TAG, "PendingIntent unable to execute request.")
}
}
LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE -> {
val errorMessage = "Location settings are inadequate, and cannot be " + "fixed here. Fix in Settings."
Log.e(TAG, errorMessage)
}
else -> {
// For some device Technically GooglePlayServices is available but not functional
Log.e(TAG, "startLocationUpdates: err ${e.message.toString()}", e)
}
}
})
task.addOnCanceledListener(this, OnCanceledListener {
Log.d(TAG, "startLocationUpdates: OnCanceledListener")
})
} else {
//Non Play Services devices
nonPlayServicesLocationManager()
}
After a few minutes(3 minutes) later GoogleService returns as below:
2021-09-06 14:19:39.093 18455-19014/com.company.app.uat W/FA: Tasks have been queued for a long time
2021-09-06 14:21:21.305 18455-18455/com.company.app.uat E/GmsClientSupervisor: Timeout waiting for ServiceConnection callback com.google.android.gms.measurement.START
java.lang.Exception
at bu.handleMessage(:com.google.android.gms.dynamite_measurementdynamite#213016065#21.30.16 (100400-0):3)
at android.os.Handler.dispatchMessage(Handler.java:103)
at android.os.Looper.loop(Looper.java:224)
at android.app.ActivityThread.main(ActivityThread.java:7562)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
2021-09-06 14:21:21.307 18455-18455/com.company.app.uat D/FA: Service connection suspended
Check this maybe it works for you
public boolean isGooglePlayServicesAvailable(Activity activity) {
GoogleApiAvailability googleApiAvailability = GoogleApiAvailability.getInstance();
int status = googleApiAvailability.isGooglePlayServicesAvailable(activity);
if(status != ConnectionResult.SUCCESS) {
if(googleApiAvailability.isUserResolvableError(status)) {
googleApiAvailability.getErrorDialog(activity, status, 2404).show();
}
return false;
}
return true;
}
Just Add condition
if(isGooglePlayServicesAvailable()) {
Log.d("TAG","Play service availbable");
}else{
Log.d("TAG","Play service not availbable");
}
I'm working on an app which support subscription method my project is working completely fine and now I'm testing my app but the problem which struggling alot how to handle process if user unsubscribed and his subscription time expired and also what to know litle bit about aknowledge with no consumable and if user user unsubcribed in trial days or user unsubscribed in his validity period.
here is the code where purchase will get process
it will be called after query of skus where
i process method like purchased or pending purchased.....
.......
private fun processPurchases(purchasesResult: Set<Purchase>) =
CoroutineScope(Job() + Dispatchers.IO).launch {
// Log.d(LOG_TAG, "processPurchases called")
val validPurchases = HashSet<Purchase>(purchasesResult.size)
// Log.d(LOG_TAG, "processPurchases newBatch content $purchasesResult")
purchasesResult.forEach { purchase ->
if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
// subscriptionPrefs.setSubscriptionValue(true)
if(purchase.sku==PlayBillingUtils.MONTHLY_PACKAGE){
subscriptionPurchased.postValue("you have successfully subscribed to monthly package")
subscriptionPrefs.setSubscriptionValue(false)
}
else if (purchase.sku == PlayBillingUtils.THREE_MONTH_PACKAGE){
purchasingMutableLiveData.postValue("you have sucessfully subscribed to 3 month package")
subscriptionPrefs.setSubscriptionValue(false)
}
if (isSignatureValid(purchase)) {
validPurchases.add(purchase) }
} else if (purchase.purchaseState == Purchase.PurchaseState.PENDING) {
purchasingMutableLiveData.postValue("Purchase Pending")
}
}
// here the token get acknowledge
acknowledgeNonConsumablePurchasesAsync(purchasesResult.toList())
}
Purchase all purchase Update will be invoked here but there is no method which will handle future call like when user unsubscribed or time expired want to know how to solve this problem
override fun onPurchasesUpdated(billingResult: BillingResult?, purchasemutableList: MutableList<Purchase>?) {
billingResult?.let {
when (billingResult.responseCode) {
BillingClient.BillingResponseCode.OK -> {
// will handle server verification, consumables, and updating the local cache
purchasemutableList?.apply { processPurchases(this.toSet()) }
}
BillingClient.BillingResponseCode.ITEM_NOT_OWNED -> {
subscriptionPrefs.setSubscriptionValue(false)
}
BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED -> {
// item already owned? call queryPurchasesAsync to verify and process all such items
println( billingResult.debugMessage)
subscriptionPrefs.setSubscriptionValue(true)
purchasingMutableLiveData.postValue("Item Already owned , enjoy ")
queryPurchasesAsync()
}
BillingClient.BillingResponseCode.SERVICE_DISCONNECTED -> {
purchasingMutableLiveData.postValue("Service Disconnected")
connectToPlayBillingService()
}
BillingClient.BillingResponseCode.ITEM_NOT_OWNED->{
purchasingMutableLiveData.postValue("Service Disconnected")
}
BillingClient.BillingResponseCode.SERVICE_TIMEOUT->{
purchasingMutableLiveData.postValue("Service Time Out try again")
}
BillingClient.BillingResponseCode.USER_CANCELED->{
subscriptionPrefs.setSubscriptionValue(false)
purchasingMutableLiveData.postValue("service is cancelled")
}
else -> {
println(billingResult.debugMessage)
}
}}
}
here i am passing subscription is aknowledge
private fun acknowledgeNonConsumablePurchasesAsync(nonConsumables: List<Purchase>) {
nonConsumables.forEach { purchase ->
val params = AcknowledgePurchaseParams.newBuilder().setPurchaseToken(purchase
.purchaseToken).build()
playstoreBillingCLient.acknowledgePurchase(params) { billingResult ->
when (billingResult.responseCode) {
BillingClient.BillingResponseCode.OK -> {
println("acknowledged")
}
else ->
println( "acknowledgeNonConsumablePurchasesAsync response is ${billingResult.debugMessage}")
}
}
}
}
I'm implementing AppUpdateType.IMMEDIATE in-app-updates from Play Market using this guide: https://developer.android.com/guide/app-bundle/in-app-updates
When I test I can't finish update process - it just throws me back to old app where I get prompted to update again and again.
What am I doing wrong?
SplashActivity.kt
private fun checkAppUpdate() {
val appUpdateManager = getUpdateManager()
val appUpdateInfoTask = appUpdateManager.appUpdateInfo
appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
&& appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)
) {
showError("Update available. You should update")
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo,
AppUpdateType.IMMEDIATE,
this,
UPDATE_REQUEST_CODE
)
} else {
viewModel.loadData()
}
}
}
Maybe I should listen to apk download and start installing manually?
I saw this dialog always appears when I open old version of Tokopedia app. Tokopedia suggests me to update the app.
The dialog gives me two methods to update the app:
Update now
Update when Wi-Fi available
If I select Lain Kali (Cancel), the dialog appears again on the next app open. But, if I select the second option, I open Play Store and see this behavior:
It really do update on background until my device is connected to Wi-Fi.
I want to mimic the same action like Tokopedia did, because some version of my app contains critical bug. I want to give users a better user experience.
Do you know how to show the dialog above?
This is possible using In-App Update provided by Google.
In-app updates works only with devices running Android 5.0 (API level 21) or higher, and requires you to use Play Core library 1.5.0 or higher. There are two types - 1.Flexible and 2. Immediate.
Follow this link and implement In-App Update as per your requirement.
https://developer.android.com/guide/app-bundle/in-app-updates
You can achieve this by using Support in-app updates
It only work from Android 5.0 (API level 21) or higher.
There are two types of Update Available with UX for in-app updates:
Flexible
Immediate
To Check for update availability
// Creates instance of the manager.
AppUpdateManager appUpdateManager = AppUpdateManagerFactory.create(context);
// Returns an intent object that you use to check for an update.
Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();
// Checks that the platform will allow the specified type of update.
appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
// For a flexible update, use AppUpdateType.FLEXIBLE
&& appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
// Request the update.
}
});
Okay, here's the complete code as requested by #akshay_shahane.
Firstly, add this line on your app's build.gradle:
dependencies {
implementation 'com.google.android.play:core:1.6.1'
}
And inside your activity:
class MainActivity : AppCompatActivity(), InstallStateUpdatedListener {
private val appUpdateManager by lazy {
AppUpdateManagerFactory.create(this).also { it.registerListener(this) }
}
override fun onDestroy() {
if (Build.VERSION.SDK_INT >= 21) {
appUpdateManager.unregisterListener(this)
}
super.onDestroy()
}
override fun onResume() {
super.onResume()
if (Build.VERSION.SDK_INT >= 21) {
appUpdateManager.appUpdateInfo.addOnSuccessListener { appUpdateInfo ->
// If the update is downloaded but not installed, notify the user to complete the update.
if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) {
popupSnackbarForCompleteUpdate()
} else if (it.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS
&& it.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
appUpdateManager.startUpdateFlowForResult(it, AppUpdateType.IMMEDIATE, this, REQUEST_CODE_UPDATE_APP)
}
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == ActivityResult.RESULT_IN_APP_UPDATE_FAILED && requestCode == REQUEST_CODE_UPDATE_APP) {
Toast.makeText(this, "Update failed", Toast.LENGTH_SHORT).show()
}
}
override fun onStateUpdate(state: InstallState) {
when (state.installStatus()) {
InstallStatus.DOWNLOADED -> popupSnackbarForCompleteUpdate()
InstallStatus.REQUIRES_UI_INTENT -> {
Snackbar.make(findViewById(R.id.activity_main_layout),
"To perform the installation, a Play Store UI flow needs to be started.",
Snackbar.LENGTH_LONG
).show()
}
else -> {
val stateString = when (state.installStatus()) {
InstallStatus.FAILED -> "failed"
InstallStatus.PENDING -> "pending"
InstallStatus.DOWNLOADING -> "downloading"
InstallStatus.INSTALLING -> "installing"
InstallStatus.INSTALLED -> "installed"
InstallStatus.CANCELED -> "canceled"
else -> null
}
if (stateString != null) {
Snackbar.make(findViewById(R.id.activity_main_layout),
"An update is $stateString.",
Snackbar.LENGTH_SHORT
).show()
}
}
}
}
private fun popupSnackbarForCompleteUpdate() {
Snackbar.make(findViewById(R.id.activity_main_layout),
"An update is ready to install.",
Snackbar.LENGTH_INDEFINITE
).apply {
setAction("INSTALL") { appUpdateManager.completeUpdate() }
show()
}
}
#RequiresApi(21)
fun checkUpdateViaGooglePlay() {
appUpdateManager.appUpdateInfo.addOnSuccessListener { appUpdateInfo ->
when (appUpdateInfo.updateAvailability()) {
UpdateAvailability.UPDATE_AVAILABLE -> {
if (appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo, AppUpdateType.FLEXIBLE, this, REQUEST_CODE_UPDATE_APP)
} else if (appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo, AppUpdateType.IMMEDIATE, this, REQUEST_CODE_UPDATE_APP)
}
}
UpdateAvailability.UPDATE_NOT_AVAILABLE -> {
Toast.makeText(this, R.string.no_updates_found, Toast.LENGTH_SHORT).show()
}
}
}.addOnFailureListener {
Toast.makeText(this, R.string.error_check_update, Toast.LENGTH_SHORT).show()
}
}
companion object {
const val REQUEST_CODE_UPDATE_APP = 8
}
}