Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save danielesegato/74c1d0c8e14dd5925ec227b89f636827 to your computer and use it in GitHub Desktop.
Save danielesegato/74c1d0c8e14dd5925ec227b89f636827 to your computer and use it in GitHub Desktop.
Accessing the Android Application Context statically - before being horrified, read the comment
package multiplatform
import android.app.Application
import android.content.Context
import kotlin.reflect.KProperty
/**
* Hi developer being horrified at this. Please READ ON. This wasn't done lightly.
*
* There was a post in the long-gone Google+ social by Dianne Hackborn, an Android Framework
* engineer. This was the URL: https://plus.google.com/u/0/+AnderWebbs/posts/DsfpW51Vvow
*
* She said (taken from [this reddit post](https://www.reddit.com/r/androiddev/comments/7zf0pp/where_does_android_community_stands_with_respect/)):
*
* ==================
*
* The only reason Application exists as something
* you can derive from is because during the pre-1.0
* development one of our application developers was
* continually bugging me about needing to have a
* top-level application object they can derive from
* so they could have a more "normal" to them
* application model, and I eventually gave in.
*
* I will forever regret giving in on that one. :)
*
* There is no reason to subclass from Application.
* It is no different than making a singleton, just
* likely to be something you regret in the future
* as you find your Application object becoming this
* big tangled mess of what should be independent
* application logic.
*
* If what you want is some global state that can be
* shared across different parts of your app, use a
* singleton. The singleton won't get GCed, because
* you have a global static reference on it. And
* this leads more naturally to how you should be
* managing these things -- initializing them on
* demand.
*
* The framework itself has tons and tons of
* singletons for all the little shared data it
* maintains for the app, such as caches of loaded
* resources, pools of objects, etc. It works great.
*
* ==================
*
* They were talking about having global state in an Android application. And not about holding
* a static reference to the Application object.
*
* However, in my opinion, Dianne regrets shouldn't have been making the application extendable.
* It should have been not making the application object statically available.
*
* If you disagree with that opinion you'll disagree with this class. However Dianne confirms that
* the Application object is a singleton. All this class does is make it accessible statically.
*
* It does something more, which is also kind of awful: if it hasn't been explicitly initialized
* it try to extract it through reflection from the Android framework actual static singletons
* classes.
*
* That part is what makes it easier but more dangerous to use it.
* Those methods are marked with [android.compat.annotation.UnsupportedAppUsage] annotation.
* I still suggest you set the application manually.
*
* I we do not have a static way to access the Application for historically reasons and there's
* actually no real reason why that shouldn't be possible. The Android team was afraid making
* Application static would cause issues for them down the line and they chose to keep it a class
* initialized by the framework that couldn't be accessed statically. Fast forward more than 10
* years there are a lot of applications that made the application accessible even without using
* reflection and I strongly doubt this will ever become dangerous or problematic.
*
* Multi-process will still go through the same initialization process twice, and therefore each
* process will have its own Application object and a separate static accessor.
*
* It might still be a good idea to wrap platform interactions with interfaces so that they can be
* mocked out as needed. However forcing to pass the application context everywhere just because the
* Android team made a design mistake is kind of stupid in my opinion. Activity / Service / etc
* still needs to be passed around because those Context are NOT actually singletons.
*
* Look at any other platform: iOS, Web, ... no other platform hide access to the OS APIs behind
* an object initialized by the framework without giving static access to that object.
*
* You should still use dependency injection. However you shouldn't be forced to pass an object that
* you have no control over to your classes. For instance instead of directly grabbing a
* context.fileDir you could pass a File object to your class or hide that behind an interface.
* However at some point you'll need an implementation of it and that implementation shouldn't need
* to force every part of the DI hierarchy to pass down the Application object. That's my opinion.
*
* I still believe another huge mistake of the Android framework (which directly relates to this
* choice) is that it insist to create instances of every entry point class (Activity, Service, ...)
* instead of letting the developer create the objects and providing an API to obtain the context.
* This however has nothing to do with this class so I'll stop here :)
*
* On the implementation: there are two, undocumented, ways of grabbing the Application statically.
* They are both in the code, but only one is used, I chose the one that felt more right to me.
*/
object StaticAndroidApplicationSingleton {
private var application: Application? = null
operator fun getValue(thisRef: Any?, property: KProperty<*>): Context {
return application
?: staticApplicationThroughReflection()?.also { application = it }
?: throw IllegalStateException("Application hasn't been initialized")
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Context) {
require(value is Application) { "Context must be an Application" }
application = value
}
private fun staticApplicationThroughReflection(): Application? {
return staticApplicationThroughAppGlobals()
}
private fun staticApplicationThroughAppGlobals(): Application? {
try {
val appGlobals = Class.forName("android.app.AppGlobals")
val getInitialApplication = appGlobals.getMethod("getInitialApplication")
return getInitialApplication.invoke(null) as Application
} catch (e: Exception) {
return null
}
}
private fun staticApplicationThroughActivityThread(): Application? {
try {
val activityThread = Class.forName("android.app.ActivityThread")
val currentActivityThread = activityThread.getMethod("currentActivityThread").invoke(null)
val application = activityThread.getMethod("getApplication").invoke(currentActivityThread)
return application as Application
} catch (e: Exception) {
return null
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment