The Comments App is a Gluon code sample. For a full list of Gluon code samples, refer to the Gluon website.

In this tutorial, we’ll explain how to create the Comments application that can be deployed on desktop, Android and iOS devices. Before you start, be sure that you have checked the list of prerequisites, and you have installed the Gluon plugin for your IDE. Otherwise follow these instructions.

Note: This tutorial will use the plugin for NetBeans, but it works as well on Eclipse and IntelliJ.

Code: The code for this project can be found in the samples repository at GitHub. The sample is located under the directory comments. The reader can clone this repository or create the entire project from scratch, based on the following steps.

Creating the project

Let’s create a new project using the Gluon plugin. In NetBeans, click File→New Project…​ and select Gluon on the left. Select Gluon Mobile - Glisten-Afterburner Project from the list of available Projects:

Plugins Window

Add a proper name to the application (Comments), find a proper location, add the package name and change the main class name if required.

Name and Location

Press Next and change the name of the primary and secondary views, to Comments for the former and Edition for the latter.

Name of Views

Press Finish and the project will be created and opened. The main class Comments contains the call to instantiate the views CommentsView and EditionView, and the navigation drawer.

Project

The views are created with FXML with the AppView class, making use of the Afterburner framework. The AppViewManager class takes care of adding the views to an AppViewRegistry instance.

When registerViewsAndDrawer is called, the views in AppViewRegistry are registered. These views are then passed to the NavigationDrawer fetched from MobileApplication.

Modifying the project

Let’s start modifying the default project to create our Comments application.

Note class

Our model will be a Comment class, with two String fields holding the author and content of the comment.

Comment.java
public class Comment {

    private String author;
    private String content;

    public Comment() {
    }

    public Comment(String author, String content) {
        this.author = author;
        this.content = content;
    }

    // getters and setters

}

Comments View

The FXML files can be edited with Scene Builder. Since the version 8.3.0, the Gluon Charm dependencies are included, and you can easily design Gluon Mobile views.

For starters, let’s add a ListView to the main view, that will contain each and every comment we create.

So let’s edit comments.fxml, remove the content, and add a ListView control in the center.

Comments view

Back to the CommentsPresenter add the list and a FloatingActionButton that allows switching to the edit view to add comments:

CommentsPresenter.java
public class CommentsPresenter extends GluonPresenter<Comments> {

    @FXML
    private View comments;

    @FXML
    private ListView<Comment> commentsList;

    public void initialize() {
        comments.showingProperty().addListener((obs, oldValue, newValue) -> {
            if (newValue) {
                AppBar appBar = getApp().getAppBar();
                appBar.setNavIcon(MaterialDesignIcon.MENU.button(e ->
                        getApp().getDrawer().open()));
                appBar.setTitleText("Comments");
            }
        });

        final FloatingActionButton floatingActionButton = new FloatingActionButton(MaterialDesignIcon.ADD.text,
                        e -> AppViewManager.EDITION_VIEW.switchView());
        floatingActionButton.showOn(comments);
    }
}

Now, let’s create a custom cell factory for commentsList, using a ListTile control to wrap the text for the comment in the center, and a button as graphic on the right.

CommentListCell.java
public class CommentListCell extends ListCell<Comment> {

    private final ListTile tile = new ListTile();
    private Comment comment;

    {
        final Button button = MaterialDesignIcon.DELETE.button();
        tile.setSecondaryGraphic(new VBox(button));
    }

    @Override
    protected void updateItem(Comment item, boolean empty) {
        super.updateItem(item, empty);
        comment = item;
        if (!empty && item != null) {
            tile.textProperty().setAll(item.getAuthor(), item.getContent());
            setGraphic(tile);
        } else {
            setGraphic(null);
        }
    }
}

and add the cell factory to commentsList:

CommentsPresenter.java
public class CommentsPresenter extends GluonPresenter<Comments> {

    public void initialize() {
        ...
        commentsList.setCellFactory(p -> new CommentListCell());
        commentsList.setPlaceholder(new Label("There are no comments"));
    }
}

Since the button will use a light color (under a dark swatch) over a white cell, we have to change it to a dark color, or it won’t be visible:

comments.css
.button.icon-toggle {
    -fx-text-fill: -primary-swatch-900;
}

