All Projects → cuba-labs → Java Electron Tutorial

cuba-labs / Java Electron Tutorial

How to build Java Desktop applications with Electron

Programming Languages

java
68154 projects - #9 most used programming language

Projects that are alternatives of or similar to Java Electron Tutorial

Vaadin Core
An evolving set of free, open source web components for building mobile and desktop web applications in modern browsers.
Stars: ✭ 382 (+77.67%)
Mutual labels:  vaadin
Mycollab
An open source, free, high performance, stable and secure Java Application Business Platform of Project Management and Document
Stars: ✭ 1,063 (+394.42%)
Mutual labels:  vaadin
Framework
Vaadin 6, 7, 8 is a Java framework for modern Java web applications.
Stars: ✭ 1,715 (+697.67%)
Mutual labels:  vaadin
Vaadin
An evolving set of open source web components for building mobile and desktop web applications in modern browsers.
Stars: ✭ 424 (+97.21%)
Mutual labels:  vaadin
Jpetstore 6 Vaadin Spring Boot
This project is an exercise to port the original JPetStore-6 to Vaadin 8 and Spring Boot.
Stars: ✭ 38 (-82.33%)
Mutual labels:  vaadin
Vaadin Upload
The Web Component for uploading multiple files with progress indication. Part of the Vaadin components.
Stars: ✭ 87 (-59.53%)
Mutual labels:  vaadin
Flow
Flow is a Java framework binding Vaadin web components to Java. This is part of Vaadin 10+.
Stars: ✭ 296 (+37.67%)
Mutual labels:  vaadin
Vaadin Date Picker
The Web Component providing a date selection field with scrollable month calendar. Part of the Vaadin components.
Stars: ✭ 158 (-26.51%)
Mutual labels:  vaadin
Spreadsheet
Spreadsheet for Vaadin Framework
Stars: ✭ 40 (-81.4%)
Mutual labels:  vaadin
Gradle Vaadin Plugin
A Gradle plugin for building Vaadin applications
Stars: ✭ 122 (-43.26%)
Mutual labels:  vaadin
Runnable Jar Vaadin
Runnable, single-jar Vaadin application
Stars: ✭ 5 (-97.67%)
Mutual labels:  vaadin
Vaadin Form Layout
The Web Component providing configurable responsive layout for form elements. Part of the Vaadin components.
Stars: ✭ 15 (-93.02%)
Mutual labels:  vaadin
Vaadin Microservices Demo
A microservices example developed with Spring Cloud and Vaadin
Stars: ✭ 108 (-49.77%)
Mutual labels:  vaadin
Vaadin Grid
vaadin-grid is a free, high quality data grid / data table Web Component. Part of the Vaadin components.
Stars: ✭ 383 (+78.14%)
Mutual labels:  vaadin
Vaadin On Kotlin
Writing full-stack statically-typed web apps on JVM at its simplest
Stars: ✭ 141 (-34.42%)
Mutual labels:  vaadin
Electron Java App
Electron Java application - What if you could build Desktop applications on Java with HTML 5 UI
Stars: ✭ 340 (+58.14%)
Mutual labels:  vaadin
Vaadin By Example
Learn Vaadin by working example projects.
Stars: ✭ 63 (-70.7%)
Mutual labels:  vaadin
Metl
Metl is a simple, web-based integration platform that allows for several different styles of data integration including messaging, file based Extract/Transform/Load (ETL), and remote procedure invocation via Web Services. Read more at www.jumpmind.com/products/metl/overview
Stars: ✭ 185 (-13.95%)
Mutual labels:  vaadin
Spring
Spring integration for Vaadin
Stars: ✭ 141 (-34.42%)
Mutual labels:  vaadin
Vaadin Combo Box
The Web Component for displaying a list of items with filtering. Part of the Vaadin components.
Stars: ✭ 113 (-47.44%)
Mutual labels:  vaadin

Java Electron Tutorial

In this short enough tutorial I will show you how to craft your own Java Desktop toolkit on top of Electron, Jetty and Vaadin.

Getting started

We will start with Gradle build system and Node.js installation. Download and install the latest stable version of Gradle: https://gradle.org/releases/ and Node.js: https://nodejs.org/en/download/

Create a new directory and run using command line in this directory:

gradle init --type java-application

Gradle will produce a set of project stub files. Now, you can easily open the directory using Intellij Idea or Eclipse as Java project.

Let’s remove src/main/java/App.java and src/test/java/AppTest.java files and open build.gradle file.

Modify build.gradle file to match with the following:

apply plugin: 'java'

repositories {
    jcenter()
}

dependencies {
}

Vaadin UI at the speed of light

