How to work with Scopes when using Workspaces in TornadoFX? - kotlin

I'm using the Workspace feature of TornadoFX and I'm trying to make them work with Scopes. When I'm starting up the application I need to set some things up and when it is done I start the TornadoFX application and I supply a Scope to my first view. After that I want to be able to dock other views into my Workspace in a new Scope but this doesn't work for some reason: the original View doesn't get undocked but the new View gets docked I just don't see it on the screen. What could be the problem? Here is my code to reproduce the problem:
fun main(args: Array<String>) {
Application.launch(TestApp::class.java, *args)
}
class ParentScope : Scope() {
val id = UUID.randomUUID().toString().substring(0, 4)
}
class ChildScope : Scope() {
val id = UUID.randomUUID().toString().substring(0, 4)
}
class TestApp : App(Workspace::class) {
override fun onBeforeShow(view: UIComponent) {
workspace.dock<ParentView>(ParentScope())
}
}
class ParentView : View("parent") {
override val scope = super.scope as ParentScope
override val root = hbox {
button("new child") {
action {
workspace.dock<ChildView>(ChildScope())
}
}
}
override fun onDock() {
logger.info("Docking parent (${scope.id})")
}
override fun onUndock() {
logger.info("Undocking parent (${scope.id})")
}
}
class ChildView : View("child") {
override val scope = super.scope as ChildScope
override val root = hbox {
text("In child")
}
override fun onDock() {
logger.info("Docking child (${scope.id})")
}
override fun onUndock() {
logger.info("Undocking child (${scope.id})")
}
}
The output after I click thew new child button is this:
10:56:19.415 [JavaFX Application Thread] INFO Test - Docking parent (d202)
10:56:23.967 [JavaFX Application Thread] INFO Test - Docking child (cbc5)
What I see is the same ParentView. If I click new child again the ChildView gets undocked and a new ChildView gets docked but I still only see the ParentView:
10:56:31.228 [JavaFX Application Thread] INFO Test - Undocking child (cbc5)
10:56:31.228 [JavaFX Application Thread] INFO Test - Docking child (1dd8)

The App already defines a Scope with a corresponding Workspace instance, so while your initial View is docked in the Workspace, your new ParentScope defines a new Workspace instance. When you dock the ChildView into that Workspace it does exactly that, but the Workspace in question is not shown on screen.
To remedy this you can override the primary scope of the your application like this:
class TestApp : WorkspaceApp(ParentView::class) {
init {
scope = ParentScope()
}
}
Notice also that I use the WorkspaceApp subclass so I don't need to dock the ParentView manually.
While looking at this issue I realized that if you dock a UIComponent into a Workspace, it should assume the Workspace it has been docked in. For that reason, I've committed a fix so that your initial code will work without modification.

Related

Weird function behaviour inside composable function

Whenever I tell my NavGraph to start on this composable screen:
#Composable
fun LiveTrainingScreen(viewModel: LiveTrainingViewModel = viewModel(), navController: NavController) {
viewModel.context = LocalContext.current
viewModel.scope = rememberCoroutineScope()
viewModel.navController = navController
val largeVideoPlayerHandler = viewModel.InitLargeVideoDisplay(CameraLink.cockPitRight) //exoplayer saved within this handler and refrences is made within the viewModel
val smallVideoPlayerHandler = viewModel.InitSmallVideoDisplay(CameraLink.navigationAndAltitudeDisplay) //exoplayer saved within this handler and refrences is made within the viewModel
//lots of code
}
and when I want to switch the mediaType/video that is being displayed of the Exoplayer this function will work:
class LiveTrainingViewModel(application: Application) : AndroidViewModel(application) {
fun SwitchLargeVideoDisplay(cameraLinkObject: CameraLinkObject) {
UpdateLargeVideoDisplayCameraLinkObject(cameraLinkObject)
largeVideoDisplay.videoPlayer.setMediaItem(MediaItem.fromUri(cameraLinkObject.url))
largeVideoDisplay.videoPlayer.prepare()
}
}
But whenever I load this Screen from another screen this large screen update function won't work for some reason?
Does anybody know why?

How do you add an externally instantiated object to Dagger?

I'm using JavaFX, where you create a class that extends JavaFX Application, and then you pass the class to JavaFX's launch method. Inside the Application class you override the start method which gets an instance of Stage passed into it.
How can I make this instance of Stage available as a dependency for other objects?
// Kotlin
fun main() {
launch(MyApplication::class.java)
}
class MyApplication : Application()
{
override fun start(stage: Stage)
{
// I want other objects to be able to have stage injected into them.
val myWindow = MyWindow
stage.run {
scene = Scene(myWindow, 800.0, 600.0)
show()
}
}
}
In Spring I think you would do something like this
// Java
Object externalyDefinedBean = ...;
GenericApplicationContext parentContext = new StaticApplicationContext();
parentContext.getBeanFactory().registerSingleton("injectedBean", externalyDefinedBean);

