https://kotlinlang.org logo
#android
Title
# android
d

Deepti M

04/16/2020, 10:09 PM
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

Cody Engel

04/16/2020, 10:19 PM
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

Deepti M

04/16/2020, 10:22 PM
I am using it mainly for fetching the resources, shared-pref, external-storage or similar things
c

Cody Engel

04/16/2020, 10:26 PM
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

Deepti M

04/16/2020, 10:27 PM
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

Cody Engel

04/16/2020, 10:27 PM
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

Deepti M

04/16/2020, 10:30 PM
Yes, have created all the providers as you said, and need context in all of them
c

Cody Engel

04/16/2020, 10:31 PM
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

Deepti M

04/16/2020, 10:32 PM
Using Koin
c

Cody Engel

04/16/2020, 10:33 PM
What does your
startKoin
function look like?
d

Deepti M

04/16/2020, 10:33 PM
Copy code
startKoin {
    androidLogger()
    androidContext(this@BaseApplication)
    modules(allModules)
}
c

Cody Engel

04/16/2020, 10:34 PM
Can you share a snippet of a module you’re having difficulty providing the context with?
d

Deepti M

04/16/2020, 10:36 PM
No,
Copy code
androidContext()
is available in all modules
and I can pass it to any class
c

Cody Engel

04/16/2020, 10:37 PM
I’m not following the issue then…
d

Deepti M

04/16/2020, 10:38 PM
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

Cody Engel

04/16/2020, 10:44 PM
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

Deepti M

04/16/2020, 10:45 PM
O'Great
Thanks for this suggestion
c

Cody Engel

04/16/2020, 10:47 PM
Yeah you’re welcome
d

Deepti M

04/16/2020, 10:47 PM
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

Cody Engel

04/16/2020, 10:49 PM
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

Deepti M

04/16/2020, 10:53 PM
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

Pablo L.

04/16/2020, 11:57 PM
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

venomvendor

04/17/2020, 4:38 AM
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

Ahmed Ibrahim

04/17/2020, 8:22 AM
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

Cody Engel

04/17/2020, 1:25 PM
@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

Ahmed Ibrahim

04/17/2020, 1:29 PM
@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

Cody Engel

04/17/2020, 1:40 PM
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

Ahmed Ibrahim

04/17/2020, 1:41 PM
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

Cody Engel

04/17/2020, 1:45 PM
That seems like an interesting use case..
a

Ahmed Ibrahim

04/17/2020, 1:47 PM
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

Cody Engel

04/17/2020, 1:56 PM
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

Ahmed Ibrahim

04/17/2020, 2:00 PM
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.