We can add manually a comment to the list in order to test what we have so far:

Testing Comments view

Edit View

In order to create and edit the comments, let’s add now to the Edition view a TextField for the author and a TextArea for the content of the comment. Also, a pair of Button controls to cancel or submit the comment.

Edition FXML view

Once-the fx:id tags have been added to each control, select View→Show sample Controller Skeleton from Scene Builder, copy the content and paste it in the EditionPresenter.

EditionPresenter.java
public class EditionPresenter extends GluonPresenter<Comments> {

    @FXML
    private View edition;

    @FXML
    private TextField authorText;

    @FXML
    private TextArea commentsText;

    @FXML
    private Button submit;

    public void initialize() {
        edition.setShowTransitionFactory(BounceInRightTransition::new);

        edition.showingProperty().addListener((obs, oldValue, newValue) -> {
            if (newValue) {
                AppBar appBar = getApp().getAppBar();
                appBar.setNavIcon(MaterialDesignIcon.MENU.button(e ->
                        getApp().getDrawer().open()));
                appBar.setTitleText("Edition");
            }
        });

        submit.disableProperty().bind(Bindings.createBooleanBinding(() -> {
                return authorText.textProperty().isEmpty()
                        .or(commentsText.textProperty().isEmpty()).get();
            }, authorText.textProperty(), commentsText.textProperty()));
    }

    @FXML
    void onCancel(ActionEvent event) {
    }

    @FXML
    void onSubmit(ActionEvent event) {
    }
}

Running the application, we can access the edit view either through the floating action button or via the navigation drawer:

Edition view

This sample uses Gluon CloudLink to store and synchronize data in the cloud. A pair of key and secret tokens, specific for the application, are used for signing the requests that are made to Gluon CloudLink.

To obtain a key and secret, you need a valid subscription to Gluon CloudLink. You can get it here (there is also a 30-day free trial). Sign up and get a valid account on Gluon CloudLink and a link to access the Gluon Dashboard.

Open the Dashboard in your browser, and sign in using the Gluon account credentials provided to create the account.

Dashboard

Go to the App Management link, and you will find a pair of key/secret tokens. Click on the download button to download the file gluoncloudlink_config.json, and then store it under your project src/main/resources/ folder.

Configuration File

The content of the file is a JSON object with the key and secret that will grant access to Gluon CloudLink:

src/main/resources/gluoncloudlink_config.json
{
  "gluonCredentials": {
    "applicationKey" : "f88XXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
    "applicationSecret": "7fbXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
  }
}

Service class

We can now build a new DataClient, which is the access point to the Data Storage service of Gluon CloudLink. We use the @PostContruct annotation on the method that will be called by the Afterburner framework, once the service is instanced, but before it can be used.

Service.java
public class Service {

    private DataClient dataClient;

    @PostConstruct
    public void postConstruct() {
        dataClient = DataClientBuilder.create().build();
    }
}

Once we have a DataClient reference, we can retrieve data from and store data to Gluon CloudLink. In this case, the data will be a list with comments.

Using a DataProvider as an entry point to retrieve a GluonObservableList, we need to supply a proper ListDataReader, a valid entity with the ability to read a list of objects. For that, the DataClient instance already has a method: createListDataReader(). All it needs is an identifier (CLOUD_LIST_ID), and the object class to be read (Comment.class).

By default, it will also apply the following data synchronization flags:

  • LIST_WRITE_THROUGH, means that changes in the local list are reflected to the remote copy of that list on Gluon CloudLink.

  • LIST_READ_THROUGH, means that changes in the remote list on Gluon CloudLink are reflected to the local copy of that list: Any change done in any client app will be reflected in the cloud, and immediately broadcast to all the listening applications.

Service.java
public class Service {

    private DataClient dataClient;
    private static final String CLOUD_LIST_ID = "comments";
    private final ListProperty<Comment> commentsList =
            new SimpleListProperty<>(FXCollections.<Comment>observableArrayList());

    @PostConstruct
    public void postConstruct() {
        dataClient = DataClientBuilder.create().build();
    }

