How to retrieve a value from the SQLite Database in Android and assign it to a variable? I want to use the previously saved value for a few functions in the code further.
My Database has 3 columns, 1st one is auto-generated int id (key), 2nd one is named type
which stores strings that are used to filter out data. The last one is content
which also stores string. This third one is actually the one which contains the useful data.
The Dao:
@Upsert
suspend fun upsert(appData: AppData)
@Delete
suspend fun delete(appData: AppData)
@Query("SELECT * from appData WHERE type = :type ORDER BY id DESC")
fun getData(type: String): Flow<List<AppData>>
@Query("SELECT * from appData WHERE type = :type ORDER BY id DESC")
fun getDataList(type: String): List<AppData>
The Repository interface:
fun getDataStream(type: String): Flow<List<AppData?>>
fun getDataListStream(type: String): List<AppData?>
suspend fun upsertData(data: AppData)
suspend fun deleteData(data: AppData)
And this is the Offline Repository implementation:
override fun getDataStream(type: String): Flow<List<AppData?>> = appDataDao.getData(type)
override suspend fun deleteData(data: AppData) = appDataDao.delete(data)
override suspend fun upsertData(data: AppData) = appDataDao.upsert(data)
override fun getDataListStream(type: String): List<AppData?> = appDataDao.getDataList(type)
I tried to retrieve data directly from my composable function and use synchronization methods, but because none of them worked, latest version of my code I is this:
var lastDateObjectList by remember { mutableStateOf(emptyList<AppData>()) }
LaunchedEffect(streakCounterViewModel) {
coroutineScope {
streakCounterViewModel.appDataRepository.getDataStream("lastDate")
.collect { newDataList ->
lastDateObjectList = newDataList as List<AppData>
}
}
}
if (lastDateObjectList.size > 1) {/*TODO*/
for (i in 1 until lastDateObjectList.size) {
LaunchedEffect(Unit) {
streakCounterViewModel.deleteData(
id = lastDateObjectList[i]!!.id,
type = lastDateObjectList[i]!!.type,
content = lastDateObjectList[i]!!.content
)
}
}
}
val lastDateObject: AppData?/* = lastDateObjectList[0] ?: null*/
lastDateObject = if (lastDateObjectList.isNotEmpty())
lastDateObjectList[0]
else null
var lastDate = lastDateObject?.content ?: "00-00-0000"
Only your view model may access the repository, your composables only access the view model.
So actually calling getDataStream
is done in the view model. Currently you have its type
parameter hard-coded to "lastDate"
, which you probably want to be variable. For that you need to introduce another property in the view model to hold the currently selected type, like this:
private val selectedType: MutableStateFlow<String?> = MutableStateFlow(null)
fun selectType(type: String) {
selectedType.value = type
}
The UI can now change which type should be selected by calling selectType
. This will change the value of the property selectedType
. We need a flow here so we can map its content to the return value from the repository's getDataStream
function like this:
@OptIn(ExperimentalCoroutinesApi::class)
val appData: StateFlow<List<AppData>> = selectedType
.flatMapLatest { type ->
if (type == null) flowOf(emptyList())
else appDataRepository.getDataStream(type)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = emptyList(),
)
The stateIn
part at the end converts the flow to a StateFlow
. That is needed so that your composable can properly collect it, like this:
val lastDateObjectList by streakCounterViewModel.appData.collectAsStateWithLifecycle()
You need the gradle dependency androidx.lifecycle:lifecycle-runtime-compose
for that.
This replaces the lastDateObjectList
variable you had before. You can now also remove the entire LaunchedEffect
block.
In your Repository you had, for some reason, changed the type of the flow to contain null
values in your list. This can never happen, so you should change its return type from
Flow<List<AppData?>>
to
Flow<List<AppData>>
One last word regarding the getDataList
function in your Dao: This function would block the current thread until the database query finished. Never do that. Instead, add the suspend
modifier, like this:
suspend fun getDataList(type: String): List<AppData>
This forces the function to be called from a coroutine that will run asynchronously and not block your main thread.
Since you already use the dao function that returns a flow (which is preferable) you won't need this function anyways, so just delete it.