0

I want to store an access token in the DataStore when it is received. As such i created a class(DataStore) that handles the getting,saving and clearing. Since I want the same instance of DataStore to be available everywhere, I instantiated it in the MainApplication. My thinking was that all other classes will be able to access it with MyApplication.dataStore. I am however getting a memory leak warning from the IDE. What is the advised way to do this? thank you

class MyApplication : Application() {
    companion object {
        lateinit var dataStore: DataStore // memory leak warning here. StaticFieldLeak
    }
    private var tokenRetrievalScope: CoroutineScope? = null
    override fun onCreate() {
        super.onCreate()
        dataStore= DataStore(context = this)
        
    }
}

interface DataStoreManager {
    suspend fun getToken(): String
    suspend fun saveToken(token: String)
    suspend fun clearToken()  // Function to clear the token if needed
}

class DataStore(private val context: Context) : DataStoreManager {

    val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")

    override suspend fun getToken(): String = withContext(Dispatchers.IO) {
        val preferences = context.dataStore.data.first() // Fetch Preferences
        preferences[PreferencesKeys.TOKEN] ?: "" // Access token with default
    }

1
  • Since the Context you are passing is the application context (and not an activity, broadcast, or other short-lived context), this is not actually a memory leak, and you can safely ignore the warning. If you hover over it, there should be an option provided to suppress the warning using an annotation. Commented Mar 25, 2024 at 0:16

2 Answers 2

0

In order to use DataStore safely, use a dependency injection framework like Hilt or Koin. This would also promote better code organization by limiting unnecessary exposure of the DataStore.

Below I shared how you can use Hilt to be able to provide DataStore as a Singleton object.

@Module
@InstallIn(SingletonComponent::class)
class DataStoreModule {

    @Singleton
    @Provides
    fun provideDataStore(application: Application): DataStore { 
        return DataStore(application.applicationContext)
    }
}

This would allow you to inject DataStore like this:

@HiltViewModel
class MyViewModel @Inject constructor(
    private val dataStore: DataStore
) : ViewModel() {

    fun useToken() {
        viewModelScope.launch { 
            val token = dataStore.getToken()
        }
    }
}

If you're not familiar with Hilt i recommend you check this out: https://developer.android.com/training/dependency-injection/hilt-android

For Koin: https://insert-koin.io/docs/quickstart/android/

Sign up to request clarification or add additional context in comments.

Comments

0

The companion object of a class exists even when there is no instance of that class around. In that regard it is similar to static fields in Java. There is no regular way to clean that up. That is especially problematic when context objects are stored this way because they prevent their associated objects (activities, services, etc.) from being removed from memory when they are not needed aymore. In your case, however, the context is the application context. Since that will never need to be cleaned up as long as your app runs, this entire problem scenario doesn't apply. The compiler, however, just detects that some context is stored in a companion object and warns you of a memory leak, without realizing that it is just the (unproblematic) application context.

You can therefore safely ignore the compiler warning.

You can avoid this entire situation, though, if you use a dependency injection framework. That way you don't instantiate the objects you need yourself, you let the DI framework inject them to where they are needed. You can annotate a class with @Singleton so that the same instance is shared everywhere. If you use Hilt for dependency injection on android, you can also easily retrieve the application context that you need for your data store:

@Singleton
class DataStore @Inject constructor(
    @ApplicationContext private val context: Context,
) {
    ...
}

Now, everytime you get a DataStore object injected, it will always be the same instance.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.