migrate Lists from RxJava to Kotlin coroutines (#2537)
* migrate Lists from RxJava to Kotlin coroutines * use DROP_OLDEST when creating MutableSharedFlow
This commit is contained in:
parent
51f3794e78
commit
5abb82004a
6 changed files with 168 additions and 150 deletions
|
|
@ -17,92 +17,103 @@
|
|||
package com.keylesspalace.tusky.viewmodel
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.keylesspalace.tusky.entity.TimelineAccount
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.util.Either
|
||||
import com.keylesspalace.tusky.util.Either.Left
|
||||
import com.keylesspalace.tusky.util.Either.Right
|
||||
import com.keylesspalace.tusky.util.RxAwareViewModel
|
||||
import com.keylesspalace.tusky.util.withoutFirstWhich
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.subjects.BehaviorSubject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
data class State(val accounts: Either<Throwable, List<TimelineAccount>>, val searchResult: List<TimelineAccount>?)
|
||||
|
||||
class AccountsInListViewModel @Inject constructor(private val api: MastodonApi) : RxAwareViewModel() {
|
||||
class AccountsInListViewModel @Inject constructor(private val api: MastodonApi) : ViewModel() {
|
||||
|
||||
val state: Observable<State> get() = _state
|
||||
private val _state = BehaviorSubject.createDefault(State(Right(listOf()), null))
|
||||
val state: Flow<State> get() = _state
|
||||
private val _state = MutableStateFlow(State(Right(listOf()), null))
|
||||
|
||||
fun load(listId: String) {
|
||||
val state = _state.value!!
|
||||
val state = _state.value
|
||||
if (state.accounts.isLeft() || state.accounts.asRight().isEmpty()) {
|
||||
api.getAccountsInList(listId, 0).subscribe(
|
||||
{ accounts ->
|
||||
updateState { copy(accounts = Right(accounts)) }
|
||||
},
|
||||
{ e ->
|
||||
updateState { copy(accounts = Left(e)) }
|
||||
}
|
||||
).autoDispose()
|
||||
viewModelScope.launch {
|
||||
api.getAccountsInList(listId, 0).fold(
|
||||
{ accounts ->
|
||||
updateState { copy(accounts = Right(accounts)) }
|
||||
},
|
||||
{ e ->
|
||||
updateState { copy(accounts = Left(e)) }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addAccountToList(listId: String, account: TimelineAccount) {
|
||||
api.addCountToList(listId, listOf(account.id))
|
||||
.subscribe(
|
||||
{
|
||||
updateState {
|
||||
copy(accounts = accounts.map { it + account })
|
||||
viewModelScope.launch {
|
||||
api.addAccountToList(listId, listOf(account.id))
|
||||
.fold(
|
||||
{
|
||||
updateState {
|
||||
copy(accounts = accounts.map { it + account })
|
||||
}
|
||||
},
|
||||
{
|
||||
Log.i(
|
||||
javaClass.simpleName,
|
||||
"Failed to add account to list: ${account.username}"
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
Log.i(
|
||||
javaClass.simpleName,
|
||||
"Failed to add account to the list: ${account.username}"
|
||||
)
|
||||
}
|
||||
)
|
||||
.autoDispose()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteAccountFromList(listId: String, accountId: String) {
|
||||
api.deleteAccountFromList(listId, listOf(accountId))
|
||||
.subscribe(
|
||||
{
|
||||
updateState {
|
||||
copy(
|
||||
accounts = accounts.map { accounts ->
|
||||
accounts.withoutFirstWhich { it.id == accountId }
|
||||
}
|
||||
viewModelScope.launch {
|
||||
api.deleteAccountFromList(listId, listOf(accountId))
|
||||
.fold(
|
||||
{
|
||||
updateState {
|
||||
copy(
|
||||
accounts = accounts.map { accounts ->
|
||||
accounts.withoutFirstWhich { it.id == accountId }
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
Log.i(
|
||||
javaClass.simpleName,
|
||||
"Failed to remove account from list: $accountId"
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
Log.i(javaClass.simpleName, "Failed to remove account from thelist: $accountId")
|
||||
}
|
||||
)
|
||||
.autoDispose()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun search(query: String) {
|
||||
when {
|
||||
query.isEmpty() -> updateState { copy(searchResult = null) }
|
||||
query.isBlank() -> updateState { copy(searchResult = listOf()) }
|
||||
else -> api.searchAccounts(query, null, 10, true)
|
||||
.subscribe(
|
||||
{ result ->
|
||||
updateState { copy(searchResult = result) }
|
||||
},
|
||||
{
|
||||
updateState { copy(searchResult = listOf()) }
|
||||
}
|
||||
).autoDispose()
|
||||
else -> viewModelScope.launch {
|
||||
api.searchAccounts(query, null, 10, true)
|
||||
.fold(
|
||||
{ result ->
|
||||
updateState { copy(searchResult = result) }
|
||||
},
|
||||
{
|
||||
updateState { copy(searchResult = listOf()) }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun updateState(crossinline fn: State.() -> State) {
|
||||
_state.onNext(fn(_state.value!!))
|
||||
_state.value = fn(_state.value)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,19 +16,22 @@
|
|||
|
||||
package com.keylesspalace.tusky.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.keylesspalace.tusky.entity.MastoList
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.util.RxAwareViewModel
|
||||
import com.keylesspalace.tusky.util.replacedFirstWhich
|
||||
import com.keylesspalace.tusky.util.withoutFirstWhich
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.subjects.BehaviorSubject
|
||||
import io.reactivex.rxjava3.subjects.PublishSubject
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.IOException
|
||||
import java.net.ConnectException
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class ListsViewModel @Inject constructor(private val api: MastodonApi) : RxAwareViewModel() {
|
||||
internal class ListsViewModel @Inject constructor(private val api: MastodonApi) : ViewModel() {
|
||||
enum class LoadingState {
|
||||
INITIAL, LOADING, LOADED, ERROR_NETWORK, ERROR_OTHER
|
||||
}
|
||||
|
|
@ -39,86 +42,94 @@ internal class ListsViewModel @Inject constructor(private val api: MastodonApi)
|
|||
|
||||
data class State(val lists: List<MastoList>, val loadingState: LoadingState)
|
||||
|
||||
val state: Observable<State> get() = _state
|
||||
val events: Observable<Event> get() = _events
|
||||
private val _state = BehaviorSubject.createDefault(State(listOf(), LoadingState.INITIAL))
|
||||
private val _events = PublishSubject.create<Event>()
|
||||
val state: Flow<State> get() = _state
|
||||
val events: Flow<Event> get() = _events
|
||||
private val _state = MutableStateFlow(State(listOf(), LoadingState.INITIAL))
|
||||
private val _events = MutableSharedFlow<Event>(replay = 0, extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
||||
|
||||
fun retryLoading() {
|
||||
loadIfNeeded()
|
||||
}
|
||||
|
||||
private fun loadIfNeeded() {
|
||||
val state = _state.value!!
|
||||
val state = _state.value
|
||||
if (state.loadingState == LoadingState.LOADING || state.lists.isNotEmpty()) return
|
||||
updateState {
|
||||
copy(loadingState = LoadingState.LOADING)
|
||||
}
|
||||
|
||||
api.getLists().subscribe(
|
||||
{ lists ->
|
||||
updateState {
|
||||
copy(
|
||||
lists = lists,
|
||||
loadingState = LoadingState.LOADED
|
||||
)
|
||||
viewModelScope.launch {
|
||||
api.getLists().fold(
|
||||
{ lists ->
|
||||
updateState {
|
||||
copy(
|
||||
lists = lists,
|
||||
loadingState = LoadingState.LOADED
|
||||
)
|
||||
}
|
||||
},
|
||||
{ err ->
|
||||
updateState {
|
||||
copy(
|
||||
loadingState = if (err is IOException || err is ConnectException)
|
||||
LoadingState.ERROR_NETWORK else LoadingState.ERROR_OTHER
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{ err ->
|
||||
updateState {
|
||||
copy(
|
||||
loadingState = if (err is IOException || err is ConnectException)
|
||||
LoadingState.ERROR_NETWORK else LoadingState.ERROR_OTHER
|
||||
)
|
||||
}
|
||||
}
|
||||
).autoDispose()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun createNewList(listName: String) {
|
||||
api.createList(listName).subscribe(
|
||||
{ list ->
|
||||
updateState {
|
||||
copy(lists = lists + list)
|
||||
viewModelScope.launch {
|
||||
api.createList(listName).fold(
|
||||
{ list ->
|
||||
updateState {
|
||||
copy(lists = lists + list)
|
||||
}
|
||||
},
|
||||
{
|
||||
sendEvent(Event.CREATE_ERROR)
|
||||
}
|
||||
},
|
||||
{
|
||||
sendEvent(Event.CREATE_ERROR)
|
||||
}
|
||||
).autoDispose()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun renameList(listId: String, listName: String) {
|
||||
api.updateList(listId, listName).subscribe(
|
||||
{ list ->
|
||||
updateState {
|
||||
copy(lists = lists.replacedFirstWhich(list) { it.id == listId })
|
||||
viewModelScope.launch {
|
||||
api.updateList(listId, listName).fold(
|
||||
{ list ->
|
||||
updateState {
|
||||
copy(lists = lists.replacedFirstWhich(list) { it.id == listId })
|
||||
}
|
||||
},
|
||||
{
|
||||
sendEvent(Event.RENAME_ERROR)
|
||||
}
|
||||
},
|
||||
{
|
||||
sendEvent(Event.RENAME_ERROR)
|
||||
}
|
||||
).autoDispose()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteList(listId: String) {
|
||||
api.deleteList(listId).subscribe(
|
||||
{
|
||||
updateState {
|
||||
copy(lists = lists.withoutFirstWhich { it.id == listId })
|
||||
viewModelScope.launch {
|
||||
api.deleteList(listId).fold(
|
||||
{
|
||||
updateState {
|
||||
copy(lists = lists.withoutFirstWhich { it.id == listId })
|
||||
}
|
||||
},
|
||||
{
|
||||
sendEvent(Event.DELETE_ERROR)
|
||||
}
|
||||
},
|
||||
{
|
||||
sendEvent(Event.DELETE_ERROR)
|
||||
}
|
||||
).autoDispose()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun updateState(crossinline fn: State.() -> State) {
|
||||
_state.onNext(fn(_state.value!!))
|
||||
_state.value = fn(_state.value)
|
||||
}
|
||||
|
||||
private fun sendEvent(event: Event) {
|
||||
_events.onNext(event)
|
||||
private suspend fun sendEvent(event: Event) {
|
||||
_events.emit(event)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue