russhwolf / multiplatform-settings

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

NullPointerException - Parameter specified as non-null is null (updatedKey) #108

Closed abdurahmanadilovic closed 2 years ago

abdurahmanadilovic commented 2 years ago

Hi, first of all, thanks for this awesome library, we are using in our KMM project, and its working great. We do have this issue that occurs on some devices, we have seen it on android 11 and 12, and not on android 10 and 9. Here is the stack trace

java.lang.NullPointerException: Parameter specified as non-null is null: method com.russhwolf.settings.AndroidSettings.addListener$lambda-1, parameter updatedKey
    at com.russhwolf.settings.AndroidSettings.addListener$lambda-1
    at com.russhwolf.settings.AndroidSettings.lambda$ejYPvc8UpZPLw0Pu9AuA51-Vbq8
    at com.russhwolf.settings.-$$Lambda$AndroidSettings$ejYPvc8UpZPLw0Pu9AuA51-Vbq8.onSharedPreferenceChanged
    at android.app.SharedPreferencesImpl$EditorImpl.notifyListeners(SharedPreferencesImpl.java:629)
    at android.app.SharedPreferencesImpl$EditorImpl.lambda$notifyListeners$0$SharedPreferencesImpl$EditorImpl(SharedPreferencesImpl.java:643)
    at android.app.SharedPreferencesImpl$EditorImpl$$ExternalSyntheticLambda0.run
    at android.os.Handler.handleCallback(Handler.java:938)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loopOnce(Looper.java:226)
    at android.os.Looper.loop(Looper.java:313)
    at android.app.ActivityThread.main(ActivityThread.java:8633)
    at java.lang.reflect.Method.invoke(Method.java)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:567)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1133)

What's strange is that we do not attach any listeners when we are creating the Settings class in our Koin setup. So I am puzzled about how and why is Android SharedPreferences calling notifyListeners.

abdurahmanadilovic commented 2 years ago

We do use FlowSettings, which adds listeners for getXFlow method calls which we do use in our codebase, so I guess that is why addListener is invoked.

The current way to cause a crash like this is to use getXFlow to observe some changes with the FlowSettings class, then perform a delete on the underlying SharedPreferences store, which will then cause a crash like this. We are removing user data upon logout, but I guess we have some listeners active, so we have to make sure that we have no active listeners before SharedPreferences gets deleted right?

russhwolf commented 2 years ago

Huh, I guess I missed this change in Android 11. I'm not sure how it hasn't already come up before as this looks like it will crash anytime an app sets a listener and then later calls clear(). Should be a pretty easy fix, though. In the meantime, yes the workaround would be to remove listeners before calling clear.

russhwolf commented 2 years ago

Oh scratch that, it should be rare because AndroidSettings.clear() doesn't call clear() on the SharedPreferences, it calls remove() on every key instead. The only time you'll see this issue is if you call preferences.edit().clear().apply() after setting listeners on AndroidSettings. So a better workaround is to call clear() on the Settings instead of the SharedPreferences.

russhwolf commented 2 years ago

This is fixed in version 0.9

abdurahmanadilovic commented 2 years ago

Thank you for a swift response and sorry for the late reply. Keep up the good work 👍