47

How can we access application context inside companion object in Android kotlin? I have a companion object inside an abstract class and I want to access context to read Shared Preferences, but I'm not able to get the context.

UPDATE: I'm working with this stuff in an Android library and also the class that I'm working in is abstract

Hamza
  • 741
  • 1
  • 6
  • 12

6 Answers6

46

please see this go to link

class MainApplication : Application() {

    init {
        instance = this
    }

    companion object {
        private var instance: MainApplication? = null

        fun applicationContext() : Context {
            return instance!!.applicationContext
        }
    }

    override fun onCreate() {
        super.onCreate()
        // initialize for any

        // Use ApplicationContext.
        // example: SharedPreferences etc...
        val context: Context = MainApplication.applicationContext()
    }
}
Zoe
  • 27,060
  • 21
  • 118
  • 148
Raifur-rahim
  • 543
  • 1
  • 3
  • 12
  • Thanks for the answer @Raifur-rahim. I already tried it and it didn't work because I'm working in an Android library, and this library is being used in more than one app, so adding this name `MainApplication` in `AndroidManifest.xml` causes merging problems. However, I found a solution and will post it. – Hamza Jan 09 '19 at 12:03
  • You could use `requireNotNull()` instead of the null-asserted call like this: `requireNotNull(instance).applicationContext` – Max Feb 19 '23 at 21:14
32

Extends Application class like this

import android.app.Application
import android.content.Context

class MyApplication : Application() {

    override fun onCreate() {
        super.onCreate()
        MyApplication.appContext = applicationContext
    }

    companion object {

        lateinit  var appContext: Context
  
    }
}

then get context like this

     val context = MyApplication.appContext
Ahmet Gokdayi
  • 301
  • 9
  • 14
sasikumar
  • 12,540
  • 3
  • 28
  • 48
21

Actually I'm working inside an Android library and the class is abstract, so can't go with the already suggested solutions. However, I found way to do that.

  1. Creat a lateinit Context field inside companion object.
abstract class MyClass {

    companion object {

        private lateinit var context: Context

        fun setContext(con: Context) {
            context=con
        }
    }
}
  1. And then set it after the app has started
public class WelcomeActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_welcome);

        MyClass.Companion.setContext(this);
    }
}
Hamza
  • 741
  • 1
  • 6
  • 12
  • 1
    FYI you can drop the Companion reference by adding @JvmStatic to your setContext method. – 0xMatthewGroves Oct 27 '19 at 07:23
  • 14
    what about memory leaks? – AlexS May 07 '21 at 13:45
  • Non-static inner classes have an implicit reference to their outer class. If that outer class is for example a Fragment or Activity, then this reference means that the long-running handler/loader/task will hold a reference to the activity which prevents it from getting garbage collected. – Rafael Jul 02 '21 at 19:13
  • This is bad. When the activity is destroyed and recreated, such as during a screen orientation change, the context reference results in a memory leak, because it references a destroyed context. I believe you can demonstrate this problem by changing your screen orientation until your app crashes due to the leak. – Daniel C Jacobs Sep 24 '21 at 16:07
  • I think a way to accomplish OPs goal without memory leaks would be to pass the application object to `MyClass.setContext`, and save that to variable `private lateinit var application: Application`. Then whenever you need the context just call `application.appContext`. This won't have leaks because the application is active for the entirety of the runtime, while the activity can get created and destroyed multiple times during the lifecycle of the application. – Daniel C Jacobs Sep 24 '21 at 16:29
5

There is a super cool article from the guys from Firebase explaining how their SDK gets hold of the context.

Basically my contentprovider looks like this:

/**
 * This content provider is only responsible to inject the application context into the common module.
 */
class ContextProvider : ContentProvider() {

    companion object {
        private val TAG = ContextProvider::class.java.simpleName
    }

    override fun onCreate(): Boolean {
        context?.let {
            Common.setContext(it)
            return true
        }
        Logger.e(TAG, "Context injection to common failed. Context is null! Check ContextProvider registration in the Manifest!")
        return false
    }

    override fun query(uri: Uri, projection: Array<String>?, selection: String?, selectionArgs: Array<String>?, sortOrder: String?): Cursor? = null

    override fun getType(uri: Uri): String? = null

    override fun insert(uri: Uri, values: ContentValues?): Uri? = null

    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int = 0

    override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>?): Int = 0
}

And the Common object, which I treat like an sibling of any Application class looks like this:

/**
 * Partially working like an Application class by holding the appContext which makes it accessible inside this module.
 */
@SuppressLint("StaticFieldLeak")
object Common {
    /**
     * App appContext
     */
    @Volatile
    lateinit var appContext: Context

    var isStoreVersion: Boolean = false

    fun setContext(context: Context) {
        appContext = context
    }
}

As you can see I also enriched the Common object with a flag to store if the current build is a store version or not. Mainly because the BuildConfig of the app module is also not available in a module or library.

Don't forget to add the ContentProvider to the AndroidManifest of your library within the <application> tag

<provider android:name=".util.ContextProvider"
          android:authorities="${applicationId}.common.util.contextprovider"
          android:exported="false" />
WarrenFaith
  • 57,492
  • 25
  • 134
  • 150
  • Thanks for the answer @WarrenFaith. I already tried this and have also posted this approach as an answer. – Hamza Jan 09 '19 at 13:51
  • I have seen that but there are multiple down sides: 1. you actually have to "init" your library by hand somewhere while your ContentProvider would do that automatically 2. `lateinit` can lead to runtime crashes if the initialization wasn't done or done too late (and your library was called somewhere else first). Anyway if your solution is working and the disadvantages are manageable for you, great! – WarrenFaith Jan 10 '19 at 12:15
5

You can save the instance directly inside a companion object and accessing it outside without problems, I think this approach is the simplest.

IMPORTANT: change the visibility of the instance property to private to ensure no one but Application has write access.

class App : Application() {

    override fun onCreate() {
        super.onCreate()
        instance = this
    }

    companion object {
        lateinit var instance: App
            private set
    }
}
Ricard
  • 1,223
  • 1
  • 13
  • 17
-1
class Test { 

    companion object {
        lateinit var sharedPreferences: SharedPreferences

        fun init(context: Context) {
            // to prevent multiple initialization
            if (!Companion::sharedPreferences.isInitialized) {
                sharedPreferences = context.getSharedPreferences("preference_name", Context.MODE_PRIVATE)   
            }
        }
    }
}
Zoe
  • 27,060
  • 21
  • 118
  • 148
Ganesh Jogam
  • 3,117
  • 1
  • 13
  • 10