russhwolf / multiplatform-settings

A Kotlin Multiplatform library for saving simple key-value data
Apache License 2.0
1.7k stars 67 forks source link

no-arg and settings persistence #52

Closed sergiocasero closed 2 years ago

sergiocasero commented 4 years ago

Hello!

I'm facing the next issue: I've tested with no-arg module and everything looks good but... every time I close the app (Android), the preferences are being deleted, is that behaviour expected??

For example, i'm testing something like this:

val settings = Settings()
println("Setting: " + settings.getBoolean("TEST"))
settings.putBoolean(("TEST", true)
println("Setting: " + settings.getBoolean("TEST"))

Every time I close the app, I get this output:

Setting: false
Setting: true

Lib version: 0.6 Kotlin version: 1.3.72

Maybe I'm missing something? Thanks for the lib!

russhwolf commented 4 years ago

Nope! That's not expected, and doesn't match my tests. Let's troubleshoot.

What Android version are you using, and what device/emulator model?

What are you doing to close the app? That sometimes means different things to different people (eg home button, back button, force quit, reboot emulator)

You can use the Device File Explorer in the IDE to inspect the actual xml file that gets stored on the device. It will be in /data/data/your.app.id/shared_prefs/your.app.id_preferences.xml. Is that file present, with the contents you'd expect? Is it still present after you close the app?

russhwolf commented 4 years ago

Also, do you see the same behavior if you don't use no-arg, and instead manually construct an AndroidSettings?

sergiocasero commented 4 years ago

Hello!:

What Android version are you using, and what device/emulator model?: Samsung S10+ and Poco F1, both with Android10

What are you doing to close the app?: Force stop or reinstall with installDebug

You can use the Device File Explorer: Yep I know that but the file isn't in the folder WTF, because the preferences work as expected until the app stop

Also, do you see the same behavior if you don't use no-arg: I can't use no-arg version of the lib because gradle can't find android classes, I executed gradlew dependencies and I can see that for Android, gradle is getting -jvm artifact, not the correct one.

Hope this will be helpful for you!

russhwolf commented 4 years ago

SharedPreferences works by updating some in-memory state immediately, and then persisting to disk in the background. I wonder if that's silently failing for you for some reason. I'd be interested to see your project configuration to understand the dependency issue. But I also wonder if there's an issue with your device. If you create a normal Android app and save something to SharedPreferences, do you see the same issue?

sergiocasero commented 4 years ago

Yes, the preferences are working well in our other projects :(. I'll try to replicate this issue in another project that could be OpenSource or something, it's really really strange

dzolnai commented 4 years ago

I have run into this, what I found weird that it only occurred in release builds, but not in debug builds. I did some experimenting, and it turns out that in debug builds my app used com.russhwolf.settings.AndroidSettings, but in release builds it switched over to com.russhwolf.settings.JvmPreferencesSettings...

When I do gradle refresh when my debug build type is selected, I can see the no-arg-android-debug package, if I switch over to my release build type, the android-debug package disappears, and it is replaced with no-arg-jvm.

I replaced the no-arg dependency with a specific iOS dependency for the iOS target, and Android depedency for the Android target: implementation "com.russhwolf:multiplatform-settings-no-arg-android:0.6.1" After doing a sync, my project complained that it could not find the package, even though I have added mavenCentral() everywhere. When I did an assemble from the command line, I got the following error:

Could not determine the dependencies of task ':common:compileReleaseKotlinAndroid'.
> Could not resolve all files for configuration ':common:debugRuntimeClasspath'.
   > Could not resolve com.russhwolf:multiplatform-settings-no-arg-android:0.6.1.
     Required by:
         project :common
      > No matching variant of com.russhwolf:multiplatform-settings-no-arg-android:0.6.1 was found. The consumer was configured to find a runtime of a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'debug', attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm' but:
          - Variant 'android-releaseApiElements' capability com.russhwolf:multiplatform-settings-no-arg-android:0.6.1 declares a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm':
              - Incompatible because this component declares an API of a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'release' and the consumer needed a runtime of a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'debug'
          - Variant 'android-releaseRuntimeElements' capability com.russhwolf:multiplatform-settings-no-arg-android:0.6.1 declares a runtime of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm':
              - Incompatible because this component declares a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'release' and the consumer needed a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'debug'
          - Variant 'metadata-api' capability com.russhwolf:multiplatform-settings-no-arg-android:0.6.1:
              - Incompatible because this component declares a usage of 'kotlin-metadata' of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'common' and the consumer needed a runtime of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm'
              - Other compatible attribute:
                  - Doesn't say anything about com.android.build.api.attributes.BuildTypeAttr (required 'debug')
          - Variant 'metadata-commonMainMetadataElements' capability com.russhwolf:multiplatform-settings-no-arg-android:0.6.1:
              - Incompatible because this component declares a usage of 'kotlin-api' of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'common' and the consumer needed a runtime of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm'
              - Other compatible attribute:
                  - Doesn't say anything about com.android.build.api.attributes.BuildTypeAttr (required 'debug')

I'm still trying to figure out how this can be fixed, but it seems that it does not have a package for release types, only for debug?

Perhaps this could be the culprit: https://kotlinlang.org/docs/reference/mpp-publish-lib.html#publish-an-android-library

russhwolf commented 4 years ago

That's a really interesting data point. Does it work for you if you use the core module instead of no-arg? I don't understand why they would behave differently.

dzolnai commented 4 years ago

I have checked it with the core module. On a debug build, I see the following libraries being imported:

multiplatform-settings-android-debug
multiplatform-settings-iosx64
multiplatform-settings-metadata

When switching to a non-debug build, I see:

multiplatform-settings-jvm
multiplatform-settings-iosx64
multiplatform-settings-metadata

So it has the same issue. If I reference AndroidSettings in my androidMain code, it will also fail to resolve it after the switch.

dzolnai commented 4 years ago

I finally found the culprit. The issue was that I was using a build type with a non-default name (prelease). That's why it did not pair it with neither the debug version or the android release version of the library. I guess it matched with the jvm version because that one does not have any subtypes, so it matches anything, and android is a subtype of jvm... Anyways, the solution was to add release as a matching fallback for my prerelease build type:

buildTypes {
        ...
        prerelease {
            matchingFallbacks = ["release"]
            ...
        }
    }

I had to do this at all places where I had the prerelease build type defined, in common and also the android app. I still don't know why this came up just now, I have at least 10 other Kotlin MP libraries in my app. Perhaps because this library publishes separate debug and release variants?

russhwolf commented 4 years ago

Interesting. I don't think this is related to publishing both debug and release, because if you don't publish debug that you need to specify matching fallbacks when you do debug builds, and I don't think many libs are making consumers do that. I would expect this to also come up for any other libs that publish both Android and JVM targets. But a lot of KMP libs just use a JVM target for Android so maybe this issue is less common than one might expect.

russhwolf commented 3 years ago

This may be better now in Kotlin 1.5.30. See https://kotlinlang.org/docs/whatsnew1530.html#new-default-publishing-setup-for-android-artifacts

russhwolf commented 2 years ago

I'm pretty sure there's nothing left to do here, so I'm closing this. Feel free to reopen, or start a discussion, if anyone is still having trouble.