Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the best practice to prevent memory leaks using Datastore?

I try to store & get data using a datastore-preference alpha07, everything working fine, I got some memory leak issue in the datastore

What's the best practice to prevent memory leaks using Datastore?

here is my sample code:

// Preferences DataStore
    implementation "androidx.datastore:datastore-preferences:1.0.0-alpha07"
// Leakcanary find memory leak
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.6'

UserManager.kt

import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

class UserManager(val dataStore: DataStore<Preferences>) {

    //Create some keys
    companion object {
        val USER_AGE_KEY = intPreferencesKey("USER_AGE")
        val USER_FIRST_NAME_KEY = stringPreferencesKey("USER_FIRST_NAME")
        val USER_LAST_NAME_KEY = stringPreferencesKey("USER_LAST_NAME")
        val USER_GENDER_KEY = booleanPreferencesKey("USER_GENDER")
    }

    //Store user data
    suspend fun storeUser(age: Int, fname: String, lname: String, isMale: Boolean) {
        dataStore.edit {
            it[USER_AGE_KEY] = age
            it[USER_FIRST_NAME_KEY] = fname
            it[USER_LAST_NAME_KEY] = lname
            it[USER_GENDER_KEY] = isMale

        }
    }

    //Create an age flow
    val userAgeFlow: Flow<Int?> = dataStore.data.map {
        it[USER_AGE_KEY]
    }

    //Create a fname flow
    val userFirstNameFlow: Flow<String?> = dataStore.data.map {
        it[USER_FIRST_NAME_KEY]
    }

    //Create a lname flow
    val userLastNameFlow: Flow<String?> = dataStore.data.map {
        it[USER_LAST_NAME_KEY]
    }

    //Create a gender flow
    val userGenderFlow: Flow<Boolean?> = dataStore.data.map {
        it[USER_GENDER_KEY]
    }

}

User.kt

import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore

// At the top level of your kotlin file:
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "user_prefs")

SignInFragment.kt

 //Get reference to our userManager class
               var userManager = UserManager(requireContext().dataStore)
        
                //Stores the values
                lifecycleScope.launch {
                    userManager.storeUser(1, "android", "studio", true)
                }

HomeFragment.kt

var userManager = UserManager(requireContext().dataStore)
       userManager.userAgeFlow.asLiveData().observe(requireActivity(), {
            if (it != null) {
                age = it
                //tv_age.text = it.toString()
            }
        })

Memory leak issue: enter image description here

enter image description here

Below solution not working I tried, still I am getting leaks even tried this solution, Observer should be bind with component's lifecycle in this case Fragment's lifecycle . Use viewLifeCycleOwner instead to bind the observer.

HomeFragment.kt

 var userManager = UserManager(viewLifecycleOwner.dataStore)
           userManager.userAgeFlow.asLiveData().observe(viewLifeCycleOwner, {
                if (it != null) {
                    // age = it
                    // tv_age.text = it.toString()
                }
            })

Ref screenshot (again replicated) enter image description here

like image 604
Hari Shankar S Avatar asked Mar 07 '21 07:03

Hari Shankar S


1 Answers

Suggested fix

Using your Activity's or Fragment's context is indeed fishy, and actually there's no need to do so. Instead, if you pass a global context of your application (applicationContext), then DataStore won't leak any UI contexts and will process your Preferences requests independently from the lifecycle of the UI component on which they start.

In your Fragments, write the following:

...
var userManager = UserManager(requireContext().applicationContext.dataStore) 
...

In your MainActivity, it will be even simpler:

...
var userManager = UserManager(applicationContext.dataStore) 
...

Result

That's how many leaks have been detected:

enter image description here

like image 118
Anatolii Avatar answered Oct 13 '22 05:10

Anatolii