Reputation: 331
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
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