    public void retrieveComments() {
        GluonObservableList<Comment> retrieveList = DataProvider.<Comment>retrieveList(
                dataClient.createListDataReader(CLOUD_LIST_ID, Comment.class));

        retrieveList.stateProperty().addListener((obs, ov, nv) -> {
            if (ConnectState.SUCCEEDED.equals(nv)) {
                commentsList.set(retrieveList);
            }
        });

    }

    public void addComment(Comment comment) {
        commentsList.get().add(comment);
    }

    public ListProperty<Comment> commentsProperty() {
        return commentsList;
    }
}

We’ll wrap this remote observable list into an ListProperty in order to expose it to the view and bind it with the ListView content. We also create an addComment() method, that will be used to add comments from the Edition view.

Injecting the Service

Back to CommentsPresenter, we can inject a singleton instance of the Service by using the @Inject annotation, and then add the list of comments to the ListView, and call service.retrieveComments() to initially retrieve the list from the cloud.

CommentsPresenter.java
public class CommentsPresenter extends GluonPresenter<Comments> {

    @Inject
    private Service service;

    public void initialize() {
        ...
        commentsList.setItems(service.commentsProperty());

        service.retrieveComments();
    }
}

Also, on EditionPresenter, we can inject the service as well, so the user can submit new comments.

EditionPresenter.java
public class EditionPresenter extends GluonPresenter<Comments> {

    @Inject
    private Service service;

    @FXML
    void onCancel(ActionEvent event) {
        authorText.setText("");
        commentsText.setText("");
        getApp().switchView(HOME_VIEW);
    }

    @FXML
    void onSubmit(ActionEvent event) {
        service.addComment(new Comment(authorText.getText(), commentsText.getText()));
        authorText.setText("");
        commentsText.setText("");
        getApp().switchView(HOME_VIEW);
    }
}

Confirmation Dialog

The last step to complete the app will be adding a confirmation dialog before deleting a comment. By calling from the cell listViewProperty().get().getItems().remove(item), this will be propagated to the Cloud, and from there to the rest of the clients.

CommentListCell.java
public class CommentListCell extends ListCell<Comment> {

    {
        final Button button = MaterialDesignIcon.DELETE.button(e -> showDialog(comment));
    }

    private void showDialog(Comment item) {
        Alert alert = new Alert(AlertType.CONFIRMATION);
        alert.setTitleText("Confirm deletion");
        alert.setContentText("This comment will be deleted permanently.\nDo you want to continue?");

        Button yes = new Button("Yes, delete permanently");
        yes.setOnAction(e -> {
            alert.setResult(ButtonType.YES);
            alert.hide();
        });
        yes.setDefaultButton(true);

        Button no = new Button("No");
        no.setCancelButton(true);
        no.setOnAction(e -> {
            alert.setResult(ButtonType.NO);
            alert.hide();
        });
        alert.getButtons().setAll(yes,no);

        Optional result = alert.showAndWait();
        if(result.isPresent() && result.get().equals(ButtonType.YES)){
            listViewProperty().get().getItems().remove(item);
        }
    }
}

Testing the App

Run the application first on your Desktop. Create some comments, close the application and open it again: the list will be filled with those comments once they are retrieved from the cloud.

Try to delete one comment. Close and open the app. That comment won’t be there anymore.

Delete message

If everything is running fine on your desktop, it is time to deploy it as well on mobile.

To deploy it to Android run the androidInstall task. For iOS, select launchIOSDevice.

AndroidInstall

Once the app is on your mobile, run it as well on desktop, and see how comments created in one client appear immediately in all the clients, and those deleted, disappear from every client.

Edit on Android App
Android App

Tracking data with the Dashboard

At any moment, we can launch the Dashboard web application to track the data changes, in the Data Management link:

Dashboard

Conclusions

Throughout this tutorial we have covered in detail the basic steps to create a fully operative app for different platforms, with Cloud services and native UI controls, using only JavaFX and Gluon Mobile, based on the Glisten-Afterburner project generated with the Gluon Plugin.

If you have made it this far, congratulations, we hope this sample and the documentation was helpful to you! In case you have any questions, your first stop should be the Gluon support page, where you can access the latest documentation and the Gluon knowledge base. Gluon also recommends the community to support each other over at the Gluon StackOverflow page. Finally, Gluon offers commercial support as well, to kick start your projects.