Remove misused kotlinx.coroutines.NonCancellable

This commit is contained in:
Rocka 2026-05-03 18:11:42 +08:00
parent 306fdaef77
commit e605129e68
No known key found for this signature in database
GPG Key ID: 28031158FFDD6853
10 changed files with 134 additions and 146 deletions

View File

@ -13,7 +13,6 @@ import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import org.fcitx.fcitx5.android.data.clipboard.ClipboardManager
@ -47,7 +46,7 @@ class ClipboardEditActivity : Activity() {
private fun finishEditing(copy: Boolean = false) {
val str = editText.str
scope.launch(NonCancellable) {
scope.launch {
ClipboardManager.updateText(entryId, str)
if (copy) {
clipboardManager.setPrimaryClip(ClipData.newPlainText("", str))

View File

@ -42,13 +42,22 @@ class DeveloperFragment : PaddingPreferenceFragment() {
return@registerForActivityResult
}
val ctx = requireContext()
lifecycleScope.launch(NonCancellable + Dispatchers.IO) {
runCatching {
ctx.contentResolver.openOutputStream(uri)!!.use { o ->
hprofFile.inputStream().use { i -> i.copyTo(o) }
lifecycleScope.launch {
try {
withContext(Dispatchers.IO) {
ctx.contentResolver.openOutputStream(uri)!!.use { o ->
hprofFile.inputStream().use { i -> i.copyTo(o) }
}
}
}.let { ctx.toast(it) }
hprofFile.delete()
} catch (e: Exception) {
ctx.toast(e)
} finally {
withContext(NonCancellable) {
withContext(Dispatchers.IO) {
hprofFile.delete()
}
}
}
}
}
}
@ -87,12 +96,8 @@ class DeveloperFragment : PaddingPreferenceFragment() {
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok) { _, _ ->
lifecycleScope.launch {
withContext(NonCancellable + Dispatchers.IO) {
FcitxDaemon.restartFcitx()
withContext(Dispatchers.Main) {
context.toast(R.string.done)
}
}
FcitxDaemon.restartFcitx()
context.toast(R.string.done)
}
}
.show()
@ -102,11 +107,11 @@ class DeveloperFragment : PaddingPreferenceFragment() {
.setTitle(R.string.delete_and_sync_data)
.setMessage(R.string.delete_and_sync_data_message)
.setPositiveButton(android.R.string.ok) { _, _ ->
lifecycleScope.launch(NonCancellable + Dispatchers.IO) {
DataManager.deleteAndSync()
withContext(Dispatchers.Main) {
context.toast(R.string.synced)
lifecycleScope.launch {
withContext(Dispatchers.IO) {
DataManager.deleteAndSync()
}
context.toast(R.string.synced)
}
}
.setNegativeButton(android.R.string.cancel, null)
@ -117,11 +122,11 @@ class DeveloperFragment : PaddingPreferenceFragment() {
.setTitle(R.string.clear_clb_db)
.setMessage(R.string.clear_clp_db_confirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
lifecycleScope.launch(NonCancellable + Dispatchers.IO) {
ClipboardManager.nukeTable()
withContext(Dispatchers.Main) {
context.toast(R.string.done)
lifecycleScope.launch {
withContext(Dispatchers.IO) {
ClipboardManager.nukeTable()
}
context.toast(R.string.done)
}
}
.setNegativeButton(android.R.string.cancel, null)

View File

@ -17,8 +17,8 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.fcitx.fcitx5.android.FcitxApplication
import org.fcitx.fcitx5.android.R
import org.fcitx.fcitx5.android.databinding.ActivityLogBinding
@ -41,12 +41,14 @@ class LogActivity : AppCompatActivity() {
private fun registerLauncher() {
launcher = registerForActivityResult(CreateDocument("text/plain")) { uri ->
if (uri == null) return@registerForActivityResult
lifecycleScope.launch(NonCancellable + Dispatchers.IO) {
lifecycleScope.launch {
runCatching {
contentResolver.openOutputStream(uri)!!.use { stream ->
stream.bufferedWriter().use { writer ->
writer.write(DeviceInfo.get(this@LogActivity))
writer.write(logView.currentLog)
withContext(Dispatchers.IO) {
contentResolver.openOutputStream(uri)!!.use { stream ->
stream.bufferedWriter().use { writer ->
writer.write(DeviceInfo.get(this@LogActivity))
writer.write(logView.currentLog)
}
}
}
}.let { toast(it) }

View File

@ -16,8 +16,8 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.fcitx.fcitx5.android.R
import org.fcitx.fcitx5.android.core.reloadPinyinCustomPhrase
import org.fcitx.fcitx5.android.data.pinyin.CustomPhraseManager
@ -193,8 +193,10 @@ class PinyinCustomPhraseFragment : Fragment(), OnItemChangedListener<PinyinCusto
private fun saveConfig() {
if (!dustman.dirty) return
resetDustman()
lifecycleScope.launch(NonCancellable + Dispatchers.IO) {
CustomPhraseManager.save(ui.entries.toTypedArray())
lifecycleScope.launch {
withContext(Dispatchers.IO) {
CustomPhraseManager.save(ui.entries.toTypedArray())
}
viewModel.fcitx.runOnReady {
reloadPinyinCustomPhrase()
}

View File

@ -19,7 +19,6 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.fcitx.fcitx5.android.R
@ -132,7 +131,7 @@ class PinyinDictionaryFragment : Fragment(), OnItemChangedListener<PinyinDiction
val ctx = requireContext()
val cr = ctx.contentResolver
val nm = ctx.notificationManager
lifecycleScope.launch(NonCancellable + Dispatchers.IO) {
lifecycleScope.launch {
val id = IMPORT_ID++
val fileName = cr.queryFileName(uri) ?: return@launch
if (PinyinDictionary.Type.fromFileName(fileName) == null) {
@ -153,12 +152,11 @@ class PinyinDictionaryFragment : Fragment(), OnItemChangedListener<PinyinDiction
.setPriority(NotificationCompat.PRIORITY_HIGH)
.build().let { nm.notify(id, it) }
try {
val inputStream = cr.openInputStream(uri)!!
val imported = PinyinDictManager.importFromInputStream(inputStream, fileName)
.getOrThrow()
withContext(Dispatchers.Main) {
ui.addItem(item = imported)
val imported = withContext(Dispatchers.IO) {
val inputStream = cr.openInputStream(uri)!!
PinyinDictManager.importFromInputStream(inputStream, fileName).getOrThrow()
}
ui.addItem(item = imported)
} catch (e: Exception) {
ctx.importErrorDialog(e)
}
@ -173,7 +171,7 @@ class PinyinDictionaryFragment : Fragment(), OnItemChangedListener<PinyinDiction
// right before the Fragment detached from Activity, and at the time reload completes,
// Fragment is no longer attached to a Context, thus unable to cancel the notification.
val nm = requireContext().notificationManager
lifecycleScope.launch(NonCancellable + Dispatchers.IO) {
lifecycleScope.launch {
if (busy.compareAndSet(false, true)) {
val id = RELOAD_ID++
NotificationCompat.Builder(requireContext(), CHANNEL_ID)

View File

@ -10,7 +10,6 @@ import android.view.View
import android.view.inputmethod.EditorInfo
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.fcitx.fcitx5.android.R
@ -147,14 +146,15 @@ class QuickPhraseEditFragment : ProgressFragment(), OnItemChangedListener<QuickP
private fun saveConfig() {
if (!dustman.dirty) return
resetDustman()
lifecycleScope.launch(NonCancellable + Dispatchers.IO) {
quickPhrase.saveData(QuickPhraseData(ui.entries))
launch(Dispatchers.Main) {
// tell parent that we need to reload
parentFragmentManager.setFragmentResult(
RESULT,
Bundle().apply { putParcelable(RESULT, quickPhrase) })
lifecycleScope.launch {
withContext(Dispatchers.IO) {
quickPhrase.saveData(QuickPhraseData(ui.entries))
}
// tell parent that we need to reload
parentFragmentManager.setFragmentResult(
RESULT,
Bundle().apply { putParcelable(RESULT, quickPhrase) }
)
}
}

View File

@ -21,7 +21,6 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.fcitx.fcitx5.android.R
@ -222,7 +221,7 @@ class QuickPhraseListFragment : Fragment(), OnItemChangedListener<QuickPhrase> {
val ctx = requireContext()
val cr = ctx.contentResolver
val nm = ctx.notificationManager
lifecycleScope.launch(NonCancellable + Dispatchers.IO) {
lifecycleScope.launch {
val id = IMPORT_ID++
val fileName = cr.queryFileName(uri) ?: return@launch
val extName = fileName.substringAfterLast('.')
@ -244,12 +243,11 @@ class QuickPhraseListFragment : Fragment(), OnItemChangedListener<QuickPhrase> {
.setPriority(NotificationCompat.PRIORITY_HIGH)
.build().let { nm.notify(id, it) }
try {
val inputStream = cr.openInputStream(uri)!!
val imported = QuickPhraseManager.importFromInputStream(inputStream, fileName)
.getOrThrow()
withContext(Dispatchers.Main) {
ui.addItem(item = imported)
val imported = withContext(Dispatchers.IO) {
val inputStream = cr.openInputStream(uri)!!
QuickPhraseManager.importFromInputStream(inputStream, fileName).getOrThrow()
}
ui.addItem(item = imported)
} catch (e: Exception) {
ctx.importErrorDialog(e)
}
@ -263,7 +261,7 @@ class QuickPhraseListFragment : Fragment(), OnItemChangedListener<QuickPhrase> {
// save the reference to NotificationManager, in case we need to cancel notification
// after Fragment detached
val nm = requireContext().notificationManager
lifecycleScope.launch(NonCancellable + Dispatchers.IO) {
lifecycleScope.launch {
if (busy.compareAndSet(false, true)) {
val id = RELOAD_ID++
NotificationCompat.Builder(requireContext(), CHANNEL_ID)

View File

@ -20,7 +20,6 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.fcitx.fcitx5.android.R
@ -211,7 +210,7 @@ class TableInputMethodFragment : Fragment(), OnItemChangedListener<TableBasedInp
val ctx = requireContext()
val cr = ctx.contentResolver
val nm = ctx.notificationManager
lifecycleScope.launch(NonCancellable + Dispatchers.IO) {
lifecycleScope.launch {
val importId = IMPORT_ID++
val fileName = cr.queryFileName(uri) ?: return@launch
if (!fileName.endsWith(".zip")) {
@ -227,11 +226,11 @@ class TableInputMethodFragment : Fragment(), OnItemChangedListener<TableBasedInp
.setPriority(NotificationCompat.PRIORITY_HIGH)
.build().let { nm.notify(importId, it) }
try {
val inputStream = cr.openInputStream(uri)!!
val imported = TableManager.importFromZip(inputStream).getOrThrow()
withContext(Dispatchers.Main) {
ui.addItem(item = imported)
val imported = withContext(Dispatchers.IO) {
val inputStream = cr.openInputStream(uri)!!
TableManager.importFromZip(inputStream).getOrThrow()
}
ui.addItem(item = imported)
} catch (e: Exception) {
ctx.importErrorDialog(e)
}
@ -279,7 +278,7 @@ class TableInputMethodFragment : Fragment(), OnItemChangedListener<TableBasedInp
}
return
}
lifecycleScope.launch(NonCancellable + Dispatchers.IO) {
lifecycleScope.launch {
val importId = IMPORT_ID++
val confName = cr.queryFileName(confUri) ?: return@launch
val dictName = cr.queryFileName(dictUri) ?: return@launch
@ -292,25 +291,21 @@ class TableInputMethodFragment : Fragment(), OnItemChangedListener<TableBasedInp
.setPriority(NotificationCompat.PRIORITY_HIGH)
.build().let { nm.notify(importId, it) }
try {
val confStream = cr.openInputStream(confUri)!!
val dictStream = cr.openInputStream(dictUri)!!
withContext(Dispatchers.Main) {
updateFilesSelectionDialogButton(importing = true)
}
val imported =
updateFilesSelectionDialogButton(importing = true)
val imported = withContext(Dispatchers.IO) {
val confStream = cr.openInputStream(confUri)!!
val dictStream = cr.openInputStream(dictUri)!!
TableManager.importFromConfAndDict(confName, confStream, dictName, dictStream)
.getOrThrow()
withContext(Dispatchers.Main) {
dismissFilesSelectionDialog()
ui.addItem(item = imported)
}
dismissFilesSelectionDialog()
ui.addItem(item = imported)
} catch (e: Exception) {
ctx.importErrorDialog(e)
}
nm.cancel(importId)
withContext(Dispatchers.Main) {
} finally {
updateFilesSelectionDialogButton(importing = false)
}
nm.cancel(importId)
}
}
@ -320,7 +315,7 @@ class TableInputMethodFragment : Fragment(), OnItemChangedListener<TableBasedInp
val nm = ctx.notificationManager
val im = tableToReplace ?: return
tableToReplace = null
lifecycleScope.launch(NonCancellable + Dispatchers.IO) {
lifecycleScope.launch {
val importId = IMPORT_ID++
val dictName = cr.queryFileName(uri) ?: return@launch
if (Dictionary.Type.fromFileName(dictName) == null) {
@ -336,11 +331,12 @@ class TableInputMethodFragment : Fragment(), OnItemChangedListener<TableBasedInp
.setPriority(NotificationCompat.PRIORITY_HIGH)
.build().let { nm.notify(importId, it) }
try {
val dictStream = cr.openInputStream(uri)!!
im.table = TableManager.replaceTableDict(im, dictName, dictStream).getOrThrow()
withContext(Dispatchers.Main) {
ui.updateItem(ui.indexItem(im), im)
val imported = withContext(Dispatchers.IO) {
val dictStream = cr.openInputStream(uri)!!
TableManager.replaceTableDict(im, dictName, dictStream).getOrThrow()
}
im.table = imported
ui.updateItem(ui.indexItem(im), im)
dustman.forceDirty()
} catch (e: Exception) {
ctx.importErrorDialog(e)

View File

@ -13,7 +13,6 @@ import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceScreen
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -52,31 +51,30 @@ class AdvancedSettingsFragment : ManagedPreferenceFragment(AppPrefs.getInstance(
val ctx = requireContext()
val cr = ctx.contentResolver
lifecycleScope.withLoadingDialog(ctx) {
withContext(NonCancellable + Dispatchers.IO) {
val name = cr.queryFileName(uri) ?: return@withContext
if (!name.endsWith(".zip")) {
ctx.importErrorDialog(R.string.exception_user_data_filename, name)
return@withContext
}
try {
// stop fcitx before overwriting files
FcitxDaemon.stopFcitx()
val name = cr.queryFileName(uri) ?: return@withLoadingDialog
if (!name.endsWith(".zip")) {
ctx.importErrorDialog(R.string.exception_user_data_filename, name)
return@withLoadingDialog
}
try {
// stop fcitx before overwriting files
FcitxDaemon.stopFcitx()
val metadata = withContext(Dispatchers.IO) {
val inputStream = cr.openInputStream(uri)!!
val metadata = UserDataManager.import(inputStream).getOrThrow()
lifecycleScope.launch(NonCancellable + Dispatchers.Main) {
delay(400L)
AppUtil.exit()
}
withContext(Dispatchers.Main) {
AppUtil.showRestartNotification(ctx)
val exportTime = formatDateTime(metadata.exportTime)
ctx.toast(getString(R.string.user_data_imported, exportTime))
}
} catch (e: Exception) {
// re-start fcitx in case importing failed
FcitxDaemon.startFcitx()
ctx.importErrorDialog(e)
UserDataManager.import(inputStream).getOrThrow()
}
AppUtil.showRestartNotification(ctx)
val exportTime = formatDateTime(metadata.exportTime)
ctx.toast(getString(R.string.user_data_imported, exportTime))
// delay exit to ensure Notification and Toast has been created
lifecycleScope.launch {
delay(400L)
AppUtil.exit()
}
} catch (e: Exception) {
// restart fcitx in case importing failed
FcitxDaemon.startFcitx()
ctx.importErrorDialog(e)
}
}
}
@ -84,17 +82,15 @@ class AdvancedSettingsFragment : ManagedPreferenceFragment(AppPrefs.getInstance(
registerForActivityResult(ActivityResultContracts.CreateDocument("application/zip")) { uri ->
if (uri == null) return@registerForActivityResult
val ctx = requireContext()
lifecycleScope.withLoadingDialog(requireContext()) {
withContext(NonCancellable + Dispatchers.IO) {
try {
lifecycleScope.withLoadingDialog(ctx) {
try {
withContext(Dispatchers.IO) {
val outputStream = ctx.contentResolver.openOutputStream(uri)!!
UserDataManager.export(outputStream, exportTimestamp).getOrThrow()
} catch (e: Exception) {
e.printStackTrace()
withContext(Dispatchers.Main) {
ctx.toast(e)
}
}
} catch (e: Exception) {
e.printStackTrace()
ctx.toast(e)
}
}
}
@ -120,11 +116,9 @@ class AdvancedSettingsFragment : ManagedPreferenceFragment(AppPrefs.getInstance(
}) else null
)
screen.addPreference(R.string.export_user_data) {
lifecycleScope.launch {
lifecycleScope.withLoadingDialog(ctx) {
viewModel.fcitx.runOnReady {
save()
}
lifecycleScope.withLoadingDialog(ctx) {
viewModel.fcitx.runOnReady {
save()
}
exportTimestamp = System.currentTimeMillis()
exportLauncher.launch("fcitx5-android_${iso8601UTCDateTime(exportTimestamp)}.zip")

View File

@ -15,7 +15,6 @@ import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.fcitx.fcitx5.android.R
@ -83,31 +82,28 @@ class ThemeListFragment : Fragment() {
val ctx = requireContext()
val cr = ctx.contentResolver
lifecycleScope.withLoadingDialog(ctx) {
withContext(NonCancellable + Dispatchers.IO) {
val name = cr.queryFileName(uri) ?: return@withContext
val ext = name.substringAfterLast('.')
if (ext != "zip") {
ctx.importErrorDialog(R.string.exception_theme_filename, ext)
return@withContext
}
try {
val name = cr.queryFileName(uri) ?: return@withLoadingDialog
val ext = name.substringAfterLast('.')
if (ext != "zip") {
ctx.importErrorDialog(R.string.exception_theme_filename, ext)
return@withLoadingDialog
}
try {
val (newCreated, theme, migrated) = withContext(Dispatchers.IO) {
val inputStream = cr.openInputStream(uri)!!
val (newCreated, theme, migrated) =
ThemeFilesManager.importTheme(inputStream).getOrThrow()
ThemeManager.refreshThemes()
withContext(Dispatchers.Main) {
if (newCreated) {
themeListAdapter.prependTheme(theme)
} else {
themeListAdapter.replaceTheme(theme)
}
if (migrated) {
ctx.toast(R.string.theme_migrated)
}
}
} catch (e: Exception) {
ctx.importErrorDialog(e)
ThemeFilesManager.importTheme(inputStream).getOrThrow()
}
ThemeManager.refreshThemes()
if (newCreated) {
themeListAdapter.prependTheme(theme)
} else {
themeListAdapter.replaceTheme(theme)
}
if (migrated) {
ctx.toast(R.string.theme_migrated)
}
} catch (e: Exception) {
ctx.importErrorDialog(e)
}
}
}
@ -118,15 +114,13 @@ class ThemeListFragment : Fragment() {
val exported = beingExported ?: return@registerForActivityResult
beingExported = null
lifecycleScope.withLoadingDialog(requireContext()) {
withContext(NonCancellable + Dispatchers.IO) {
try {
try {
withContext(Dispatchers.IO) {
val outputStream = ctx.contentResolver.openOutputStream(uri)!!
ThemeFilesManager.exportTheme(exported, outputStream).getOrThrow()
} catch (e: Exception) {
withContext(Dispatchers.Main) {
ctx.toast(e)
}
}
} catch (e: Exception) {
ctx.toast(e)
}
}
}