I need to access to the context of an application, but doing it this way it says "Do not place Android context classes in static fields" on the third line. Tried to remove the private val but without it I can't access to the context on the copyDatabase function.
I need the context to copy a database in the assets folder to the data folder of the application.
class Database constructor(private val ctx: Context) : ManagedSQLiteOpenHelper(ctx, "dex.db", null, 1) {
companion object {
private var instance: Database? = null
#Synchronized
fun getInstance(ctx: Context): Database {
if (instance == null) {
instance = Database(ctx.applicationContext)
}
return instance!!
}
}
private fun copyDatabase() {
val input = ctx.assets.open("databases/dex.db")
FileOutputStream(ctx.getDatabasePath("dex.db").path).use { out ->
input.copyTo(out)
}
}
}
Thanks
By using a companion object, whose lifetime equals the lifetime of the loaded Database class, you have created a lifecycle mismatch between the Context you capture in the database instance and the database instance itself.
When your application is put to background and restored, or if you just rotate the device, the context (typically the Activity instance) will be destroyed without destroying the whole application, so the database instance will survive with a disposed context. This will reliably lead to application failure.
Although you could be careful to use an Application context instead, which would most probably match the lifecycle, the general practice of retaining the context instance is ill-advised, hence the warning.
Instead put the database instance as a property of your main activity class, or, alternatively, commit to using a dependency injection framework that will deal with this as a separate concern.
As an aside, your current code uses a broken variant of the double-checking lazy initialization idiom. If you need lazy initialization, in Kotlin you should always leave this to the by lazy property delegate instead of rolling your own.
Keeping the Context inside a static variable prevent the garbage collector to free the memory afterwards and will lead to a memory leak. You should pass a Context to your constructor and initialize everything inside it and should not have to retain it inside a variable.
Related
I am running calling a native function in Kotlin that takes a Unix file descriptor as a parameter. After the native function runs for a few minutes it report EBADF.
The code looks something like
class A(val file: ParcelFileDescriptor) : AutoCloseable {
private var fileDes: Int = -1
private external fun longRunningNativeFn(fd : Int) : FnResult
init {
fileDes = file.fd
}
fun process() : FnResult {
longRunningNativeFn(fileDes)
}
override fun close {
}
}
The file object passed into the constructor is not held anywhere else.
My working theory is that since file is only used in the init block, file then becomes a candidate for garbage collection so after a few minutes when the garbage collector kicks in, the file object calls close resulting in the native code getting a bad file descriptor.
So
is this theory correct?
If so what determines the lifetime of parameters in the constructor?
Does adding file.close() to the close function extend the lifetime of file for the duration of the class?
Note after adding the file.close(), I am no longer getting the BADF in my native code. Even though as #Ivo points out adding val to the primary constructor makes file a class member, the JRE might be smart enough to see nothing is using file and garbage collect it early since it needs to stick around until close is called
It's not merely a parameter of the constructor. You defined it as a property by writing val in front of it. You can access it throughout the entire lifetime of the instance. If you only want to have it as parameter you need to leave out the val like
class A(file: ParcelFileDescriptor) : AutoCloseable {
As for the lifetime if you write it like that, I personally don't know but I assume it gets garbage collected after the last init block but that's just a guess. And of course it also matters whether there are other references to that object outside of that class. I also have no idea why that EBADF happens
I am working on refactoring an android application (I'm not the original author) which uses a pre-created sqlite database file received from the backend. It is done like this because my client's use case needs a local database in which one of the tables can have 1 million rows in some cases. It is a stock-taking app for a rugged device which needs to work offline which means that the device needs to store the entire database of all the various products that can be found for the given warehouse so that the workers can see a product's information after scanning it's barcode. Every day at the start of the work on a new project, the pre-created database gets acquired from the backend and is used for the remainder of the project for the rest of the day.
I use Room for the database and also use Hilt. Normally everything works fine. The problem arises when/if the client uses a functionality in which the app can re-download the pre-created database from the backend which means that the entire database file Room uses gets rewritten. To avoid having references to a database that no longer exists, I close the database by calling my closeDatabase() method which then later gets recreated. The database class is the following (I shortened it and changed names due to NDA reasons):
#Database(
entities = [
ItemTable::class
],
exportSchema = false,
version = 1
)
abstract class ProjectDatabase : RoomDatabase() {
abstract fun roomItemDao(): ItemDao
companion object {
#Volatile
private var INSTANCE: ProjectDatabase? = null
fun getDatabase(): ProjectDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
MyApplication.appContext,
ProjectDatabase::class.java,
getDbNameWithPath()
).createFromAsset(getDbNameWithPath())
.build()
INSTANCE = instance
instance
}
}
fun closeDatabase() {
INSTANCE?.close()
INSTANCE = null
}
private fun getDbNameWithPath(): String {
return MyApplication.appContext.filesDir.toString() +
File.separator + Constants.PROJECT_DATABASE_NAME
}
}
}
I also use a Hilt module for the database like this:
#Module
#InstallIn(SingletonComponent::class)
class ProjectDatabaseModule {
#Provides
fun provideProjectDatabase(): ProjectDatabase {
return ProjectDatabase.getDatabase()
}
#Provides
fun provideItemDao(
projectDatabase: ProjectDatabase
): ItemDao {
return projectDatabase.roomItemDao()
}
}
My problem is that when I set the INSTANCE to null, then the next call to getDatabase() creates a new instance, but all the references previously created by Hilt for the various classes still reference the old instance.
If I don't call INSTANCE = null then the database doesn't get reopened. If I don't close the database, then Room goes insane due to having its entire underlying database completely changed. Previously I always called getDatabase().xyz which worked but was kinda ugly, thus I started to use Hilt for it.
Is there a solution for this via Hilt? I'm afraid I'll have to go back to my old solution. Even if I change my scope to something else, the already existing classes will use the old reference.
Basically what I wish for is a call to ProjectDatabase.getDatabase.roomItemDao() every time I call a method of ItemDao.
I decided that the best solution in this case is to either ask the user to restart your app or do it programmatically according to your given use case.
Thanks to #MikeT for his reassuring comment.
I used a lot of static data in Activities, Adapters, Application, etc with like
companion object{
const val SEND_MY_DATA = "sendta"
const val SEND_MY_DATA_1 = "sendta1"
const val SEND_MY_DATA_2 = "sendta2"
}
to have common name for intent extras to match the same name between two activities. So, this static data are used in the activity & in another activity, and even some adapters.
And also I used this in Application class like
// this is used somewhere.
fun updateContext(){
appContext = applicationContext
}
companion object{
var appContext: Context? = null
fun myFunction(context: Context){
// use context param here.
}
}
Is this a bad approach or not? Is there any better way to improve this?
Just a note about saving data in the Application class, I encountered this by dealing with local resources in Room.
Generally, as in the case of the linked question, it can be a good solution (Android Studio shows a memory leak warning).
According to your needs you have to be careful to save data in this way, because Android OS can actually kill processes, including your Application instance.
To determine which processes should be killed when low on memory, Android places each process into an "importance hierarchy" based on the components running in them and the state of those components.
Check Processes and Application Lifecycle for more information.
A complete discussion about this can be found on this post.
If you're going to make a static application Context reference, I think this is cleaner:
companion object {
lateinit var context: Context
private set
}
override fun onCreate() {
super.onCreate()
context = applicationContext
}
But if you use dependency injection, you shouldn't need it. The singleton Context pattern makes unit testing difficult.
As for storing your constants, companion objects are fine. They do result in an extra class that's compiled, but that should be trivial since you shouldn't have very many activities.
Will there be any memory leaks if I access resources statically from My Application class like so:
class App : Application() {
companion object {
fun getResources(): Resources {
return this.getResources()
}
}
}
I was looking for a way to access my resources directly from my view models without passing a context object I just had to use AndroidViewModel instead of ViewModel from android arch components.
Thanks all.
Companion objects are not static, as the documentation describes:
Note that, even though the members of companion objects look like static members in other languages, at runtime those are still instance members of real objects, and can, for example, implement interfaces
You can't use companion object like this to get access to the App class. In your example, this refers to companion object itself, thus you're creating endless recursive call: this.getResources() just calls itself since this == App.Companion.
You cannot access App instance from its companion object, but you can access your companion instance from App class. That means, if you want to access your app context globally, you have to do smth like this:
class App {
override fun onCreate() {
super.onCreate()
appContext = this
}
companion object {
lateinit var appContext: Context
fun getResources(): Resources = appContext.resources
}
}
I've read a number of articles and stackoverflow posts regarding this question (mainly context instead of resources). A lot of people said it's OK because the Application class and the application context should always be alive throughout the life of the app, but some still discourage doing this sighting unknown possibilities (which made me doubtful - hence this post).
On the other hand, passing Context to every Object's function is quite daunting. In Java, one way to overcome this was to do something like UtilClass.getInstance(context) but you can't do that with Kotlin objects and I'm not too sure about implementing Kotlin Singletons with Arguments.
So, I have the following code:
class App : MultiDexApplication() {
override fun onCreate() {
super.onCreate()
_resources = resources
_context = applicationContext
//..other code
}
companion object {
val resources: Resources
get() = _resources!!
val context: Context
get() = _context!!
private var _resources: Resources? = null
private var _context: Context? = null
}
}
As you can see, I'm keeping a static reference to the application context and resources in a companion object when onCreate is called. With this, I'm able to (as examples):
call App.context in a util object that make API calls with Ion.
call App.resources in a util object that uses resource strings.
It's a convenience as compared to passing Context and/or Resources every time I call an object's method. Is this a safe solution? If not, are there any better alternatives?