Is it allowed to put ActivityContext/ApplicationContext in a variable? - android

Please tell me is it allowed to put ActivityContext/ApplicationContext in a variable as shown in the code below?
class MainActivity : Activity() {
private val context = applicationContext
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
As you know in Android, the roots of the garbage collector are: static variables and active threads.
Case 1. We put applicationContext in the context variable.
private val context = applicationContext
Will there be memory leak? I don't think so, because applicationContext is a singleton in the process of running the application.
Case 2. We put activity or this in the context variable.
private val context = this
Will there be memory leak? My intuition tells me not to do this. Because Activity can be destroyed (for example, when the screen is rotated). If I'm right, please explain why there will be a memory leak, because no garbage collector root is used. Maybe I'm wrong and this behavior is allowed.
P.S. Will the situation be similar if we put variable context in companion object?
class MainActivity : Activity() {
companion object {
private val context = applicationContext // or this
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}

Those are properties, not variables. It's important to understand the distinction, although not for this particular question.
Your very first code block is fine because the application context reference will be cleared when the Activity instance is released, and Activities have shorter lifetimes than the Application.
The code example Case 1 is perfectly fine in any situation (static reference or instance reference) for the reason you said. The Application context is a singleton that lives for the lifetime of your app so it cannot be leaked.
For case 2: if you use this in a companion object, this refers to the companion object, not the Activity instance. (There would be no way for the companion object to statically get an Activity instance on its own because there is no single Activity instance for it to find at any given moment.) However, if you do this, you will indeed leak the Activity instance:
class MainActivity : Activity() {
companion object {
private var context? Context? = null
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
context = this
}
}
// or:
private var context? Context? = null // top-level property
class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
context = this
}
}
An Activity context should never be stored in a static property.
Anything referenced by an object property or top-level property (outside a class) cannot be garbage collected because there is a static reference to it. Not sure what you mean by no garbage collector root being used. object and top-level properties are static references, which you said yourself are roots for the garbage collector.

Related

Android Kotlin: Having a global Context object for all activities (best practice)

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) {}

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.

Why companion object for newStartIntent in Android+Kotlin?

I have seen quite a few examples in Kotlin where an activity class has a companion object to encapsulate the creation of a start intent like the following. It seems particularly Java inspired.
class HomeActivity : AppCompatActivity() {
companion object {
fun newStartIntent(context: Context): Intent {
val intent = Intent(context, HomeActivity::class.java)
return intent
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
this.setContentView(R.layout.activity_home)
// ...
}
// ...
}
Since Kotlin has top level functions, why not skip the companion object and just have a top level function?
fun newHomeActivityStartIntent(context: Context): Intent {
val intent = Intent(context, HomeActivity::class.java)
return intent
}
class HomeActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
this.setContentView(R.layout.activity_home)
// ...
}
// ...
}
There is nothing wrong in your approach actually. I thought of a few reasons why I would choose a companion object over top-level functions:
Top-level functions visible for everyone, thus every time you start typing new... you will get a list of partially irrelevant results;
Companion objects can hold private values that you would not like to make open to the public and keep them visible only within your class but still make them static. Maybe there are some arguments that are calculated under this function invocation and passed with intent, and you would like to hide these calculations or arguments keys;
This is not your case but still relevant: using companion object you can make all constructors private and control all arguments passed to object initialization. This is how Singleton can be created in Kotlin;
Opinionated For me personally it makes things look tidy. I usually extract only simple and relatively vastly used functions. Like Date conversion functions, or math function calculations.
It is a matter of style. Just pick one and be consistent!

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

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.

How to get context from MainActivity from anywhere and how to access it's functions?

I've just recently started going on about android developing not long after I've got stuck.
I want to know how can I get MainActivity's context and it's functions globaly (if that's possible)
The only method I've got to work is passing the context as an argument to another classes' constructor. this#MainActivity doesn't work nor does trying to get a function such as MainActivity.getContext() which returns the context.
class MainActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
webView1.settings.javaScriptEnabled = true
webView1.webViewClient = Client(this) //Initialize the class
webView1.loadUrl("https://www.urbs.curitiba.pr.gov.br/mobile/cartao")
}
fun getContext(): Context //Not accessible from Client
{
return getContext()
}
}
//Client class
class Client(context: Context): WebViewClient()
{
val context: Context = context //This works
override fun onPageFinished(view: WebView, url: String)
{
webView1.loadUrl("https://kotlinlang.org/docs/reference/this-expressions.html")
Toast.makeText(context, "Uau", Toast.LENGTH_SHORT).show()
//using this#MainActivity as context is not defined
}
}
It would be great if I could use functions from the mainactivity class but I'm not really familiarized with the language and therefore I can't do so (if it's possible) right now.
This is my first post at stackoverflow and I'm really sorry if it is low quality, I'm not yet familiriazed with the system.
Welcome to StackOverflow!
In your specific case, there's no need to pass an instance of Context through the constructor, you can instead access the WebView's context property: all View subclasses in Android have a reference to a Context instance.
//Client class
class Client : WebViewClient()
{
override fun onPageFinished(view: WebView, url: String)
{
webView1.loadUrl("https://kotlinlang.org/docs/reference/this-expressions.html")
Toast.makeText(view.context, "Uau", Toast.LENGTH_SHORT).show()
}
}
In general, if you ever need a reference to Context, prefer passing it explicitly rather than storing it in global variables - this way you'll be able to prevent memory leaks.
MainActivity.getContext() means there is a public static method called getContext in MainActivity class while there isn't;
about your scenario, you already have passed a MainActivity instance to the Client class so you can just cast it and call getContext() method:
class Client(context: Context): WebViewClient()
{
private val context: MainActivity = context as MainActivity //This works
override fun onPageFinished(view: WebView, url: String)
{
webView1.loadUrl("https://kotlinlang.org/docs/reference/this-expressions.html")
Toast.makeText(context, "Uau", Toast.LENGTH_SHORT).show()
//now its possible
}
}
about accessing a class instances (like your MainActivity) globally, you can always put your instances in a public static variable and access them from everywhere(but implementing this for Android Activity class is a bit tricky )

Categories

Resources