is there an elegant way to achieve something like ...
# android
m
is there an elegant way to achieve something like this:
Copy code
interface PreferenceAccessor {
    val fileName: String
    val context: Context
    val prefs: SharedPreferences 
            get() = context.getSharedPreferences(fileName, Context.MODE_PRIVATE)
}

class PrefAccessorImpl(ctx: Context): PreferenceAccessor {
    override val fileName: String
        get() = "somePrefFileName"
    override val context: Context
        get() = ctx
}
What I’m looking for is a way to define a bunch of shared preference implementations with different file names, but save on the boilerplate of using context to initialize them. i’d also like to avoid having
Context
objects actually injected
google 1
f
this doesn't seems to be an optimum way to use an interface as the interface assume that the implementation will be using sharedPreferences. Why not simply declare a method on a class that take String as a parameter and return the fileName?
e
you can have extension functions that directly have the filename injected:
fun Context.namePreferences() = this.getSharedPreferences("name", Context.MODE_PRIVATE)
this way you can have as many functions as you need to access the different shared preferences, with minimum boiler plate and without have to pass instances of
Context
around
m
i don’t hate the extension function idea, except for the fact that i rely pretty heavily on mockito for unit testing, so i’d need to inject preference objects still, since they can’t be mocked
what i’m really trying to achieve here is separation of responsibility for different aspects of the cache. so something like
Copy code
class PrefAccessor1(ctx: Context) {
    private val cacheFileName1 = "1"
    private val prefs = ctx.getSharedPreferences(cacheFileName1, Context.MODE_PRIVATE)

    // Business logic specific to this section of the cache
}

class PrefAccessor2(ctx: Context) {
    private val cacheFileName1 = "2"
    private val prefs = ctx.getSharedPreferences(cacheFileName2, Context.MODE_PRIVATE)

    // Business logic specific to this section of the cache
}
but i’m looking for a way to codify that they all follow the same contract of A: having different preference file names B: all initialize the preferences the same way
all while not having references to
Context
in them, as they could be long-living
i
do you inject with dagger or yourself?
If it’s yourself, retain the ctx.applicationContext
if it’s with dagger, you can receive Application instead of Context
and provide Application in initialization
But I am not sure that business logic should be in preferenceaccessor
it’s better to inject the sharedpref in your business logic
a
So, somewhere you're going to need to use Context to retrieve preferences whether it's android preferences or from a file. Whether they're long-lived or not shouldn't matter because once you retrieve them they're just objects. They're not changing while android is away are they?
p
Your application must work when all cacheing is disabled so putting business logic into the cache would not be wise. If you want to unit test that logic that would be inside of the cache you would need android context which does not make sense.
e
You should specify the implementation details in the interface. But you can have a base abstract implementation and let the final implementations fill in the blanks.
Copy code
interface Cache {
    fun cache(item: Any) //this would depend on how you will use them, maybe use generics, etc
    fun getCached(key: Any) //this would depend on how you will use them, maybe use generics, etc
}

abstract class NamedPreferencesCache(name: String, ctx: Context) : Cache {
    protected val preferences = ctx.getSharedPreferences(name, Context.MODE_PRIVATE)
    //common caching logic
}

class FirstPreferencesCache(ctx: Context) : NamedPreferencesCache("first", ctx) {
    //specific caching logic
}
Also, as some already said you should have business logic in the caches, only caching logic
For the context part there is no way to avoid it unless you make a wrapper for retrieving preferences and inject that instead of the context. But then you have the dependency on the SharedPreferences. In the end, to test the data layer completely you will have dependencies to one or the other because your code depends on them.
m
SharedPreference dependency isn’t bad for me because A: it’s easily mockable and B: it’s not a god object
your solution was pretty much exactly what i was looking for