1. Gluon Connect Overview

Gluon Connect is a client-side library that simplifies binding your data from any source and format to your JavaFX UI controls. It works by retrieving data from a DataSource and converting that data from a specific format with a Converter into JavaFX observable lists and observable objects that can be used directly in JavaFX UI controls. Gluon Connect is designed to allow developers to easily add support for custom data sources and data formats.

By default, Gluon Connect already provides easy-to-use implementations for the most commonly used data sources:

  • File provider

  • REST provider

2. File provider

The File provider enables reading from and writing to a file that is located on the file system of the device. You can find the complete sample from our gluon-samples repository on GitHub: https://github.com/gluonhq/gluon-samples. Inside the repository, look for the folder named gluon-connect-file-provider.

2.1. Retrieving a list

Let’s assume that we have a file on the local file system with the following JSON content:

languages.json
[
  {"name":"Java","ratings":20.956},
  {"name":"C","ratings":13.223},
  {"name":"C++","ratings":6.698},
  {"name":"C#","ratings":4.481},
  {"name":"Python","ratings":3.789}
]

And the following POJO that will map the JSON objects from the file above to a Java object:

Language.java
public class Language {
    private String name;
    private double ratings;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getRatings() {
        return ratings;
    }

    public void setRatings(double ratings) {
        this.ratings = ratings;
    }
}

We can then assign a list of programming language objects to a JavaFX ListView control with the following code:

// create a FileClient to the specified File
FileClient fileClient = FileClient.create(new File("languages.json"));

// create a JSON converter that converts the nodes from a JSON array into language objects
InputStreamIterableInputConverter<Language> converter = new JsonIterableInputConverter<>(Language.class);

// retrieve a list from a ListDataReader created from the FileClient
GluonObservableList<Language> languages = DataProvider.retrieveList(fileClient.createListDataReader(converter));

2.2. Retrieving an object

Retrieving a single object from a file resource looks similar to retrieving a list. Assume that we have a user.json file with the following JSON content:

user.json
{"name":"Duke","subscribed":true}

And the following POJO for mapping the JSON object to a Java object:

User.java
public class User {
    private String name;
    private boolean subscribed;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isSubscribed() {
        return subscribed;
    }

    public void setSubscribed(boolean subscribed) {
        this.subscribed = subscribed;
    }
}

The user object can then be retrieved from the JSON file with the following code:

// create a FileClient to the specified File
FileClient fileClient = FileClient.create(new File("user.json"));

// create a JSON converter that converts a JSON object into a user object
InputStreamInputConverter<User> converter = new JsonInputConverter<>(User.class);

// retrieve an object from an ObjectDataReader created from the FileClient
GluonObservableObject<User> user = DataProvider.retrieveObject(fileClient.createObjectDataReader(converter));

2.3. Storing an object

We can use the same file and POJO again to show you how to store an object into a file:

// create an instance of a User to store
User user = new User();
user.setName("Duchess");
user.setSubscribed(false);

// create a FileClient to the specified File
FileClient fileClient = FileClient.create(new File("user.json"));

// create a JSON converter that converts the user object into a JSON object
OutputStreamOutputConverter<User> outputConverter = new JsonOutputConverter<>(User.class);

// store an object with an ObjectDataWriter created from the FileClient
GluonObservableObject<User> gluonUser = DataProvider.storeObject(user, fileClient.createObjectDataWriter(converter));

3. REST provider

The REST provider enables reading from and writing to an HTTP URL resource. For demonstrating this we will make use of the public StackExchange API. You can find the complete sample from our gluon-samples repository on GitHub: https://github.com/gluonhq/gluon-samples. Inside the repository, look for the folder named gluon-connect-rest-provider.

3.1. Retrieving a list

The URL we are going to use for retrieving a list, is the error REST endpoint. Here is how you would retrieve a list from a URL with Gluon Connect:

// create a RestClient to the specific URL
RestClient restClient = RestClient.create()
        .method("GET")
        .host("https://api.stackexchange.com")
        .path("/2.2/errors");

// retrieve a list from the DataProvider
GluonObservableList<Error> errors = DataProvider.retrieveList(restClient.buildListDataReader(Error.class));

// create a JavaFX ListView and populate it with the retrieved list
ListView<Error> lvErrors = new ListView<>(errors);

