I am trying to do some stuff on background and then displaying it to the user, but for some reason this does not work as it should and I am not sure what I am doing wrong.
It is an app with possibility to encrypt images and storing them on app-specific folder and holding the reference inside a database. While presenting it to the user following steps are done:
- Get the reference of pictures and metadata from database.
- read encrypted images and decrypt them while reading.
- Print the pictures in composable.
How it works is: Composable asks for getting data -> the repository gets the data -> my storage manager reads the files and uses the cryptomanager to decrypt them -> decrypted pictures are stored as live data
But the operation above blocks the interaction with the UI. Here is some Code:
Composable:
@Composable
fun WelcomeView(
viewModel: WelcomeViewModel = hiltViewModel()
) {
LaunchedEffect(Unit) {
viewModel.getGalleryItems()
}
val list = viewModel.images.observeAsState()
Column() {
//this button does not response until the data request and processing is done
Button(onClick = {}){
Text(text = "Click me while pictures are requested")
}
LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 128.dp)) {
if (list.value != null) {
items(list.value as List<GalleryElement>) { item: GalleryElement ->
GalleryItem(element = item)
}
}
}
}
}
Thats the view model:
@HiltViewModel
class WelcomeViewModel @Inject constructor(
private val secretDataManager: SecretDataManager,
) : ViewModel() {
private val _images = MutableLiveData<List<GalleryElement>>()
val images: LiveData<List<GalleryElement>> = _images
suspend fun getGalleryItems() {
viewModelScope.launch {
_images.value = secretDataManager.getImages()
}
}
}
User data manager:
class SecretDataManager @Inject constructor(
private val cryptoManager: CryptoManager,
private val storageManager: StorageManager,
private val repo: EncryptedVaultDataRepo,
@ApplicationContext
private val ctx: Context
) : SecretDataManagerService {
override suspend fun getImages(): List<GalleryElement> {
val result: MutableList<GalleryElement> = mutableListOf()
repo.getAll().forEach {
var image: ByteArray
storageManager.readFile(File("${ctx.filesDir}/${it.name}").toUri()).use { b ->
image = cryptoManager.decrypt(it.iv, b?.readBytes()!!)
}
result.add(GalleryElement(BitmapFactory.decodeByteArray(image, 0, image.size)))
}
return result
}
}
Any ideas what I am doing wrong?
CodePudding user response:
I believe the main problem is that the viewModelScope.launch(){} starts on the Dispatchers.Main(UI) thread. I recommend going to viewModelScope.launch(Dispatchers.IO){}. I am trying to find the documentation to support that but should be an easy change. I also recommended populating the list on the initialization of the view model.
@Composable
fun WelcomeView(
viewModel: WelcomeViewModel = hiltViewModel()
) {
val list = viewModel.images.observeAsState()
Column() {
//this button does not response until the data request and processing is done
Button(onClick = {}){
Text(text = "Click me while pictures are requested")
}
LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 128.dp)) {
if (list.value != null) {
items(list.value as List<GalleryElement>) { item: GalleryElement ->
GalleryItem(element = item)
}
}
}
}
}
@HiltViewModel
class WelcomeViewModel @Inject constructor(
private val secretDataManager: SecretDataManager,
) : ViewModel() {
private val _images = MutableLiveData<List<GalleryElement>>()
val images: LiveData<List<GalleryElement>> = _images
init{
getGalleryImages()
}
fun getGalleryItems() {
viewModelScope.launch(Dispatchers.Default) {
_images.value = secretDataManager.getImages()
}
}
}