Para facilitar a organização e injeção de dependências, será utilizada a biblioteca Koin, um framework DI leve e fácil de usar. O Preferences DataStore será configurado separadamente para Android e iOS, com o Koin fornecendo instâncias dessas soluções de armazenamento.
Para começar, adicione as dependências necessárias no seu arquivo *libs.versions.toml*:
Bibliotecas |
|
datastore |
{ module = “androidx.datastore:datastore”, version.ref = “datastore” } |
datastore-preferences |
{ module = “androidx.datastore:datastore-preferences”, version.ref = “datastore” } |
koin-core |
{ module = “io.insert-koin:koin-core”, version.ref = “koin” } |
koin-compose |
{ module = “io.insert-koin:koin-compose”, version.ref = “koinCompose” } |
koin-compose-viewmodel |
{ module = “io.insert-koin:koin-compose-viewmodel”, version.ref = “koinComposeViewModel” } |
koin-android |
{ module = “io.insert-koin:koin-android”, version.ref = “koin” } |
No seu módulo compartilhado/composeApp (build.gradle.kts), adicione as seguintes implementações:
sourceSets {
androidMain.dependencies {
implementation(libs.koin.android)
}
commonMain.dependencies {
implementation(libs.koin.core)
implementation(libs.koin.compose)
implementation(libs.koin.compose.viewmodel)
implementation(libs.datastore)
implementation(libs.datastore.preferences)
}
}
Criando uma instância do DataStore
Crie uma instância do DataStore em Compose Multiplatform no pacote commonMain. Salve o seguinte código como DataStoreInstance.kt em commonMain:
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import androidx.datastore.preferences.core.Preferences
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.internal.SynchronizedObject
import kotlinx.coroutines.internal.synchronized
import okio.Path.Companion.toPath
@OptIn(InternalCoroutinesApi::class)
private val lock = SynchronizedObject() // Usado para segurança de threads
private lateinit var dataStore: DataStore
@OptIn(InternalCoroutinesApi::class)
fun createDataStore(producePath: () -> String): DataStore {
return synchronized(lock) {
if (::dataStore.isInitialized) {
dataStore
} else {
PreferenceDataStoreFactory.createWithPath(produceFile = { producePath().toPath() })
.also { dataStore = it }
}
}
}
internal const val DATA_STORE_FILE_NAME = "storage.preferences_pb"
No Android, chame essa função com o argumento de caminho (DataStoreInstance.android.kt):
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
fun createDataStore(context: Context): DataStore {
return createDataStore {
context.filesDir.resolve(DATA_STORE_FILE_NAME).absolutePath
}
}
E, no iOS (DataStoreInstance.ios.kt):
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import kotlinx.cinterop.ExperimentalForeignApi
import platform.Foundation.NSDocumentDirectory
import platform.Foundation.NSFileManager
import platform.Foundation.NSUserDomainMask
@OptIn(ExperimentalForeignApi::class)
fun createDataStore(): DataStore {
return createDataStore {
val directory = NSFileManager.defaultManager.URLForDirectory(
directory = NSDocumentDirectory,
inDomain = NSUserDomainMask,
appropriateForURL = null,
create = false,
error = null,
)
requireNotNull(directory).path + "/$DATA_STORE_FILE_NAME"
}
}
Configurando o Koin para injeção de dependência
Com a configuração do DataStore completa, configure o Koin para injeção de dependência, injetando ou fornecendo o DataStore no seu repositório ou ViewModel.
No commonMain, crie um DataStoreModule com a variável esperada:
commonMain (DataStoreModule.kt)
import org.koin.core.module.Module
expect val dataStoreModule: Module
Adicione a declaração real em androidMain e iosMain:
androidMain (DataStoreModule.android.kt)
import com.rainday.datastorecmp.createDataStore
import org.koin.android.ext.koin.androidContext
import org.koin.core.module.Module
import org.koin.dsl.module
actual val dataStoreModule: Module
get() = module { single { createDataStore(androidContext()) } }
iosMain (DataStoreModule.ios.kt)
actual val dataStoreModule: Module
get() = module { single { createDataStore() } }
Na implementação do Android, é necessário um Context para criar o Preferences DataStore, enquanto no iOS não há dependência de contexto. Para lidar com essa diferença, você pode modificar a função de inicialização do Koin (initKoin) para aceitar uma função de configuração (config). Isso permite configurar o contexto específico do Android sem afetar a plataforma iOS.
// commonMain
fun initKoin(
config: (KoinApplication.() -> Unit)? = null
) {
startKoin {
config?.invoke(this)
modules(dataStoreModule)
}
}
Inicialize o Koin no Android e iOS:
androidMain (Application Class)
class YourApplicationClass: Application() {
override fun onCreate() {
super.onCreate()
initKoin(
config = {
androidContext(this@BaseApplication)
}
)
}
}
iosMain (MainViewController.kt)
fun MainViewController() = ComposeUIViewController(
configure = {
initKoin()
}
) { App() }
Implementando o DataStore em um ViewModel
O Preferences DataStore está pronto para ser usado em um repositório ou ViewModel. Veja um exemplo de como usá-lo:
ViewModel em pacote commonMain:
class AppViewModel(
private val dataStore: DataStore
): ViewModel() {
private val key = stringPreferencesKey("name")
private var _name = MutableStateFlow("")
val name = _name.asStateFlow()
init {
viewModelScope.launch {
dataStore.data.collect { storedData ->
_name.update {
storedData.get(key).orEmpty()
}
}
}
}
fun updateName(name: String) = _name.update { name }
fun storeToDataStore() {
viewModelScope.launch {
dataStore.updateData {
it.toMutablePreferences().apply {
set(key, name.value)
}
}
}
}
}
Defina um módulo Koin em commonMain que fornece uma instância AppViewModel para injeção de dependência.
val viewModelModule = module {
viewModel { AppViewModel(get()) }
// automaticamente injetando os parâmetros necessários usando get()
}
Atualize a função initKoin:
fun initKoin(
config: (KoinApplication.() -> Unit)? = null
) {
startKoin {
config?.invoke(this)
modules(viewModelModule, dataStoreModule) // add viewModelModule
}
}
Camada de UI (Interface de Usuário Compartilhada com Jetpack Compose)
Injete AppViewModel em Composable App():
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import org.jetbrains.compose.ui.tooling.preview.Preview
import org.koin.compose.KoinContext
import org.koin.compose.viewmodel.koinViewModel
import org.koin.core.annotation.KoinExperimentalAPI
@OptIn(KoinExperimentalAPI::class)
@Composable
@Preview
fun App() {
MaterialTheme {
KoinContext {
val viewModel = koinViewModel()
val name by viewModel.name.collectAsStateWithLifecycle()
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Column(
modifier = Modifier.padding(16.dp)
) {
TextField(
value = name,
onValueChange = viewModel::updateName,
label = { Text("Name") }
)
Button(onClick = viewModel::storeToDataStore, modifier = Modifier.padding(top = 8.dp)) {
Text("Store")
}
}
}
}
}
}
Agora o Preferences DataStore está totalmente configurado e pronto para uso em Android e iOS.
Este conteúdo foi auxiliado por Inteligência Artificiado, mas escrito e revisado por um humano.
Via dev.to