As you can see, we didn’t specify any Converter. That is because the RestClient will try to find a suitable Converter based on the Content-Type response header. If the response header is not specified, it assumes that the response will be formatted in JSON. Sometimes, the automatic detection of the Converter fails or you need to use a custom Converter. In that case, you use the second method for creating a ListDataReader where you provide the converter that you want to use.

// create a RestClient to the specific URL
RestClient restClient = RestClient.create()
        .method("GET")
        .host("https://api.stackexchange.com")
        .path("/2.2/errors");

// create a custom converter
InputStreamIterableInputConverter<Error> converter = new ItemsIterableInputConverter<>(Error.class);

// retrieve a list from the DataProvider using the custom converter
GluonObservableList<Error> errors = DataProvider.retrieveList(restClient.createListDataReader(converter));

3.2. Retrieving an object

For retrieving a single object, we will use the questions REST endpoint.

// create a RestClient to the specific URL
RestClient restClient = RestClient.create()
        .method("GET")
        .host("https://api.stackexchange.com")
        .path("/2.2/questions/36243147")
        .queryParam("order", "desc")
        .queryParam("sort", "activity")
        .queryParam("site", "stackoverflow");

// retrieve an object from the DataProvider
GluonObservableObject<Question> question = DataProvider.retrieveObject(restClient.createObjectDataReader(Question.class));

// show information on the retrieved object
Label lbTitle = new Label();
question.initializedProperty().addListener((obs, ov, nv) -> {
    if (nv && question.get() != null) {
        lbTitle.textProperty().bind(question.get().titleProperty());
    }
});

4. Deep Dive

In the following section we will dive deeper into the concepts of Gluon Connect and what the relation is between these concepts. The two implementations that we talked about above also make use of the same concepts, but they hide them for the user in the internal implementation. If you want to know a bit more about the inner workings of these providers or if you are interested in adding support for a custom DataSource, you should continue reading the following sections.

4.1. Concepts

Gluon Connect consists of the following basic concepts:

  • DataSource: specifies where the data is read from and/or written to

  • Converter: converts the read data into objects and vice versa converts objects into data to be written

  • DataProvider: takes care of mapping the data into usable observable lists and objects

4.1.1. DataSource

The DataSource defines where Gluon Connect should read and/or store the data. The DataSource makes use of the standard java.io.InputStream and java.io.OutputStream classes. It is split up into two parts: the InputDataSource that provides an InputStream to read the data from; the OutputDataSource that provides an OutputStream to write the data into. For convenience purposes, there is also an IODataSource that has access to both the InputStream and the OutputStream.

Gluon Connect includes by default two DataSource implementations:

  • FileDataSource: reads and writes data from a File that is accessible from the local system

  • RestDataSource: reads and writes data from an HTTP URL resource

4.1.2. Converter

The Converter is able to convert data from an Object into a specific data format or convert data from a certain data format back into an Object. The Converter is split up into three different parts:

All three interfaces do not specify where the data is coming from. This allows for maximum extensibility. Gluon Connect provides an abstract implementation for these Converters. The InputConverter and IterableInputConverter both have an implementation where the data to convert is taken from an InputStream. The OutputConverter has an analogous implementation that converts the object by writing into an OutputStream.

Gluon Connect provides Converter implementations for JSON and String out of the box.

4.1.3. DataProvider

The DataProvider is the class that will ultimately provide the observable lists or objects that you can use with your JavaFX UI controls. Gluon Connect provides a custom observable list and observable object called GluonObservableList and GluonObservableObject respectively.

The DataProvider itself has four methods:

All these methods will return the GluonObservableList or GluonObservableObject instances immediately. The actual process of retrieving, storing or removing the list or object happens asynchronously in a background thread. For example, when retrieving a list or object you can listen for the initialized property to know when the list or object is fully initialized by the provided reader.

If we for instance look at the retrieveObject method, we see that it has an ObjectDataReader as parameter. The ObjectDataReader is an interface that is able to create a new instance of GluonObservableObject and read an object of a certain type. The source where the object is read from and the format of the data is completely left open to the actual implementing classes. Usually though, you will combine a DataSource with a Converter to implement this functionality. The most basic DataProvider that you can use as an ObjectDataReader is the InputStreamObjectDataReader. This class takes an InputSource as the DataSource and an InputStreamConverter as the Converter. When an instance of InputStreamObjectDataReader is passed into the Dataprovider.retrieveObject method, the object will be retrieved by getting the InputStream from the InputSource and then pass the InputStream to the InputStreamConverter, which will read the data from the InputStream and convert it into the desired object.

