From 77974c1d8b86aa43d8322f6a19547343d33e0cd8 Mon Sep 17 00:00:00 2001 From: Ash Date: Sun, 27 Mar 2022 10:30:25 +0800 Subject: [PATCH] Improve coroutines --- app/src/main/java/me/ash/reader/App.kt | 2 +- .../me/ash/reader/data/constant/Filter.kt | 2 +- .../data/repository/AbstractRssRepository.kt | 35 ++++----- .../data/repository/LocalRssRepository.kt | 72 ++++++++++--------- .../ash/reader/ui/page/home/HomeViewModel.kt | 16 ++--- .../page/home/drawer/feed/DeleteFeedDialog.kt | 2 +- .../home/drawer/feed/FeedOptionViewModel.kt | 21 +++--- .../reader/ui/page/home/feeds/FeedsPage.kt | 6 +- .../ui/page/home/feeds/FeedsViewModel.kt | 15 ++-- .../feeds/subscribe/SubscribeViewModel.kt | 9 +-- .../reader/ui/page/home/flow/FlowViewModel.kt | 2 +- .../reader/ui/page/home/read/ReadViewModel.kt | 35 +++++---- 12 files changed, 102 insertions(+), 115 deletions(-) diff --git a/app/src/main/java/me/ash/reader/App.kt b/app/src/main/java/me/ash/reader/App.kt index fe16943..3c99daa 100644 --- a/app/src/main/java/me/ash/reader/App.kt +++ b/app/src/main/java/me/ash/reader/App.kt @@ -58,7 +58,7 @@ class App : Application(), Configuration.Provider { override fun onCreate() { super.onCreate() - applicationScope.launch { + applicationScope.launch(Dispatchers.IO) { accountInit() workerInit() } diff --git a/app/src/main/java/me/ash/reader/data/constant/Filter.kt b/app/src/main/java/me/ash/reader/data/constant/Filter.kt index 9d70590..c433113 100644 --- a/app/src/main/java/me/ash/reader/data/constant/Filter.kt +++ b/app/src/main/java/me/ash/reader/data/constant/Filter.kt @@ -21,7 +21,7 @@ class Filter( companion object { val Starred = Filter( index = 0, - important = 13, + important = 666, icon = Icons.Rounded.StarOutline, filledIcon = Icons.Rounded.Star, ) diff --git a/app/src/main/java/me/ash/reader/data/repository/AbstractRssRepository.kt b/app/src/main/java/me/ash/reader/data/repository/AbstractRssRepository.kt index 384c048..3c95cb5 100644 --- a/app/src/main/java/me/ash/reader/data/repository/AbstractRssRepository.kt +++ b/app/src/main/java/me/ash/reader/data/repository/AbstractRssRepository.kt @@ -10,7 +10,6 @@ import dagger.assisted.AssistedInject import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.* import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.withContext import me.ash.reader.currentAccountId import me.ash.reader.data.account.AccountDao import me.ash.reader.data.article.Article @@ -104,24 +103,22 @@ abstract class AbstractRssRepository constructor( } } - suspend fun pullImportant( + fun pullImportant( isStarred: Boolean = false, isUnread: Boolean = false, ): Flow> { - return withContext(Dispatchers.IO) { - Log.i("RLog", "thread:pullImportant ${Thread.currentThread().name}") - val accountId = context.currentAccountId - Log.i( - "RLog", - "pullImportant: accountId: ${accountId}, isStarred: ${isStarred}, isUnread: ${isUnread}" - ) - when { - isStarred -> articleDao - .queryImportantCountWhenIsStarred(accountId, isStarred) - isUnread -> articleDao - .queryImportantCountWhenIsUnread(accountId, isUnread) - else -> articleDao.queryImportantCountWhenIsAll(accountId) - } + Log.i("RLog", "thread:pullImportant ${Thread.currentThread().name}") + val accountId = context.currentAccountId + Log.i( + "RLog", + "pullImportant: accountId: ${accountId}, isStarred: ${isStarred}, isUnread: ${isUnread}" + ) + return when { + isStarred -> articleDao + .queryImportantCountWhenIsStarred(accountId, isStarred) + isUnread -> articleDao + .queryImportantCountWhenIsUnread(accountId, isUnread) + else -> articleDao.queryImportantCountWhenIsAll(accountId) }.flowOn(Dispatchers.IO) } @@ -154,10 +151,8 @@ abstract class AbstractRssRepository constructor( } suspend fun deleteFeed(feed: Feed) { - withContext(Dispatchers.IO) { - articleDao.deleteByFeedId(context.currentAccountId, feed.id) - feedDao.delete(feed) - } + articleDao.deleteByFeedId(context.currentAccountId, feed.id) + feedDao.delete(feed) } companion object { diff --git a/app/src/main/java/me/ash/reader/data/repository/LocalRssRepository.kt b/app/src/main/java/me/ash/reader/data/repository/LocalRssRepository.kt index 91c17e7..e995ed5 100644 --- a/app/src/main/java/me/ash/reader/data/repository/LocalRssRepository.kt +++ b/app/src/main/java/me/ash/reader/data/repository/LocalRssRepository.kt @@ -17,7 +17,9 @@ import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext -import me.ash.reader.* +import me.ash.reader.MainActivity +import me.ash.reader.R +import me.ash.reader.currentAccountId import me.ash.reader.data.account.AccountDao import me.ash.reader.data.article.Article import me.ash.reader.data.article.ArticleDao @@ -89,49 +91,56 @@ class LocalRssRepository @Inject constructor( withContext(Dispatchers.IO) { val preTime = System.currentTimeMillis() val accountId = context.currentAccountId - val feeds = async { feedDao.queryAll(accountId) } - val articles = feeds.await().also { feed -> + val articles = mutableListOf
() + feedDao.queryAll(accountId).also { feed -> updateSyncState { it.copy( feedCount = feed.size, ) } - Log.i("RLog", "thread:sync ${Thread.currentThread().name}") }.map { feed -> async { - val articles = syncFeed(accountId, feed) - articles + syncFeed(accountId, feed) } + }.awaitAll().forEach { + if (it.isNotify) { + notify(it.articles) + } + articles.addAll(it.articles) } - articles.awaitAll().sumOf { it.size }.let { count -> - Log.i( - "RlOG", - "[${count}] onCompletion: ${System.currentTimeMillis() - preTime}" + articleDao.insertList(articles) + Log.i( + "RlOG", + "onCompletion: ${System.currentTimeMillis() - preTime}" + ) + accountDao.queryById(accountId)?.let { account -> + accountDao.update( + account.apply { + updateAt = Date() + } + ) + } + updateSyncState { + it.copy( + feedCount = 0, + syncedCount = 0, + currentFeedName = "" ) - accountDao.queryById(accountId)?.let { account -> - accountDao.update( - account.apply { - updateAt = Date() - } - ) - } - updateSyncState { - it.copy( - feedCount = 0, - syncedCount = 0, - currentFeedName = "" - ) - } } } } } + data class ArticleNotify( + val articles: List
, + val isNotify: Boolean, + ) + private suspend fun syncFeed( accountId: Int, feed: Feed - ): List
{ + ): ArticleNotify { val latest = articleDao.queryLatestByFeedId(accountId, feed.id) val articles = rssHelper.queryRssXml( rssNetworkDataSource, @@ -143,21 +152,16 @@ class LocalRssRepository @Inject constructor( rssHelper.queryRssIcon(feedDao, feed, it.first().link) } } - - Log.i("RLog", "thread:syncFeed ${Thread.currentThread().name}") updateSyncState { it.copy( syncedCount = it.syncedCount + 1, currentFeedName = feed.name ) } - if (articles.isNotEmpty()) { - articleDao.insertList(articles) - if (feed.isNotification) { - notify(articles) - } - } - return articles + return ArticleNotify( + articles = articles, + isNotify = articles.isNotEmpty() && feed.isNotification + ) } private fun notify( diff --git a/app/src/main/java/me/ash/reader/ui/page/home/HomeViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/HomeViewModel.kt index 50202da..4ce0512 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/HomeViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/HomeViewModel.kt @@ -1,17 +1,14 @@ package me.ash.reader.ui.page.home import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.PagerState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch import me.ash.reader.data.constant.Filter import me.ash.reader.data.feed.Feed import me.ash.reader.data.group.Group @@ -36,7 +33,7 @@ class HomeViewModel @Inject constructor( fun dispatch(action: HomeViewAction) { when (action) { - is HomeViewAction.Sync -> sync(action.callback) + is HomeViewAction.Sync -> sync() is HomeViewAction.ChangeFilter -> changeFilter(action.filterState) is HomeViewAction.ScrollToPage -> scrollToPage( action.scope, @@ -46,11 +43,8 @@ class HomeViewModel @Inject constructor( } } - private fun sync(callback: () -> Unit = {}) { - viewModelScope.launch { - rssRepository.get().doSync() - callback() - } + private fun sync() { + rssRepository.get().doSync() } private fun changeFilter(filterState: FilterState) { @@ -80,9 +74,7 @@ data class HomeViewState( ) sealed class HomeViewAction { - data class Sync( - val callback: () -> Unit = {}, - ) : HomeViewAction() + object Sync : HomeViewAction() data class ChangeFilter( val filterState: FilterState diff --git a/app/src/main/java/me/ash/reader/ui/page/home/drawer/feed/DeleteFeedDialog.kt b/app/src/main/java/me/ash/reader/ui/page/home/drawer/feed/DeleteFeedDialog.kt index 4ced62d..51e1985 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/drawer/feed/DeleteFeedDialog.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/drawer/feed/DeleteFeedDialog.kt @@ -49,7 +49,7 @@ fun DeleteFeedDialog( confirmButton = { TextButton( onClick = { - viewModel.dispatch(FeedOptionViewAction.Delete(){ + viewModel.dispatch(FeedOptionViewAction.Delete { viewModel.dispatch(FeedOptionViewAction.HideDeleteDialog) viewModel.dispatch(FeedOptionViewAction.Hide(scope)) Toast.makeText(context, deletedTip, Toast.LENGTH_SHORT).show() diff --git a/app/src/main/java/me/ash/reader/ui/page/home/drawer/feed/FeedOptionViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/drawer/feed/FeedOptionViewModel.kt index 6e95494..2d48eb1 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/drawer/feed/FeedOptionViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/drawer/feed/FeedOptionViewModel.kt @@ -8,11 +8,13 @@ import androidx.lifecycle.viewModelScope import com.google.accompanist.pager.ExperimentalPagerApi import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import me.ash.reader.data.feed.Feed import me.ash.reader.data.group.Group import me.ash.reader.data.repository.RssRepository @@ -30,7 +32,7 @@ class FeedOptionViewModel @Inject constructor( val viewState: StateFlow = _viewState.asStateFlow() init { - viewModelScope.launch { + viewModelScope.launch(Dispatchers.IO) { rssRepository.get().pullGroups().collect { groups -> _viewState.update { it.copy( @@ -88,7 +90,7 @@ class FeedOptionViewModel @Inject constructor( } private fun selectedGroup(groupId: String) { - viewModelScope.launch { + viewModelScope.launch(Dispatchers.IO) { _viewState.value.feed?.let { rssRepository.get().updateFeed( it.copy( @@ -109,7 +111,7 @@ class FeedOptionViewModel @Inject constructor( } private fun changeParseFullContentPreset() { - viewModelScope.launch { + viewModelScope.launch(Dispatchers.IO) { _viewState.value.feed?.let { rssRepository.get().updateFeed( it.copy( @@ -122,7 +124,7 @@ class FeedOptionViewModel @Inject constructor( } private fun changeAllowNotificationPreset() { - viewModelScope.launch { + viewModelScope.launch(Dispatchers.IO) { _viewState.value.feed?.let { rssRepository.get().updateFeed( it.copy( @@ -136,10 +138,11 @@ class FeedOptionViewModel @Inject constructor( private fun delete(callback: () -> Unit = {}) { _viewState.value.feed?.let { - viewModelScope.launch { + viewModelScope.launch(Dispatchers.IO) { rssRepository.get().deleteFeed(it) - }.invokeOnCompletion { - callback() + withContext(Dispatchers.Main) { + callback() + } } } } @@ -201,6 +204,6 @@ sealed class FeedOptionViewAction { val callback: () -> Unit = {} ) : FeedOptionViewAction() - object ShowDeleteDialog: FeedOptionViewAction() - object HideDeleteDialog: FeedOptionViewAction() + object ShowDeleteDialog : FeedOptionViewAction() + object HideDeleteDialog : FeedOptionViewAction() } diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt index 90ee02b..1095860 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt @@ -61,7 +61,7 @@ fun FeedsPage( ) LaunchedEffect(Unit) { - viewModel.dispatch(FeedsViewAction.FetchAccount()) + viewModel.dispatch(FeedsViewAction.FetchAccount) } LaunchedEffect(homeViewModel.filterState) { @@ -89,7 +89,7 @@ fun FeedsPage( actions = { IconButton(onClick = { if (syncState.isSyncing) return@IconButton - homeViewModel.dispatch(HomeViewAction.Sync()) + homeViewModel.dispatch(HomeViewAction.Sync) }) { Icon( modifier = Modifier.rotate(if (syncState.isSyncing) angle else 0f), @@ -135,7 +135,7 @@ fun FeedsPage( Banner( title = filterState.filter.getName(), desc = filterState.filter.getDesc(), - icon = viewState.filter.icon, + icon = filterState.filter.icon, action = { Icon( imageVector = Icons.Outlined.KeyboardArrowRight, diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsViewModel.kt index 2849476..a6de0fe 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsViewModel.kt @@ -29,33 +29,32 @@ class FeedsViewModel @Inject constructor( fun dispatch(action: FeedsViewAction) { when (action) { - is FeedsViewAction.FetchAccount -> fetchAccount(action.callback) + is FeedsViewAction.FetchAccount -> fetchAccount() is FeedsViewAction.FetchData -> fetchData(action.filterState) is FeedsViewAction.AddFromFile -> addFromFile(action.inputStream) is FeedsViewAction.ScrollToItem -> scrollToItem(action.index) } } - private fun fetchAccount(callback: () -> Unit = {}) { - viewModelScope.launch { + private fun fetchAccount() { + viewModelScope.launch(Dispatchers.IO) { _viewState.update { it.copy( account = accountRepository.getCurrentAccount() ) } - callback() } } private fun addFromFile(inputStream: InputStream) { - viewModelScope.launch { + viewModelScope.launch(Dispatchers.IO) { opmlRepository.saveToDatabase(inputStream) rssRepository.get().doSync() } } private fun fetchData(filterState: FilterState) { - viewModelScope.launch { + viewModelScope.launch(Dispatchers.IO) { pullFeeds( isStarred = filterState.filter.isStarred(), isUnread = filterState.filter.isUnread(), @@ -144,9 +143,7 @@ sealed class FeedsViewAction { val filterState: FilterState, ) : FeedsViewAction() - data class FetchAccount( - val callback: () -> Unit = {}, - ) : FeedsViewAction() + object FetchAccount: FeedsViewAction() data class AddFromFile( val inputStream: InputStream diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeViewModel.kt index 21d3e9c..c53154c 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeViewModel.kt @@ -5,11 +5,8 @@ import androidx.lifecycle.viewModelScope import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.PagerState import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.async +import kotlinx.coroutines.* import kotlinx.coroutines.flow.* -import kotlinx.coroutines.launch import me.ash.reader.R import me.ash.reader.data.article.Article import me.ash.reader.data.feed.Feed @@ -73,7 +70,7 @@ class SubscribeViewModel @Inject constructor( private fun subscribe() { val feed = _viewState.value.feed ?: return val articles = _viewState.value.articles - viewModelScope.launch { + viewModelScope.launch(Dispatchers.IO) { val groupId = async { if ( _viewState.value.newGroupSelected && @@ -129,7 +126,7 @@ class SubscribeViewModel @Inject constructor( private fun search(scope: CoroutineScope) { searchJob?.cancel() - viewModelScope.launch { + viewModelScope.launch(Dispatchers.IO) { try { _viewState.update { it.copy( diff --git a/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowViewModel.kt index 591404a..70f9906 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowViewModel.kt @@ -42,7 +42,7 @@ class FlowViewModel @Inject constructor( } private fun fetchData(filterState: FilterState) { - viewModelScope.launch { + viewModelScope.launch(Dispatchers.Default) { rssRepository.get().pullImportant(filterState.filter.isStarred(), true) .collect { importantList -> _viewState.update { diff --git a/app/src/main/java/me/ash/reader/ui/page/home/read/ReadViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/read/ReadViewModel.kt index 1313b07..6d9d27d 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/read/ReadViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/read/ReadViewModel.kt @@ -4,6 +4,7 @@ import androidx.compose.foundation.lazy.LazyListState import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -65,44 +66,42 @@ class ReadViewModel @Inject constructor( } private fun markUnread(isUnread: Boolean) { - _viewState.value.articleWithFeed?.let { + val articleWithFeed = _viewState.value.articleWithFeed ?: return + viewModelScope.launch(Dispatchers.IO) { _viewState.update { it.copy( - articleWithFeed = it.articleWithFeed?.copy( - article = it.articleWithFeed.article.copy( + articleWithFeed = articleWithFeed.copy( + article = articleWithFeed.article.copy( isUnread = isUnread ) ) ) } - viewModelScope.launch { - rssRepository.get().updateArticleInfo( - it.article.copy( - isUnread = isUnread - ) + rssRepository.get().updateArticleInfo( + articleWithFeed.article.copy( + isUnread = isUnread ) - } + ) } } private fun markStarred(isStarred: Boolean) { - _viewState.value.articleWithFeed?.let { + val articleWithFeed = _viewState.value.articleWithFeed ?: return + viewModelScope.launch(Dispatchers.IO) { _viewState.update { it.copy( - articleWithFeed = it.articleWithFeed?.copy( - article = it.articleWithFeed.article.copy( + articleWithFeed = articleWithFeed.copy( + article = articleWithFeed.article.copy( isStarred = isStarred ) ) ) } - viewModelScope.launch { - rssRepository.get().updateArticleInfo( - it.article.copy( - isStarred = isStarred - ) + rssRepository.get().updateArticleInfo( + articleWithFeed.article.copy( + isStarred = isStarred ) - } + ) } }