Previously, I’ve already told you about our experience with Vaadin in the blog post: https://vaadin.com/blog/cuba-studio-how-we-use-vaadin-for-our-web-development-tool

For me, it is battle-proven Java framework that enables us to build complex UI without a single line of HTML and JS. Let’s employ it as a basis for our UI.

I will create a simple Vaadin application from scratch. First, we need to add necessary dependencies to build.gradle script, enable war and gretty plugins:

plugins {
   id 'org.akhikhl.gretty' version '2.0.0'
}

apply plugin: 'java'
apply plugin: 'war'

repositories {
   jcenter()
}

dependencies {
   compile 'javax.servlet:javax.servlet-api:3.0.1'
   compile 'com.vaadin:vaadin-server:8.1.6'
   compile 'com.vaadin:vaadin-push:8.1.6'
   compile 'com.vaadin:vaadin-client-compiled:8.1.6'
   compile 'com.vaadin:vaadin-themes:8.1.6'
}

gretty {
   contextPath = '/app'
}

Refresh your Gradle project in IDE and you will be able to create UI using Vaadin.

Let’s build Hello World on Vaadin. Simply create package demo and Java class AppUI inside of it.

@Theme(ValoTheme.THEME_NAME)
public class AppUI extends UI {
   @Override
   protected void init(VaadinRequest request) {
       TextField nameField = new TextField();
       nameField.setCaption("Your name");

       Button button = new Button("Hello", event ->
               new Notification(
                   "Hello " + nameField.getValue()
               ).show(getPage())
       );

       VerticalLayout content = new VerticalLayout();
       content.addComponents(nameField, button);
       setContent(content);
   }
}

As you see, we implement UI using Java API. After that, we define servlet class demo.AppServlet:

@WebServlet(urlPatterns = "/*", name = "AppServlet")
@VaadinServletConfiguration(ui = AppUI.class, productionMode = false)
public class AppServlet extends VaadinServlet {
}

Finally, build and start the app using gradle:

gradle assemble jettyStart

Open http://localhost:8080/app in your favorite web browser. That was easy!

Vaadin UI

At the moment we have a pretty standard web application, it can be deployed to server or we can give it to Desktop users along with a servlet container (Tomcat, for instance) and make them use it from a web browser.

How to embed Jetty into Java applications

We will gradually transform our application into Desktop form. First step - get rid of WAR and external servlet container.

Modify build.gradle file:

apply plugin: 'java'
apply plugin: 'application'

repositories {
   jcenter()
}

dependencies {
   compile 'javax.servlet:javax.servlet-api:3.0.1'
   compile 'com.vaadin:vaadin-server:8.1.6'
   compile 'com.vaadin:vaadin-push:8.1.6'
   compile 'com.vaadin:vaadin-client-compiled:8.1.6'
   compile 'com.vaadin:vaadin-themes:8.1.6'

   compile 'org.eclipse.jetty:jetty-server:9.3.20.v20170531'
   compile 'org.eclipse.jetty:jetty-webapp:9.3.20.v20170531'
   compile 'org.eclipse.jetty:jetty-continuation:9.3.20.v20170531'
}

applicationName = 'demo'
mainClassName = 'demo.Launcher'

I’ve added jetty jars to the project dependencies and replaced war and gretty plugins with application plugin. The only thing left to do is to implement demo.Launcher class.

That’s quite an easy task because the process of Jetty embedding is already described in the official manual: http://www.eclipse.org/jetty/documentation/current/embedding-jetty.html

Thus, our Launcher will look as follows:

public class Launcher {
   public static void main(String[] args) {
       System.out.println("Server starting...");

       ServletContextHandler contextHandler =
               new ServletContextHandler(null, "/", true, false);
       contextHandler.setSessionHandler(new SessionHandler());
       contextHandler.addServlet(new ServletHolder(AppServlet.class), "/*");

       Server embeddedServer = new Server(8080);
       embeddedServer.setHandler(contextHandler);

       try {
           embeddedServer.start();
           embeddedServer.join();
       } catch (Exception e) {
           System.err.println("Server error:\n" + e);
       }
       System.out.println("Server stopped");
   }
}

Now, we will be able to start our application as a single executable without external web server applications:

gradle run

The application will be accessible on http://localhost:8080. Moreover, we can build it to a single ZIP archive with all the dependencies and distribute it to our users:

gradle distZip

Simple Electron application

A basic Electron app consists of three files: package.json (metadata), main.js (code) and index.html (graphical user interface). The framework is provided by the Electron executable file (electron.exe in Windows, electron.app on macOS and electron on Linux).

At this stage, we will create simple electron application without our server side using Electron quick start guide: https://github.com/electron/electron/blob/master/docs/tutorial/quick-start.md. First, create src/main/electron/package.json file:

