rijamyazid
rijamyazid

Reputation: 331

Get data asynchronously with Coroutine from Room Database

so i'm building an export function to export data from Room Database to external file (*.txt file), i'm trying to achieve this by using suspend function from Dao to ViewModel to get all the data, I think i don't really need LiveData because i dont observe it and just calling it one time. Here are the codes

ItemDao

@Dao
interface ItemDao {

   @Query("SELECT * FROM item_table")
   suspend fun readItemWithUnits_(): List<ItemModel>

}

ViewModel

@HiltViewModel
class HomeViewModel @Inject constructor (private val itemDao: ItemDao): ViewModel() {

   fun readItemWithUnits_(): Deferred<List<ItemModel>> {
       return viewModelScope.async(Dispatchers.IO) {
           itemDao.readItemWithUnits_()
       }
   }

}

And calling the Deferred from a Fragment inside withContext(Dispatchers.Main)

Fragment

val viewModel: HomeViewModel by viewModels()

// Codes before //

 private val writeExample = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
    if (it.resultCode == Activity.RESULT_OK) {

        val userChosenUri = it.data?.data
        val outStream = requireContext().contentResolver.openOutputStream(userChosenUri!!)
            lifecycleScope.launchWhenCreated {
                withContext(Dispatchers.Main) {
                val listOfItemWithUnit = viewModel.readItemWithUnits_().await()
                var exportContent = "#item_table\n"
                listOfItemWithUnit.forEach { itemModel ->
                    exportContent += "${itemModel.itemId};${itemModel.itemName};${itemModel.itemNote}\n"
                }
                exportContent.byteInputStream().use { input ->
                    outStream.use { output ->
                        input.copyTo(output!!)
                    }
                }
            }
        }
    }
}

// Codes After //

For now these codes work just fine, the question is, am i doing it correctly? since i will be dealing with a lot of data or is there a better way?

Edit

I've tried something like this, change from lifecycleScope.launchWhenCreated to CoroutineScope(Dispatchers.IO).launch

 private val writeExample = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
    if (it.resultCode == Activity.RESULT_OK) {
        val userChosenUri = it.data?.data
        val outStream = requireContext().contentResolver.openOutputStream(userChosenUri!!)
        CoroutineScope(Dispatchers.IO).launch {
             val listOfItemWithUnit = viewModel.readItemWithUnits_().await()
             var exportContent = "#item_table\n"
             listOfItemWithUnit.forEach { itemModel ->
                 exportContent += "${itemModel.item.itemId};${itemModel.item.itemName};${itemModel.item.itemNote}\n"
             }
             exportContent.byteInputStream().use { input ->
                 outStream.use { output ->
                        input.copyTo(output!!)
                 }
             }
        }
    }
}

It return an error

java.lang.IllegalStateException: Method addObserver must be called on the main thread

And this, without withContext(Dispatchers.Main)

 private val writeExample = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
    if (it.resultCode == Activity.RESULT_OK) {
        val userChosenUri = it.data?.data
        val outStream = requireContext().contentResolver.openOutputStream(userChosenUri!!)
        lifecycleScope.launchWhenCreated {
             val listOfItemWithUnit = viewModel.readItemWithUnits_().await()
             var exportContent = "#item_table\n"
             listOfItemWithUnit.forEach { itemModel ->
                 exportContent += "${itemModel.item.itemId};${itemModel.item.itemName};${itemModel.item.itemNote}\n"
             }
             exportContent.byteInputStream().use { input ->
                 outStream.use { output ->
                        input.copyTo(output!!)
                 }
             }
        }
    }
}

It return an error

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState

Upvotes: 0

Views: 2487

Answers (1)

Narendra_Nath
Narendra_Nath

Reputation: 5185

Structure your code like this

CoroutineScope(Dispatchers.IO).launch{ // do your background tasks here 

withContext(Dispatchers.Main){ //do tasks on the main thread that you want with that data

} }

Since it is a database operation without anything on the main thread the whole code will be in the CoroutineScope(Dispatchers.IO).launch block.

Upvotes: 0

Related Questions