Optimize LiveData from flows

pull/26/head
Koitharu 5 years ago
parent 7fd71c13f3
commit e674e0f36f

@ -101,7 +101,7 @@ dependencies {
implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0' implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0'
implementation 'com.tomclaw.cache:cache:1.0' implementation 'com.tomclaw.cache:cache:1.0'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.5' debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.6'
testImplementation 'junit:junit:4.13.1' testImplementation 'junit:junit:4.13.1'
testImplementation 'org.json:json:20201115' testImplementation 'org.json:json:20201115'

@ -77,7 +77,7 @@ class DetailsViewModel(
} }
) )
} }
}.flowOn(Dispatchers.Default).asLiveData(viewModelScope.coroutineContext) }.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default)
init { init {
launchLoadingJob(Dispatchers.Default) { launchLoadingJob(Dispatchers.Default) {

@ -1,7 +1,6 @@
package org.koitharu.kotatsu.favourites.ui package org.koitharu.kotatsu.favourites.ui
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable
import android.view.* import android.view.*
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
@ -27,7 +26,6 @@ class FavouritesContainerFragment : BaseFragment<FragmentFavouritesBinding>(),
private val editDelegate by lazy(LazyThreadSafetyMode.NONE) { private val editDelegate by lazy(LazyThreadSafetyMode.NONE) {
CategoriesEditDelegate(requireContext(), this) CategoriesEditDelegate(requireContext(), this)
} }
private var adapterState: Parcelable? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -49,25 +47,6 @@ class FavouritesContainerFragment : BaseFragment<FragmentFavouritesBinding>(),
viewModel.onError.observe(viewLifecycleOwner, ::onError) viewModel.onError.observe(viewLifecycleOwner, ::onError)
} }
override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
// (savedInstanceState?.getParcelable(KEY_ADAPTER_STATE) ?: adapterState)?.let {
// (binding.pager.adapter as FavouritesPagerAdapter).restoreState(it)
// }
}
override fun onDestroyView() {
adapterState = (binding.pager.adapter as? FavouritesPagerAdapter)?.saveState()
super.onDestroyView()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
adapterState = (bindingOrNull()?.pager?.adapter as? FavouritesPagerAdapter)?.saveState()
?: adapterState
outState.putParcelable(KEY_ADAPTER_STATE, adapterState)
}
override fun onWindowInsetsChanged(insets: Insets) { override fun onWindowInsetsChanged(insets: Insets) {
binding.tabs.updatePadding( binding.tabs.updatePadding(
left = insets.left, left = insets.left,
@ -132,8 +111,6 @@ class FavouritesContainerFragment : BaseFragment<FragmentFavouritesBinding>(),
companion object { companion object {
private const val KEY_ADAPTER_STATE = "adapter_state"
fun newInstance() = FavouritesContainerFragment() fun newInstance() = FavouritesContainerFragment()
} }
} }

@ -1,12 +1,11 @@
package org.koitharu.kotatsu.favourites.ui.categories package org.koitharu.kotatsu.favourites.ui.categories
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.flowOn
import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
class FavouritesCategoriesViewModel( class FavouritesCategoriesViewModel(
private val repository: FavouritesRepository private val repository: FavouritesRepository
@ -15,7 +14,7 @@ class FavouritesCategoriesViewModel(
private var reorderJob: Job? = null private var reorderJob: Job? = null
val categories = repository.observeCategories() val categories = repository.observeCategories()
.flowOn(Dispatchers.Default).asLiveData(viewModelScope.coroutineContext) .asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default)
fun createCategory(name: String) { fun createCategory(name: String) {
launchJob(Dispatchers.Default) { launchJob(Dispatchers.Default) {

@ -1,14 +1,13 @@
package org.koitharu.kotatsu.favourites.ui.categories.select package org.koitharu.kotatsu.favourites.ui.categories.select
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn
import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
class MangaCategoriesViewModel( class MangaCategoriesViewModel(
private val manga: Manga, private val manga: Manga,
@ -26,7 +25,7 @@ class MangaCategoriesViewModel(
isChecked = it.id in checked isChecked = it.id in checked
) )
} }
}.flowOn(Dispatchers.Default).asLiveData(viewModelScope.coroutineContext) }.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default)
fun setChecked(categoryId: Long, isChecked: Boolean) { fun setChecked(categoryId: Long, isChecked: Boolean) {
launchJob(Dispatchers.Default) { launchJob(Dispatchers.Default) {

@ -4,7 +4,6 @@ import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
@ -14,7 +13,7 @@ import org.koitharu.kotatsu.list.ui.model.EmptyState
import org.koitharu.kotatsu.list.ui.model.LoadingState import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.list.ui.model.toErrorState import org.koitharu.kotatsu.list.ui.model.toErrorState
import org.koitharu.kotatsu.list.ui.model.toUi import org.koitharu.kotatsu.list.ui.model.toUi
import org.koitharu.kotatsu.utils.ext.asLiveData import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import org.koitharu.kotatsu.utils.ext.onFirst import org.koitharu.kotatsu.utils.ext.onFirst
class FavouritesListViewModel( class FavouritesListViewModel(
@ -43,7 +42,7 @@ class FavouritesListViewModel(
isLoading.postValue(false) isLoading.postValue(false)
}.catch { }.catch {
emit(listOf(it.toErrorState(canRetry = false))) emit(listOf(it.toErrorState(canRetry = false)))
}.flowOn(Dispatchers.Default).asLiveData(viewModelScope.coroutineContext, listOf(LoadingState)) }.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
override fun onRefresh() = Unit override fun onRefresh() = Unit

@ -15,7 +15,7 @@ import org.koitharu.kotatsu.history.domain.MangaWithHistory
import org.koitharu.kotatsu.list.ui.MangaListViewModel import org.koitharu.kotatsu.list.ui.MangaListViewModel
import org.koitharu.kotatsu.list.ui.model.* import org.koitharu.kotatsu.list.ui.model.*
import org.koitharu.kotatsu.utils.SingleLiveEvent import org.koitharu.kotatsu.utils.SingleLiveEvent
import org.koitharu.kotatsu.utils.ext.asLiveData import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import org.koitharu.kotatsu.utils.ext.daysDiff import org.koitharu.kotatsu.utils.ext.daysDiff
import org.koitharu.kotatsu.utils.ext.onFirst import org.koitharu.kotatsu.utils.ext.onFirst
import java.util.* import java.util.*
@ -51,7 +51,7 @@ class HistoryListViewModel(
isLoading.postValue(false) isLoading.postValue(false)
}.catch { }.catch {
it.toErrorState(canRetry = false) it.toErrorState(canRetry = false)
}.flowOn(Dispatchers.Default).asLiveData(viewModelScope.coroutineContext, listOf(LoadingState)) }.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
override fun onRefresh() = Unit override fun onRefresh() = Unit

@ -50,6 +50,9 @@ abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
private var paginationListener: PaginationScrollListener? = null private var paginationListener: PaginationScrollListener? = null
private val spanResolver = MangaListSpanResolver() private val spanResolver = MangaListSpanResolver()
private val spanSizeLookup = SpanSizeLookup() private val spanSizeLookup = SpanSizeLookup()
private val listCommitCallback = Runnable {
spanSizeLookup.invalidateCache()
}
open val isSwipeRefreshEnabled = true open val isSwipeRefreshEnabled = true
protected abstract val viewModel: MangaListViewModel protected abstract val viewModel: MangaListViewModel
@ -148,8 +151,7 @@ abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
} }
private fun onListChanged(list: List<ListModel>) { private fun onListChanged(list: List<ListModel>) {
spanSizeLookup.invalidateCache() listAdapter?.setItems(list, listCommitCallback)
listAdapter?.items = list
} }
private fun onError(e: Throwable) { private fun onError(e: Throwable) {
@ -178,7 +180,7 @@ abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
@CallSuper @CallSuper
protected open fun onLoadingStateChanged(isLoading: Boolean) { protected open fun onLoadingStateChanged(isLoading: Boolean) {
binding.swipeRefreshLayout.isEnabled = binding.swipeRefreshLayout.isEnabled = binding.swipeRefreshLayout.isRefreshing ||
isSwipeRefreshEnabled && !isLoading isSwipeRefreshEnabled && !isLoading
if (!isLoading) { if (!isLoading) {
binding.swipeRefreshLayout.isRefreshing = false binding.swipeRefreshLayout.isRefreshing = false

@ -2,7 +2,6 @@ package org.koitharu.kotatsu.list.ui
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
@ -10,6 +9,7 @@ import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
abstract class MangaListViewModel( abstract class MangaListViewModel(
private val settings: AppSettings private val settings: AppSettings
@ -21,16 +21,20 @@ abstract class MangaListViewModel(
val gridScale = settings.observe() val gridScale = settings.observe()
.filter { it == AppSettings.KEY_GRID_SIZE } .filter { it == AppSettings.KEY_GRID_SIZE }
.map { settings.gridSize / 100f } .map { settings.gridSize / 100f }
.onStart { emit(settings.gridSize / 100f) } .asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.IO) {
.flowOn(Dispatchers.IO) settings.gridSize / 100f
.asLiveData(viewModelScope.coroutineContext) }
protected fun createListModeFlow() = settings.observe() protected fun createListModeFlow() = settings.observe()
.filter { it == AppSettings.KEY_LIST_MODE } .filter { it == AppSettings.KEY_LIST_MODE }
.map { settings.listMode } .map { settings.listMode }
.onStart { emit(settings.listMode) } .onStart { emit(settings.listMode) }
.distinctUntilChanged() .distinctUntilChanged()
.onEach { listMode.postValue(it) } .onEach {
if (listMode.value != it) {
listMode.postValue(it)
}
}
abstract fun onRefresh() abstract fun onRefresh()

@ -6,6 +6,7 @@ import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.ui.DateTimeAgo
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.MangaGridModel import org.koitharu.kotatsu.list.ui.model.MangaGridModel
import org.koitharu.kotatsu.list.ui.model.MangaListDetailedModel import org.koitharu.kotatsu.list.ui.model.MangaListDetailedModel
@ -38,6 +39,10 @@ class MangaListAdapter(
.addDelegate(ITEM_TYPE_EMPTY, emptyStateListAD()) .addDelegate(ITEM_TYPE_EMPTY, emptyStateListAD())
} }
fun setItems(list: List<ListModel>, commitCallback: Runnable) {
differ.submitList(list, commitCallback)
}
private class DiffCallback : DiffUtil.ItemCallback<ListModel>() { private class DiffCallback : DiffUtil.ItemCallback<ListModel>() {
override fun areItemsTheSame(oldItem: ListModel, newItem: ListModel) = when { override fun areItemsTheSame(oldItem: ListModel, newItem: ListModel) = when {
@ -50,6 +55,9 @@ class MangaListAdapter(
oldItem is MangaGridModel && newItem is MangaGridModel -> { oldItem is MangaGridModel && newItem is MangaGridModel -> {
oldItem.id == newItem.id oldItem.id == newItem.id
} }
oldItem is DateTimeAgo && newItem is DateTimeAgo -> {
oldItem == newItem
}
else -> oldItem.javaClass == newItem.javaClass else -> oldItem.javaClass == newItem.javaClass
} }

@ -2,13 +2,10 @@ package org.koitharu.kotatsu.local.ui
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException
@ -24,6 +21,7 @@ import org.koitharu.kotatsu.list.ui.model.toUi
import org.koitharu.kotatsu.local.domain.LocalMangaRepository import org.koitharu.kotatsu.local.domain.LocalMangaRepository
import org.koitharu.kotatsu.utils.MediaStoreCompat import org.koitharu.kotatsu.utils.MediaStoreCompat
import org.koitharu.kotatsu.utils.SingleLiveEvent import org.koitharu.kotatsu.utils.SingleLiveEvent
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import org.koitharu.kotatsu.utils.ext.safe import org.koitharu.kotatsu.utils.ext.safe
import org.koitharu.kotatsu.utils.ext.sub import org.koitharu.kotatsu.utils.ext.sub
import java.io.IOException import java.io.IOException
@ -51,9 +49,7 @@ class LocalListViewModel(
list.isEmpty() -> listOf(EmptyState(R.string.text_local_holder)) list.isEmpty() -> listOf(EmptyState(R.string.text_local_holder))
else -> list.toUi(mode) else -> list.toUi(mode)
} }
}.onStart { }.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
emit(listOf(LoadingState))
}.flowOn(Dispatchers.Default).asLiveData(viewModelScope.coroutineContext)
init { init {
onRefresh() onRefresh()

@ -1,9 +1,10 @@
package org.koitharu.kotatsu.main.ui package org.koitharu.kotatsu.main.ui
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import org.koitharu.kotatsu.base.domain.MangaProviderFactory import org.koitharu.kotatsu.base.domain.MangaProviderFactory
import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
@ -11,6 +12,7 @@ import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.history.domain.HistoryRepository import org.koitharu.kotatsu.history.domain.HistoryRepository
import org.koitharu.kotatsu.utils.SingleLiveEvent import org.koitharu.kotatsu.utils.SingleLiveEvent
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
class MainViewModel( class MainViewModel(
private val historyRepository: HistoryRepository, private val historyRepository: HistoryRepository,
@ -24,9 +26,7 @@ class MainViewModel(
.filter { it == AppSettings.KEY_SOURCES_ORDER || it == AppSettings.KEY_SOURCES_HIDDEN } .filter { it == AppSettings.KEY_SOURCES_ORDER || it == AppSettings.KEY_SOURCES_HIDDEN }
.onStart { emit("") } .onStart { emit("") }
.map { MangaProviderFactory.getSources(settings, includeHidden = false) } .map { MangaProviderFactory.getSources(settings, includeHidden = false) }
.distinctUntilChanged() .asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default)
.flowOn(Dispatchers.Default)
.asLiveData(viewModelScope.coroutineContext)
fun openLastReader() { fun openLastReader() {
launchLoadingJob { launchLoadingJob {

@ -5,7 +5,6 @@ import android.net.Uri
import android.util.LongSparseArray import android.util.LongSparseArray
import android.webkit.URLUtil import android.webkit.URLUtil
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
@ -58,7 +57,7 @@ class ReaderViewModel(
chapterNumber = chapter?.number ?: 0, chapterNumber = chapter?.number ?: 0,
chaptersTotal = chapters.size() chaptersTotal = chapters.size()
) )
}.flowOn(Dispatchers.Default).asLiveData(viewModelScope.coroutineContext) }.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default)
val content = MutableLiveData<ReaderContent>(ReaderContent(emptyList(), null)) val content = MutableLiveData<ReaderContent>(ReaderContent(emptyList(), null))
val manga: Manga? val manga: Manga?
@ -68,9 +67,7 @@ class ReaderViewModel(
.filter { it == AppSettings.KEY_READER_ANIMATION } .filter { it == AppSettings.KEY_READER_ANIMATION }
.map { settings.readerAnimation } .map { settings.readerAnimation }
.onStart { emit(settings.readerAnimation) } .onStart { emit(settings.readerAnimation) }
.distinctUntilChanged() .asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.IO)
.flowOn(Dispatchers.IO)
.asLiveData(viewModelScope.coroutineContext)
val onZoomChanged = SingleLiveEvent<Unit>() val onZoomChanged = SingleLiveEvent<Unit>()

@ -5,7 +5,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn
import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
@ -15,7 +14,7 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.list.ui.MangaFilterConfig import org.koitharu.kotatsu.list.ui.MangaFilterConfig
import org.koitharu.kotatsu.list.ui.MangaListViewModel import org.koitharu.kotatsu.list.ui.MangaListViewModel
import org.koitharu.kotatsu.list.ui.model.* import org.koitharu.kotatsu.list.ui.model.*
import org.koitharu.kotatsu.utils.ext.asLiveData import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import java.util.* import java.util.*
class RemoteListViewModel( class RemoteListViewModel(
@ -49,7 +48,7 @@ class RemoteListViewModel(
result result
} }
} }
}.flowOn(Dispatchers.Default).asLiveData(viewModelScope.coroutineContext, listOf(LoadingState)) }.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
init { init {
loadList(false) loadList(false)

@ -5,14 +5,13 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.list.ui.MangaListViewModel import org.koitharu.kotatsu.list.ui.MangaListViewModel
import org.koitharu.kotatsu.list.ui.model.* import org.koitharu.kotatsu.list.ui.model.*
import org.koitharu.kotatsu.utils.ext.asLiveData import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import java.util.* import java.util.*
class SearchViewModel( class SearchViewModel(
@ -46,7 +45,7 @@ class SearchViewModel(
result result
} }
} }
}.flowOn(Dispatchers.Default).asLiveData(viewModelScope.coroutineContext, listOf(LoadingState)) }.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
init { init {
loadList(append = false) loadList(append = false)

@ -11,7 +11,7 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.list.ui.MangaListViewModel import org.koitharu.kotatsu.list.ui.MangaListViewModel
import org.koitharu.kotatsu.list.ui.model.* import org.koitharu.kotatsu.list.ui.model.*
import org.koitharu.kotatsu.search.domain.MangaSearchRepository import org.koitharu.kotatsu.search.domain.MangaSearchRepository
import org.koitharu.kotatsu.utils.ext.asLiveData import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import org.koitharu.kotatsu.utils.ext.onFirst import org.koitharu.kotatsu.utils.ext.onFirst
import java.util.* import java.util.*
@ -46,7 +46,7 @@ class GlobalSearchViewModel(
result result
} }
} }
}.flowOn(Dispatchers.Default).asLiveData(viewModelScope.coroutineContext, listOf(LoadingState)) }.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
init { init {
onRefresh() onRefresh()

@ -8,7 +8,6 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOn
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.model.TrackingLogItem import org.koitharu.kotatsu.core.model.TrackingLogItem
@ -17,7 +16,7 @@ import org.koitharu.kotatsu.list.ui.model.LoadingFooter
import org.koitharu.kotatsu.list.ui.model.LoadingState import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.tracker.domain.TrackingRepository import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.tracker.ui.model.toFeedItem import org.koitharu.kotatsu.tracker.ui.model.toFeedItem
import org.koitharu.kotatsu.utils.ext.asLiveData import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import org.koitharu.kotatsu.utils.ext.mapItems import org.koitharu.kotatsu.utils.ext.mapItems
class FeedViewModel( class FeedViewModel(
@ -41,7 +40,7 @@ class FeedViewModel(
isHasNextPage -> list + LoadingFooter isHasNextPage -> list + LoadingFooter
else -> list else -> list
} }
}.flowOn(Dispatchers.Default).asLiveData(viewModelScope.coroutineContext, listOf(LoadingState)) }.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
init { init {
loadList(append = false) loadList(append = false)

@ -26,14 +26,40 @@ fun <T> LiveData<T>.observeWithPrevious(owner: LifecycleOwner, observer: Buffere
} }
} }
fun <T> Flow<T>.asLiveData( fun <T> Flow<T>.asLiveDataDistinct(
context: CoroutineContext = EmptyCoroutineContext
): LiveData<T> = liveData(context) {
collect {
if (it != latestValue) {
emit(it)
}
}
}
fun <T> Flow<T>.asLiveDataDistinct(
context: CoroutineContext = EmptyCoroutineContext, context: CoroutineContext = EmptyCoroutineContext,
defaultValue: T defaultValue: T
): LiveData<T> = liveData(context) { ): LiveData<T> = liveData(context, 0L) {
if (latestValue == null) { if (latestValue == null) {
emit(defaultValue) emit(defaultValue)
} }
collect { collect {
if (it != latestValue) {
emit(it) emit(it)
} }
}
}
fun <T> Flow<T>.asLiveDataDistinct(
context: CoroutineContext = EmptyCoroutineContext,
defaultValue: suspend () -> T
): LiveData<T> = liveData(context) {
if (latestValue == null) {
emit(defaultValue())
}
collect {
if (it != latestValue) {
emit(it)
}
}
} }

@ -1,13 +1,12 @@
package org.koitharu.kotatsu.widget.shelf package org.koitharu.kotatsu.widget.shelf
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn
import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import org.koitharu.kotatsu.widget.shelf.model.CategoryItem import org.koitharu.kotatsu.widget.shelf.model.CategoryItem
import java.util.* import java.util.*
@ -27,7 +26,7 @@ class ShelfConfigViewModel(
CategoryItem(it.id, it.title, selectedId == it.id) CategoryItem(it.id, it.title, selectedId == it.id)
} }
list list
}.flowOn(Dispatchers.Default).asLiveData(viewModelScope.coroutineContext) }.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default)
var checkedId: Long by selectedCategoryId::value var checkedId: Long by selectedCategoryId::value
} }
Loading…
Cancel
Save