{
 "name"    : "demo-app",
 "version" : "0.1.0",
 "main"    : "main.js"
}

We will show the stub HTML page src/main/electron/index.html:

<h1>
   Hello world!
</h1>

As it is described in Electron quick start, we will use the following JS code in src/main/electron/main.js:

const {app, BrowserWindow} = require('electron');
const path = require('path');
const url = require('url');

let win;

function createWindow() {
   win = new BrowserWindow({width: 800, height: 600});

   win.loadURL(url.format({
       pathname: path.join(__dirname, 'index.html'),
       protocol: 'file:',
       slashes: true
   }));

   win.on('closed', () => {
       win = null
   })
}

app.on('ready', createWindow);

app.on('window-all-closed', () => {
   if (process.platform !== 'darwin') {
       app.quit()
   }
});

app.on('activate', () => {
   if (win === null) {
       createWindow()
   }
});

Now, we are ready to install electron using NPM. Go to src/main/electron directory and execute the following command:

npm install electron --save-dev

NPM will download and install electron to your PC. Let’s start it!

npx electron

Electron UI

At the moment, we've got all the pieces of the puzzle sitting right there on the table. Now all we have to do is put them in the right order.

Bring all together

First, simply remove src/main/electron/index.html file. We will open our application UI right on the application start.

Then, build the application using Gradle and install it to the build/install directory:

gradle installDist

Copy build/install/demo directory into src/main/electron/demo. Add +x permission for demo/bin/demo file if you use Mac OS or Linux.

The hardest part is to start a Java process from Electron runtime and maintain the consistent state of the Java executable and a browser window. It can be done using child_process subsystem of Node.js: https://nodejs.org/api/child_process.html The startup of the application server process should look as follows:

platform = process.platform;

// Check operating system
if (platform === 'win32') {
   serverProcess = require('child_process')
       .spawn('cmd.exe', ['/c', 'demo.bat'],
           {
               cwd: app.getAppPath() + '/demo/bin'
           });
} else {
   serverProcess = require('child_process')
       .spawn(app.getAppPath() + '/demo/bin/demo');
}

let appUrl = 'http://localhost:8080';

const openWindow = function () {
   mainWindow = new BrowserWindow({
       title: 'Demo',
       width: 640,
       height: 480
   });

   mainWindow.loadURL(appUrl);

   mainWindow.on('closed', function () {
       mainWindow = null;
   });

   mainWindow.on('close', function (e) {
       if (serverProcess) {
           e.preventDefault();
           // kill Java executable
       }
   });
};

const startUp = function () {
   const requestPromise = require('minimal-request-promise');

   requestPromise.get(appUrl).then(function (response) {
       console.log('Server started!');
       openWindow();
   }, function (response) {
       console.log('Waiting for the server start...');

       setTimeout(function () {
           startUp();
       }, 200);
   });
};

startUp();

Here we use the minimal-request-promise package to check if an application has started, install it using NPM (we call npm/npx commands from src/main/electron directory):

npm install minimal-request-promise

In order to stop the Java part, we will use tree-kill package. Install it:

npm install tree-kill

In the mainWindow close callback we should kill the server process:

// kill Java executable
const kill = require('tree-kill');
kill(serverProcess.pid, 'SIGTERM', function () {
   console.log('Server process killed');

   serverProcess = null;

   mainWindow.close();
});

Finally, we can start the application:

npx electron .

Application

It is alive!

In fact, any Java application can be started using this approach, you can run your existing Spring Boot application or tomcat with a deployed WAR file. There is no limitation to use only Vaadin! Feel free to start your favorite backend!

Periphery integration

Well, it seems that this really simple example does work, but how we can employ peripheral devices or communicate with OS?

Since we have full-featured Java process, we can easily write/read local files and use all the features of OS. For instance, let’s print OS info to a local printer.

  1. First, we create a text document with OS information
Button button = new Button("Print Hello", event -> {
   Runtime runtime = Runtime.getRuntime();

   printHelloDocument(String.format(
           "Hello %s!\n" +
                   "Your PC is so powerful:\n" +
                   "%s processors\n" +
                   "%s free memory\n" +
                   "%s max memory",
           nameField.getValue(),
           runtime.availableProcessors(),
           runtime.freeMemory(),
           runtime.maxMemory()));
});
  1. In Java, we have access to the special PrintServiceLookup class that enables us to query for available printers and send documents to the queue.
PrintRequestAttributeSet aset = new HashPrintRequestAttributeSet();

DocFlavor flavor = DocFlavor.READER.TEXT_PLAIN;
Doc doc = new SimpleDoc(new StringReader(value), flavor, null);

