Access application context in companion object in kotlin
Asked Answered
M

6

52

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

Milka answered 7/1, 2019 at 13:48 Comment(0)
M
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);
    }
}
Milka answered 9/1, 2019 at 12:18 Comment(5)
FYI you can drop the Companion reference by adding @JvmStatic to your setContext method.Economically
what about memory leaks?Excursive
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.Diversion
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.Volga
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.Volga
M
49

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()
    }
}
Macrospore answered 7/1, 2019 at 14:13 Comment(2)
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.Milka
You could use requireNotNull() instead of the null-asserted call like this: requireNotNull(instance).applicationContextFrech
A
37

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
Adductor answered 7/1, 2019 at 14:14 Comment(1)
You can use it in your app by adding a name attribute in your manifest e.g. <application ... android:name="fully.qualified.package.name.MyApplication" as mentioned here: https://mcmap.net/q/353890/-extending-android-application-classPia
M
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);
    }
}
Milka answered 9/1, 2019 at 12:18 Comment(5)
FYI you can drop the Companion reference by adding @JvmStatic to your setContext method.Economically
what about memory leaks?Excursive
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.Diversion
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.Volga
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.Volga
V
8

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
    }
}
Vicenta answered 21/9, 2021 at 10:29 Comment(1)
You also need to add this class to <application android:name in your AndroidManifest.xmlTayler
T
6

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" />
Tuatara answered 9/1, 2019 at 12:25 Comment(2)
Thanks for the answer @WarrenFaith. I already tried this and have also posted this approach as an answer.Milka
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!Tuatara
C
-2
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)   
            }
        }
    }
}
Cotquean answered 11/5, 2019 at 14:11 Comment(2)
make sure to call Test.init(context) before using Test. sharedPreferences usually in Application oncreate methodCotquean
This doesn't compileBert

© 2022 - 2024 — McMap. All rights reserved.