I'm looking into In-App Updates by Play Store API. I followed the examples in the Documentations, and some other articles. But I still can't get it to show the Update Dialog provided by Play Store.
I've seen some apps do it, show a dialog to prompt the user to start the update, but using a FakeAppUpdateManager doesn't seem to do it.
Do I have to use a real release to test it out? Or is there some configuration for FakeAppUpdateManager that I need to do?
private fun checkForUpdates() {
val appUpdateManager = if (BuildConfig.DEBUG) {
FakeAppUpdateManager(this).apply {
setUpdateAvailable(UpdateAvailability.UPDATE_AVAILABLE)
setUpdatePriority(5)
}
} else {
AppUpdateManagerFactory.create(this)
}
// 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 &&
// For a flexible update, use AppUpdateType.FLEXIBLE
appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)
) {
// Create a listener to track request state updates.
val listener = { state: InstallState ->
// (Optional) Provide a download progress bar.
if (state.installStatus() == InstallStatus.DOWNLOADING) {
val bytesDownloaded = state.bytesDownloaded()
val totalBytesToDownload = state.totalBytesToDownload()
// Show update progress bar.
}
// Log state or install the update.
}
// Before starting an update, register a listener for updates.
appUpdateManager.registerListener(listener)
// Request the update.
appUpdateManager.startUpdateFlowForResult(
// Pass the intent that is returned by 'getAppUpdateInfo()'.
appUpdateInfo,
// Or 'AppUpdateType.FLEXIBLE' for flexible updates.
AppUpdateType.FLEXIBLE,
// The current activity making the update request.
this,
// Include a request code to later monitor this update request.
UPDATE_RC,
)
// When status updates are no longer needed, unregister the listener.
// appUpdateManager.unregisterListener(listener)
}
}
}
https://ibb.co/BLCKmnx
FakeAppUpdataManager is very rudimentary and will not show any UI or even invoke activity's onActivityResult based on my experience of testing and looking into decompiled code of it:
(please notice activity is not passed any further, thus no way to further do onActivityResult call by the manager)
What you can and should do though, is to check isConfirmationDialogVisible for AppUpdateType.FLEXIBLE scenario and isImmediateFlowVisible for AppUpdateType.IMMEDIATE scenario - but these dialogs/flows will never be shown on UI.
Example sequence for former:
assert(fakeAppUpdateManager.isConfirmationDialogVisible)
fakeAppUpdateManager.userAcceptsUpdate()
fakeAppUpdateManager.downloadStarts()
fakeAppUpdateManager.downloadCompletes()
assert(fakeAppUpdateManager.isInstallSplashScreenVisible)
fakeAppUpdateManager.installCompletes()
Example sequence for latter:
assert(fakeAppUpdateManager.isImmediateFlowVisible)
fakeAppUpdateManager.userAcceptsUpdate()
fakeAppUpdateManager.downloadStarts()
fakeAppUpdateManager.downloadCompletes()
assert(fakeAppUpdateManager.isInstallSplashScreenVisible)
fakeAppUpdateManager.installCompletes()
Related
I implement In-App Update feature in my android application but it not working untill i go to playstore app and view update button.
Please suggest me some solution.
Thanks
public void ImmidateUpdate()
{
AppUpdateManager appUpdateManager = AppUpdateManagerFactory.create(getActivity());
// 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)
{
if(appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE))
{
try {
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.
getActivity(),
// Include a request code to later monitor this update request.
1210);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
}
}
});
}
Set the priority using the Google Play Developer API as described in the Play Developer API documentation. In-app update priority should be specified in the Edit.tracks resource passed in the Edit.tracks: update method.
please refer to the documntation
I was looking at the flow documentation on the Android Developer site and I have a question.
https://developer.android.com/kotlin/flow#callback
If you look at the above link, you will see code like this.
class FirestoreUserEventsDataSource(
private val firestore: FirebaseFirestore
) {
// Method to get user events from the Firestore database
fun getUserEvents(): Flow<UserEvents> = callbackFlow {
// Reference to use in Firestore
var eventsCollection: CollectionReference? = null
try {
eventsCollection = FirebaseFirestore.getInstance()
.collection("collection")
.document("app")
} catch (e: Throwable) {
// If Firebase cannot be initialized, close the stream of data
// flow consumers will stop collecting and the coroutine will resume
close(e)
}
// Registers callback to firestore, which will be called on new events
val subscription = eventsCollection?.addSnapshotListener { snapshot, _ ->
if (snapshot == null) { return#addSnapshotListener }
// Sends events to the flow! Consumers will get the new events
try {
offer(snapshot.getEvents())
} catch (e: Throwable) {
// Event couldn't be sent to the flow
}
}
// The callback inside awaitClose will be executed when the flow is
// either closed or cancelled.
// In this case, remove the callback from Firestore
awaitClose { subscription?.remove() }
}
}
In the code above, awaitClose is explained to be executed when the coroutine is closed or cancelled.
But, there is no close() in the code except for the try-catch statement that initializes the eventsCollection.
Additionally, says offer does not add the element to the channel and **returns false** immediately at the bottom of the Android Developer page.
My question is, in the code above, when offer(snapshot.getEvents()) is executed, does the coroutine cancel with return false, so awaitClose is executed?
Expectation:
As the documentation says:
When you try to add a new element to a full channel, send suspends the
producer until there's space for the new element, whereas offer does
not add the element to the channel and returns false immediately.
Ergo:
It Immediately adds the specified element to this channel, if this doesn’t violate its capacity restrictions, and returns the successful result. Otherwise, returns failed or closed result. This is synchronous variant of send, which backs off in situations when send suspends or throws.
So when trySend call returns a non-successful result, it guarantees that the element was not delivered to the consumer, and it does not call onUndeliveredElement that was installed for this channel. See “Undelivered elements” section in Channel documentation for details on handling undelivered elements.
Conclusion:
A typical usage for onDeliveredElement is to close a resource that is being transferred via the channel. The following code pattern guarantees that opened resources are closed even if producer, consumer, and/or channel are cancelled. Resources are never lost. So no it doesn't return false.
I am currently playing around with Kotlin coroutines and flows. In my scenario, a MutableStateFlow represents a connection state (CONNECTING, CONNECTED, CLOSING, CLOSED). It is also possible to login, logout and login again.
For further use of the connection, I have to check the state and wait until it is CONNECTED. If it is already CONNECTED, I can continue. If not, I have to wait until the state reaches CONNECTED. The connect() call does return immediately, the result is propagated via a callback that updates the MutableStateFlow. My current idea is to do the following:
connect()
if (connectionState.value != State.CONNECTED) { // connectionState = MutableStateFlow(State.CLOSED)
suspendCoroutine<Boolean> { continuation ->
scope.launch { // scope = MainScope()
connectionState.collect {
if (it == State.CONNECTED) {
continuation.resume(true)
}
}
}
}
}
// continue
As I am fairly new to the topic, I don't know if this is good practice and I was also not able to find a more suitable concept in the Kotlin documenation. Is there some better way of doing it?
A while back I had the same question:
It is preferred to use first() to suspend till the predicate is matched.
if (connectionState.value != State.CONNECTED) {
connectionState.first { it == State.CONNECTED }
}
I have an app published in the play store with versionCode 3, in a new version, I want to implement the in app update functionality, so in build.gradle I add:
implementation 'com.google.android.play:core:1.8.2'
And in the main activity:
public class MainActivity extends BaseActivity {
//...
private AppUpdateManager mAppUpdateManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
//....
checkForUpdate();
}
/**
* check for update
*/
private void checkForUpdate() {
// Creates instance of the manager.
mAppUpdateManager = AppUpdateManagerFactory.create(this);
mAppUpdateManager.registerListener(state -> {
if (state.installStatus() == InstallStatus.DOWNLOADED) {
// After the update is downloaded, show a notification
// and request user confirmation to restart the app.
popupSnackbarForCompleteUpdate();
}
});
// Returns an intent object that you use to check for an update.
Task<AppUpdateInfo> appUpdateInfoTask = mAppUpdateManager.getAppUpdateInfo();
// Checks that the platform will allow the specified type of update.
appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
//&& appUpdateInfo.clientVersionStalenessDays() != null
//&& appUpdateInfo.clientVersionStalenessDays() >= DAYS_FOR_FLEXIBLE_UPDATE
&& appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
// Request the update.
try {
mAppUpdateManager.startUpdateFlowForResult(
// Pass the intent that is returned by 'getAppUpdateInfo()'.
appUpdateInfo,
// Or 'AppUpdateType.IMMEDIATE' for flexible updates.
AppUpdateType.FLEXIBLE,
// The current activity making the update request.
this,
// Include a request code to later monitor this update request.
MY_REQUEST_CODE);
} catch (IntentSender.SendIntentException e) {
Toast.makeText(this, R.string.update_failed, Toast.LENGTH_SHORT).show();
}
}
});
}
/**
* Displays the snackbar notification and call to action.
*/
private void popupSnackbarForCompleteUpdate() {
final Snackbar snackbar =
Snackbar.make(
findViewById(R.id.activity_main_layout),
R.string.an_update_has_been_just_downloaded,
Snackbar.LENGTH_INDEFINITE);
snackbar.setAction(R.string.install, view -> mAppUpdateManager.completeUpdate());
snackbar.setActionTextColor(
ContextCompat.getColor(this, R.color.color_snackbar_action_text_color));
snackbar.show();
}
}
To be able to test this functionality, I change the versionCode to a lower number than the one published in the Playstore (2 in this case and the app published with 3)When I run my app, it shows me the dialog to update the app and when I click the update button the update starts and installs without any errors, but when the app restarts, it shows me the update dialog again and the app seems to be not updated, I don't know if this is a bug in my app or i should upload the app to play store to be working correctly.
I am using a app bundle (.aab)
You need a Signed .apk for In App Update. Apps in debug mode will show you update dialog & even download new version, but app wont be installed/updated.
Started to experiment with In-app updates. Everything seems to work prefect.
But... when entering some edge cases, like for example, canceling the process of update in the process of downloading the update, the API for some reason stops working as intended.
Example: I start my activity and the first thing in onResume(), I do this:
//In app update code. Only works for Android 5.0 and above.
if (Build.VERSION.SDK_INT >= 21) {
AppUpdateManager appUpdateManager = AppUpdateManagerFactory.create(this);
Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();
appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) {
try {
appUpdateManager.startUpdateFlowForResult(appUpdateInfo, AppUpdateType.IMMEDIATE, this, 1333);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
} else if (appUpdateInfo.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
// If an in-app update is already running, resume the update.
try {
appUpdateManager.startUpdateFlowForResult(appUpdateInfo, AppUpdateType.IMMEDIATE,this, 1333);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
}
});
}
When I click on Update button and the screen for updating appears, I immediately cancel the update. It returns with onActivityResult the request code (1333) and result code (STATUS_UNKNOWN = 0) . I call finish() in that if statement, because this is a critical update that has to be updated.
The problem is now, when entering the app for the second time, onActivityResult is fired again, without showing any dialog from in-app update API and just returning the request/result code in onActivityResult callback.
Anyone with a similar problem?
looks like there is open bug for the same issue. https://issuetracker.google.com/issues/133092990 . As per comment its Play-store issue and will be released in next few weeks.