-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added VersionCheck class and support for verifying data and showing a…
… default dialog (#5, #6, #7) * Added VersionData and VersionDataConveter * Added DisplayState and Status data classes * Added URLFetcher interface and NetworkURLFetcher implementation * Added UpgradeDialog and default implementation * Removed unused libraries * Fixed containsBlockedVersion logic and added test case * Added VersionCheck which exposes flows for state and status * UpgradeDialog collects flow and reacts to activity lifecycle changes * Added unit tests
- Loading branch information
1 parent
7d88556
commit 1c51994
Showing
24 changed files
with
945 additions
and
75 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
56 changes: 56 additions & 0 deletions
56
app/src/main/java/com/steamclock/versioncheckkotlinsample/App.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package com.steamclock.versioncheckkotlinsample | ||
|
||
import android.app.Application | ||
import androidx.lifecycle.LifecycleObserver | ||
import androidx.lifecycle.ProcessLifecycleOwner | ||
import com.steamclock.versioncheckkotlin.VersionCheck | ||
import com.steamclock.versioncheckkotlin.VersionCheckConfig | ||
import com.steamclock.versioncheckkotlin.interfaces.DefaultUpgradeDialog | ||
import com.steamclock.versioncheckkotlin.interfaces.URLFetcher | ||
import kotlinx.coroutines.* | ||
import java.net.URL | ||
import kotlin.random.Random | ||
|
||
class App: Application(), LifecycleObserver { | ||
|
||
override fun onCreate() { | ||
super.onCreate() | ||
setupVersionCheck() | ||
} | ||
|
||
private fun setupVersionCheck() { | ||
val versionChecker = VersionCheck( | ||
VersionCheckConfig( | ||
appVersionName = BuildConfig.VERSION_NAME, | ||
appVersionCode = BuildConfig.VERSION_CODE, | ||
url = "https://doesn't_matter", | ||
urlFetcher = MockURLFetcher | ||
) | ||
) | ||
val upgradeDialog = DefaultUpgradeDialog(versionChecker.displayStateFlow) | ||
|
||
ProcessLifecycleOwner.get().lifecycle.addObserver(versionChecker) | ||
registerActivityLifecycleCallbacks(upgradeDialog) | ||
} | ||
} | ||
|
||
object MockURLFetcher: URLFetcher { | ||
override fun getData(url: URL): String? { | ||
return """ | ||
{ | ||
"ios" : { | ||
"minimumVersion": "1.1", | ||
"blockedVersions": ["1.2.0", "1.2.1", "@301"], | ||
"latestTestVersion": "1.4.2@400" | ||
}, | ||
"android" : { | ||
"minimumVersion": "1.1", | ||
"blockedVersions": ["1.2.0", "1.2.1", "@301"], | ||
"latestTestVersion": "1.4.2@400" | ||
}, | ||
"serverForceVersionFailure": false, | ||
"serverMaintenance": false | ||
} | ||
""" | ||
} | ||
} |
2 changes: 1 addition & 1 deletion
2
app/src/main/java/com/steamclock/versioncheckkotlinsample/MainActivity.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
24 changes: 0 additions & 24 deletions
24
...kkotlin/src/androidTest/java/com/steamclock/versioncheckkotlin/ExampleInstrumentedTest.kt
This file was deleted.
Oops, something went wrong.
149 changes: 149 additions & 0 deletions
149
versioncheckkotlin/src/main/java/com/steamclock/versioncheckkotlin/UpgradeDialog.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
package com.steamclock.versioncheckkotlin.interfaces | ||
|
||
import android.app.Activity | ||
import android.app.AlertDialog | ||
import android.app.Application | ||
import android.content.Context | ||
import android.os.Bundle | ||
import com.steamclock.versioncheckkotlin.models.DisplayState | ||
import kotlinx.coroutines.MainScope | ||
import kotlinx.coroutines.flow.StateFlow | ||
import kotlinx.coroutines.flow.collect | ||
import kotlinx.coroutines.launch | ||
import java.lang.ref.WeakReference | ||
|
||
class DefaultUpgradeDialog(private val versionDisplayState: StateFlow<DisplayState>) : Application.ActivityLifecycleCallbacks { | ||
private var dialog: AlertDialog? = null | ||
private val coroutineScope = MainScope() | ||
private var needToShowDialog = false | ||
private var currentActivityContext: WeakReference<Context>? = null | ||
|
||
init { | ||
coroutineScope.launch { | ||
versionDisplayState.collect { displayState -> | ||
when (val ctx = currentActivityContext?.get()) { | ||
null -> needToShowDialog = true | ||
else -> showForState(ctx, displayState) | ||
} | ||
} | ||
} | ||
} | ||
|
||
private fun dismissDialog() { | ||
if (dialog?.isShowing == true) { | ||
dialog?.dismiss() | ||
} | ||
} | ||
|
||
private fun reshowDialog() { | ||
dismissDialog() | ||
dialog?.show() | ||
} | ||
|
||
private fun showForState(context: Context, state: DisplayState) { | ||
dismissDialog() | ||
when (state) { | ||
DisplayState.ForceUpdate -> { | ||
createBasicDialog( | ||
context, | ||
"Must Update", | ||
"The version of the application is out of date and cannot run. Please update to the latest version from the Play Store.", | ||
requiresUpdate = true, | ||
canDismiss = false | ||
) | ||
} | ||
DisplayState.SuggestUpdate -> { | ||
createBasicDialog( | ||
context, | ||
"Should Update", | ||
"The version of the application is out of date and should not run. Please update to the latest version from the Play Store.", | ||
requiresUpdate = true, | ||
canDismiss = true | ||
) | ||
} | ||
// todo 2021-09 Handle down for maintenance | ||
else -> { | ||
dialog?.dismiss() | ||
dialog = null | ||
} | ||
} | ||
dialog?.show() | ||
needToShowDialog = false | ||
} | ||
|
||
private fun createBasicDialog( | ||
context: Context, | ||
title: String, | ||
message: String, | ||
requiresUpdate: Boolean, | ||
canDismiss: Boolean | ||
) { | ||
|
||
val builder = AlertDialog.Builder(context).apply { | ||
setTitle(title) | ||
setMessage(message) | ||
setCancelable(canDismiss) | ||
|
||
if (requiresUpdate) { | ||
// Do not set click listener here, we do not want the button to dismiss the dialog. | ||
setNeutralButton("Upgrade", null) | ||
} | ||
|
||
if (canDismiss) { | ||
setPositiveButton("OK") { _, _ -> | ||
dialog = null | ||
/* todo */ | ||
} | ||
} | ||
} | ||
|
||
dialog = builder.create() | ||
|
||
// Tap dance a little to override the click listener so the dialog will not dismiss | ||
dialog?.apply { | ||
setOnShowListener { | ||
getButton(AlertDialog.BUTTON_NEUTRAL)?.setOnClickListener { | ||
// todo Proxy out to Play Store. | ||
} | ||
} | ||
} | ||
} | ||
|
||
//----------------------------------------------------------------- | ||
// Using ActivityLifecycleCallbacks to launch version checks and collect | ||
// state flows. | ||
//----------------------------------------------------------------- | ||
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { | ||
/* No op */ | ||
} | ||
|
||
override fun onActivityStarted(activity: Activity) { | ||
/* No op */ | ||
} | ||
|
||
override fun onActivityResumed(activity: Activity) { | ||
currentActivityContext = WeakReference(activity) | ||
if (needToShowDialog) { | ||
showForState(activity, versionDisplayState.value) | ||
} else { | ||
reshowDialog() | ||
} | ||
} | ||
|
||
override fun onActivityPaused(activity: Activity) { | ||
currentActivityContext = null | ||
dismissDialog() | ||
} | ||
|
||
override fun onActivityStopped(activity: Activity) { | ||
/* No op */ | ||
} | ||
|
||
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) { | ||
/* No op */ | ||
} | ||
|
||
override fun onActivityDestroyed(activity: Activity) { | ||
/* No op */ | ||
} | ||
} |
Oops, something went wrong.