4.2. Sample

The following sample will use the three concepts by providing a DataSource that reads from a classpath resource, a Converter that converts JSON into objects and a DataProvider that combines the DataSource with the Converter to get access to GluonObservable objects. The complete sample for this can be found in our gluon-samples repository on GitHub: https://github.com/gluonhq/gluon-samples. Inside the repository, look for the folder named gluon-connect-basic-usage.

4.2.1. Retrieving a list

Let’s presume we have the following simple languages.json file that is accessible from the root of the classpath.

/languages.json
[
  {"name":"Java","ratings":20.956},
  {"name":"C","ratings":13.223},
  {"name":"C++","ratings":6.698},
  {"name":"C#","ratings":4.481},
  {"name":"Python","ratings":3.789}
]

And the following POJO that will map the JSON objects from the list above to a Java object:

Language.java
public class Language {
    private String name;
    private double ratings;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getRatings() {
        return ratings;
    }

    public void setRatings(double ratings) {
        this.ratings = ratings;
    }
}

We can then assign a list of programming language objects to a JavaFX ListView control with the following code:

// create a DataSource that loads data from a classpath resource
InputDataSource dataSource = new BasicInputDataSource(Main.class.getResourceAsStream("/languages.json"));

// create a Converter that converts a json array into a list
InputStreamIterableInputConverter<ProgrammingLanguage> converter = new JsonIterableInputConverter<>(ProgrammingLanguage.class);

// create a ListDataReader that will read the data from the DataSource and converts
// it from json into a list of objects
ListDataReader<ProgrammingLanguage> listDataReader = new InputStreamListDataReader<>(dataSource, converter);

// retrieve a list from the DataProvider
GluonObservableList<ProgrammingLanguage> programmingLanguages = DataProvider.retrieveList(listDataReader);

// create a JavaFX ListView and populate it with the retrieved list
ListView<ProgrammingLanguage> lvProgrammingLanguages = new ListView<>(programmingLanguages);

4.2.2. Retrieving an object

Retrieving an object looks somewhat similar to retrieving a list. However, instead of converting a JSON array into a list, we will convert a JSON object directly into a Java object. Below we have the user.json file and the POJO that will hold the information in the JSON object:

/user.json
{"name":"Duke","subscribed":true}
User.java
public class User {
    private StringProperty name = new SimpleStringProperty();
    private BooleanProperty subscribed = new BooleanProperty();

    public String getName() {
        return name.get();
    }

    public void setName(String name) {
        this.name.set(name);
    }

    public StringProperty nameProperty() {
        return name;
    }

    public boolean isSubscribed() {
        return subscribed.get();
    }

    public void setSubscribed(boolean subscribed) {
        this.subscribed.set(subscribed);
    }

    public BooleanProperty subscribedProperty() {
        return subscribed;
    }
}

As you can see, we use JavaFX properties here instead of native Java types. This allows us to bind the properties to a JavaFX UI control, e.g. a Label. To retrieve the object from the JSON file, we use the following code:

// create a DataSource that loads data from a classpath resource
InputDataSource dataSource = new BasicInputDataSource(Main.class.getResourceAsStream("/user.json"));

// create a Converter that converts a json object into a java object
InputStreamInputConverter<User> converter = new JsonInputConverter<>(User.class);

// create an ObjectDataReader that will read the data from the DataSource and converts
// it from json into an object
ObjectDataReader<User> objectDataReader = new InputStreamObjectDataReader<>(dataSource, converter);

// retrieve an object from the DataProvider
GluonObservableObject<User> user = DataProvider.retrieveObject(objectDataReader);

// when the object is initialized, bind its properties to the JavaFX UI controls
Label lbName = new Label();
CheckBox cbSubscribed = new CheckBox("Subscribed?");
user.initializedProperty().addListener((obs, oldValue, newValue) -> {
    if (newValue) {
        lbName.textProperty().bind(user.get().nameProperty());
        cbSubscribed.selectedProperty().bindBidirectional(user.get().subscribedProperty());
    }
});

5. Gluon Connect JavaDoc

The Gluon Connect JavaDoc can be found here.