156 lines
5.6 KiB
Kotlin
156 lines
5.6 KiB
Kotlin
package jp.juggler.util.coroutine
|
|
|
|
import androidx.appcompat.app.AppCompatActivity
|
|
import androidx.lifecycle.lifecycleScope
|
|
import jp.juggler.util.log.LogCategory
|
|
import jp.juggler.util.log.showError
|
|
import jp.juggler.util.log.showToast
|
|
import jp.juggler.util.ui.ProgressDialogEx
|
|
import jp.juggler.util.ui.dismissSafe
|
|
import kotlinx.coroutines.CancellationException
|
|
import kotlinx.coroutines.CoroutineScope
|
|
import kotlinx.coroutines.Job
|
|
import kotlinx.coroutines.async
|
|
import kotlinx.coroutines.launch
|
|
import kotlinx.coroutines.supervisorScope
|
|
import kotlin.coroutines.CoroutineContext
|
|
import kotlin.coroutines.EmptyCoroutineContext
|
|
|
|
private val log = LogCategory("EmptyScope")
|
|
|
|
// kotlinx.coroutines 1.5.0 で GlobalScopeがdeprecated になったが、
|
|
// プロセスが生きてる間ずっと動いててほしいものや特にキャンセルのタイミングがないコルーチンでは使い続けたい
|
|
object EmptyScope : CoroutineScope {
|
|
override val coroutineContext: CoroutineContext
|
|
get() = EmptyCoroutineContext + AppDispatchers.MainImmediate
|
|
}
|
|
|
|
// メインスレッド上で動作するコルーチンを起動して、終了を待たずにリターンする。
|
|
// 起動されたアクティビティのライフサイクルに関わらず中断しない。
|
|
fun launchMain(block: suspend CoroutineScope.() -> Unit): Job =
|
|
EmptyScope.launch {
|
|
try {
|
|
block()
|
|
} catch (ex: Throwable) {
|
|
if (ex is CancellationException) {
|
|
log.w("lainchMain cancelled.")
|
|
} else {
|
|
log.e(ex, "launchMain failed.")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Default Dispatcherで動作するコルーチンを起動して、終了を待たずにリターンする。
|
|
// 起動されたアクティビティのライフサイクルに関わらず中断しない。
|
|
fun launchDefault(block: suspend CoroutineScope.() -> Unit): Job =
|
|
EmptyScope.launch(context = AppDispatchers.DEFAULT) {
|
|
try {
|
|
block()
|
|
} catch (ex: Throwable) {
|
|
log.e(ex, "launchDefault failed.")
|
|
}
|
|
}
|
|
|
|
// IOスレッド上で動作するコルーチンを起動して、終了を待たずにリターンする。
|
|
// 起動されたアクティビティのライフサイクルに関わらず中断しない。
|
|
fun launchIO(block: suspend CoroutineScope.() -> Unit): Job =
|
|
EmptyScope.launch(context = AppDispatchers.IO) {
|
|
try {
|
|
block()
|
|
} catch (ex: Throwable) {
|
|
log.e(ex, "launchIO failed.")
|
|
}
|
|
}
|
|
|
|
fun AppCompatActivity.launchAndShowError(
|
|
errorCaption: String? = null,
|
|
block: suspend CoroutineScope.() -> Unit,
|
|
): Job = lifecycleScope.launch {
|
|
try {
|
|
block()
|
|
} catch (ex: Throwable) {
|
|
when (ex) {
|
|
is CancellationException -> {
|
|
log.w(errorCaption ?: "launchAndShowError cancelled.")
|
|
}
|
|
|
|
else -> {
|
|
log.e(ex, errorCaption ?: "launchAndShowError failed.")
|
|
showError(ex, errorCaption)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
|
|
suspend fun <T:Any?> AppCompatActivity.withProgress(
|
|
caption:String,
|
|
progressInitializer: suspend (ProgressDialogEx) -> Unit = {},
|
|
block: suspend (progress :ProgressDialogEx)->T,
|
|
):T {
|
|
val activity = this
|
|
var progress: ProgressDialogEx? = null
|
|
try {
|
|
progress = ProgressDialogEx(activity)
|
|
progress.setCancelable(true)
|
|
progress.isIndeterminateEx = true
|
|
progress.setMessageEx(caption)
|
|
progressInitializer(progress)
|
|
progress.show()
|
|
return supervisorScope {
|
|
val task = async(AppDispatchers.MainImmediate) { block(progress) }
|
|
progress.setOnCancelListener { task.cancel() }
|
|
task.await()
|
|
}
|
|
} finally {
|
|
progress?.dismissSafe()
|
|
}
|
|
}
|
|
|
|
fun <T : Any?> AppCompatActivity.launchProgress(
|
|
caption: String,
|
|
doInBackground: suspend CoroutineScope.(ProgressDialogEx) -> T,
|
|
afterProc: suspend CoroutineScope.(result: T) -> Unit = {},
|
|
progressInitializer: suspend CoroutineScope.(ProgressDialogEx) -> Unit = {},
|
|
preProc: suspend CoroutineScope.() -> Unit = {},
|
|
postProc: suspend CoroutineScope.() -> Unit = {},
|
|
) {
|
|
val activity = this
|
|
EmptyScope.launch {
|
|
val progress = ProgressDialogEx(activity)
|
|
try {
|
|
progress.setCancelable(true)
|
|
progress.isIndeterminateEx = true
|
|
progress.setMessageEx("$caption…")
|
|
progressInitializer(progress)
|
|
try {
|
|
preProc()
|
|
} catch (ex: Throwable) {
|
|
log.e(ex, "launchProgress: preProc failed.")
|
|
}
|
|
val result = supervisorScope {
|
|
val task = async(AppDispatchers.IO) {
|
|
doInBackground(progress)
|
|
}
|
|
progress.setOnCancelListener { task.cancel() }
|
|
progress.show()
|
|
task.await()
|
|
}
|
|
if (result != null) afterProc(result)
|
|
} catch (ex: Throwable) {
|
|
log.e(ex, "launchProgress: $caption failed.")
|
|
if (ex !is CancellationException) {
|
|
showToast(ex, "$caption failed.")
|
|
}
|
|
} finally {
|
|
progress.dismissSafe()
|
|
try {
|
|
postProc()
|
|
} catch (ex: Throwable) {
|
|
log.e(ex, "launchProgress: postProc failed.")
|
|
}
|
|
}
|
|
}
|
|
}
|