I am starting to learn Coroutines
what this code will look like on Coroutines ?
I would like to leave RX
and start using Coroutines
open class DataState<out T>(val newState: T, val oldState: T?, val json: String)
object EventBus {
private val dataPublisher: MutableMap<String, PublishSubject<DataState<Any>>> = mutableMapOf()
inline fun <reified T : Any> fire(event: DataState<T>) = fire(T::class.java.name, event)
fun <T : Any> fire(clazz: Class<T>, event: DataState<T>) = EventBus.fire(clazz.name, event)
fun <T : Any> fire(className: String, event: DataState<T>) {
synchronized(this) {
dataPublisher[className]?.onNext(event)
}
}
inline fun <reified T : Any> on(): Observable<DataState<T>> = on(T::class.java)
fun <T> on(dataType: Class<T>): Observable<DataState<T>> {
synchronized(this) {
var pub = dataPublisher[dataType.name]
if (pub == null) {
pub = PublishSubject.create()
dataPublisher[dataType.name] = pub
}
return pub.ofType(DataState::class.java) as Observable<DataState<T>>
}
}
fun reset() {
synchronized(this) {
dataPublisher.values.forEach { it.onComplete() }
dataPublisher.clear()
}
}
}
I really have no idea how to do the same thing with Coroutines, but I'm willing to entertain the possibility that it will be an improvement. Could someone show me how this would be done with Coroutines, and explain what's better about the Coroutines way?
Related
ı am trying to use a method which is getValues(). Im trying to take all my value variable from my SQL table and trying to make an addition with them. At the end, ı am trying to print my "value list" but it is just returning "Unit". The result that ı'm trying to reach: sum them all and get the total result.
var incomeList: List<Int> = mIncomeViewModel.getValues() // it is automaticly corrects me as incomeList: Unit
#Dao
interface IncomeDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun addIncome(income: Income)
#Update
suspend fun updateIncome(income: Income)
#Delete
suspend fun deleteIncome(income: Income)
#Query("DELETE FROM income_table")
suspend fun deleteAllIncomes()
#Query("SELECT * FROM income_table ORDER BY id ASC")
fun readAllData(): LiveData<List<Income>>
#Query("SELECT value FROM income_table ")
fun getValues(): LiveData<List<Int>>
}
class IncomeRepository (private val incomeDao: IncomeDao) {
val readAllData: LiveData<List<Income>> = incomeDao.readAllData()
suspend fun addIncome(income: Income){
incomeDao.addIncome(income)
}
suspend fun updateIncome(income: Income){
incomeDao.updateIncome(income)
}
suspend fun deleteIncome(income: Income){
incomeDao.deleteIncome(income)
}
suspend fun deleteAllIncomes(){
incomeDao.deleteAllIncomes()
}
fun getValues(): LiveData<List<Int>> {
return incomeDao.getValues()
}
}
class IncomeViewModel(application: Application): AndroidViewModel(application) {
val readAllData: LiveData<List<Income>> //if anything happens by the private of this variable make it public again
private val repository: IncomeRepository
init {
val incomeDao = IncomeDatabase.getDatabase(application).incomeDao()
repository = IncomeRepository(incomeDao)
readAllData = repository.readAllData
}
fun addIncome(income: Income){
viewModelScope.launch(Dispatchers.IO){
repository.addIncome(income)
}
}
fun updateIncome(income: Income){
viewModelScope.launch(Dispatchers.IO){
repository.updateIncome(income)
}
}
fun deleteIncome(income: Income){
viewModelScope.launch(Dispatchers.IO) {
repository.deleteIncome(income)
}
}
fun deleteAllIncomes(){
viewModelScope.launch(Dispatchers.IO) {
repository.deleteAllIncomes()
}
}
fun getValues(){
viewModelScope.launch(Dispatchers.IO) {
repository.getValues()
}
}
}
Edit:
I fixed my problem with returning the ViewModel methods as LiveData<List>
fun getValues(): LiveData<List<Int>>{
return repository.getValues()
}
I want to be able to call functions from the anonymous constructor's suspend function in the following example:
data class SuspendableStep(
val condition: SuspendableCondition,
val continuation: Continuation<Unit>
)
class WaitCondition(cycles: Int) : SuspendableCondition() {
private val timer = SomeTimer(cycles)
override fun resume(): Boolean = timer.elapsed() // timer is handled somewhere else
override fun toString(): String = "WaitCondition_$timer"
}
class BasicContinuation : Continuation<Unit> {
var coroutine: Continuation<Unit>
override val context: CoroutineContext = EmptyCoroutineContext
private var nextStep: SuspendableStep? = null
constructor(task: suspend () -> Unit) {
coroutine = task.createCoroutine(completion = this)
}
override fun resumeWith(result: Result<Unit>) {
nextStep = null
result.exceptionOrNull()?.let { e -> Logger.handle("Error with plugin!", e) }
}
suspend fun wait(cycles: Int): Unit = suspendCoroutine {
check(cycles > 0) { "Wait cycles must be greater than 0." }
nextStep = SuspendableStep(WaitCondition(cycles), it)
}
}
fun main() {
BasicContinuation({
println("HELLO")
wait(1)
println("WORLD")
}).coroutine.resume(Unit)
}
There only other option I found was to override a suspend function by creating an anonymous inner class and calling another function to set the coroutine:
fun main() {
val bc = BasicContinuation() {
override suspend fun test() : Unit {
println("HELLO")
wait(1)
println("WORLD")
}
}
bc.set() // assign coroutine to suspend { test }.createCoroutine(completion = this)
bc.coroutine.resume(Unit)
}
I used CoroutineScope to extend the scope of the functions I could access:
class BasicContinuation : Continuation<Unit> {
var coroutine: Continuation<Unit>
override val context: CoroutineContext = EmptyCoroutineContext
private var nextStep: SuspendableStep? = null
constructor(task: (suspend BasicContinuation.(CoroutineScope) -> Unit)) {
coroutine = suspend { task.invoke(this, CoroutineScope(context)) }.createCoroutine(completion = this)
}
override fun resumeWith(result: Result<Unit>) {
nextStep = null
result.exceptionOrNull()?.let { e -> Logger.handle("Error with plugin!", e) }
}
suspend fun wait(cycles: Int): Unit = suspendCoroutine {
check(cycles > 0) { "Wait cycles must be greater than 0." }
nextStep = SuspendableStep(WaitCondition(cycles), it)
}
}
fun main() {
val bc = BasicContinuation({
println("Hello")
wait(1)
println("World")
})
bc.coroutine.resume(Unit) // print "Hello"
// increment timer
bc.coroutine.resume(Unit) // print "World
}
I am discovering Kotlin and android app dev. I fail to get data from my room database (because of Cannot access database on the main thread). So I try with lifecyclescope.
The concerned code, in Fragment onViewCreated function, is :
lifecycleScope.launch {
withContext(Dispatchers.Default) {
val accountConfiguration = viewModel.get();
println("{${accountConfiguration}}")
}
}
The called function (in viewModel) is :
fun get() = viewModelScope.launch {
repository.get()
}
There is the "full" code (simplified), Entity & DAO :
#Entity
data class AccountConfiguration(
#PrimaryKey val server_address: String,
#ColumnInfo(name = "user_name") val user_name: String,
// [...]
)
#Dao
interface AccountConfigurationDao {
#Query("SELECT * FROM accountconfiguration LIMIT 1")
fun flow(): Flow<AccountConfiguration?>
#Query("SELECT * FROM accountconfiguration LIMIT 1")
suspend fun get(): AccountConfiguration?
// [...]
}
Repository :
package fr.bux.rollingdashboard
import androidx.annotation.WorkerThread
import kotlinx.coroutines.flow.Flow
class AccountConfigurationRepository(private val accountConfigurationDao: AccountConfigurationDao) {
val accountConfiguration: Flow<AccountConfiguration?> = accountConfigurationDao.flow()
// [...]
#Suppress("RedundantSuspendModifier")
#WorkerThread
suspend fun get() : AccountConfiguration? {
return accountConfigurationDao.get()
}
}
ViewModel & Factory :
class AccountConfigurationViewModel(private val repository: AccountConfigurationRepository) : ViewModel() {
val accountConfiguration: LiveData<AccountConfiguration?> = repository.accountConfiguration.asLiveData()
// [...]
fun get() = viewModelScope.launch {
repository.get()
}
// [...]
}
class AccountConfigurationViewModelFactory(private val repository: AccountConfigurationRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(AccountConfigurationViewModel::class.java)) {
#Suppress("UNCHECKED_CAST")
return AccountConfigurationViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
Fragment :
class AccountConfigurationFragment : Fragment() {
private var _binding: AccountConfigurationFragmentBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
private val viewModel: AccountConfigurationViewModel by activityViewModels {
AccountConfigurationViewModelFactory(
(activity?.application as RollingDashboardApplication).account_configuration_repository
)
}
lateinit var accountConfiguration: AccountConfiguration
// [...]
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.buttonGoBackMain.setOnClickListener {
findNavController().navigate(R.id.action_AccountConfigurationFragment_to_DashboardFragment)
}
lifecycleScope.launch {
withContext(Dispatchers.Default) {
val accountConfiguration = viewModel.get();
println("{${accountConfiguration}}")
}
}
binding.buttonSave.setOnClickListener {
save()
}
}
// [...]
}
In your current code,
lifecycleScope.launch {
withContext(Dispatchers.Default) {
val accountConfiguration = viewModel.get();
println("{${accountConfiguration}}")
}
}
viewModel.get() is not a suspend function, so it returns immediately and proceeds to the next line. It actually returns the Job created by viewModelScope.launch().
If you want your coroutine to wait for the result before continuing you should make the get() function suspend and return the AccountConfiguration?
suspend fun get(): AccountConfiguration? {
return repository.get()
}
You need not change dispatchers to Dispatchers.Default because Room itself will switch to a background thread before executing any database operation.
Right now if there is a configuration change while coroutines inside lifecyclerScope are running, everything will get cancelled and restarted.
A better way would have been to put the suspending calls inside the ViewModel and expose a LiveData/Flow to the UI.
The problem is the viewModel function :
fun get() = viewModelScope.launch {
repository.get()
}
This function must be the coroutine instead launch the coroutine itself. Correct code is :
suspend fun get(): AccountConfiguration? {
return repository.get()
}
I've come accross a case where I want to "chain" mutliple delegates (piping the output of one into the other).
This seems to be possible:
private val errorLogList by listSO(listOf<StateObject<Luxeption>>(), SODest.NONE, publicSOAccessRights())
val errorLog: StateObject<List<StateObject<Luxeption>>> by addToSet(errorLogList)
However, this does not look too well :). I'd like to do it in one line like this:
val errorLog: StateObject<List<StateObject<Luxeption>>> by addToSet(
listSO(listOf<StateObject<Luxeption>>(), SODest.NONE, publicSOAccessRights())
)
My question: Is this type of creating properties through delegates possible in Kotlin?
Here are both implementations of my delegates:
addToSet:
open class ChildSOReturner {
val set: Set<StateObject<*>> = setOf()
inline fun <reified T> addToSet(so: T) = object: ReadOnlyProperty<Any?, T> {
override operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
if (thisRef is T) {
set.plus(so)
return so
} else throw IllegalArgumentException()
}
}
}
listSo:
fun <O> listSO(
initialState: List<StateObject<O>>,
soDest: SODest,
soAccessRights: SOAccessRights
) = object : ReadOnlyProperty<Any?, StateObject<List<StateObject<O>>>> {
override operator fun getValue(thisRef: Any?, property: KProperty<*>): StateObject<List<StateObject<O>>> {
val meta = SOMeta(SOId(property.name), soDest, soAccessRights)
return StateObjectList(initialState, meta)
}
}
It turned out to be quite tricky, but possible (unless I am missing something, and it isn't tested but the idea should work):
fun <T, U, V> composeProperties(prop: ReadOnlyProperty<T, U>, f: (U) -> ReadOnlyProperty<T, V>) : ReadOnlyProperty<T, V> {
var props = mutableMapOf<Pair<T, KProperty<*>>, ReadOnlyProperty<T, V>>()
return object : ReadOnlyProperty<T, V> {
override operator fun getValue(thisRef: T, property: KProperty<*>): V {
val prop1 = props.getOrPut(Pair(thisRef, property)) {
f(prop.getValue(thisRef, property))
}
return prop1.getValue(thisRef, property)
}
}
}
And then to use
val errorLog: ... by composeProperties(listSO(...)) { addToSet(it) }
I'm looking for suspending way writing to file. I found this example from Kotlin/coroutines-examples which is wraping AsynchronousFileChannel with suspendCoroutine { ... } but I'm wondering if there is any benefit from wrapping synchronous call with withContext(IO){}
private suspend fun File.writeTextAsync(text: String): Unit = suspendCoroutine { cont ->
val aFileChannel = AsynchronousFileChannel.open(toPath(), StandardOpenOption.WRITE)
val byteBuffer = ByteBuffer.wrap(text.toByteArray())
aFileChannel.write(
byteBuffer,
0,
Unit,
object : java.nio.channels.CompletionHandler<Int, Unit> {
override fun completed(bytesRead: Int, attachment: Unit) {
cont.resume(Unit)
}
override fun failed(exception: Throwable, attachment: Unit) {
cont.resumeWithException(exception)
}
})
}
vs.
withContext(IO) { File(...).writeText(text) }