Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hot reload of Views #96

Closed
edvin opened this issue Apr 26, 2016 · 10 comments
Closed

Hot reload of Views #96

edvin opened this issue Apr 26, 2016 · 10 comments

Comments

@edvin
Copy link
Owner

edvin commented Apr 26, 2016

I was asked if TornadoFX could support hot reloading of Views in the same way we now support hot reloading of Stylesheets. I created the feature/hot-reload branched and played with it, and it is indeed possible.

To make this work satisfactory, I introduced a method called load in UIComponent which will be called every time a View is created. The difference compared to putting code in the init block, is that code inside load will also run when the View is reloaded. This makes it possible to support reloading of builder based views as well. It is totally optional to use the load method, but reloading will only work if you build your View inside there.

A small caveat for code based views is that the root node should only be instantiated in the declaration, you should not put any child nodes or otherwise manipulate the root node in init, because those changes will be lost on reload.

This is OK:

class LoginScreen : View() {
  override val root: VBox()

  override fun load() {
    root.label("Hello world")
  }
}

This however, would result in a missing Label after reload:

class LoginScreen : View() {
   override val root: VBox(Label("Hello world"))
}

This will have the same problem:

class LoginScreen : View() {
   override val root: VBox()

   init {
      root.label("Hello world")
   }
}

The reason for this is that the VBox() will be re-instantiated on reload, before the load method is run.

I think the tradeoff is worth it to support this feature, even though it might be possible to shoot yourself in the foot. In any case, reloading should just be enabled in development mode, so possibly doing something like this in the App init:

class LoginApp : App() {
    override val primaryView = LoginScreen::class

    override fun start(stage: Stage) {
        if (parameters.named["hotreload"] == "true") {
            reloadViewsOnFocus()
            reloadStylesheetsOnFocus()
        }
        super.start(stage)
    }
}

Only do hot reloading when --hotreload=true is passed as a program argument.

Any thoughts?

@edvin
Copy link
Owner Author

edvin commented Apr 26, 2016

Videos showing the hot reload feature:

FXML Version: https://www.youtube.com/watch?v=OCVb1QxZctM
Builder Version: https://www.youtube.com/watch?v=HbPngSnvaWQ

@voddan
Copy link

voddan commented Apr 26, 2016

If I understand correctly, the current implementation cleans root to the "initial" state and then runs load. This creates a problem that all side effects from init disappear.

Consider this life cycle: call init, clone root somewhere, run load on the clone and return it to the user. When reloading clone the initial instance again and run load again. That way we preserve the state outside of root, and all side effects before load are there too.

A problem might be that user stored a ref to root somewhere (it is public). A possible solution would be to use a backing field for root and to the conning stuff directly with it.

@edvin
Copy link
Owner Author

edvin commented Apr 26, 2016

That could work, but there is no existing/easy way to clone a node AFAIK. I think a small set of best practices when using the reload function is needed.

An alternative would be a function that returns the initial view state. This method could be called when the View is created, and called again when the view is reloaded. What do you think about that?

@voddan
Copy link

voddan commented Apr 26, 2016

I think the primary objective here is to have one way of writing views, not two. Because the most likely scenario is when a developer tries out TornadoFX , creates a half of an app using init and staff, and then wants to use reloading, but can't.

Also I really liked constructing the nodes in the primary constructor, would be cool to keep that. But that is subjective

@edvin
Copy link
Owner Author

edvin commented Apr 26, 2016

Then I think @mikehearn is on to something with his proposal to simply reload the whole view. State would be lost, but this is a development feature after all. Maybe that's OK?

Then there would be no surprises, apart from the lost state of course.

edvin pushed a commit that referenced this issue Apr 26, 2016
@edvin
Copy link
Owner Author

edvin commented Apr 26, 2016

I've changed the implementation to reload the actual views and commited to master now. There is no need for the load method after all.

@edvin edvin closed this as completed Apr 26, 2016
@edvin
Copy link
Owner Author

edvin commented Apr 26, 2016

For completeness: State can now be transfered between the obsolete and the replaced View using the pack and unpack functions.

@pdichone
Copy link

pdichone commented Jan 7, 2020

So, in 2020 where do we stand on hot reload? Is it a go? If so, how do I set it up painlessly in Intellij?
Thanks, guys.

@edvin
Copy link
Owner Author

edvin commented Jan 7, 2020

I'm happy to report that hot reload in 2020 works exactly as it did in 2019. Simply check the "Live Views" checkbox in the TornadoFX Run Configuration, or start your application with --live-views on the command line. Make sure you run in debug mode. Once you've made a change, hit Ctrl+F9 to recompile and wait for IntelliJ to compile/copy the compiled files to your classpath. Once that has happened, you can refocus the app and the view will reload.

@pdichone
Copy link

pdichone commented Jan 7, 2020

Great! Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants