问题描述
现在,我在 JavaFx(使用 TornadoFx)中使用 Kotlin 协程时遇到问题。问题是我的 Coroutinescope
在 Kotlin init
块 + launch
扩展和一些 CoroutineContext
也不起作用之后不起作用。
- 我的视图抽象类。
abstract class View(
title: String? = null,icon: Node? = null
) : tornadofx.View(title,icon),CoroutineHandler,Injectable,Cleanable {
val viewScope: Coroutinescope =
Coroutinescope(SupervisorJob() + dispatchers.JavaFx.immediate)
open val fragmentContainer: Pane? get() = null
abstract val viewmodel: viewmodel
override fun launch(start: Coroutinestart,block: suspend Coroutinescope.() -> Unit): Job =
viewScope.launch(start = start,block = block)
override fun launchdefault(start: Coroutinestart,block: suspend Coroutinescope.() -> Unit): Job =
viewScope.launch(dispatchers.Default,start,block)
override fun launchMain(start: Coroutinestart,block: suspend Coroutinescope.() -> Unit): Job =
viewScope.launch(dispatchers.JavaFx,block)
override fun launchMainImmediate(start: Coroutinestart,block: suspend Coroutinescope.() -> Unit): Job =
viewScope.launch(dispatchers.JavaFx.immediate,block)
override fun launchIO(start: Coroutinestart,block: suspend Coroutinescope.() -> Unit): Job =
viewScope.launch(dispatchers.IO,block)
override fun launchUnconfined(start: Coroutinestart,block: suspend Coroutinescope.() -> Unit): Job =
viewScope.launch(dispatchers.Unconfined,block)
override fun <T> Flow<T>.start() = launchIn(viewScope)
open fun openFragment(fragment: Fragment) {
with(fragmentContainer!!.children) {
if (isNotEmpty()) clear()
add(fragment)
}
}
override fun clean() {
viewScope.cancel()
viewmodel.clean()
}
override fun onDock() {
clean()
super.onDock()
}
- 我的 viewmodel 抽象类
abstract class viewmodel : tornadofx.viewmodel(),Cleanable {
val viewmodelScope: Coroutinescope =
Coroutinescope(SupervisorJob() + dispatchers.JavaFx.immediate)
override fun launch(start: Coroutinestart,block: suspend Coroutinescope.() -> Unit): Job =
viewmodelScope.launch(start = start,block: suspend Coroutinescope.() -> Unit): Job =
viewmodelScope.launch(dispatchers.Default,block: suspend Coroutinescope.() -> Unit): Job =
viewmodelScope.launch(dispatchers.JavaFx,block: suspend Coroutinescope.() -> Unit): Job =
viewmodelScope.launch(dispatchers.JavaFx.immediate,block: suspend Coroutinescope.() -> Unit): Job =
viewmodelScope.launch(dispatchers.IO,block: suspend Coroutinescope.() -> Unit): Job =
viewmodelScope.launch(dispatchers.Unconfined,block)
override fun <T> Flow<T>.start() = launchIn(viewmodelScope)
override fun clean() {
viewmodelScope.cancel()
}
}
- 查看实现 (LoginView)。只需转到
init
块和controlsInitialization
方法。
class LoginView : View("Login") {
override val root: VBox by fxml()
private val usernameTextField: TextField by fxid()
private val passwordField: PasswordField by fxid()
private val submitButton: Button by fxid()
private val backButton: Button by fxid()
private val resultLabel: Label by fxid()
@Inject
override lateinit var viewmodel: Loginviewmodel
init {
viewScope.launch { println("LAUNCH VIEW") } //Printed
launchMain { println("MAIN VIEW") } //Not Printed
launchMainImmediate { println("MAIN IMMEDIATE VIEW") } //Printed
launchdefault {
println("DEFAULT VIEW 1") //Printed
appComponent.inject(this@LoginView)
controlsInitialization()
collectorsInitialization()
println("DEFAULT VIEW") //Not Printed
}
launchIO { println("IO VIEW") } //Printed
launchUnconfined { println("UNCONFINED VIEW") } //Printed
}
private fun collectorsInitialization() {
with(viewmodel) {
getUsername().onEach {
with(usernameTextField) {
if (it != null) {
invisible()
passwordField.visible()
submitButton.text = "Login"
backButton.visible()
resultLabel.invisible()
submitButton.enable()
} else {
showResultLabel("Username salah.")
}
enable()
}
}.start()
isSuccessLogin().onEach {
if (it) {
MainView().openWindow(owner = null)
close()
} else showResultLabel("Password salah.")
}.start()
getLoginFailedCount().onEach {
passwordField.clear()
if (it == 5 || it == 7 || it >= 9) {
val text = "Anda gagal login sebanyak $it," +
"silahkan tunggu selama "
val waitTime = WaitTime(waitTimeInMinute)
var Now = LocalTime.Now()
var lastSecond = Now.second
launchdefault {
with(waitTime) {
while (isNotOverYet) {
if (lastSecond != Now.second) {
if (second == 0) {
minute--
second = 59
} else second--
viewScope.launch {
resultLabel.text = "$text $waitTime : $second"
}
lastSecond = Now.second
}
Now = LocalTime.Now()
}
}
waitTimeInMinute += 5
submitButton.enable()
resultLabel.invisible()
}
} else {
submitButton.enable()
}
}.start()
}
}
private fun controlsInitialization() {
with(backButton) {
setonAction {
invisible()
passwordField.apply {
invisible()
clear()
}
usernameTextField.visible()
if (!resultLabel.text.contains("Anda")) resultLabel.invisible()
}
}
submitButton.setonAction {
submitButton.disable()
if (usernameTextField.isVisible) {
with(usernameTextField) {
disable()
println("$text 1") //Printed
launchIO { "$text 2" } //Not Printed
viewmodel.checkIsUserExist(text)
}
} else {
with(passwordField) {
disable()
launchIO {
viewmodel.login(text)
}
}
}
}
}
private fun showResultLabel(text: String) {
with(resultLabel) {
this.text = text
visible()
}
}
}
class Loginviewmodel @Inject constructor(
private val loginUseCase: LoginUseCase
) : viewmodel() {
private val username = MutableStateFlow<String?>(null)
private val isSuccessLogin = MutableStateFlow(false)
private val loginFailedCount = MutableStateFlow(0)
var waitTimeInMinute: Int = 5
init {
viewmodelScope.launch { println("LAUNCH VIEW MODEL") } //Not Printed
launchMain { println("MAIN VIEW MODEL") } //Not Printed
launchMainImmediate { println("MAIN IMMEDIATE VIEW MODEL") } //Not Printed
launchdefault { println("DEFAULT VIEW MODEL") } //Printed
launchIO { println("IO VIEW MODEL") } //Printed
launchUnconfined { println("UNCONFINED VIEW MODEL") } //Printed
}
fun checkIsUserExist(username: String) {
println("$username AAAA") //Printed
launchIO {
println(username) //Not Printed
val isExist = loginUseCase.checkIsUserExist(username)
println(username) //Not Printed
println("isExist: $isExist") //Not Printed
with(this@Loginviewmodel.username) {
if (isExist) {
emit(username)
} else {
emit(null)
with(loginFailedCount) { emit(value + 1) }
}
}
}
println("$username ZZZZ") //Printed
}
fun login(password: String) = launchIO {
if (username.value == null) {
isSuccessLogin.emit(false)
with(loginFailedCount) { emit(value + 1) }
} else {
val user = loginUseCase.login(username.value!!,password)
isSuccessLogin.value = if (user != null) {
CashierApplication.build(user)
true
} else {
with(loginFailedCount) { emit(value + 1) }
false
}
}
}
fun getUsername(): StateFlow<String?> = username
fun isSuccessLogin(): StateFlow<Boolean> = isSuccessLogin
fun getLoginFailedCount(): StateFlow<Int> = loginFailedCount
- 我的 CoroutineHandler 接口
interface CoroutineHandler {
fun <T> Flow<T>.start(): Job
fun launch(
start: Coroutinestart = Coroutinestart.DEFAULT,block: suspend Coroutinescope.() -> Unit
): Job
fun launchdefault(
start: Coroutinestart = Coroutinestart.DEFAULT,block: suspend Coroutinescope.() -> Unit
): Job
fun launchMain(
start: Coroutinestart = Coroutinestart.DEFAULT,block: suspend Coroutinescope.() -> Unit
): Job
fun launchMainImmediate(
start: Coroutinestart = Coroutinestart.DEFAULT,block: suspend Coroutinescope.() -> Unit
): Job
fun launchIO(
start: Coroutinestart = Coroutinestart.DEFAULT,block: suspend Coroutinescope.() -> Unit
): Job
fun launchUnconfined(
start: Coroutinestart = Coroutinestart.DEFAULT,block: suspend Coroutinescope.() -> Unit
): Job
suspend fun <T> withDefault(block: suspend Coroutinescope.() -> T): T =
withContext(dispatchers.Default,block)
suspend fun <T> withMain(block: suspend Coroutinescope.() -> T): T =
withContext(dispatchers.Main,block)
suspend fun <T> withMainImmediate(block: suspend Coroutinescope.() -> T): T =
withContext(dispatchers.Main.immediate,block)
suspend fun <T> withIO(block: suspend Coroutinescope.() -> T): T =
withContext(dispatchers.IO,block)
suspend fun <T> withUnconfined(block: suspend Coroutinescope.() -> T): T =
withContext(dispatchers.Unconfined,block)
}
解决方法
实际上,我是 override
错误的方法。我需要override
的方法是onUndock
而不是onDock
。