Improve coroutines

This commit is contained in:
Ash 2022-03-27 10:30:25 +08:00
parent 435eb67c55
commit 77974c1d8b
12 changed files with 102 additions and 115 deletions

View File

@ -58,7 +58,7 @@ class App : Application(), Configuration.Provider {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
applicationScope.launch { applicationScope.launch(Dispatchers.IO) {
accountInit() accountInit()
workerInit() workerInit()
} }

View File

@ -21,7 +21,7 @@ class Filter(
companion object { companion object {
val Starred = Filter( val Starred = Filter(
index = 0, index = 0,
important = 13, important = 666,
icon = Icons.Rounded.StarOutline, icon = Icons.Rounded.StarOutline,
filledIcon = Icons.Rounded.Star, filledIcon = Icons.Rounded.Star,
) )

View File

@ -10,7 +10,6 @@ import dagger.assisted.AssistedInject
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.withContext
import me.ash.reader.currentAccountId import me.ash.reader.currentAccountId
import me.ash.reader.data.account.AccountDao import me.ash.reader.data.account.AccountDao
import me.ash.reader.data.article.Article import me.ash.reader.data.article.Article
@ -104,24 +103,22 @@ abstract class AbstractRssRepository constructor(
} }
} }
suspend fun pullImportant( fun pullImportant(
isStarred: Boolean = false, isStarred: Boolean = false,
isUnread: Boolean = false, isUnread: Boolean = false,
): Flow<List<ImportantCount>> { ): Flow<List<ImportantCount>> {
return withContext(Dispatchers.IO) {
Log.i("RLog", "thread:pullImportant ${Thread.currentThread().name}") Log.i("RLog", "thread:pullImportant ${Thread.currentThread().name}")
val accountId = context.currentAccountId val accountId = context.currentAccountId
Log.i( Log.i(
"RLog", "RLog",
"pullImportant: accountId: ${accountId}, isStarred: ${isStarred}, isUnread: ${isUnread}" "pullImportant: accountId: ${accountId}, isStarred: ${isStarred}, isUnread: ${isUnread}"
) )
when { return when {
isStarred -> articleDao isStarred -> articleDao
.queryImportantCountWhenIsStarred(accountId, isStarred) .queryImportantCountWhenIsStarred(accountId, isStarred)
isUnread -> articleDao isUnread -> articleDao
.queryImportantCountWhenIsUnread(accountId, isUnread) .queryImportantCountWhenIsUnread(accountId, isUnread)
else -> articleDao.queryImportantCountWhenIsAll(accountId) else -> articleDao.queryImportantCountWhenIsAll(accountId)
}
}.flowOn(Dispatchers.IO) }.flowOn(Dispatchers.IO)
} }
@ -154,11 +151,9 @@ abstract class AbstractRssRepository constructor(
} }
suspend fun deleteFeed(feed: Feed) { suspend fun deleteFeed(feed: Feed) {
withContext(Dispatchers.IO) {
articleDao.deleteByFeedId(context.currentAccountId, feed.id) articleDao.deleteByFeedId(context.currentAccountId, feed.id)
feedDao.delete(feed) feedDao.delete(feed)
} }
}
companion object { companion object {
val mutex = Mutex() val mutex = Mutex()

View File

@ -17,7 +17,9 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext 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.account.AccountDao
import me.ash.reader.data.article.Article import me.ash.reader.data.article.Article
import me.ash.reader.data.article.ArticleDao import me.ash.reader.data.article.ArticleDao
@ -89,25 +91,28 @@ class LocalRssRepository @Inject constructor(
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val preTime = System.currentTimeMillis() val preTime = System.currentTimeMillis()
val accountId = context.currentAccountId val accountId = context.currentAccountId
val feeds = async { feedDao.queryAll(accountId) } val articles = mutableListOf<Article>()
val articles = feeds.await().also { feed -> feedDao.queryAll(accountId).also { feed ->
updateSyncState { updateSyncState {
it.copy( it.copy(
feedCount = feed.size, feedCount = feed.size,
) )
} }
Log.i("RLog", "thread:sync ${Thread.currentThread().name}")
}.map { feed -> }.map { feed ->
async { async {
val articles = syncFeed(accountId, feed) syncFeed(accountId, feed)
articles
} }
}.awaitAll().forEach {
if (it.isNotify) {
notify(it.articles)
}
articles.addAll(it.articles)
} }
articles.awaitAll().sumOf { it.size }.let { count -> articleDao.insertList(articles)
Log.i( Log.i(
"RlOG", "RlOG",
"[${count}] onCompletion: ${System.currentTimeMillis() - preTime}" "onCompletion: ${System.currentTimeMillis() - preTime}"
) )
accountDao.queryById(accountId)?.let { account -> accountDao.queryById(accountId)?.let { account ->
accountDao.update( accountDao.update(
@ -126,12 +131,16 @@ class LocalRssRepository @Inject constructor(
} }
} }
} }
}
data class ArticleNotify(
val articles: List<Article>,
val isNotify: Boolean,
)
private suspend fun syncFeed( private suspend fun syncFeed(
accountId: Int, accountId: Int,
feed: Feed feed: Feed
): List<Article> { ): ArticleNotify {
val latest = articleDao.queryLatestByFeedId(accountId, feed.id) val latest = articleDao.queryLatestByFeedId(accountId, feed.id)
val articles = rssHelper.queryRssXml( val articles = rssHelper.queryRssXml(
rssNetworkDataSource, rssNetworkDataSource,
@ -143,21 +152,16 @@ class LocalRssRepository @Inject constructor(
rssHelper.queryRssIcon(feedDao, feed, it.first().link) rssHelper.queryRssIcon(feedDao, feed, it.first().link)
} }
} }
Log.i("RLog", "thread:syncFeed ${Thread.currentThread().name}")
updateSyncState { updateSyncState {
it.copy( it.copy(
syncedCount = it.syncedCount + 1, syncedCount = it.syncedCount + 1,
currentFeedName = feed.name currentFeedName = feed.name
) )
} }
if (articles.isNotEmpty()) { return ArticleNotify(
articleDao.insertList(articles) articles = articles,
if (feed.isNotification) { isNotify = articles.isNotEmpty() && feed.isNotification
notify(articles) )
}
}
return articles
} }
private fun notify( private fun notify(

View File

@ -1,17 +1,14 @@
package me.ash.reader.ui.page.home package me.ash.reader.ui.page.home
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.PagerState import com.google.accompanist.pager.PagerState
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import me.ash.reader.data.constant.Filter import me.ash.reader.data.constant.Filter
import me.ash.reader.data.feed.Feed import me.ash.reader.data.feed.Feed
import me.ash.reader.data.group.Group import me.ash.reader.data.group.Group
@ -36,7 +33,7 @@ class HomeViewModel @Inject constructor(
fun dispatch(action: HomeViewAction) { fun dispatch(action: HomeViewAction) {
when (action) { when (action) {
is HomeViewAction.Sync -> sync(action.callback) is HomeViewAction.Sync -> sync()
is HomeViewAction.ChangeFilter -> changeFilter(action.filterState) is HomeViewAction.ChangeFilter -> changeFilter(action.filterState)
is HomeViewAction.ScrollToPage -> scrollToPage( is HomeViewAction.ScrollToPage -> scrollToPage(
action.scope, action.scope,
@ -46,11 +43,8 @@ class HomeViewModel @Inject constructor(
} }
} }
private fun sync(callback: () -> Unit = {}) { private fun sync() {
viewModelScope.launch {
rssRepository.get().doSync() rssRepository.get().doSync()
callback()
}
} }
private fun changeFilter(filterState: FilterState) { private fun changeFilter(filterState: FilterState) {
@ -80,9 +74,7 @@ data class HomeViewState(
) )
sealed class HomeViewAction { sealed class HomeViewAction {
data class Sync( object Sync : HomeViewAction()
val callback: () -> Unit = {},
) : HomeViewAction()
data class ChangeFilter( data class ChangeFilter(
val filterState: FilterState val filterState: FilterState

View File

@ -49,7 +49,7 @@ fun DeleteFeedDialog(
confirmButton = { confirmButton = {
TextButton( TextButton(
onClick = { onClick = {
viewModel.dispatch(FeedOptionViewAction.Delete(){ viewModel.dispatch(FeedOptionViewAction.Delete {
viewModel.dispatch(FeedOptionViewAction.HideDeleteDialog) viewModel.dispatch(FeedOptionViewAction.HideDeleteDialog)
viewModel.dispatch(FeedOptionViewAction.Hide(scope)) viewModel.dispatch(FeedOptionViewAction.Hide(scope))
Toast.makeText(context, deletedTip, Toast.LENGTH_SHORT).show() Toast.makeText(context, deletedTip, Toast.LENGTH_SHORT).show()

View File

@ -8,11 +8,13 @@ import androidx.lifecycle.viewModelScope
import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.ExperimentalPagerApi
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import me.ash.reader.data.feed.Feed import me.ash.reader.data.feed.Feed
import me.ash.reader.data.group.Group import me.ash.reader.data.group.Group
import me.ash.reader.data.repository.RssRepository import me.ash.reader.data.repository.RssRepository
@ -30,7 +32,7 @@ class FeedOptionViewModel @Inject constructor(
val viewState: StateFlow<FeedOptionViewState> = _viewState.asStateFlow() val viewState: StateFlow<FeedOptionViewState> = _viewState.asStateFlow()
init { init {
viewModelScope.launch { viewModelScope.launch(Dispatchers.IO) {
rssRepository.get().pullGroups().collect { groups -> rssRepository.get().pullGroups().collect { groups ->
_viewState.update { _viewState.update {
it.copy( it.copy(
@ -88,7 +90,7 @@ class FeedOptionViewModel @Inject constructor(
} }
private fun selectedGroup(groupId: String) { private fun selectedGroup(groupId: String) {
viewModelScope.launch { viewModelScope.launch(Dispatchers.IO) {
_viewState.value.feed?.let { _viewState.value.feed?.let {
rssRepository.get().updateFeed( rssRepository.get().updateFeed(
it.copy( it.copy(
@ -109,7 +111,7 @@ class FeedOptionViewModel @Inject constructor(
} }
private fun changeParseFullContentPreset() { private fun changeParseFullContentPreset() {
viewModelScope.launch { viewModelScope.launch(Dispatchers.IO) {
_viewState.value.feed?.let { _viewState.value.feed?.let {
rssRepository.get().updateFeed( rssRepository.get().updateFeed(
it.copy( it.copy(
@ -122,7 +124,7 @@ class FeedOptionViewModel @Inject constructor(
} }
private fun changeAllowNotificationPreset() { private fun changeAllowNotificationPreset() {
viewModelScope.launch { viewModelScope.launch(Dispatchers.IO) {
_viewState.value.feed?.let { _viewState.value.feed?.let {
rssRepository.get().updateFeed( rssRepository.get().updateFeed(
it.copy( it.copy(
@ -136,13 +138,14 @@ class FeedOptionViewModel @Inject constructor(
private fun delete(callback: () -> Unit = {}) { private fun delete(callback: () -> Unit = {}) {
_viewState.value.feed?.let { _viewState.value.feed?.let {
viewModelScope.launch { viewModelScope.launch(Dispatchers.IO) {
rssRepository.get().deleteFeed(it) rssRepository.get().deleteFeed(it)
}.invokeOnCompletion { withContext(Dispatchers.Main) {
callback() callback()
} }
} }
} }
}
private fun hideDeleteDialog() { private fun hideDeleteDialog() {
_viewState.update { _viewState.update {
@ -201,6 +204,6 @@ sealed class FeedOptionViewAction {
val callback: () -> Unit = {} val callback: () -> Unit = {}
) : FeedOptionViewAction() ) : FeedOptionViewAction()
object ShowDeleteDialog: FeedOptionViewAction() object ShowDeleteDialog : FeedOptionViewAction()
object HideDeleteDialog: FeedOptionViewAction() object HideDeleteDialog : FeedOptionViewAction()
} }

View File

@ -61,7 +61,7 @@ fun FeedsPage(
) )
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
viewModel.dispatch(FeedsViewAction.FetchAccount()) viewModel.dispatch(FeedsViewAction.FetchAccount)
} }
LaunchedEffect(homeViewModel.filterState) { LaunchedEffect(homeViewModel.filterState) {
@ -89,7 +89,7 @@ fun FeedsPage(
actions = { actions = {
IconButton(onClick = { IconButton(onClick = {
if (syncState.isSyncing) return@IconButton if (syncState.isSyncing) return@IconButton
homeViewModel.dispatch(HomeViewAction.Sync()) homeViewModel.dispatch(HomeViewAction.Sync)
}) { }) {
Icon( Icon(
modifier = Modifier.rotate(if (syncState.isSyncing) angle else 0f), modifier = Modifier.rotate(if (syncState.isSyncing) angle else 0f),
@ -135,7 +135,7 @@ fun FeedsPage(
Banner( Banner(
title = filterState.filter.getName(), title = filterState.filter.getName(),
desc = filterState.filter.getDesc(), desc = filterState.filter.getDesc(),
icon = viewState.filter.icon, icon = filterState.filter.icon,
action = { action = {
Icon( Icon(
imageVector = Icons.Outlined.KeyboardArrowRight, imageVector = Icons.Outlined.KeyboardArrowRight,

View File

@ -29,33 +29,32 @@ class FeedsViewModel @Inject constructor(
fun dispatch(action: FeedsViewAction) { fun dispatch(action: FeedsViewAction) {
when (action) { when (action) {
is FeedsViewAction.FetchAccount -> fetchAccount(action.callback) is FeedsViewAction.FetchAccount -> fetchAccount()
is FeedsViewAction.FetchData -> fetchData(action.filterState) is FeedsViewAction.FetchData -> fetchData(action.filterState)
is FeedsViewAction.AddFromFile -> addFromFile(action.inputStream) is FeedsViewAction.AddFromFile -> addFromFile(action.inputStream)
is FeedsViewAction.ScrollToItem -> scrollToItem(action.index) is FeedsViewAction.ScrollToItem -> scrollToItem(action.index)
} }
} }
private fun fetchAccount(callback: () -> Unit = {}) { private fun fetchAccount() {
viewModelScope.launch { viewModelScope.launch(Dispatchers.IO) {
_viewState.update { _viewState.update {
it.copy( it.copy(
account = accountRepository.getCurrentAccount() account = accountRepository.getCurrentAccount()
) )
} }
callback()
} }
} }
private fun addFromFile(inputStream: InputStream) { private fun addFromFile(inputStream: InputStream) {
viewModelScope.launch { viewModelScope.launch(Dispatchers.IO) {
opmlRepository.saveToDatabase(inputStream) opmlRepository.saveToDatabase(inputStream)
rssRepository.get().doSync() rssRepository.get().doSync()
} }
} }
private fun fetchData(filterState: FilterState) { private fun fetchData(filterState: FilterState) {
viewModelScope.launch { viewModelScope.launch(Dispatchers.IO) {
pullFeeds( pullFeeds(
isStarred = filterState.filter.isStarred(), isStarred = filterState.filter.isStarred(),
isUnread = filterState.filter.isUnread(), isUnread = filterState.filter.isUnread(),
@ -144,9 +143,7 @@ sealed class FeedsViewAction {
val filterState: FilterState, val filterState: FilterState,
) : FeedsViewAction() ) : FeedsViewAction()
data class FetchAccount( object FetchAccount: FeedsViewAction()
val callback: () -> Unit = {},
) : FeedsViewAction()
data class AddFromFile( data class AddFromFile(
val inputStream: InputStream val inputStream: InputStream

View File

@ -5,11 +5,8 @@ import androidx.lifecycle.viewModelScope
import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.PagerState import com.google.accompanist.pager.PagerState
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.*
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import me.ash.reader.R import me.ash.reader.R
import me.ash.reader.data.article.Article import me.ash.reader.data.article.Article
import me.ash.reader.data.feed.Feed import me.ash.reader.data.feed.Feed
@ -73,7 +70,7 @@ class SubscribeViewModel @Inject constructor(
private fun subscribe() { private fun subscribe() {
val feed = _viewState.value.feed ?: return val feed = _viewState.value.feed ?: return
val articles = _viewState.value.articles val articles = _viewState.value.articles
viewModelScope.launch { viewModelScope.launch(Dispatchers.IO) {
val groupId = async { val groupId = async {
if ( if (
_viewState.value.newGroupSelected && _viewState.value.newGroupSelected &&
@ -129,7 +126,7 @@ class SubscribeViewModel @Inject constructor(
private fun search(scope: CoroutineScope) { private fun search(scope: CoroutineScope) {
searchJob?.cancel() searchJob?.cancel()
viewModelScope.launch { viewModelScope.launch(Dispatchers.IO) {
try { try {
_viewState.update { _viewState.update {
it.copy( it.copy(

View File

@ -42,7 +42,7 @@ class FlowViewModel @Inject constructor(
} }
private fun fetchData(filterState: FilterState) { private fun fetchData(filterState: FilterState) {
viewModelScope.launch { viewModelScope.launch(Dispatchers.Default) {
rssRepository.get().pullImportant(filterState.filter.isStarred(), true) rssRepository.get().pullImportant(filterState.filter.isStarred(), true)
.collect { importantList -> .collect { importantList ->
_viewState.update { _viewState.update {

View File

@ -4,6 +4,7 @@ import androidx.compose.foundation.lazy.LazyListState
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
@ -65,46 +66,44 @@ class ReadViewModel @Inject constructor(
} }
private fun markUnread(isUnread: Boolean) { private fun markUnread(isUnread: Boolean) {
_viewState.value.articleWithFeed?.let { val articleWithFeed = _viewState.value.articleWithFeed ?: return
viewModelScope.launch(Dispatchers.IO) {
_viewState.update { _viewState.update {
it.copy( it.copy(
articleWithFeed = it.articleWithFeed?.copy( articleWithFeed = articleWithFeed.copy(
article = it.articleWithFeed.article.copy( article = articleWithFeed.article.copy(
isUnread = isUnread isUnread = isUnread
) )
) )
) )
} }
viewModelScope.launch {
rssRepository.get().updateArticleInfo( rssRepository.get().updateArticleInfo(
it.article.copy( articleWithFeed.article.copy(
isUnread = isUnread isUnread = isUnread
) )
) )
} }
} }
}
private fun markStarred(isStarred: Boolean) { private fun markStarred(isStarred: Boolean) {
_viewState.value.articleWithFeed?.let { val articleWithFeed = _viewState.value.articleWithFeed ?: return
viewModelScope.launch(Dispatchers.IO) {
_viewState.update { _viewState.update {
it.copy( it.copy(
articleWithFeed = it.articleWithFeed?.copy( articleWithFeed = articleWithFeed.copy(
article = it.articleWithFeed.article.copy( article = articleWithFeed.article.copy(
isStarred = isStarred isStarred = isStarred
) )
) )
) )
} }
viewModelScope.launch {
rssRepository.get().updateArticleInfo( rssRepository.get().updateArticleInfo(
it.article.copy( articleWithFeed.article.copy(
isStarred = isStarred isStarred = isStarred
) )
) )
} }
} }
}
private fun scrollToItem(index: Int) { private fun scrollToItem(index: Int) {
viewModelScope.launch { viewModelScope.launch {