Posts‎ > ‎

Say no to Electron! Using JavaFX to write a fast, responsive desktop application.

posted Oct 3, 2017, 6:11 AM by Renato Athaydes   [ updated Oct 21, 2017, 7:42 AM ]
        This article has been translated to Russian!

Lately, there has been a lot of discussions (see here, here and here for a few examples - and today this one) in programming forums about Electron and its impact on the desktop app world.

If you don't know Electron, it's basically a web browser (Chromium) that hosts only your web application... as if it were a desktop application (no, it's not a joke)... that lets you use the web stack to develop cross-platform desktop applications.

Most new, hipster desktop apps these days are  built on Electron, including Slack, VS Code, Atom and GitHub DesktopThis is an extraordinary development.

We've been writing desktop apps for decades. The web, on the other hand, only really got started less than 20 years ago, and most of that time it was only used for serving documents and animated gifs, not creating full-fledged applications, or even simple ones!

To think that the web stack would be used to create desktop applications 10 years ago would be unthinkable. But here we are, in 2017, and a lot of intelligent people think that Electron is a great idea!

This is not as much a result of the superiority of the web stack for building applications (far from that, I don't think anyone disagrees that the web is a mess), as a failure of the current desktop UI frameworks. If people are preferring to ship a full web browser with their apps just so they can use great tools such as JavaScript (sarcasm) to build them, something must have gone terribly wrong.

So, what are these terrible alternatives that are losing out badly to the web stack?

I decided to have a look and build myself a real application with one of them.

Electron alternatives


If you don't mind having different teams developing for each popular OS, the options seem to be, currently, AppKit on MacOS, WPF on Windows (I am not a specialist in platform-specific development, so please let me know if other options are more popular nowadays).

However, the real competitors to Electron are the multi-platform frameworks. I believe the most common multi-platform frameworks today are GTK+Qt and JavaFX.

Gtk+


GTK+ is written in C but has bindings to many other languages as well. It was used to develop the beautiful GNOME-3 platform.

Qt


Qt seems to be the most often suggested alternative to Electron in the discussions I've seen... It's a C++ library but also has bindings in other languages (though it seems that none of them are supported commercially and it's hard to say how complete they are). It looks like a popular choice for embedded systems.

More alternatives!

UPDATE (2017-Oct-21): Thanks to comments from people on Reddit, I decided to add a link to a few more alternatives here:


Embeddable HTML/CSS/script engine for modern UI development.


Open source Python library for rapid development of applications
that make use of innovative user interfaces, such as multi-touch apps.


NW.js (previously known as node-webkit) lets you call all Node.js modules directly from DOM and enables a new way of writing applications with all Web technologies.


Lazarus is a Delphi compatible cross-platform IDE for Rapid Application Development. It has variety of components ready for use and a graphical form designer to easily create complex graphical user interfaces.


JavaFX


In this post, however, I will focus on development of a desktop application using JavaFX because I believe JavaFX and the JVM are great for developing desktop applications.

Whatever you think about the JVM, no other platform (except perhaps Electron itself!) is as easy to use for development on multiple platforms. Once you've created your jar, in any platform, you can distribute it to any user on any OS and it will just work.

With the large variety of languages that currently run on the JVM, language shouldn't be an issue: there will definitely be one that you like (even including JavaScript if you just can't help it), and you can use JavaFX with any JVM language without big issues. In this blog post, besides Java, I will show a little bit of Kotlin code as well.

The UI itself can be built with just code (in which case you have wonderful IDE support from IntelliJ, Eclipse or NetBeans, all excellent, free IDEs that probably beat any of the competitors and, by the way, probably the best examples of Java desktop applications), or using a UI visual builder: SceneBuilder (which can be integrated into IntelliJ) or NetBeans' Visual Debugger.


JavaFX history

JavaFX is not a new technology. It was first released in December 2008 and looked quite different from what it looks like today. The idea was to create a modern UI framework to replace the ageing Swing Framework which had been the official framework in the JVM since the late 90's.

Oracle almost messed up its future from the beginning by creating a special, declarative language that was supposed to be used to create application UIs. That didn't go down well with Java developers and it almost killed JavaFX.

Noticing that, Oracle decided to release JavaFX 2 in 2011 without the special language, but using instead FXML as an option to pure Java code (as we'll see below).

Around 2012, JavaFX gained some traction and Oracle spent a substantial effort trying to improve it and make it become more popular. With version 2.2, JavaFX became a fairly complete platform, but it still was not included in the standard Java runtime (though it was always shipped with the JDK).

Only with JavaFX 8 (the version change was just to match Java 8) did it become part of the standard Java runtime.

Today, JavaFX may not be a very large player in the UI world but it still has a good number of real-world applications, a quite a large number of related libraries, and has even been ported to mobile.


Creating a JavaFX application


For my application, a log viewer I called, un-inspiringly, LogFX, I chose to just use Java (because it's mostly pretty low level code and I wanted to focus on speed and small package size) and IntelliJ for an IDE. I almost went with Kotlin, but IntelliJ's Java support is just so great that having to write Java (or let IntelliJ do it for me would be a better description) was not an issue large enough to justify adding an extra 0.9MB to my packaged application.

I chose not to use FXML (the XML view-descriptor language) or a UI visual builder as the UI was really simple.

So, let's get started by looking at some code!

Java Hello World


A JavaFX application is just a class that extends javafx.application.Application and shows a JavaFX Stage.

Here's a JavaFX Hello World:


public class JavaFxExample extends Application {

@Override
public void start(Stage primaryStage) throws Exception {
Text helloWorld = new Text("Hello world");
StackPane root = new StackPane(helloWorld);
primaryStage.setScene(new Scene(root, 300, 120));
primaryStage.centerOnScreen();
primaryStage.show();
}

public static void main(String[] args) {
launch(JavaFxExample.class, args);
}
}
 src/main/java/main/JavaFxExample.java


On Mac, this will show something like this:


JavaFX Hello World


FXML+Java Hello World


If you have a hard time writing code for UIs and prefer using a markup language, here's the equivalent code + FXML:


 <?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.Scene?>
<?import javafx.scene.text.Text?>
<Scene xmlns="http://javafx.com/javafx"
width="300.0" height="120.0">
<StackPane>
<Text>Hello world</Text>
</StackPane>
</Scene>
 src/main/resources/main/Example.fxml


public class JavaFxExample extends Application {

@Override
public void start(Stage primaryStage) throws Exception {
Scene scene = FXMLLoader.load(getClass().getResource("Example.fxml"));
primaryStage.setScene(scene);
primaryStage.centerOnScreen();
primaryStage.show();
}

public static void main(String[] args) {
launch(JavaFxExample.class, args);
}
}
 src/main/java/main/JavaFxExample.java


Notice that IntelliJ has support for FXML and will link its contents to the relevant Java code and vice-versa, highlight errors, auto-complete, manage imports, show documentation inline and so on, which is pretty cool... but as I said before, I decided to not use FXML as the UI I had in mind was very simple and quite dynamic.... so I will not show any more FXML. Have a look at the FXML tutorial if you're interested in it.

Kotlin+TornadoFX Hello World


Before we move on, let's see what this would look like with a modern language like Kotlin and its own library for writing JavaFX applications, TornadoFX:


class Main : App() {
override val primaryView = HelloWorld::class
}

class HelloWorld : View() {
override val root = stackpane {
prefWidth = 300.0
prefHeight = 120.0
text("Hello world")
}
}
src/main/kotlin/main/app.kt


Using Kotlin and JavaFX may be attractive to many teams, specially if you like type-safety (TornadoFX has a nice feature called type-safe stylesheets) and if adding up an extra 5MB to your application is not a big concern.

Even if the overhead is too much and you want to avoid including a framework (and having to deal with all its complexity and quirks), Kotlin works just fine with pure JavaFX as well.


Styling and theming JavaFX user interfaces



Now that we've seen how to get started with JavaFX, let's see how JavaFX applications can be styled.

Just as there are different layout approaches, there are different ways to style things in JavaFX.

Let's say we want to make the background dark and the text white as shown below:


Styled JavaFX Hello World


Programmatic and inline styles


One (painful, though type-safe) way is to do it programmatically:


root.setBackground(new Background(new BackgroundFill(
Color.DARKSLATEGRAY, CornerRadii.EMPTY, Insets.EMPTY)));

helloWorld.setStroke(Color.WHITE);


An easier way to do it programmatically is by setting the styles as in CSS:


root.setStyle("-fx-background-color: darkslategray");
helloWorld.setStyle("-fx-stroke: white");


Notice that IntelliJ, again, provides auto-complete support for the String values above.

If you're using FXML:


 <StackPane style="-fx-background-color: darkslategray">
<Text style="-fx-stroke: white">Hello world</Text>
</StackPane>

Same deal...

Using separate stylesheets


If you prefer to stay close to the web world and set styles using a separate stylesheet, JavaFX also allows that! That's the approach I decided to use because that allows me to style everything in a central place and even let users provide a custom stylesheet to change all styles to their own preferences.

To do that, first create a stylesheet:


.root {
-fx-base: darkslategray;
}

Text {
-fx-stroke: white;
}

src/main/resources/css/hello.css


Now, add the stylesheet to the Scene:


primaryStage.getScene().getStylesheets().add("css/hello.css");


And that's it.

Notice that the stylesheet sets not only the StackPane's background color to darkslategray, it changes the base color of the theme.

That means that all controls and "background" items will take a color that's based on this color. That's a pretty neat feature as you can set colors based on the base color, ensuring that if you ever change the base color, most things will look good automatically.

For example, a better stylesheet for our case would not set the text stroke to white, but to an "opposite" color to the theme's base color, so that the text is always readable:


-fx-stroke: ladder(-fx-base, white 49%, black 50%);


JavaFX stylesheets are pretty smart, check the CSS Reference Guide for more information.

Here's an example of a simple application where we replace the Text with a Button, with the default styles on the left, and with the stylesheet we just used above on the right:


JavaFX Button with and without styling
On the left: default JavaFX styles. On the right: using the custom stylesheet introduced above.


In my application, I wanted to have a dark theme by default, but also allow users to provide their own stylesheets so they could use whatever theme they like best.

This is what LogFX ended up looking like with the default theme:


LogFX default theme


Notice that I used FontAwesome's icons in the buttons. It was fairly simple to get the buttons styled with css. Just make sure to install the font first as early as possible with this instruction:


Font.loadFont( LogFX.class.getResource( "/fonts/fontawesome-webfont.ttf" ).toExternalForm(), 12 );


With a custom stylesheet, the look-and-feel can be completely changed. For example, here's an overly green theme in Linux Mint:



LogFX styled green in Linux


Although the good taste of doing this may be questionable, it shows that JavaFX allows for powerful styling, you can achieve pretty much anything your imagination comes up with.

To finalise, I would like to mention the cool effects JavaFX provides... I wanted to make a start screen that looked good with just a nicely formatted text.

JavaFX makes that easy. This is what I came up with (I based this on the GroovyFX example):


LogFX logo


And here's the stylesheet I used to achieve that:


Text {
-fx-fill: white;
}

#logfx-text-log {
-fx-font-family: sans-serif;
-fx-font-weight: 700;
-fx-font-size: 70;
-fx-fill: linear-gradient(to top, cyan, dodgerblue);
}

#logfx-text-fx {
-fx-font-family: sans-serif;
-fx-font-weight: 700;
-fx-font-size: 86;
-fx-fill: linear-gradient(to top, cyan, dodgerblue);
-fx-effect: dropshadow(gaussian, dodgerblue, 15, 0.25, 5, 5);
}


Very nice effects are available. See the tutorial for more details.

In the next sections I will discuss how to change your views, hot reload code and refresh stylesheets all at runtime.


Designing, debugging and hot code reloading


To write user interfaces without being able to instantly see what your changes do is next to impossible. For that reason, hot code reloading or some sort of UI builder is essential in any UI framework.

JavaFX (and the JVM itself) has a few solutions to this problem.

SceneBuilder


The first one is the SceneBuilder, a visual UI builder that lets you create FXML by drag-and-dropping UI components.


SceneBuilder screenshot

It can be integrated into all Java IDEs, making it easy to create new views.

I've used SceneBuilder before to create forms and similar complex views, but I usually just use it to sketch something quickly, then start editing the code by hand to polish it off.

If you do that and later re-open the view in SceneBuilder, it still works fine, so you can alternate how you change the view between manually coding it and designing it with SceneBuilder.

ScenicView


Once you've got the basic design ready, you can use ScenicView to see and edit your scene-graph while the application is running.

You can think of it as the equivalent of the browser's developer view.


ScenicView screenshot


To start it with your application, just download the jar and pass the option -javaagent:/path-to/scenicView.jar to the JVM.

ScenicView lets you change and remove Nodes, track events, and read Javadocs for the selected nodes.


JVM hot code reload


If you want to change the application code that is not directly related to the UI, you can use the Java debugger to hotswap code as your application is running. Basic support for code reloading exists in the Oracle's JVM, HotSpot, and I believe also in the OpenJDK JVM.

However, the built-in code reloading is pretty basic: you can only change the implementation of existing methods.

But there is a HotSpot VM extension called DCEVM (Dynamic Code Evolution VM) that allows you to do a lot more: add/remove methods and fields, add/remove classes, change the value of final variables and more. I wrote about this and other ways to reload code in a running JVM in another post.

I used this to develop LogFX and it worked great. Views that you don't close and re-open are not automatically changed when you reload code, but that's not a problem if you change something that goes into a Stage that can be closed and re-opened... besides, if you want to change the UI component only, you can use the ScenicView or just go back to ScenicBuilder and re-design it the way you want.  

To try DCEVM, you just need to install it and make sure you get the correct version for the version of the JVM you're using. After that, you can run your application with a debugger attached to it and every time you recompile it in the IDE, the code will be automatically reloaded into the running application.

In IntelliJ, you will see something like this when you change a class and re-compile (Cmd+F9 on Mac):


IntelliJ code reload



Refreshing stylesheets



JavaFX does not automatically refresh stylesheets. In LogFX, I wanted to make it possible to change the stylesheets and see immediately the effect of doing so in the application.

As LogFX is a log viewer, it has a pretty advanced FileChangeWatcher that I could use to watch stylesheets and reload them quite easily.

But this only works if the stylesheets come from a file, not from within the application jar itself.

As I already allowed users to specify a custom spreadsheet file to use, that was not a problem for me.

I used this feature during development, and it was really awesome. If you need this feature, you can implement your own file watcher or copy mine (it's open source after all).

To specify a stylesheet as a file (as opposed to a jar resource), you must use different syntax in Unix/Mac and Windows, unfortunately! I used this method to fix that problem:


private static String toAbsoluteFileUri( File file ) {
String absolutePath = file.getAbsolutePath();
if ( File.separatorChar == '\\' ) {
// windows stuff
return "file:///" + absolutePath.replace( "\\", "/" );
} else {
return "file:" + absolutePath;
}
}


This works in Mac, Windows and Linux Mint. But this was one of the only two problems I had related to differences in OS's (the other one was the icon in the system tray on Mac does not work, but there is an ugly workaround for that). JavaFX abstracts that away pretty well, most of the time!

Finally, when you detect a change in a stylesheet file, you can refresh it simply by removing it, then immediately adding it back:


Runnable resetStylesheet = () -> Platform.runLater( () -> {
scene.getStylesheets().clear();
scene.getStylesheets().add( stylesheet );
} );


This works pretty well. But if you don't want to build it yourself, ScenicView can also watch stylesheet in external files (but not inside jars), and TornadoFX also supports that, so you have a few choices there.


Conclusion


Writing an application in JavaFX was a pretty nice experience. I had a brief experience writing a JavaFX application professionally several years ago (when JavaFX was still in its early days, which is not the case anymore), so I certainly had a good head start... but I've also worked as a web developer and I find it hard to believe that anyone would prefer the webstack to working with a sane environment like the JVM.

The application I wrote, LogFX, works very well in my opinion, and it achieves the goals of being very fast, responsive and at the same time look good on all operating systems without changes... please try it yourself and let me know what you think:


curl -sSfL https://jcenter.bintray.com/com/athaydes/logfx/logfx/0.7.0/logfx-0.7.0-all.jar -o logfx.jar


Even though it is a fully functional app, the jar weighs in at just 303 KB. That's 0.3MB, and it includes a couple of pictures, the fontawesome TTF file and some HTML and CSS besides the Java class files!!

Of course, that does not include the JVM itself, but the JVM is not part of the application and can be shared between many apps! With Java 9, you can create native executables with just the JVM parts you need, though, so if your users would not be satisfied with just the jar, you can package it as a native app, as I showed in a previous blog post (a small native JVM app seems to take up around 35MB, or 21MB after stripping it).

To run LogFX you need around 50MB of RAM (not for LogFX itself, nearly all of that is used up by JavaFX itself). You can verify that by starting it with this command:


java -Xmx50m -jar logfx.jar


That's a world of difference from an Electron app, which typically needs 200MB just to open.

JavaFX is not perfect and there's many areas where some improvements are still needed. Distribution and auto-update is one of them. The current solution, JNLP and Java WebStart, seems to be poorly implemented, though there are community alternatives such as Getdown and FxLauncher, or if you want a proper native installer, the commercial solution is Install4J (notice that Install4J has free licenses for open source projects).

There is a lot of things I did not have time to mention in this already rather long blog post about JavaFX that I think you should check out if you're interested:


 
Comments