I'm attempting to configure a RecyclerView adapter and I have a line of code
val decorator = AppCompatResources.getDrawable(context, R.drawable.decorator)
This line of code will not work however as context is nullable and getDrawable requires context. I can think of 4 potential options for getting a non-nullable context in onViewCreated:
Wrap the code in context?.let {}
Wrap the code in activity?.applicationContext?.let {}
Use requireContext()
Get context from my application. My application class includes the following code:
override fun onCreate() {
super.onCreate()
instance = this
}
companion object {
lateinit var instance: MyApp
private set
val context: Context
get() = instance
}
My dev lead is not a fan of requireContext, and will request that I remove it if he sees it in a pull request. Is he wrong? Help me get Context smart, which of these options are the best and why?
Related
I have a global app settings class as follows:
class AppSettings : MultiDexApplication() {
override fun onCreate() {
super.onCreate()
instance = this
resourses = applicationContext.resources
outputPathCache = cacheDir.absolutePath
}
companion object {
lateinit var instance: AppSettings
private set
val context: Context
get() { return activityContext.get()!! }
lateinit var activityContext: WeakReference<Context>
var database: SQLiteDatabase? = null
var resourses: Resources? = null
private set
lateinit var dialog: AlertDialog
const val defLanguage = Enum.Language.ENGLISH
const val defIdLanguage = Enum.LanguageId.ENGLISH
const val screenshotFilename = "xxx"
const val actionBarTitleColor = "#0D0D0D"
const val footerColor = "#8a8a8a"
const val activityBackground = "#ffffff"
... whatever
}
}
And as you see I have a static Context variable as follows:
lateinit var activityContext: WeakReference<Context>
(I use WeakReference so the IDE doesn't complain about memory leaks).
And I have a constant Context like the next:
val context: Context
get() { return activityContext.get()!! }
I assign a value for the first time to activityContext in SplashActivity as follows (I do this because the first activity is a OnBoarding class that doesn't inherit from BaseActivity):
AppSettings.activityContext = WeakReference(this)
The same in BaseActivity onCreate (most of my activities inherit from this class):
AppSettings.activityContext = WeakReference(this)
And then, in any activity which extends BaseActivity I can use the context simply like this:
AppSettings.context
For the activities that doesn't inherit from BaseActivity I just initialise the context to be used in the activity in the same way as in Base, so I can always get it as "AppSettings.context".
The reason of not simply using "this" in all activities to get context (or to use any sort of Context creation in Base) is that I'm using MVVM and there are classes outside activities (like ViewModel) with methods that may need a context, and I just don't wan't to pass it as a parameter (this is why I'm expecting to have a global context that can be accessed anywhere).
Although I have just finished and I haven't fully tested yet, it is apparently working great, but I wonder to know if this is the recommended way to deal with this, or if there is a better approach to have a global Context.
There are some ways to achieve that, but I believe the most encouraged by google is with the dependency injection library dagger-hilt, which isn't hard to set-up, but saves a lot of time and prevents possible memory-leaks.
In order to inject context into any class later you just need to do:
class ExampleClass #Inject constructor(#ApplicationContext val context: Context) {}
in order to use application context anywhere, I use this code:
class App : Application() {
companion object {
lateinit var instance: App private set
fun isInstanceInitialized() = ::instance.isInitialized
}
override fun onCreate() {
super.onCreate()
instance = this
}
}
But sometimes it throws an error.
kotlin.UninitializedPropertyAccessException: lateinit property instance has not been initialized
As suggested here, setting a delay (before calling App.instance) helps, but I would like to call something without having to wait for a set delay. And besides that, isInstanceInitialzed method always returns false..
What can I write in the beginning of onCreate method in MainActivity, in order to make sure that the instance variable is initialized?
How can I define a context for the progressdialog within such an object
import dmax.dialog.SpotsDialog
object Constants {
//These are all the constants within our application
const val permission_request = 100
val firebaseAuth = FirebaseAuth.getInstance()
val progressDialog = SpotsDialog.Builder().setContext(thecontext).build()
}
You can give it a lateinit context property that you set in your Application class. Then make the property that's dependent on it Lazy. But in this case, it doesn't make sense, because a Dialog is transient. It wouldn't be a constant. You can't reuse dialogs, because Android destroys and recreates the Activities/Fragments that host them according to various lifecycle processes.
But if you do have something like a constant that needs a Context, this is how you could do it:
object Constants {
lateinit var context: Context
val foo by lazy { Foo(context) }
}
class MyApplication: Application() {
override fun onCreate() {
super.onCreate()
Constants.context = this
}
}
And make sure you set .MyApplication as the Application name in the manifest.
I am developing an android application with kotlin.
I have a DereDatabaseHelper class which has init block that uses a context given through class parameter(?)
The DereDatabaseHelper is like this.
class DereDatabaseHelper(context: Context) {
val manifestFile: File
val fumensDBFile: File
val fumenFolder: File
val musicIDToInfo: MutableMap<Int, MusicInfo> = HashMap()
val fumenIDToMusicID: SparseIntArray = SparseIntArray()
init {
val datadir = context.getExternalFilesDir(null).parentFile.parentFile
The DereDatabaseHelper class is instantiated here in SongListActivity like this.
class SongListActivity : AppCompatActivity() {
var dereDatabaseHelper : DereDatabaseHelper
init {
dereDatabaseHelper = DereDatabaseHelper(this)
}
I thought that this code was correct, but this codes throws NullPointerException.
java.lang.NullPointerException: Attempt to invoke virtual method
'java.io.File android.content.Context.getExternalFilesDir(java.lang.String)'
on a null object reference at
android.content.ContextWrapper.getExternalFilesDir(ContextWrapper.java:253)
at com.kyhsgeekcode.dereinfo.model.DereDatabaseHelper.<init>(DereDatabaseHelper.kt:21)
at com.kyhsgeekcode.dereinfo.SongListActivity.<init>(SongListActivity.kt:31)
Is this null when the execution is in init block and what initialization style should I use to fix this?
Never use the constructor of an Activity to do anything that involves the Context. Android instantiates Activities using their sole empty constructor (via reflection), and then sets up the activity's various fields before it ever calls onCreate(). Your first safe entry point to do anything in your Activity is in onCreate().
You also can't call methods of the Activity (which is itself a Context) in the constructor.
You also can't use the context in any way to even set up properties, because they'll try to access the context before onCreate:
class MyActivity: AppCompatActivity() {
val assets: AssetManager = getAssets() // This will cause a crash
}
To avoid having to make your property nullable, you can do either of the following, which allow you to avoid having your class instantiated until after onCreate() is called:
class SongListActivity : AppCompatActivity() {
lateinit var dereDatabaseHelper : DereDatabaseHelper
override fun onCreate() {
super.onCreate
dereDatabaseHelper = DereDatabaseHelper(this)
}
or
class SongListActivity : AppCompatActivity() {
val dereDatabaseHelper by lazy { DereDatabaseHelper(this) }
}
Activities are not completely initialized by the constructor or in your case init block.
Android system initializes activities and then calls the onCreate method. So you should do the following
override fun onCreate(savedInstanceState: Bundle?) {
// create instance of DareDatabaseHelper
}
Why it doesn't work with the constructor?
consider the following code snippet
var myActivity = MyActivity() // This doesn't start MainActivity
// This is how you start an activity
val intent = Intent(context, MyActivity::class.java)
startActivity(intent)
When you start any activity you never instantiate the activity class, why?
Because that is the responsibility of android system, when you do startActivity(intent) android system instantiates your activity class using the default constructor and then does all the initialization (ie. providing context) And once the activity is completely initialized the onCreate method of your activity is called where you can do your end of initialization.
I am trying to add a "static" method to my MyApplication class in kotlin
I have added (as a property) the variable :
private var context: Context? = null
in method:
override fun onCreate()
I added:
context = applicationContext
then I add a companion object like this
companion object {
#JvmStatic fun getMyApplicationContext(): Context?
{
return MyApplication().context
}
}
when I call this method from other parts of the application like
MyApplication.getMyApplicationContext() it always returns null. I have gleaned all this from several sources but I am not sure if it is anywhere near correct or not.
It sounds like you want a global application context object. Now casting aside my dislike for global variables, I think you are pretty close.
I think you just need to add the variable into the MyApplication classes companion object and use that directly. You only need the #JvmField annotation if you're going to access the field from Java.
class MyApplication {
companion object {
#JvmField
var context: Context? = null
// Not really needed since we can access the variable directly.
#JvmStatic fun getMyApplicationContext(): Context? {
return context
}
}
override fun onCreate() {
...
MyApplication.context = appContext
}
}