Global DataStore instance. Kotlin

42 Views Asked by At

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
    }

2

There are 2 best solutions below

0
Leviathan On

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.

0
Jaxon M On

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/