Q:

JavaFX : Consuming REST service and displaying the data in front-end

I am working on a JavaFX(On JDK8 with SceneBuilder) project which should connect with a Spring-MVC based server and I would like to access some Objects from the server and display it. I have already programmed the Spring server to give back the desired object upon request, but my unfamiliarity in UI programming and JavaFX is making it a bit difficult. 
In the FXML file, I already have added a grid-pane and I would like to display the objects there. I would appreciate any help from you guys to get started up. I have only basic code, but I am pasting it below :
Canvas.fxml :
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>

<GridPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8">
  <columnConstraints>
    <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
    <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
  </columnConstraints>
  <rowConstraints>
    <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
    <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
    <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
  </rowConstraints>
</GridPane>

CanvasModel class :
public class Canvas {

    private int canvasid;

    private final StringProperty canvasName;

    private final StringProperty canvasTitle;

    private final StringProperty canvasImage;

    private byte[] canvasImageInBytes;

    public Canvas(String canvasName, String canvasTitle, String canvasImage){
        this.canvasName = new SimpleStringProperty(canvasName);
        this.canvasTitle = new SimpleStringProperty(canvasTitle);
        this.canvasImage = new SimpleStringProperty(canvasImage);
    }
//Getters and setters ommitted
}

Main class :
public class Main extends Application {

    private Stage primaryStage;

    @Override
    public void start(Stage primaryStage) throws Exception{
       Parent root = FXMLLoader.load(getClass().getResource("../View/mainui.fxml"));

primaryStage.setTitle("CheckAPP");
       primaryStage.setScene(new Scene(root, 300, 600));
        primaryStage.setFullScreen(false);
        primaryStage.setMaximized(false);

        primaryStage.show();
    }
    public static void main(String[] args) {
        launch(args);
    }

    public Stage getPrimaryStage() {
        return this.primaryStage;
    }

}

Now making a request to below :
http://localhost:8080/canvaslist

It will return a java.util.List which I want to display in GridPane. The images are in String format. I understand this is not a regular question like this is not working and all, but I am trying to wrap around my head for UI programming, and I didn't knew where else to turn to. Thanks a lot for your help.

A:

Just adding my few cents to help you.

Use a background thread to fetch the data from the service, as it may take time to get response back. Executing it on the JavaFX application thread might result in undesirable user experience. In this case Task will help you.
Once you have response, construct a necessary object / collection of object from it which you will be using to update the elements on scene graph.
Since JavaFX Application Thread is the only thread on which you can access live scene graph elements, you cannot directly use them in your background thread ( Using them will result in IllegalStateException ). You can update the data on the JavaFX scene graph by either using Platform.runLater or calling the methods of the task which are guaranteed to update state on the FX Application Thread, like updateProgress(...), updateMessage(...), getValue(...) etc.
Use the object / collection of object that you constructed in Step-2  to create (or update) FX scene graph elements.
In case you have a control (like TableView or ListView) which accepts ObservableList as its contents, you can just bind the contents and one of the Task's property, to update them automatically during/ after the execution depending on their usage.
But in your case, since you have GridPane, so we might have to take a step further and write logic to create controls and add them to the GridPane.

