Reputation: 7032
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()
}
})
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)
Upvotes: 6
Views: 5424
Reputation: 14670
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)
...
That's how many leaks have been detected:
Upvotes: 4
Reputation: 21053
Adding same as answer Since the problem is resolved.
The reason Observer
is causing memory leak is because its bind to lifecycle of Activity
with use of requireActivity()
.
Observer
should be bind with component's lifecycle in this case Fragment's lifecycle . Use viewLifeCycleOwner
instead to bind the observer.
Upvotes: 1