From 515f81944c04e55c47fabe91a92f3f5b70906508 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Fri, 10 Jun 2022 04:12:31 -0700 Subject: [PATCH] Move coroutine job into its own class --- .../magisk/arch/AsyncLoadViewModel.kt | 22 ++++++++++++++++++ .../com/topjohnwu/magisk/arch/BaseFragment.kt | 5 +++- .../topjohnwu/magisk/arch/BaseViewModel.kt | 17 +------------- .../com/topjohnwu/magisk/arch/UIActivity.kt | 5 +++- .../magisk/ui/deny/DenyListViewModel.kt | 8 +++---- .../topjohnwu/magisk/ui/home/HomeViewModel.kt | 10 +++----- .../topjohnwu/magisk/ui/log/LogViewModel.kt | 10 ++++---- .../magisk/ui/module/ModuleViewModel.kt | 23 +++++++------------ .../magisk/ui/superuser/SuperuserViewModel.kt | 8 +++---- 9 files changed, 54 insertions(+), 54 deletions(-) create mode 100644 app/src/main/java/com/topjohnwu/magisk/arch/AsyncLoadViewModel.kt diff --git a/app/src/main/java/com/topjohnwu/magisk/arch/AsyncLoadViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/arch/AsyncLoadViewModel.kt new file mode 100644 index 000000000..c712c8242 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/arch/AsyncLoadViewModel.kt @@ -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() +} diff --git a/app/src/main/java/com/topjohnwu/magisk/arch/BaseFragment.kt b/app/src/main/java/com/topjohnwu/magisk/arch/BaseFragment.kt index 7891a4ed9..b0f244bf6 100644 --- a/app/src/main/java/com/topjohnwu/magisk/arch/BaseFragment.kt +++ b/app/src/main/java/com/topjohnwu/magisk/arch/BaseFragment.kt @@ -76,7 +76,10 @@ abstract class BaseFragment : Fragment(), ViewModelHo override fun onResume() { super.onResume() - viewModel.requestRefresh() + viewModel.let { + if (it is AsyncLoadViewModel) + it.startLoading() + } } protected open fun onPreBind(binding: Binding) { diff --git a/app/src/main/java/com/topjohnwu/magisk/arch/BaseViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/arch/BaseViewModel.kt index 752d5a130..20c5da318 100644 --- a/app/src/main/java/com/topjohnwu/magisk/arch/BaseViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/arch/BaseViewModel.kt @@ -16,31 +16,16 @@ import com.topjohnwu.magisk.events.BackPressEvent import com.topjohnwu.magisk.events.NavigationEvent import com.topjohnwu.magisk.events.PermissionEvent import com.topjohnwu.magisk.events.SnackbarEvent -import kotlinx.coroutines.Job abstract class BaseViewModel : ViewModel(), ObservableHost { override var callbacks: PropertyChangeRegistry? = null - val viewEvents: LiveData get() = _viewEvents - private val _viewEvents = MutableLiveData() - private var runningJob: Job? = null + val viewEvents: LiveData get() = _viewEvents open fun onSaveState(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) {} fun withPermission(permission: String, callback: (Boolean) -> Unit) { diff --git a/app/src/main/java/com/topjohnwu/magisk/arch/UIActivity.kt b/app/src/main/java/com/topjohnwu/magisk/arch/UIActivity.kt index e37f50076..73059c770 100644 --- a/app/src/main/java/com/topjohnwu/magisk/arch/UIActivity.kt +++ b/app/src/main/java/com/topjohnwu/magisk/arch/UIActivity.kt @@ -89,7 +89,10 @@ abstract class UIActivity : BaseActivity(), ViewModel override fun onResume() { super.onResume() - viewModel.requestRefresh() + viewModel.let { + if (it is AsyncLoadViewModel) + it.startLoading() + } } override fun onEventDispatched(event: ViewEvent) = when (event) { diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/deny/DenyListViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/deny/DenyListViewModel.kt index 4d82c2325..2c43273e2 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/deny/DenyListViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/deny/DenyListViewModel.kt @@ -3,9 +3,8 @@ package com.topjohnwu.magisk.ui.deny import android.annotation.SuppressLint import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES import androidx.databinding.Bindable -import androidx.lifecycle.viewModelScope 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.databinding.bindExtra import com.topjohnwu.magisk.databinding.filterableListOf @@ -16,10 +15,9 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.toCollection -import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -class DenyListViewModel : BaseViewModel() { +class DenyListViewModel : AsyncLoadViewModel() { var isShowSystem = false set(value) { @@ -49,7 +47,7 @@ class DenyListViewModel : BaseViewModel() { private set(value) = set(value, field, { field = it }, BR.loading) @SuppressLint("InlinedApi") - override fun refresh() = viewModelScope.launch { + override suspend fun doLoadWork() { loading = true val (apps, diff) = withContext(Dispatchers.Default) { val pm = AppContext.packageManager diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt index 3947d1086..89558538c 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt @@ -3,7 +3,6 @@ package com.topjohnwu.magisk.ui.home import android.content.Context import androidx.core.net.toUri import androidx.databinding.Bindable -import androidx.lifecycle.viewModelScope import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BuildConfig 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.asText import com.topjohnwu.superuser.Shell -import kotlinx.coroutines.launch import kotlin.math.roundToInt class HomeViewModel( private val svc: NetworkService -) : BaseViewModel() { +) : AsyncLoadViewModel() { enum class State { LOADING, INVALID, OUTDATED, UP_TO_DATE @@ -83,7 +81,7 @@ class HomeViewModel( private var checkedEnv = false } - override fun refresh() = viewModelScope.launch { + override suspend fun doLoadWork() { appState = State.LOADING Info.getRemote(svc)?.apply { appState = when { @@ -101,9 +99,7 @@ class HomeViewModel( ensureEnv() } - override fun onNetworkChanged(network: Boolean) { - requestRefresh() - } + override fun onNetworkChanged(network: Boolean) = startLoading() fun onProgressUpdate(progress: Float, subject: Subject) { if (subject is App) diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/log/LogViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/log/LogViewModel.kt index f6f76ccc6..eaeb831c8 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/log/LogViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/log/LogViewModel.kt @@ -5,7 +5,7 @@ import androidx.lifecycle.viewModelScope import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BuildConfig 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.repository.LogRepository import com.topjohnwu.magisk.core.utils.MediaStoreUtils @@ -24,7 +24,7 @@ import java.io.FileInputStream class LogViewModel( private val repo: LogRepository -) : BaseViewModel() { +) : AsyncLoadViewModel() { // --- empty view @@ -43,7 +43,7 @@ class LogViewModel( var consoleText = " " set(value) = set(value, field, { field = it }, BR.consoleText) - override fun refresh() = viewModelScope.launch { + override suspend fun doLoadWork() { consoleText = repo.fetchMagiskLogs() val (suLogs, diff) = withContext(Dispatchers.Default) { val suLogs = repo.fetchSuLogs().map { LogRvItem(it) } @@ -89,12 +89,12 @@ class LogViewModel( fun clearMagiskLog() = repo.clearMagiskLogs { SnackbarEvent(R.string.logs_cleared).publish() - requestRefresh() + startLoading() } fun clearLog() = viewModelScope.launch { repo.clearLogs() SnackbarEvent(R.string.logs_cleared).publish() - requestRefresh() + startLoading() } } diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt index fbe40ea31..a9d2abd2e 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt @@ -3,10 +3,9 @@ package com.topjohnwu.magisk.ui.module import android.net.Uri import androidx.databinding.Bindable import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.viewModelScope import com.topjohnwu.magisk.BR 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.base.ContentResultCallback 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.dialog.ModuleInstallDialog import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.parcelize.Parcelize -class ModuleViewModel : BaseViewModel() { +class ModuleViewModel : AsyncLoadViewModel() { val bottomBarBarrierIds = intArrayOf(R.id.module_update, R.id.module_remove) @@ -45,18 +42,14 @@ class ModuleViewModel : BaseViewModel() { } } - override fun refresh(): Job { - return viewModelScope.launch { - loading = true - loadInstalled() - loading = false - loadUpdateInfo() - } + override suspend fun doLoadWork() { + loading = true + loadInstalled() + loading = false + loadUpdateInfo() } - override fun onNetworkChanged(network: Boolean) { - requestRefresh() - } + override fun onNetworkChanged(network: Boolean) = startLoading() private suspend fun loadInstalled() { val installed = LocalModule.installed().map { LocalModuleRvItem(it) } diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserViewModel.kt index af7155a69..2b754d97f 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserViewModel.kt @@ -9,7 +9,7 @@ import androidx.databinding.ObservableArrayList import androidx.lifecycle.viewModelScope import com.topjohnwu.magisk.BR 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.di.AppContext import com.topjohnwu.magisk.core.model.su.SuPolicy @@ -29,7 +29,7 @@ import kotlinx.coroutines.withContext class SuperuserViewModel( private val db: PolicyDao -) : BaseViewModel() { +) : AsyncLoadViewModel() { 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) @SuppressLint("InlinedApi") - override fun refresh() = viewModelScope.launch { + override suspend fun doLoadWork() { if (!Utils.showSuperUser()) { loading = false - return@launch + return } loading = true val (policies, diff) = withContext(Dispatchers.IO) {