Roar Grønmo
Roar Grønmo

Reputation: 3976

How do I prevent memory leak caused by context class in static fields

Help! I have a leak (at least I think so...)

The instance value below is my "port" into this class from static companion object. I had some thoughts of using applicationContext in some way (my TODO: comment)... because I get this memory leak warning... :

Leak message

To avoid this problem, and at the same time not needing the context parameter in every call to theese functions (I want to store the context in the class somehow, and get disposed of it when it dies), is there any good practice to the problem ?

...

class Repository private constructor(
        var context: Context
)
{
    /**
     * Repository class, class for all communication between database and external
     * sources
     * */

    private val TAG = javaClass.simpleName

    val appDatabase: AppDatabase = AppDatabase.getInstance(context.applicationContext)

    //TODO: Test if better with applicationContext?

    companion object {
        private val TAG = this::class.java.simpleName

        @Volatile private var instance:Repository ?=null

        fun getInstance(context: Context)= instance
                ?: synchronized(this){
                    instance
                            ?:Repository(context).also { instance = it }
                }
 

        fun liveDataUserResult(staffId: StaffType=StaffType.DRIVER) =
            instance?.liveDataUserResult(staffId = staffId)

        fun liveDataDepartmentResult(staffId: StaffType=StaffType.DRIVER) =
            instance?.liveDataDepartmentResult(staffId = staffId)

...

        fun liveDataGalleryItemSiteNoInUserPath() = instance?.liveDataGalleryItemSiteNoInUserPath()

...

    }

    fun liveDataGalleryItemSiteNoInUserPath() =
        appDatabase.galleryItemDao().liveDataGalleryItemSiteNoInUserPath(allText = context.getString(R.string.repository_all_text))

}
 

Upvotes: 2

Views: 3773

Answers (2)

Roar Grønmo
Roar Grønmo

Reputation: 3976

All credits to ADM and @Henry Twist above for an excellent answer (as I have accepted), but I will post my result here:


class Repository private constructor(
         context: Context
)
{
    /**
     * Repository class, class for all communication between database and external
     * sources
     * */

    private val TAG = javaClass.simpleName

    val applicationContext: Context = context.applicationContext

    val appDatabase: AppDatabase = AppDatabase.getInstance(applicationContext)

    companion object {
        private val TAG = this::class.java.simpleName

        @Volatile private var instance:Repository ?=null

        fun getInstance(context: Context)= instance
                ?: synchronized(this){
                    instance
                            ?:Repository(context).also { instance = it }
                }


        fun liveDataUserResult(staffId: StaffType=StaffType.DRIVER) =
            instance?.liveDataUserResult(staffId = staffId)
        fun liveDataDepartmentResult(staffId: StaffType=StaffType.DRIVER) =
            instance?.liveDataDepartmentResult(staffId = staffId)

...
              
        fun liveDataGalleryItemSiteNoInUserPath() = instance?.liveDataGalleryItemSiteNoInUserPath()

...

    }

    fun liveDataGalleryItemSiteNoInUserPath() =
        appDatabase.galleryItemDao().liveDataGalleryItemSiteNoInUserPath(allText = applicationContext.getString(R.string.repository_all_text))
    
}

And, of course Dagger2 or Dagger-HILT will be more elegant.

Upvotes: 1

SpiritCrusher
SpiritCrusher

Reputation: 21053

Do not pass Context in the Repository except pass AppDatabase instance directly.

AppDatabase is used as Singleton throughout the application So you just initiate it once probably in Application class or in some other Singleton, and use the same Object everywhere.

It will be easy to create/inject Dependencies if you use any dependency injection framework like Dagger2 or Dagger-Hilt.

You can remove var for constructor field to make it work then it will not hold the reference globally. Thx to @Henry Twist .

class Repository private constructor(context: Context) {
    private val TAG = javaClass.simpleName
    var appDatabase: AppDatabase = AppDatabase.getInstance(context.applicationContext)
    companion object {
        @Volatile
        private var instance: Repository? = null
        @JvmStatic
        fun getInstance(context: Context) = instance
            ?: synchronized(this) {
                instance
                    ?: Repository(context).also { instance = it }
            }
    }
}

   

Upvotes: 2

Related Questions