TornadoFX load multiple FXML files

I am working on a UI using Kotlin and TornadoFX where there is a "Main" Part, and depending on which button I press the "Main" Part of the View (currently an AnchorPane) should change like this:
I tried loading in 2 FXML documents and adding the Content to my Main-Anchorpane like this:
override val root: BorderPane by fxml("/views/MainView.fxml")
val contentContainer: AnchorPane by fxid("contentContainer")
val contentBtn1: AnchorPane by fxml("/views/MainViewProject.fxml")
val btnProject: JFXButton by fxid("btnProject")
init {
btnProject.setOnAction {
contentContainer += contentBtn1
}
}
but I get the following error:
javafx.fxml.LoadException: Controller value already specified.
I solved this by creating a new View for the second FXML file:
class MainProjectView : View("My View") {
override val root: HBox by fxml()
}
Then injecting the View in my MainViewClass and adding it there:
val mainProjectView: MainProjectView by inject()
init {
mainPane += mainProjectView
}

SavedState module Android Kotlin with View Model - value does not seem to save

I have followed the instructions in Google Codelab about the Saved state module.
My gradle dependency is:
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0-rc03"
My View Model factory is:
class MyViewModelFactory(val repository: AppRepository, owner: SavedStateRegistryOwner,
defaultArgs: Bundle? = null) : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
override fun <T : ViewModel?> create(
key: String,
modelClass: Class<T>,
handle: SavedStateHandle): T {
return MyViewModel(repository, handle) as T
}
}
My View model:
class MyViewModel constructor(val repository: AppRepository, private val savedStateHandle: SavedStateHandle): ViewModel() {
fun getMyParameter(): LiveData<Int?> {
return savedStateHandle.getLiveData(MY_FIELD)
}
fun setMyParameter(val: Int) {
savedStateHandle.set(MY_FIELD, val)
}
}
My Fragment:
class MyFragment : androidx.fragment.app.Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
arguments?.let {
var myField = it.getInt(MY_FIELD)
activitiesViewModel.setMySavedValue(myField ?: 0)
}
}
}
When the app is being used, the live data for the saved state field updates correctly. But as soon as I put the app in background (and I set "don't keep activities" in developer options), and then re open app, the live data shows a value as if it was never set. In other words, the saved state handle seems to forget the field I am trying to save.
Any thoughts?
I had the same problem. It was solved by the fact that I stopped requesting saved values from SavedStateHandle in the onRestoreInstanceState method. This call is correctly used in the onCreate method.

How can I perform clean up actions upon closing a view in Kotlin/TornadoFX?

I have been searching the docs and IDE autocomplete suggestions and cannot figure this out. The closest I have found is onDelete(), and it is not working the way I envision.
I just need a way to run some clean up code when a view is closed.
Here is a failed attempt using the simple example from the docs.
import tornadofx.*
class MyApp: App(MyView::class)
class MyView: View() {
// this does not print when the window is closed
override fun onDelete() {
super.onDelete()
println("Print on close!")
}
override val root = vbox {
button("Press me")
label("Waiting")
}
}
fun main(args: Array<String>) {
launch<MyApp>(args)
}
Another failed attempt per a suggestion below:
import tornadofx.*
class MyApp: App(MyView::class)
class MyView: View() {
// "Closing" is never printed when closing this view"
override fun onDock() {
currentWindow?.onHidingProperty()?.onChangeOnce {
println("Closing")
}
}
override val root = vbox {
button("Press me")
label("Waiting")
}
}
fun main(args: Array<String>) {
launch<MyApp>(args)
}
I'm using this in my project right now. setOnCloseRequest is my go to!
override fun onDock() {
currentWindow?.setOnCloseRequest {
println("Closing")
}
}
onDelete is a callback for the Workspace in TornadoFX, and will be called if you click the Delete button in the Workspace when that View is active. What you can do is override onDock and add a once change listener to the hiding property:
override fun onDock() {
currentWindow?.onHidingProperty()?.onChangeOnce {
println("Closing")
}
}
If you're looking for cleanup code when closing the application, another option that works for child views/fragments/controllers is an FXEvent that's fired on close. The event is defined like this:
object ApplicationClosingEvent : FXEvent()
In the main App, I override stop() so that the event is fired:
override fun stop() {
fire(ApplicationClosingEvent)
}
Since stop() is running on the application thread, the event is fired and handled synchronously. Finally, in each view/fragment/controller you can subscribe like so:
subscribe<ApplicationClosingEvent> {
// Clean-up code goes here
}
The primary benefit of using an event instead of a window callback is that you can have many different views subscribing to it, rather than a single window callback.