slaviboy
slaviboy

Reputation: 1912

Wrong If check statement

I wanted to make a method that determine if the application is started for the very first time, no matter the current version of the application. People suggest that we should use SharedPreferences as seen from this qustion. Below is the function that determine if application is started for the very first time.

companion object {

    const val APP_LAUNCH_FIRST_TIME: Int = 0            // first start ever
    const val APP_LAUNCH_FIRST_TIME_VERSION: Int = 1    // first start in this version (when app is updated)
    const val APP_LAUNCH_NORMAL: Int = 2                // normal app start

    /**
     * Method that checks if the application is started for the very first time, or for the first time
     * of the updated version, or just normal start.
     */
    fun checkForFirstAppStart(context: Context): Int {
        val sharedPreferencesVersionTag = "last_app_version"

        val sharedPreferences = androidx.preference.PreferenceManager.getDefaultSharedPreferences(context)
        var appStart = APP_LAUNCH_NORMAL

        try {
            val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
            val lastVersionCode = sharedPreferences.getLong(sharedPreferencesVersionTag, -1L)
            val currentVersionCode = PackageInfoCompat.getLongVersionCode(packageInfo)
            appStart = when {
                lastVersionCode == -1L -> APP_LAUNCH_FIRST_TIME
                lastVersionCode < currentVersionCode -> APP_LAUNCH_FIRST_TIME_VERSION
                lastVersionCode > currentVersionCode -> APP_LAUNCH_NORMAL
                else -> APP_LAUNCH_NORMAL
            }

            // Update version in preferences
            sharedPreferences.edit().putLong(sharedPreferencesVersionTag, currentVersionCode).commit()

        } catch (e: PackageManager.NameNotFoundException) {
            // Unable to determine current app version from package manager. Defensively assuming normal app start
        }
        return appStart
    }
}

Now in my MainActivity I make the check in this way, but strangely enough I always end up inside the if statement, although appLaunch is different from MainActivityHelper.APP_LAUNCH_FIRST_TIME

val appLaunch = MainActivityHelper.checkForFirstAppStart(this)
if (appLaunch == MainActivityHelper.APP_LAUNCH_FIRST_TIME) {
    val c = 299_792_458L
}

Here we see that appLaunch is 2 enter image description here

Here we see that MainActivityHelper.APP_LAUNCH_FIRST_TIME is 0 enter image description here

I am in the main thread I check using Thread.currentThread(), and when I add watches in the debugger (appLaunch == MainActivityHelper.APP_LAUNCH_FIRST_TIME) I get false. So I suggest that there is some delay, and by the time the if check is made the result is changed?

Upvotes: 0

Views: 89

Answers (1)

Emanuel Moecklin
Emanuel Moecklin

Reputation: 28856

There's nothing wrong with the code. I tested it and it works as intended. I get all three return values depending on the circumstances. I simplified the code a bit but the original code should nevertheless works.

enum class AppLaunch {
    LAUNCH_FIRST_TIME,          // first start ever
    FIRST_TIME_VERSION,         // first start in this version (when app is updated)
    NORMAL                      // normal app start
}

/**
 * Method that checks if the application is started for the very first time, or for the first time
 * of the updated version, or just normal start.
 */
fun checkForFirstAppStart(context: Context): AppLaunch {
    val sharedPreferencesVersionTag = "last_app_version"

    val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)

    return try {
        val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
        val lastVersionCode = sharedPreferences.getLong(sharedPreferencesVersionTag, -1L)
        val currentVersionCode = PackageInfoCompat.getLongVersionCode(packageInfo)

        // Update version in preferences
        sharedPreferences.edit().putLong(sharedPreferencesVersionTag, currentVersionCode).commit()

        when (lastVersionCode) {
            -1L -> AppLaunch.LAUNCH_FIRST_TIME
            in 0L until currentVersionCode -> AppLaunch.FIRST_TIME_VERSION
            else -> AppLaunch.NORMAL
        }
    } catch (e: PackageManager.NameNotFoundException) {
        // Unable to determine current app version from package manager. Defensively assuming normal app start
        AppLaunch.NORMAL
    }
}

I experimented a bit and the issue you see looks like a bug in Android Studio. If the code in the if statement is a NOP (no operation) then the debugger seems to stop there. If the code does have a side effect, the debugger doesn't stop. Things like this can be infuriating but with Android, Android Studio and the tooling, bugs like this are pretty common (unfortunately).

if (appLaunch == APP_LAUNCH_FIRST_TIME) {
    val c = 299_792_458L
}

translates to the following byte code:

L3 (the if statement)
    LINENUMBER 32 L3
    ILOAD 4
    IFNE L4
L5
    LINENUMBER 33 L5
    LDC 299792458
    LSTORE 2

Converting c to a var

var c = 1L
if (appLaunch == APP_LAUNCH_FIRST_TIME) {
    c = 299_792_458L
}

results in identical byte code so it's certainly not a code problem but an issue with Android Studio.

Update

If you need fast writes with enums you can use something like this:

fun appLaunchById(id: Int, def: AppLaunch = AppLaunch.NORMAL) = AppLaunch.values().find { it.id == id } ?: def

enum class AppLaunch(val id: Int) {
    LAUNCH_FIRST_TIME(0),          // first start ever
    FIRST_TIME_VERSION(1),         // first start in this version (when app is updated)
    NORMAL(2);                     // normal app start
}

^^^ writes an Int so fast and short. Reading is certainly not super fast though.

Update 2

Generic version of the enum solution:

inline fun <reified T : Enum<*>> enumById(hash: Int, def: T) = enumValues<T>()
    .find { it.hashCode() == hash }
    ?: def

enum class AppLaunch {
    LAUNCH_FIRST_TIME,          // first start ever
    FIRST_TIME_VERSION,         // first start in this version (when app is updated)
    NORMAL                      // normal app start
}

Usage:

val enum = enumById(value.hashCode(), AppLaunch.NORMAL)

Upvotes: 1

Related Questions