Using 'this' as Context in the init block of activity? - android

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.

Related

How to force initialization on App 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 to define context in a kotlin object

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.

How "this" keyword actually works inside Intent constructor arguement?

Intent construcotr's arguements require object of Context class but down here inside this code I am passing "this" which means object of MainActivity class object. How it works? because it requires Context Class instance but i am passing MainActivity's instance.
const val EXTRA_MESSAGE = "com.example.myfirstApp.MESSAGE"
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
fun sendMessage(view: View){
val editText = findViewById<EditText>(R.id.editText)
val message = editText.text.toString()
val intent =Intent(this,DisplayMessageActivity::class.java).apply{
// if I use MainActivity() instead of this , gives me error
putExtra(EXTRA_MESSAGE, message)
}
startActivity(intent)
}
}
Here, this means MainActivity's instance, but if I use MainActivity() instead of this it gives me error. Whereas this and MainActivity() are same thing here.
In Android Activity, Service, BroadcastReceiver, Application all extends Context. So you can pass them in place of Context.
You can see all the class that an Activity class extends here.

How to get previous activity in android Kotlin

Lets say my default activity is MainActivity and I start another activity DepositActivity without using finish() in MainActivity
Now how can I access the instance of MainActivity inside DepositActivity
Now as how can I access the instance of MainActivity inside DepositActivity
AFAIK That is not possible to access instance of one activity in other Activity
if you have this type of requirement than Try to manage using Fragments
If you want to retrieve some result from DepositActivity use startActivityForResult(..., DepositActivity::class.java) method. In MainActivity override onActivityResult method:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
// retrieve data using 'data' variable
}
In DepositActivity you need to set data using method setResult() before finishing DepositActivity.
If you want to pass some data to DepositActivity use intent for that, for example:
val intent = Intent(this, DepositActivity::class.java)
intent.putExtra("Extra_Name", /*Some Data*/)
startActivity(intent)
Not Recommended: Use static reference to MainActivity (don't forget to delete it in onDestroy() method):
class MainActivity : AppCompatActivity() {
companion object {
#SuppressLint("StaticFieldLeak")
#JvmStatic
var instance: MainActivity? = null
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
instance = this
}
override fun onDestroy() {
instance = null
super.onDestroy()
}
}
In DepositActivity you can access it like this:
MainActivity.instance?./* call some method or property */
But you should not rely on onDestroy() being called, cause there are situations where the system will simply kill the activity's hosting process without calling this method (or any others) in it... So you can have memory leak
You need to declare as companion object variable and method in MainActivity. Static type of variables and methods are declared as companion object in Kotlin.
Look at below example,
Declare variables and methods in MainActivity,
val value : String = "hello from Main"
companion object {
lateinit var instance : MainActivity
fun getInstancem() : MainActivity {
return instance
}
}
Use this instance and print value in DepositActivity like,
Log.d("log_in_second_activity", "message " + MainActivity.getInstancem().value)
You can see log message.
Hope this will give you hint.

Kotlin singleton application class

On Android I want to make my application class a singleton.
Making it like this:
object MyApplication: Application(){}
won't work. The following error is thrown at runtime:
java.lang.IllegalAccessException: private com....is not accessible from class android.app.Instrumentation.
Doing this is also not possible:
class MyApp: Application() {
private val instance_: MyApp
init{
instance_ = this
}
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree());
}
}
companion object{
fun getInstance() = instance_
}
}
How can I get an instance of my application class everywhere in my app? I would like to use MyApp.instance() instead of (applicationContext as MyApp).
Also an explanation why I want this: I have classes in my app. For example, a SharedPreference Singleton which is initialised with a context, and as it’s a singleton, it can't have arguments.
You can do the same thing you would do in Java, i.e. put the Application instance in a static field. Kotlin doesn't have static fields, but properties in objects are statically accessible.
class MyApp: Application() {
override fun onCreate() {
super.onCreate()
instance = this
}
companion object {
lateinit var instance: MyApp
private set
}
}
You can then access the property via MyApp.instance.
If you want to use it to access some static properties you have there: You will only have one instance of your Application, so simply use the name you gave to the class. Don't worry about it not being an actual singleton, you can use it the same way.
Example:
class MyApp : Application() {
companion object {
const val CONSTANT = 12
lateinit var typeface: Typeface
}
override fun onCreate() {
super.onCreate()
typeface = Typeface.createFromAsset(assets, "fonts/myFont.ttf")
}
}
Then you can use MyApp.CONSTANT and MyApp.typeface anywhere in your app.
-
If what you want is to use it as an application context you can create an extension property for Context:
val Context.myApp: MyApp
get() = applicationContext as MyApp
Then you can use myApp to get the the application context anywhere you have a context.
class AppController : Application() {
init {
instance = this
}
companion object {
private var instance: AppController? = null
fun applicationContext() : AppController {
return instance as AppController
}
}
override fun onCreate() {
super.onCreate()
}
}
You cannot do that because Android creates an Application instance using its parameterless constructor.
The problem you want to solve can be easily solved with DI. Just create instances with an injector so that the Context can be injected into objects as a dependency.

Categories

Resources