Bolt UIX
Bolt UIX

Reputation: 7032

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

Upvotes: 6

Views: 5424

Answers (2)

Anatolii
Anatolii

Reputation: 14670

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

Upvotes: 4

SpiritCrusher
SpiritCrusher

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

Related Questions