Hi! I need context in many classes other than acti...
# android
d
Hi! I need context in many classes other than activity or fragment. I am passing it as a method parameter, wherever required. But I feel, as per DI framework, I should make app-context available at all places. For doing the same, I found this reference. While along with this, also found one discussion, which says my old implementation is correct. I AM CONFUSED 😞 Can anybody help? https://gist.github.com/paraya3636/bf8108a75eb49323e56c0c90dd0747e0 https://discuss.kotlinlang.org/t/access-application-context-in-companion-object-in-kotlin/11054
c
It depends. What are you using the
Context
instance for? I’ve found most project benefit from an interface
StringProvider
which has an implementation that relies on the
Context
. This can be easily added to a dagger dependency graph, but again it all depends on the use case. Singleton isn’t great but there are likely plenty of apps out there today which do that and functions perfectly well.
d
I am using it mainly for fetching the resources, shared-pref, external-storage or similar things
c
I would have a wrapper similar to the one I described, one as
ResourceProvider
another as
PreferenceProvider
and a final one as
StorageProvider
.
👍 3
d
Yes agree, singleton is not good as per standards but as a contradictory point, it is far good than making unnecessary multiple copies of same/ similar instances
c
Singletons tend to break down when you want to write unit or integration tests.
Not always because you can of course scope instances as a singleton, but if those instances aren’t constructor injected you’ll usually have a bad time if you want to write any sort of functional tests.
d
Yes, have created all the providers as you said, and need context in all of them
c
Are you using a DI framework? If so which one and is there a snippet you can share to outline where you are getting hung up?
d
Using Koin
c
What does your
startKoin
function look like?
d
Copy code
startKoin {
    androidLogger()
    androidContext(this@BaseApplication)
    modules(allModules)
}
c
Can you share a snippet of a module you’re having difficulty providing the context with?
d
No,
Copy code
androidContext()
is available in all modules
and I can pass it to any class
c
I’m not following the issue then…
d
I am working on Videos in my app and have Video.kt model.
This is one method, of that model...
Copy code
fun getVideoUrl(context: Context): String? {
    val isTab = context.resources.getBoolean(R.bool.isTab)
    val isPortrait =
        context.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT
    var resolution = PreferenceHelper.readSelectedResolution(context)
    if (resolution == CamcorderProfile.QUALITY_2160P) {
        resolution =
            if (TextUtils.isEmpty(video2kShortPortrait) ||
                TextUtils.isEmpty(video2kShortLandscape)
            ) {
                CamcorderProfile.QUALITY_1080P
            } else {
                return if (isTab && !isPortrait)
                    video2kShortLandscape
                else
                    video2kShortPortrait
            }
    }
    var videoType: String? =
        context.applicationContext?.let {
            DeviceHelper().getVideoType(it, resolution)
        }
    if (IDevice.SMALL_TABS == videoType && isPortrait) {
        videoType = IDevice.SMALL_TABS_PORTRAIT
    } else if (IDevice.LARGE_TABS == videoType && isPortrait) {
        videoType = IDevice.LARGE_TABS_PORTRAIT
    }
    var videoUrl: String? =
        getVideoByType(PreferenceHelper.readSelectedVideoQuality(context), videoType)
    if (TextUtils.isEmpty(videoUrl)) {
        videoUrl = getVideoByType(IDevice.QUALITY_HD, videoType)
    }
    return videoUrl
}
Now here, I need PreferenceProvider, StorageProvider, resources, etc... So, I am confused how to handle it.
c
You could have a
VideoFactory
which is part of your Koin dependency graph. Then in places which need to create a
Video
they could invoke
VideoFactory
to create a new instance of it. I’m not sure what the exact parameters are, it could be setup something like this…
Copy code
VideoFactory(preferenceProvider: PreferenceProvider, storageProvider: StorageProvider) {
    fun create(/* specific properties for model /*): Video {
        return Video(preferenceProvider, storageProvider, /* specific properties for model */)
    }
}
Then instead of passing context to the function, the model would already have the providers it needs to generate the data.
d
O'Great
Thanks for this suggestion
c
Yeah you’re welcome
d
Actually, I am new to Kotlin as well as Koin and was not clear with this feature
Trying to do this 'factory' integration. May I bother you again if gets blocked anywhere? Thanks!
c
Unpopular opinion depending on who is reading this, but I would try to shift towards Dagger when you can. It has a bit more going on than Koin but Google has been creating good tutorials for it and you get a bit more control over your dependencies. https://codelabs.developers.google.com/codelabs/android-dagger/#0
Happy to help when I’m online, I am signing off for the evening but I’m typically available 9 - 5 CT (-06:00 I believe?)
d
Oh, I was trying with Dagger first but wen came across with https://insert-koin.io in standard documents of Kotlin then decided to goo for Koin
p
I generally agree on what Cody is saying, but I'd like to point out how important is, as a rule of thumb, to keep your logic free of Android dependencies, e.g. Context. Following a typical Clean Architecture approach (https://fernandocejas.com/2018/05/07/architecting-android-reloaded/), the central layer should not depend on any Android feature, so that it can be easily unit-tested.
v
Copy code
startKoin {
    androidLogger()
    androidContext(this@BaseApplication) // <-- Making context glolbally available
    modules(allModules)
}

fun getVideoUrl(): String? {
  // Get context injected during initialization.
  val context: Context = get()
}
^, FYI, The above makes hard to unit test. You will rely on UI tests or roboelectric.
👍 1
a
Even though this approach is totally valid, it has one caveat in ResourcesProvider, in case you tried to resolve string from it. It won't work if your app has custom localization requirements (e.g. device language is English but the app needs to be displayed in French or other languages), then you'll need an Activity context for that. https://kotlinlang.slack.com/archives/C0B8M7BUY/p1587075968008000?thread_ts=1587074955.007300&amp;cid=C0B8M7BUY
c
@Ahmed Ibrahim I don't think that's an accurate statement (partly because I've used this approach with localized apps without issues). The main difference between application and activity contexts are themes. As a clarifying point though, I tend to have a
StringProvider
which just provides strings, the other ones from that post are other interfaces you could extract as well.
a
@Cody Engel Sorry, I forgot to add, that depends on how do you implement the language switching, if you do
Process.killProcess()
to destroy the application and to reload everything, then yeah application context would work, but if you just do`activity.recreate()` then you will need to use the activity context to load the newly configured strings. At least that how it did work for me.
c
I've only supported language switching via the phone settings which has worked fine, I wrote a small Activity which worked for switching between english and german via the phone's language settings.
Copy code
class LocalizedActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(
            TextView(baseContext)
                .also { it.text = application.getString(R.string.hello) }
        )
    }
}
a
I'm afraid my use case is different, which is device language from phone settings is English, but the app language itself is displayed in german
c
That seems like an interesting use case..
a
Changing the language settings from the Phone will definitely work with
ResourceProvider
that has application context injected, the problem becomes when your application has a custom
resources.configuration
than what's coming from the Phone settings. Then you'll need activity context to resolve strings correctly. Again that's how it did work for me, I might've been doing something wrong that made things didn't work with application context 🤷‍♂️
c
I can't think of a situation where I'd want to override how the system is intended to work though. I could be missing something as the default language for my apps has been english but I always assumed the default language could be whatever you'd want it to be
a
Yeah, I think it's wide spread in e-commerce apps and in Europe here also it's wide spread due to different localization requirements across regions.