I have an `object` as a wrapper for Android shared...
# getting-started
l
I have an
object
as a wrapper for Android shared preferences. The preferences are lazily initialized and
object
properties are delegated to the preferences like this:
Copy code
object Persistence {
    
    private val prefs by lazy { PreferencesUtil(ContextProvider.context) }

    val timestamp by prefs.readOnlyLongPref(R.string.timestamp, 0L)
}
I want to mock the object for tests of another class (where
Persistence
is a dependency). The problem is, even if I don't reference this object instance in the class under test, but just as a type - i.e.
class CartRepository(private val persistence: Persistence)
, still the test fails with an exception telling that
ContextProvider.context
has not been initialized.
The stack trace actually says that the first delegated property is being resolved (it is in line 32 in code) and that's why the
lazy
(actually it's a custom
MutableLazy
in this case, but the principle is the same) is executed to get the
prefs
value for the delegate.
Copy code
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property context has not been initialized
	at package_redacted.util.util.ContextProvider$Companion.getContext(ContextProvider.kt:8)
	at package_redacted.core.support.Persistence$prefs$2.invoke(Persistence.kt:26)
	at package_redacted.core.support.Persistence$prefs$2.invoke(Persistence.kt:26)
	at package_redacted.util.util.MutableLazy.getValue(MutableLazy.kt:24)
	at package_redacted.core.support.Persistence.getPrefs(Persistence.kt:26)
	at package_redacted.core.support.Persistence.<clinit>(Persistence.kt:32)
I find it interesting that even though I only access the
Persistence
type, not the instance itself, still the instance is apparently being created automatically at the same time in
clinit
.
And the delegates are being resolved immediately.
I guess for tests I will just have to hide the
Persistence
behind an interface.
l
The problem is how Kotlin compiles this into bytecode. An
object
is a singleton created when the app starts, so the
Persistence
instance is created. Then, checking the Kotlin bytecode, apparently the delegates are also instantiated, so this is calling
prefs.readOnly...
so
prefs
is created. Maybe consider switching to
Copy code
val timestamp: Long
  get() = prefs.readLongPref(......)
btw Android studio (and I guess IntelliJ too) has a tool to see the Kotlin bytecode (Tools › Kotlin › Show bytecode), and then "switch back to Java" via Decompile button. That's how I knew it. You find pretty good info how things are created and managed under the hood
👍 1
l
Yes, actually even hiding
Persistence
behind an interface didn't get rid of this error.
The approach with
get()
could probably work, but defeats the convenience of having delegates
l
But it works in both tests and production 😄
l
Yes 🙂
l
I'm not a fan of changing production code because of the tests, but
get() =
doesn't add extra steps / overhead / whatever. In fact, is a more direct access to the data, isn't it?
l
Yes, but you need
get() / set()
instead of just using
by
And you have to do it for every property
(Sorry, my original example wasn't complete - we also have
var
properties)
I think maybe we should switch to another dependency injection approach
l
What about not using an
object
but a
class
and then injecting the dependency? If you need a singleton mandatory, let the DI framework to take care of it
l
Yes, that could be a solution here
This is a library, so we were avoiding adding a DI framework so far to not bloat the size.
l
Untitled.cpp
l
Thanks, that's also a good approach
Thank you for your helpful suggestions!
❤️ 1