简体   繁体   中英

Kotlin CoroutineScope not working - JavaFx (with TornadoFx)

Right now, I'm having a problem using Kotlin Coroutine in JavaFx (with TornadoFx). The problem is that my CoroutineScope is not working after Kotlin init block + launch extensions with some CoroutineContext is not working too.

  1. My View abstract class.
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, start, block)

    override fun launchMainImmediate(start: CoroutineStart, block: suspend CoroutineScope.() -> Unit): Job =
        viewScope.launch(Dispatchers.JavaFx.immediate, start, block)

    override fun launchIO(start: CoroutineStart, block: suspend CoroutineScope.() -> Unit): Job =
        viewScope.launch(Dispatchers.IO, start, block)

    override fun launchUnconfined(start: CoroutineStart, block: suspend CoroutineScope.() -> Unit): Job =
        viewScope.launch(Dispatchers.Unconfined, start, 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()
    }
  1. My ViewModel abstract class
abstract class ViewModel : tornadofx.ViewModel(), CoroutineHandler, Cleanable {

    val viewModelScope: CoroutineScope =
        CoroutineScope(SupervisorJob() + Dispatchers.JavaFx.immediate)

    override fun launch(start: CoroutineStart, block: suspend CoroutineScope.() -> Unit): Job =
        viewModelScope.launch(start = start, block = block)

    override fun launchDefault(start: CoroutineStart, block: suspend CoroutineScope.() -> Unit): Job =
        viewModelScope.launch(Dispatchers.Default, start, block)

    override fun launchMain(start: CoroutineStart, block: suspend CoroutineScope.() -> Unit): Job =
        viewModelScope.launch(Dispatchers.JavaFx, start, block)

    override fun launchMainImmediate(start: CoroutineStart, block: suspend CoroutineScope.() -> Unit): Job =
        viewModelScope.launch(Dispatchers.JavaFx.immediate, start, block)

    override fun launchIO(start: CoroutineStart, block: suspend CoroutineScope.() -> Unit): Job =
        viewModelScope.launch(Dispatchers.IO, start, block)

    override fun launchUnconfined(start: CoroutineStart, block: suspend CoroutineScope.() -> Unit): Job =
        viewModelScope.launch(Dispatchers.Unconfined, start, block)

    override fun <T> Flow<T>.start() = launchIn(viewModelScope)

    override fun clean() {
        viewModelScope.cancel()
    }
}
  1. View Implementation (LoginView). Just go to the init block and controlsInitialization method.
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()
        }
    }
}
  1. ViewModel implementation (LoginViewModel). Just go to the init block and checkIsUserExist method.
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
  1. My CoroutineHandler Interface
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)
}

Actually, I was override the wrong method. The method I need to override is onUndock not onDock .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM