Skip to content

Commit a5240ab

Browse files
authored
Merge pull request #22 from motorro/migrate-to-activity-contracts
Migrate to activity contracts
2 parents 33ed337 + 654fc0e commit a5240ab

24 files changed

Lines changed: 221 additions & 167 deletions

.idea/kotlinc.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/misc.xml

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ to simplify in-app update flow.
4747
- Flexible updates are non-intrusive for app users with [UpdateFlowBreaker](#non-intrusive-flexible-updates-with-updateflowbreaker).
4848

4949
## Basics
50-
Refer to [original documentation](https://developer.android.com/guide/app-bundle/in-app-updates) to understand
50+
Refer to [original documentation](https://developer.android.com/guide/playcore/in-app-updates) to understand
5151
the basics of in-app update. This library consists of two counterparts:
5252
- [AppUpdateWrapper](appupdatewrapper/src/main/java/com/motorro/appupdatewrapper/AppUpdateWrapper.kt) is a presenter
5353
(or presentation model to some extent) that is responsible for carrying out the `IMMEDIATE` or `FLEXIBLE` update
@@ -94,7 +94,7 @@ class TestActivity : AppCompatActivity(), AppUpdateView {
9494
/********************************/
9595

9696
// AppUpdateManager needs your activity to start dialogs
97-
override val activity: Activity get() = this
97+
override val resultContractRegistry: ActivityResultRegistry = this.activityResultRegistry
9898

9999
// Called when flow starts
100100
override fun updateChecking() {
@@ -142,13 +142,13 @@ dependencies {
142142
application and `AppUpdateWrapper`. You may directly extend it in your hosting `Activity` or delegate it to some
143143
fragment. Here are the methods you may implement:
144144

145-
#### activity (mandatory)
145+
#### resultContractRegistry (mandatory)
146146
```kotlin
147-
val activity: Activity
147+
val resultContractRegistry: ActivityResultRegistry
148148
```
149-
`AppUpdateManager` launches activities on behalf of your application. Implement this value to pass the activity that
150-
will handle the `onActivityResult` and pass data to `AppUpdateWrapper.checkActivityResult`. Refer to method
151-
[documentation](#checkactivityresult) to get the details.
149+
`AppUpdateManager` launches activities on behalf of your application. Implement this value to pass the activity
150+
result registry that will handle the `onActivityResult`. Typically you pass your activity `activityResultRegistry`
151+
there.
152152

153153
#### updateReady (mandatory)
154154
```kotlin
@@ -176,7 +176,7 @@ Called by presenter when update flow starts. UI may display a spinner of some ki
176176
```kotlin
177177
fun updateDownloadStarts()
178178
```
179-
Called by presenter user confirms flexible update and background download begins.
179+
Called when user confirms flexible update and background download begins.
180180
Called in flexible flow.
181181

182182
#### updateInstallUiVisible (optional)
@@ -212,19 +212,6 @@ The library supports both `IMMEDIATE` and `FLEXIBLE` update flows.
212212

213213
Both flows implement the `AppUpdateWrapper` interface with the following methods to consider:
214214

215-
#### checkActivityResult
216-
```kotlin
217-
fun checkActivityResult(requestCode: Int, resultCode: Int): Boolean
218-
```
219-
`AppUpdateManager` launches some activities from time to time: to ask for update consent, to install, etc. It does so
220-
on behalf of your calling activity. Thus you must implement `onActivityResult` at your side and pass data to this method.
221-
If `checkActivityResult` returns true - then the result was handled. See the sample at the [top](#basics) of the article.
222-
In case your activity already uses the [request code](appupdatewrapper/src/main/java/com/motorro/appupdatewrapper/constants.kt#L23)
223-
used for application updates you can set a new one by setting a static var:
224-
```kotlin
225-
AppUpdateWrapper.REQUEST_CODE_UPDATE = 1111
226-
```
227-
228215
#### userCanceledUpdate and userConfirmedUpdate
229216
```kotlin
230217
fun userCanceledUpdate()

appupdatewrapper/build.gradle

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,13 @@ android {
6363
dependencies {
6464
implementation fileTree(dir: 'libs', include: ['*.jar'])
6565

66-
api "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
66+
api "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
6767

6868
api 'androidx.core:core-ktx:1.12.0'
6969
api 'androidx.lifecycle:lifecycle-common:2.6.2'
70-
api 'com.google.android.play:core:1.10.3'
70+
api 'androidx.activity:activity-ktx:1.8.1'
71+
api 'com.google.android.play:app-update:2.1.0'
72+
api 'com.google.android.play:app-update-ktx:2.1.0'
7173

7274
implementation 'com.jakewharton.timber:timber:5.0.1'
7375

@@ -77,7 +79,7 @@ dependencies {
7779
testImplementation 'junit:junit:4.13.2'
7880
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
7981
testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0'
80-
testImplementation 'org.robolectric:robolectric:4.10.3'
82+
testImplementation 'org.robolectric:robolectric:4.11.1'
8183
testImplementation 'androidx.lifecycle:lifecycle-runtime-testing:2.6.2'
8284
}
8385

appupdatewrapper/src/main/java/com/motorro/appupdatewrapper/AppUpdateState.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ internal abstract class AppUpdateState: AppUpdateWrapper, Tagged {
138138
* Checks activity result and returns `true` if result is an update result and was handled
139139
* Use to check update activity result in [android.app.Activity.onActivityResult]
140140
*/
141-
override fun checkActivityResult(requestCode: Int, resultCode: Int): Boolean = false
141+
open fun checkActivityResult(resultCode: Int): Boolean = false
142142

143143
/**
144144
* Cancels update installation

appupdatewrapper/src/main/java/com/motorro/appupdatewrapper/AppUpdateStateMachine.kt

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,15 @@
1515

1616
package com.motorro.appupdatewrapper
1717

18+
import androidx.activity.result.ActivityResultLauncher
19+
import androidx.activity.result.IntentSenderRequest
20+
import androidx.activity.result.contract.ActivityResultContracts.StartIntentSenderForResult
1821
import androidx.annotation.VisibleForTesting
1922
import androidx.lifecycle.DefaultLifecycleObserver
2023
import androidx.lifecycle.Lifecycle
2124
import androidx.lifecycle.LifecycleOwner
2225
import com.google.android.play.core.appupdate.AppUpdateManager
26+
import com.motorro.appupdatewrapper.AppUpdateWrapper.Companion.REQUEST_KEY_UPDATE
2327

2428
/**
2529
* App update state machine
@@ -40,6 +44,11 @@ internal interface AppUpdateStateMachine {
4044
*/
4145
val view: AppUpdateView
4246

47+
/**
48+
* Update request launcher
49+
*/
50+
val launcher: ActivityResultLauncher<IntentSenderRequest>
51+
4352
/**
4453
* Sets new update state
4554
*/
@@ -65,6 +74,12 @@ internal class AppUpdateLifecycleStateMachine(
6574
@VisibleForTesting
6675
var currentUpdateState: AppUpdateState
6776

77+
/**
78+
* Update request launcher
79+
*/
80+
override lateinit var launcher: ActivityResultLauncher<IntentSenderRequest>
81+
private set
82+
6883
init {
6984
currentUpdateState = None()
7085
lifecycle.addObserver(this)
@@ -94,6 +109,9 @@ internal class AppUpdateLifecycleStateMachine(
94109
}
95110

96111
override fun onStart(owner: LifecycleOwner) {
112+
launcher = view.resultContractRegistry.register(REQUEST_KEY_UPDATE, StartIntentSenderForResult()) {
113+
checkActivityResult(it.resultCode)
114+
}
97115
currentUpdateState.onStart()
98116
}
99117

@@ -109,13 +127,17 @@ internal class AppUpdateLifecycleStateMachine(
109127
currentUpdateState.onStop()
110128
}
111129

130+
override fun onDestroy(owner: LifecycleOwner) {
131+
launcher.unregister()
132+
}
133+
112134
/**
113135
* Checks activity result and returns `true` if result is an update result and was handled
114136
* Use to check update activity result in [android.app.Activity.onActivityResult]
115137
*/
116-
override fun checkActivityResult(requestCode: Int, resultCode: Int): Boolean {
117-
timber.d("Processing activity result: requestCode(%d), resultCode(%d)", requestCode, resultCode)
118-
return currentUpdateState.checkActivityResult(requestCode, resultCode).also {
138+
private fun checkActivityResult(resultCode: Int): Boolean {
139+
timber.d("Processing activity result: resultCode(%d)", resultCode)
140+
return currentUpdateState.checkActivityResult(resultCode).also {
119141
timber.d("Activity result handled: %b", it)
120142
}
121143
}
@@ -143,6 +165,9 @@ internal class AppUpdateLifecycleStateMachine(
143165
*/
144166
override fun cleanup() {
145167
lifecycle.removeObserver(this)
168+
if (this::launcher.isInitialized) {
169+
launcher.unregister()
170+
}
146171
currentUpdateState = None()
147172
timber.d("Cleaned-up!")
148173
}

appupdatewrapper/src/main/java/com/motorro/appupdatewrapper/AppUpdateView.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515

1616
package com.motorro.appupdatewrapper
1717

18-
import android.app.Activity
18+
import androidx.activity.ComponentActivity
19+
import androidx.activity.result.ActivityResultRegistry
1920
import androidx.lifecycle.Lifecycle.State.RESUMED
2021

2122
/**
@@ -32,12 +33,11 @@ import androidx.lifecycle.Lifecycle.State.RESUMED
3233
*/
3334
interface AppUpdateView {
3435
/**
35-
* Returns hosting activity for update process
36-
* Call [AppUpdateState.checkActivityResult] in [Activity.onActivityResult] to
37-
* check update status
38-
* @see AppUpdateState.checkActivityResult
36+
* Returns result contract registry
37+
* Wrapper will register an activity result contract to listen to update state
38+
* Pass [ComponentActivity.activityResultRegistry] or other registry to it
3939
*/
40-
val activity: Activity
40+
val resultContractRegistry: ActivityResultRegistry
4141

4242
/**
4343
* Called when update is checking or downloading data

appupdatewrapper/src/main/java/com/motorro/appupdatewrapper/AppUpdateWrapper.kt

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,16 @@
1616
package com.motorro.appupdatewrapper
1717

1818
import com.google.android.play.core.appupdate.AppUpdateManager
19+
import com.motorro.appupdatewrapper.AppUpdateWrapper.Companion.REQUEST_KEY_UPDATE
1920

2021
/**
2122
* Wraps [AppUpdateManager] interaction.
2223
* The update wrapper is designed to be a single-use object. It carries out the workflow using host
2324
* [androidx.lifecycle.Lifecycle] and terminates in either [AppUpdateView.updateComplete] or
2425
* [AppUpdateView.updateFailed].
25-
* [AppUpdateManager] pops up activities-for-result from time to time. To check if the activity result belongs to update
26-
* flow call [checkActivityResult] function of update wrapper in your hosting activity.
26+
* [AppUpdateManager] pops up activities-for-result from time to time. That is why [AppUpdateView.resultContractRegistry].
27+
* The library registers the contract itself. If you need to change contract key - set [REQUEST_KEY_UPDATE]
28+
* to the desired one
2729
*/
2830
interface AppUpdateWrapper {
2931
companion object {
@@ -37,17 +39,11 @@ interface AppUpdateWrapper {
3739
var USE_SAFE_LISTENERS = false
3840

3941
/**
40-
* The request code wrapper uses to run [AppUpdateManager.startUpdateFlowForResult]
42+
* The request key wrapper uses to register [AppUpdateManager] contract
4143
*/
42-
var REQUEST_CODE_UPDATE = REQUEST_CODE_UPDATE_DEFAULT
44+
var REQUEST_KEY_UPDATE = REQUEST_KEY_UPDATE_DEFAULT
4345
}
4446

45-
/**
46-
* Checks activity result and returns `true` if result is an update result and was handled
47-
* Use to check update activity result in [android.app.Activity.onActivityResult]
48-
*/
49-
fun checkActivityResult(requestCode: Int, resultCode: Int): Boolean
50-
5147
/**
5248
* Cancels update installation
5349
* Call when update is downloaded and user cancelled app restart

appupdatewrapper/src/main/java/com/motorro/appupdatewrapper/FlexibleUpdateState.kt

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,24 @@ package com.motorro.appupdatewrapper
1818
import android.app.Activity
1919
import androidx.annotation.CallSuper
2020
import com.google.android.play.core.appupdate.AppUpdateInfo
21+
import com.google.android.play.core.appupdate.AppUpdateOptions
2122
import com.google.android.play.core.install.InstallStateUpdatedListener
2223
import com.google.android.play.core.install.model.ActivityResult.RESULT_IN_APP_UPDATE_FAILED
2324
import com.google.android.play.core.install.model.AppUpdateType.FLEXIBLE
2425
import com.google.android.play.core.install.model.InstallErrorCode.ERROR_INSTALL_NOT_ALLOWED
2526
import com.google.android.play.core.install.model.InstallErrorCode.ERROR_INSTALL_UNAVAILABLE
26-
import com.google.android.play.core.install.model.InstallStatus.*
27+
import com.google.android.play.core.install.model.InstallStatus.CANCELED
28+
import com.google.android.play.core.install.model.InstallStatus.DOWNLOADED
29+
import com.google.android.play.core.install.model.InstallStatus.DOWNLOADING
30+
import com.google.android.play.core.install.model.InstallStatus.FAILED
31+
import com.google.android.play.core.install.model.InstallStatus.INSTALLED
32+
import com.google.android.play.core.install.model.InstallStatus.INSTALLING
33+
import com.google.android.play.core.install.model.InstallStatus.PENDING
2734
import com.google.android.play.core.install.model.UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS
2835
import com.google.android.play.core.install.model.UpdateAvailability.UPDATE_AVAILABLE
2936
import com.motorro.appupdatewrapper.AppUpdateException.Companion.ERROR_UNKNOWN_UPDATE_RESULT
3037
import com.motorro.appupdatewrapper.AppUpdateException.Companion.ERROR_UPDATE_FAILED
3138
import com.motorro.appupdatewrapper.AppUpdateException.Companion.ERROR_UPDATE_TYPE_NOT_ALLOWED
32-
import com.motorro.appupdatewrapper.AppUpdateWrapper.Companion.REQUEST_CODE_UPDATE
3339

3440
/**
3541
* Flexible update flow
@@ -102,9 +108,9 @@ internal sealed class FlexibleUpdateState : AppUpdateState(), Tagged {
102108
* takes place. This may prevent download consent popup if activity was recreated during consent display
103109
*/
104110
@CallSuper
105-
override fun checkActivityResult(requestCode: Int, resultCode: Int): Boolean {
106-
timber.d("checkActivityResult: requestCode(%d), resultCode(%d)", requestCode, resultCode)
107-
return if (REQUEST_CODE_UPDATE == requestCode && Activity.RESULT_CANCELED == resultCode) {
111+
override fun checkActivityResult(resultCode: Int): Boolean {
112+
timber.d("checkActivityResult: resultCode(%d)", resultCode)
113+
return if (Activity.RESULT_CANCELED == resultCode) {
108114
timber.d("Update download cancelled")
109115
markUserCancelTime()
110116
complete()
@@ -236,9 +242,8 @@ internal sealed class FlexibleUpdateState : AppUpdateState(), Tagged {
236242

237243
stateMachine.updateManager.startUpdateFlowForResult(
238244
updateInfo,
239-
FLEXIBLE,
240-
activity,
241-
REQUEST_CODE_UPDATE
245+
stateMachine.launcher,
246+
AppUpdateOptions.newBuilder(FLEXIBLE).build()
242247
)
243248
}
244249
}
@@ -253,9 +258,8 @@ internal sealed class FlexibleUpdateState : AppUpdateState(), Tagged {
253258
* Checks activity result and returns `true` if result is an update result and was handled
254259
* Use to check update activity result in [android.app.Activity.onActivityResult]
255260
*/
256-
override fun checkActivityResult(requestCode: Int, resultCode: Int): Boolean = when {
257-
super.checkActivityResult(requestCode, resultCode) -> true
258-
REQUEST_CODE_UPDATE != requestCode -> false
261+
override fun checkActivityResult(resultCode: Int): Boolean = when {
262+
super.checkActivityResult(resultCode) -> true
259263
else -> {
260264
when(resultCode) {
261265
Activity.RESULT_OK -> {

appupdatewrapper/src/main/java/com/motorro/appupdatewrapper/ImmediateUpdateState.kt

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ package com.motorro.appupdatewrapper
1717

1818
import android.app.Activity
1919
import com.google.android.play.core.appupdate.AppUpdateInfo
20+
import com.google.android.play.core.appupdate.AppUpdateOptions
2021
import com.google.android.play.core.install.model.AppUpdateType.IMMEDIATE
2122
import com.google.android.play.core.install.model.UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS
2223
import com.google.android.play.core.install.model.UpdateAvailability.UPDATE_AVAILABLE
2324
import com.motorro.appupdatewrapper.AppUpdateException.Companion.ERROR_NO_IMMEDIATE_UPDATE
2425
import com.motorro.appupdatewrapper.AppUpdateException.Companion.ERROR_UPDATE_FAILED
2526
import com.motorro.appupdatewrapper.AppUpdateException.Companion.ERROR_UPDATE_TYPE_NOT_ALLOWED
26-
import com.motorro.appupdatewrapper.AppUpdateWrapper.Companion.REQUEST_CODE_UPDATE
2727

2828
/**
2929
* Immediate update flow
@@ -170,9 +170,8 @@ internal sealed class ImmediateUpdateState: AppUpdateState(), Tagged {
170170

171171
updateManager.startUpdateFlowForResult(
172172
updateInfo,
173-
IMMEDIATE,
174-
activity,
175-
REQUEST_CODE_UPDATE
173+
stateMachine.launcher,
174+
AppUpdateOptions.newBuilder(IMMEDIATE).build()
176175
)
177176
updateInstallUiVisible()
178177
}
@@ -187,12 +186,8 @@ internal sealed class ImmediateUpdateState: AppUpdateState(), Tagged {
187186
* Checks activity result and returns `true` if result is an update result and was handled
188187
* Use to check update activity result in [android.app.Activity.onActivityResult]
189188
*/
190-
override fun checkActivityResult(requestCode: Int, resultCode: Int): Boolean {
191-
timber.d("checkActivityResult: requestCode(%d), resultCode(%d)", requestCode, resultCode)
192-
if (REQUEST_CODE_UPDATE != requestCode) {
193-
return false
194-
}
195-
189+
override fun checkActivityResult(resultCode: Int): Boolean {
190+
timber.d("checkActivityResult: resultCode(%d)", resultCode)
196191
if (Activity.RESULT_OK == resultCode) {
197192
timber.d("Update installation complete")
198193
complete()

0 commit comments

Comments
 (0)