PrintService[] services = PrintServiceLookup.lookupPrintServices(flavor, aset);
PrintService defaultService = PrintServiceLookup.lookupDefaultPrintService();
  1. We either send document to default printer or let a user to decide which one to use.
if (services.length == 0) {
   if (defaultService == null) {
       new Notification("No printer found", WARNING_MESSAGE).show(getPage());
   } else {
       DocPrintJob job = defaultService.createPrintJob();
       printDocument(doc, aset, job);
   }
} else {
   SwingUtilities.invokeLater(() -> {
       PrintService service = ServiceUI.printDialog(null, 200, 200,
               services, defaultService, flavor, aset);
       if (service != null) {
           DocPrintJob job = service.createPrintJob();
           printDocument(doc, aset, job);
       }
   });
}
  1. Finally, the document printing method will be:
private void printDocument(Doc doc, PrintRequestAttributeSet aset,
                           DocPrintJob job) {
   try {
       job.print(doc, aset);
       getUI().access(() ->
               new Notification(
                   "See the result!", HUMANIZED_MESSAGE
               ).show(getPage())
       );
   } catch (PrintException e) {
       // can be called from Swing thread
       getUI().access(() -> {
           new Notification(
               "Unable to print file, please check settings",
               WARNING_MESSAGE
           ).show(getPage());
       });
   }
}

Moreover, there are well-known APIs in Java for calling functions from native libraries, such as JNI or JNA. Thus, there are no restrictions for our application in comparison with web-only apps.

Implementation of the offline mode for this application essentially the same as for any Desktop application - cache data locally using an embedded database, e.g. HSQL, route business logic calls to local data in case of unavailable network, and voila!

The full code of the tutorial is available on GitHub: https://github.com/cuba-labs/java-electron-tutorial

Tips and Tricks

There are several ways to improve our solution.

Implement two-way communication between Electron UI code and Vaadin

Vaadin allows you to call JavaScript functions from Java and expose Java methods as JavaScript API. It is enough to implement simple communication bus between BrowserWindow and Java code. For instance, you will be able to use Electron native menus and Desktop notifications in operating systems.

Suppose, we want to use native menu File - Exit that will notify Java application before exit. We need to expose Java API:

JavaScript js = getPage().getJavaScript();
js.addFunction("appWindowExit", arguments -> onWindowExit());

From Electron we can call it using BrowserWindow object:

mainWindow.webContents.executeJavaScript("appWindowExit();");

The opposite call is also possible. Simply use JavaScipt object from Vaadin:

private void callElectronUiApi(String[] args) {
   JsonArray paramsArray = Json.createArray();
   int i = 0;
   for (String arg : args) {
       paramsArray.set(i, Json.create(arg));
       i++;
   }
   getPage().getJavaScript().execute(
     "callElectronUiApi(" + paramsArray.toJson() + ")"
   );
}

Use WebSocket for UI to speed up communication and strip useless HTTP headers.

Each time our application handles a user event, it sends and receives HTTP headers. They are almost useless in our application. Besides, it opens/closes HTTP connection between UI and Java part. We can speed up the communication between browser part and Java UI using WebSocket protocol.

Add org.eclipse.jetty.websocket:websocket-server dependency to build.gradle:

compile 'org.eclipse.jetty.websocket:websocket-server:9.3.20.v20170531'

Enable WebSocket for Vaadin application using @Push annotation on AppUI class:

@Push(transport = Transport.WEBSOCKET)
@Theme(ValoTheme.THEME_NAME)
public class AppUI extends UI {

Remember to enable asynchronous support for AppServlet:

@WebServlet(urlPatterns = "/*", name = "AppServlet", asyncSupported = true)

Thanks to Vaadin, that is really easy!

Unpack all the static files (CSS / images / fonts) and serve them directly from a file system instead of sending them via HTTP

Our application still sends all the static resources through Java servlets using network layer. We can make Electron read them from a file system directly!

As it is described here: https://github.com/electron/electron/blob/master/docs/api/protocol.md We can register custom protocol handler that will intercept requests to /VAADIN/ static files and read them from disk. Remember to unpack static files from jars on build stage!

See full example in:

Use Gradle Node.JS plugin com.moowork.node instead of manual Node installation

It is much easier to manage Node.js from build script than maintaining separate installation of it on developer machines. See example on: https://github.com/jreznot/electron-java-app/blob/master/build.gradle

Real-life application

Studio UI

Take a look at the CUBA Studio - Development tool based on Java, Vaadin and Electron: https://www.cuba-platform.com/discuss/t/platform-cuba-studio-se-a-desktop-application-based-on-electron/2914

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].