class StateFlowActivity : AppCompatActivity() { private val newsAdapter2 by lazy { VarietyAdapter2().apply {addProxy(NewsProxy())} } private val intents by lazy { merge( flowOf(FeedsIntent.Init(1, 5)), loadMoreFlow(), reportFlow() ) } private fun loadMoreFlow() = callbackFlow { recyclerView.setOnLoadMoreListener { trySend(FeedsIntent.More(111L, 2)) } awaitClose { recyclerView.removeOnLoadMoreListener(null) } } private fun reportFlow() = callbackFlow { reportView.setOnClickListener { val news = newsAdapter.dataList as? News news?.id?.let { trySend(FeedsIntent.Report(it)) } } awaitClose { reportView.setOnClickListener(null) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(contentView) intents .onEach(newsViewModel::send) .launchIn(lifecycleScope) newsViewModel.newState .collectIn(this) { showNews(it) } } private fun showNews(state: NewsState) { state.apply { if (isLoading) showLoading() else dismissLoading() if (isLoadingMore) showLoadingMore() else dismissLoadingMore() if (reportToast.isNotEmpty()) Toast.makeText( this@StateFlowActivity, state.reportToast, Toast.LENGTH_SHORT ).show() if (errorMessage.isNotEmpty()) tv.text = state.errorMessage if (data.isNotEmpty()) newsAdapter2.dataList = state.data } }}NewsViewModel.kt
class NewsViewModel(private val newsRepo: NewsRepo) : ViewModel() { private val _feedsIntent = MutableSharedFlow<FeedsIntent>() val newState = _feedsIntent .toPartialChangeFlow() .scan(NewsState.initial) { oldState, partialChange -> partialChange.reduce(oldState) } .flowOn(Dispatchers.IO) .stateIn(viewModelScope, SharingStarted.Eagerly,NewsState.initial) fun send(intent: FeedsIntent) { viewModelScope.launch { _feedsIntent.emit(intent) } } private fun Flow<FeedsIntent>.toPartialChangeFlow(): Flow<FeedsPartialChange> = merge( filterIsInstance<FeedsIntent.Init>().flatMapConcat { it.toPartialChangeFlow() }, filterIsInstance<FeedsIntent.More>().flatMapConcat { it.toPartialChangeFlow() }, filterIsInstance<FeedsIntent.Report>().flatMapConcat { it.toPartialChangeFlow() }, ) private fun FeedsIntent.More.toPartialChangeFlow() = newsRepo.remoteNewsFlow("", "10") .map { if (it.news.isEmpty()) More.Fail("no more news") else More.Success(it.news) } .onStart { emit(More.Loading) } .catch { emit(More.Fail("load more failed by xxx")) } private fun FeedsIntent.Init.toPartialChangeFlow() = flowOf( newsRepo.localNewsOneShotFlow, newsRepo.remoteNewsFlow(this.type.toString(), this.count.toString()) ) .flattenMerge() .transformWhile { emit(it.news) !it.abort } .map { news -> if (news.isEmpty()) Init.Fail("no more news") else Init.Success(news) } .onStart { emit(Init.Loading) } .catch { if (it is SSLHandshakeException) emit(Init.Fail("network error,show old news")) } private fun FeedsIntent.Report.toPartialChangeFlow() = newsRepo.reportNews(id) .map { if(it >= 0L) Report.Success(it) else Report.Fail} .catch { emit((Report.Fail)) }}NewsState.kt
data class NewsState( val data: List<News> = emptyList(), val isLoading: Boolean = false, val isLoadingMore: Boolean = false, val errorMessage: String = "", val reportToast: String = "",) { companion object { val initial = NewsState(isLoading = true) }}FeedsPartialChange.kt
sealed interface FeedsPartialChange { fun reduce(oldState: NewsState): NewsState}sealed class Init : FeedsPartialChange { override fun reduce(oldState: NewsState): NewsState = when (this) { Loading -> oldState.copy(isLoading = true) is Success -> oldState.copy( data = news, isLoading = false, isLoadingMore = false, errorMessage = "" ) is Fail -> oldState.copy( data = emptyList(), isLoading = false, isLoadingMore = false, errorMessage = error ) } object Loading : Init() data class Success(val news: List<News>) : Init() data class Fail(val error: String) : Init()}sealed class More : FeedsPartialChange { override fun reduce(oldState: NewsState): NewsState = when (this) { Loading -> oldState.copy( isLoading = false, isLoadingMore = true, errorMessage = "" ) is Success -> oldState.copy( data = oldState.data + news, isLoading = false, isLoadingMore = false, errorMessage = "" ) is Fail -> oldState.copy( isLoadingMore = false, isLoading = false, errorMessage = error ) } object Loading : More() data class Success(val news: List<News>) : More() data class Fail(val error: String) : More()}sealed class Report : FeedsPartialChange { override fun reduce(oldState: NewsState): NewsState = when (this) { is Success -> oldState.copy( data = oldState.data.filterNot { it.id == id }, reportToast = "举报乐成" ) Fail -> oldState.copy(reportToast = "举报失败") } class Success(val id: Long) : Report() object Fail : Report()}保举阅读