Example
I have created an example with consumes a service, parses the JSON data and creates a GridPane out of it. The JSON has list of few people on StackOverflow with their names, their likes (according to me) and profile pic on SO.
It uses a background Task to load the JSON data from the service, uses setOnSucceeded(...) handler of the Task to pass it to a createGridPane(onservableList) which creates a GridPane.
One of the columns of the GridPane contains the profile picture of the respective person. Since these images can take time to download, I spawn multiple threads to load the image for each user.
You can find the source code here.
It uses GSON as a library to convert JSON to POJO class.
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.EventHandler;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.*;
import javafx.stage.Stage;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ConsumeJSON extends Application {

    private ObservableList<PeopleOnSO> listOfPeople;
    private static final String JSON_URL = "https://api.myjson.com/bins/3jwmh";
    private static final String IMAGE_URL = "http://www.fontspring.com/presentation_20150512/images/ajax_loader_blue_512.gif";
    private final ExecutorService executorService = Executors.newCachedThreadPool();
    private Image loadImage;

    @Override
    public void start(Stage stage) throws Exception {

        loadImage = new Image(IMAGE_URL);
        VBox root = new VBox();
        root.setAlignment(Pos.TOP_CENTER);
        root.setPadding(new Insets(20));
        root.setSpacing(20);

        Button button = new Button("Fill GridPane");

        root.getChildren().addAll(button);

        button.setOnAction(e -> {
            // Display loading image
            ImageView loading = new ImageView(loadImage);
            loading.setFitWidth(60);
            loading.setFitHeight(60);
            root.getChildren().add(loading);
            executorService.submit(fetchList);
        });

        fetchList.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
            @Override
            public void handle(WorkerStateEvent t) {
                listOfPeople = FXCollections.observableArrayList(fetchList.getValue());
                GridPane gridPane = createGridPane(listOfPeople);
                //Remove Loading Image and add GridPane
                root.getChildren().remove(1);
                VBox.setVgrow(gridPane, Priority.ALWAYS);
                root.getChildren().add(gridPane);
                stage.sizeToScene();
            }
        });

        ScrollPane scrollPane = new ScrollPane(root);
        Scene scene = new Scene(scrollPane, 600, 500);
        stage.setScene(scene);
        stage.setTitle("Load Data from JSON");
        stage.show();

        stage.setOnCloseRequest(e -> {
            executorService.shutdown();
        });
    }

    public GridPane createGridPane(ObservableList<PeopleOnSO> listOfPeople){
        GridPane gridPane = new GridPane();
        gridPane.setAlignment(Pos.CENTER);
        gridPane.setGridLinesVisible(true);
        gridPane.setPadding(new Insets(20));
        gridPane.setMinHeight(500);
        gridPane.setMaxWidth(500);

        //Create headings
        Label nameHeading = new Label("Name");
        nameHeading.setStyle("-fx-font-weight: bold");
        Label likeHeading = new Label("Likes");
        likeHeading.setStyle("-fx-font-weight: bold");
        Label imageHeading = new Label("Image");
        imageHeading.setStyle("-fx-font-weight: bold");

        gridPane.add(nameHeading, 0, 0);
        gridPane.add(likeHeading, 1, 0);
        gridPane.add(imageHeading, 2, 0);

        // Aligning at center
        alignElements(nameHeading, likeHeading, imageHeading);

        // Setting Constraints
        for (int i = 0; i < 3; i++) {
            ColumnConstraints column = new ColumnConstraints(150);
            // column.setPercentWidth(80);
            gridPane.getColumnConstraints().add(column);
        }

        for (int i = 0; i < listOfPeople.size(); i++) {

            PeopleOnSO people = listOfPeople.get(i);
            Label nameLabel = new Label(people.getName());
            Label likeLabel = new Label(people.getLike());
            ImageView imageView = new ImageView(loadImage);
            imageView.setFitHeight(60);
            imageView.setFitWidth(60);

            //Thread for loading images later
            FetchImage fetchImage = new FetchImage(people.getImageUrl());
            fetchImage.setOnSucceeded(worker -> {
                imageView.setImage((Image) fetchImage.getValue());
            });

            executorService.submit(fetchImage);

            // Adding to GridPane and necessary configuration
            gridPane.add(nameLabel, 0, i + 1);
            gridPane.add(likeLabel, 1, i + 1);
            gridPane.add(imageView, 2, i + 1);

            //Aligning at center
            alignElements(nameLabel, likeLabel, imageView);

            gridPane.getRowConstraints().add(new RowConstraints(80));
        }
        return gridPane;
    }

    /**
     * Align elements at the center
     * @param nodes
     */
    private void alignElements(Node ... nodes ) {
        for(Node node : nodes) {
            GridPane.setHalignment(node, HPos.CENTER);
            GridPane.setValignment(node, VPos.CENTER);
        }
    }

    /**
     * Task to fetch details from JSONURL
     * @param <V>
     */
    private Task<List<PeopleOnSO>> fetchList = new Task() {
        @Override
        protected List<PeopleOnSO> call() throws Exception {
            List<PeopleOnSO> list = null;
            try {
                Gson gson = new Gson();
                list = new Gson().fromJson(readUrl(JSON_URL), new TypeToken<List<PeopleOnSO>>() {
                }.getType());
            } catch (Exception e) {
                e.printStackTrace();
            }
            return list;
        }
    };

    /**
     * Task to fetch images for individual ImageViews
     * @param <V>
     */
    private class FetchImage<V> extends Task<Image> {

        private String imageUrl;

        public FetchImage(String imageUrl) {
            this.imageUrl = imageUrl;
        }

        @Override
        protected Image call() throws Exception {
            Image image = new Image(imageUrl);
            return image;
        }

    }

    /**
     * Read the URL and return the json data
     * @param urlString
     * @return
     * @throws Exception
     */
    private static String readUrl(String urlString) throws Exception {
        BufferedReader reader = null;
        try {
            URL url = new URL(urlString);
            reader = new BufferedReader(new InputStreamReader(url.openStream()));
            StringBuffer buffer = new StringBuffer();
            int read;
            char[] chars = new char[1024];
            while ((read = reader.read(chars)) != -1)
                buffer.append(chars, 0, read);

            return buffer.toString();
        } finally {
            if (reader != null)
                reader.close();
        }
    }

    private class PeopleOnSO {

        private final String name;
        private final String like;
        private final String imageUrl;

        public PeopleOnSO(String name, String like, String imageUrl){
            this.name = new String(name);
            this.like = new String(like);
            this.imageUrl = new String(imageUrl);
        }

        public String getName() {
            return name;
        }

        public String getLike() {
            return like;
        }

        public String getImageUrl() {
            return imageUrl;
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

