Entonces, recientemente comencé a experimentar con corrutinas, cambié de Rxjava2 a corrutinas, aún no lo entiendo, pero aún así, me encontré con una condición en la que necesitaba observar el cambio de mi base de datos y actualizar la interfaz de usuario correspondiente a eso.
RxJava solía proporcionarme Flowables, Completeable, etc. con eso podría observar cambios en Db.
abstract fun insert(data: SomeData): Long @Query("SELECT * FROM somedata_table") abstract fun getData(): Flowable<List<SomeData>>
Así que aquí ahora solía suscribirme a getData y siempre solía observar los cambios.
Ahora ingrese coroutines, estoy usando una función suspendida con un resultado diferido para devolver mis respuestas
@Insert(onConflict = OnConflictStrategy.IGNORE) abstract fun insert(data: SomeData): Long @Query("SELECT * FROM somedata_table") abstract fun getData(): List<SomeData>
suspend fun getAllSomeData():Deferred<List<SomeData>>{ return GlobalScope.async (context= coroutineContext){ database.myDao().getData() } }
Ahora no tengo forma de escuchar las actualizaciones, ¿los canales en las corrutinas podrían ser la respuesta correcta? pero no estoy seguro de cómo usarlo con Room.
Use Room 2.2.0 Flujos y corrutinas kotlin . Es polémico, pero no me gusta LiveData , ya que te da resultados en el subproceso de la interfaz de usuario. Si tiene que hacer algún análisis de datos, tendrá que devolver todo a otro subproceso de IO. También es más limpio que usar los canales directamente, ya que tiene que hacer más llamadas openSubscription().consumeEach { .. } cada vez que quiera escuchar eventos.
Enfoque de flujo Requiere las siguientes versiones:
// esta versión usa rutinas y flujos en su versión no experimental
org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2 androidx.room:room-runtime:2.2.0 androidx.room:room-compiler:2.2.0
dao:
@Dao interface MyDao { @Query("SELECT * FROM somedata_table") fun getData(): Flow<List<SomeData>> }
clase para hacer observación:
launch { dao.getData().collect { data -> //handle data here } }
si su clase de llamada no es en sí misma un CoroutineScope , tendría que llamar al lanzamiento con el contexto de algo que sí lo es. Eso puede ser GlobalScope o alguna otra clase que cree. Aquí estoy usando lifecycleScope asumiendo que estamos en una clase de actividad.
lifecycleScope.launch { dao.getData().collect { data -> //handle data here } }
el lambda recopilado recibirá cada actualización de la tabla como una llamada Rx onNext .
Actualmente, hay dos formas diferentes de hacerlo. La primera es usar una función de generador de datos en vivo. Para que esto funcione, debe actualizar el ciclo de vida a androidx.lifecycle:*:2.2.0-alpha01
o cualquier versión más reciente. La función del constructor LiveData se usará para llamar a getData() de forma asíncrona y luego usar emit() para emitir el resultado. Usando este método, modificará su función Room getData() a una función de suspensión y hará que el tipo de retorno se envuelva como LiveData, reemplazando el Flowable usado antes.
@Query("SELECT * FROM somedata_table") abstract suspend fun getData(): LiveData<List<SomeData>>
En su modelo de vista, crea un liveData que hace referencia a su base de datos Room
val someData: LiveData<SomeData> = liveData { val data = database.myDao().getData() emit(data) }
El segundo enfoque es obtener datos de nuestra base de datos como flujo. Para usar esto, debe actualizar Room a androidx.room:room-*:2.2.0-alpha02
(actualmente la última) o una versión más nueva. Esta actualización permite que los métodos @Query DAO sean del tipo de retorno Flujo El flujo devuelto volverá a emitir un nuevo conjunto de valores si se invalidan las tablas de observación en la consulta. Declarar una función DAO con un tipo de retorno de Canal es un error
@Query("SELECT * FROM somedata_table") abstract fun getData(): Flow<List<SomeData>?>
El tipo de retorno es un flujo de una lista anulable. La lista se puede anular porque Room devolverá un valor nulo cuando la consulta no tenga datos obtenidos.
Para obtener datos del flujo, utilizaremos el operador de terminal collect{ }
en nuestro Presenter/ViewModel. Es preferible hacer esto en ViewModel ya que viene con un ViewModelScope. La solución que se proporciona a continuación supone que estamos haciendo esto en un ViewModel donde tenemos un viewModelScope proporcionado.
fun loadData(){ viewModelScope.launch { database.myDao() .getData() .distinctUntilChanged(). .collect{ it?.let{ /** Update your obsevable data here **/ } }
Dependencias de Gradle:
dependencies { compile group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-reactive', version: '1.1.1' }
Habitación Dao
@Dao interface HistoryDao : BaseDao<HistoryEntity> { @Query("select * from History order by time desc") fun observe(): Flowable<List<HistoryEntity>> ... }
Interactor ( browserHistoryInteractor debajo) (capa entre dao y Fragment/Presenter)
// To get channel of List<HistoryEntity>: import kotlinx.coroutines.reactive.openSubscription fun observe() = historyDao.observe().openSubscription() // convert list to Coroutines channel
Presentador/Fragmento/Actividad (punto final (en mi caso, es un presentador consciente del ciclo de vida))
import kotlinx.coroutines.Job import kotlinx.coroutines.launch private val compositeJob = Job() // somewhat equivalent "compositeDisposable" in rx override fun onCreate() { super.onCreate() launch(compositeJob) { // start coroutine val channel = browserHistoryInteractor.observe() for (items in channel) { // waits for next list of items (suspended) showInView { view?.setItems(items) } } } } override fun onDestroy() { compositeJob.cancel() // as in rx you need to cancel all jobs super.onDestroy() }
https://www.youtube.com/watch?v=lh2Vqt4DpHU&list=PLdb5m83JnoaBqMWF-qqhZY_01SNEhG5Qs&index=5 a las 29:25