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:
[
{"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:
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:
{"name":"Duke","subscribed":true}
And the following POJO for mapping the JSON object to a Java object:
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:
-
InputConverter
: converts data into an object -
OutputConverter
: converts an object into data -
IterableInputConverter
: provides anIterator
that converts data into a list of objects
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:
-
retrieveList
: this retrieves aGluonObservableList
using aListDataReader
-
storeObject
: this stores an object using anObjectDataWriter
and responds with aGluonObservableObject
-
retrieveObject
: this retrieves an existing Object using an ObjectDataReader and responds with aGluonObservableObject
-
removeObject
: this removes aGluonObservableObject
using anObjectDataRemover
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.
[
{"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:
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:
{"name":"Duke","subscribed":true}
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.