Move coroutine job into its own class

This commit is contained in:
topjohnwu 2022-06-10 04:12:31 -07:00
parent 46d4708386
commit 515f81944c
9 changed files with 54 additions and 54 deletions

View File

@ -0,0 +1,22 @@
package com.topjohnwu.magisk.arch
import androidx.annotation.MainThread
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
abstract class AsyncLoadViewModel : BaseViewModel() {
private var loadingJob: Job? = null
@MainThread
fun startLoading() {
if (loadingJob?.isActive == true) {
// Prevent multiple jobs from running at the same time
return
}
loadingJob = viewModelScope.launch { doLoadWork() }
}
protected abstract suspend fun doLoadWork()
}

View File

@ -76,7 +76,10 @@ abstract class BaseFragment<Binding : ViewDataBinding> : Fragment(), ViewModelHo
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
viewModel.requestRefresh() viewModel.let {
if (it is AsyncLoadViewModel)
it.startLoading()
}
} }
protected open fun onPreBind(binding: Binding) { protected open fun onPreBind(binding: Binding) {

View File

@ -16,31 +16,16 @@ import com.topjohnwu.magisk.events.BackPressEvent
import com.topjohnwu.magisk.events.NavigationEvent import com.topjohnwu.magisk.events.NavigationEvent
import com.topjohnwu.magisk.events.PermissionEvent import com.topjohnwu.magisk.events.PermissionEvent
import com.topjohnwu.magisk.events.SnackbarEvent import com.topjohnwu.magisk.events.SnackbarEvent
import kotlinx.coroutines.Job
abstract class BaseViewModel : ViewModel(), ObservableHost { abstract class BaseViewModel : ViewModel(), ObservableHost {
override var callbacks: PropertyChangeRegistry? = null override var callbacks: PropertyChangeRegistry? = null
val viewEvents: LiveData<ViewEvent> get() = _viewEvents
private val _viewEvents = MutableLiveData<ViewEvent>() private val _viewEvents = MutableLiveData<ViewEvent>()
private var runningJob: Job? = null val viewEvents: LiveData<ViewEvent> get() = _viewEvents
open fun onSaveState(state: Bundle) {} open fun onSaveState(state: Bundle) {}
open fun onRestoreState(state: Bundle) {} open fun onRestoreState(state: Bundle) {}
/** This should probably never be called manually, it's called manually via delegate. */
@Synchronized
fun requestRefresh() {
if (runningJob?.isActive == true) {
return
}
runningJob = refresh()
}
protected open fun refresh(): Job? = null
open fun onNetworkChanged(network: Boolean) {} open fun onNetworkChanged(network: Boolean) {}
fun withPermission(permission: String, callback: (Boolean) -> Unit) { fun withPermission(permission: String, callback: (Boolean) -> Unit) {

View File

@ -89,7 +89,10 @@ abstract class UIActivity<Binding : ViewDataBinding> : BaseActivity(), ViewModel
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
viewModel.requestRefresh() viewModel.let {
if (it is AsyncLoadViewModel)
it.startLoading()
}
} }
override fun onEventDispatched(event: ViewEvent) = when (event) { override fun onEventDispatched(event: ViewEvent) = when (event) {

View File

@ -3,9 +3,8 @@ package com.topjohnwu.magisk.ui.deny
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES
import androidx.databinding.Bindable import androidx.databinding.Bindable
import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.arch.BaseViewModel import com.topjohnwu.magisk.arch.AsyncLoadViewModel
import com.topjohnwu.magisk.core.di.AppContext import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.databinding.bindExtra import com.topjohnwu.magisk.databinding.bindExtra
import com.topjohnwu.magisk.databinding.filterableListOf import com.topjohnwu.magisk.databinding.filterableListOf
@ -16,10 +15,9 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.toCollection import kotlinx.coroutines.flow.toCollection
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class DenyListViewModel : BaseViewModel() { class DenyListViewModel : AsyncLoadViewModel() {
var isShowSystem = false var isShowSystem = false
set(value) { set(value) {
@ -49,7 +47,7 @@ class DenyListViewModel : BaseViewModel() {
private set(value) = set(value, field, { field = it }, BR.loading) private set(value) = set(value, field, { field = it }, BR.loading)
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
override fun refresh() = viewModelScope.launch { override suspend fun doLoadWork() {
loading = true loading = true
val (apps, diff) = withContext(Dispatchers.Default) { val (apps, diff) = withContext(Dispatchers.Default) {
val pm = AppContext.packageManager val pm = AppContext.packageManager

View File

@ -3,7 +3,6 @@ package com.topjohnwu.magisk.ui.home
import android.content.Context import android.content.Context
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.databinding.Bindable import androidx.databinding.Bindable
import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.BuildConfig import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
@ -23,12 +22,11 @@ import com.topjohnwu.magisk.ktx.await
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.utils.asText import com.topjohnwu.magisk.utils.asText
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.launch
import kotlin.math.roundToInt import kotlin.math.roundToInt
class HomeViewModel( class HomeViewModel(
private val svc: NetworkService private val svc: NetworkService
) : BaseViewModel() { ) : AsyncLoadViewModel() {
enum class State { enum class State {
LOADING, INVALID, OUTDATED, UP_TO_DATE LOADING, INVALID, OUTDATED, UP_TO_DATE
@ -83,7 +81,7 @@ class HomeViewModel(
private var checkedEnv = false private var checkedEnv = false
} }
override fun refresh() = viewModelScope.launch { override suspend fun doLoadWork() {
appState = State.LOADING appState = State.LOADING
Info.getRemote(svc)?.apply { Info.getRemote(svc)?.apply {
appState = when { appState = when {
@ -101,9 +99,7 @@ class HomeViewModel(
ensureEnv() ensureEnv()
} }
override fun onNetworkChanged(network: Boolean) { override fun onNetworkChanged(network: Boolean) = startLoading()
requestRefresh()
}
fun onProgressUpdate(progress: Float, subject: Subject) { fun onProgressUpdate(progress: Float, subject: Subject) {
if (subject is App) if (subject is App)

View File

@ -5,7 +5,7 @@ import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.BuildConfig import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseViewModel import com.topjohnwu.magisk.arch.AsyncLoadViewModel
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.repository.LogRepository import com.topjohnwu.magisk.core.repository.LogRepository
import com.topjohnwu.magisk.core.utils.MediaStoreUtils import com.topjohnwu.magisk.core.utils.MediaStoreUtils
@ -24,7 +24,7 @@ import java.io.FileInputStream
class LogViewModel( class LogViewModel(
private val repo: LogRepository private val repo: LogRepository
) : BaseViewModel() { ) : AsyncLoadViewModel() {
// --- empty view // --- empty view
@ -43,7 +43,7 @@ class LogViewModel(
var consoleText = " " var consoleText = " "
set(value) = set(value, field, { field = it }, BR.consoleText) set(value) = set(value, field, { field = it }, BR.consoleText)
override fun refresh() = viewModelScope.launch { override suspend fun doLoadWork() {
consoleText = repo.fetchMagiskLogs() consoleText = repo.fetchMagiskLogs()
val (suLogs, diff) = withContext(Dispatchers.Default) { val (suLogs, diff) = withContext(Dispatchers.Default) {
val suLogs = repo.fetchSuLogs().map { LogRvItem(it) } val suLogs = repo.fetchSuLogs().map { LogRvItem(it) }
@ -89,12 +89,12 @@ class LogViewModel(
fun clearMagiskLog() = repo.clearMagiskLogs { fun clearMagiskLog() = repo.clearMagiskLogs {
SnackbarEvent(R.string.logs_cleared).publish() SnackbarEvent(R.string.logs_cleared).publish()
requestRefresh() startLoading()
} }
fun clearLog() = viewModelScope.launch { fun clearLog() = viewModelScope.launch {
repo.clearLogs() repo.clearLogs()
SnackbarEvent(R.string.logs_cleared).publish() SnackbarEvent(R.string.logs_cleared).publish()
requestRefresh() startLoading()
} }
} }

View File

@ -3,10 +3,9 @@ package com.topjohnwu.magisk.ui.module
import android.net.Uri import android.net.Uri
import androidx.databinding.Bindable import androidx.databinding.Bindable
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseViewModel import com.topjohnwu.magisk.arch.AsyncLoadViewModel
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.base.ContentResultCallback import com.topjohnwu.magisk.core.base.ContentResultCallback
import com.topjohnwu.magisk.core.model.module.LocalModule import com.topjohnwu.magisk.core.model.module.LocalModule
@ -16,12 +15,10 @@ import com.topjohnwu.magisk.events.GetContentEvent
import com.topjohnwu.magisk.events.SnackbarEvent import com.topjohnwu.magisk.events.SnackbarEvent
import com.topjohnwu.magisk.events.dialog.ModuleInstallDialog import com.topjohnwu.magisk.events.dialog.ModuleInstallDialog
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
class ModuleViewModel : BaseViewModel() { class ModuleViewModel : AsyncLoadViewModel() {
val bottomBarBarrierIds = intArrayOf(R.id.module_update, R.id.module_remove) val bottomBarBarrierIds = intArrayOf(R.id.module_update, R.id.module_remove)
@ -45,18 +42,14 @@ class ModuleViewModel : BaseViewModel() {
} }
} }
override fun refresh(): Job { override suspend fun doLoadWork() {
return viewModelScope.launch {
loading = true loading = true
loadInstalled() loadInstalled()
loading = false loading = false
loadUpdateInfo() loadUpdateInfo()
} }
}
override fun onNetworkChanged(network: Boolean) { override fun onNetworkChanged(network: Boolean) = startLoading()
requestRefresh()
}
private suspend fun loadInstalled() { private suspend fun loadInstalled() {
val installed = LocalModule.installed().map { LocalModuleRvItem(it) } val installed = LocalModule.installed().map { LocalModuleRvItem(it) }

View File

@ -9,7 +9,7 @@ import androidx.databinding.ObservableArrayList
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseViewModel import com.topjohnwu.magisk.arch.AsyncLoadViewModel
import com.topjohnwu.magisk.core.data.magiskdb.PolicyDao import com.topjohnwu.magisk.core.data.magiskdb.PolicyDao
import com.topjohnwu.magisk.core.di.AppContext import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.core.model.su.SuPolicy import com.topjohnwu.magisk.core.model.su.SuPolicy
@ -29,7 +29,7 @@ import kotlinx.coroutines.withContext
class SuperuserViewModel( class SuperuserViewModel(
private val db: PolicyDao private val db: PolicyDao
) : BaseViewModel() { ) : AsyncLoadViewModel() {
private val itemNoData = TextItem(R.string.superuser_policy_none) private val itemNoData = TextItem(R.string.superuser_policy_none)
@ -48,10 +48,10 @@ class SuperuserViewModel(
private set(value) = set(value, field, { field = it }, BR.loading) private set(value) = set(value, field, { field = it }, BR.loading)
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
override fun refresh() = viewModelScope.launch { override suspend fun doLoadWork() {
if (!Utils.showSuperUser()) { if (!Utils.showSuperUser()) {
loading = false loading = false
return@launch return
} }
loading = true loading = true
val (policies, diff) = withContext(Dispatchers.IO) { val (policies, diff) = withContext(Dispatchers.IO) {