0

I'm reasonably new to Java and JavaFX and I'm developing a JavaFX app and am consuming a REST api inside a javafx.concurrent.ScheduledService Task to update a TableView. I am able to get the response from my SpringBoot REST service and can see that the data is being set on the Model Object, the ObservableList as well the TableView itself, but the Table still remains empty.

SpringBoot Api Endpoint:

 @RequestMapping(value = "/getAllActive", method = RequestMethod.GET)
public List<IssuedTicket> getAllActive () {
    List<IssuedTicket> issuedTicketList = issuedTicketService.findAll();
    return issuedTicketList;
}

JSON response from above EndPoint:

[{"id":2,"ticketId":1230717013545,"dateArrived":"23-07-17","timeArrived":"01:35:45","deviceId":1},{"id":3,"ticketId":1230717013552,"dateArrived":"23-07-17","timeArrived":"01:35:52","deviceId":1},{"id":4,"ticketId":1230717013556,"dateArrived":"23-07-17","timeArrived":"01:35:56","deviceId":1}]

In my JavaFX Application:

HomeSceneController.java

package com.ronintech.bayTrans.ui.main;

import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXTextField;
import com.ronintech.bayTrans.model.ActiveTickets;
import com.ronintech.bayTrans.utils.RestErrorHandler;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.AnchorPane;
import javafx.util.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.*;
import org.springframework.web.client.RestTemplate;

import java.net.URL;
import java.util.List;
import java.util.ResourceBundle;

public class HomeSceneController implements Initializable{

@FXML
private AnchorPane homeAnchorPane;

@FXML
private JFXTextField ticketTxt;

@FXML
private JFXButton scanTicketBtn;

@FXML
private TableView<ActiveTickets> activeTicketsTable;

@FXML
private TableColumn<ActiveTickets, Long > ticketIdCol;

@FXML
private TableColumn<ActiveTickets, String > dateArrivedCol;

@FXML
private TableColumn<ActiveTickets, String> timeArrivedCol;

private ObservableList<ActiveTickets> activeTicketsList;

private static final Logger LOGGER = LoggerFactory.getLogger(HomeSceneController.class);
private static final String ACTIVE_TICKETS_URL = "http://localhost:9090/api/ticket/getAllActive";

@Override
public void initialize(URL location, ResourceBundle resources) {
    getActiveTickets.reset();
    getActiveTickets.setPeriod(Duration.seconds(20));
    getActiveTickets.start();

    getActiveTickets.setOnSucceeded(event -> {
        LOGGER.info("onSuccess");
        List<ActiveTickets> activeList = getActiveTickets.getValue();
        activeTicketsList = FXCollections.observableArrayList(activeList);

        ticketIdCol.setCellValueFactory(new PropertyValueFactory<ActiveTickets, Long>("ticketId"));
        dateArrivedCol.setCellValueFactory(new PropertyValueFactory<ActiveTickets, String>("dateArrived"));
        timeArrivedCol.setCellValueFactory(new PropertyValueFactory<ActiveTickets, String>("timeArrived"));
        activeTicketsTable.setItems(activeTicketsList);

        LOGGER.info("Items in Table");
        LOGGER.info(activeTicketsTable.getItems().toString());
    });

    getActiveTickets.setOnFailed(event -> {
        LOGGER.error("service task FAILED");
    });
}

@FXML
void openTicketModal(ActionEvent event) {
}

private ScheduledService<List<ActiveTickets>> getActiveTickets = new ScheduledService<List<ActiveTickets>>() {
    @Override
    protected Task<List<ActiveTickets>> createTask() {
        return new Task<List<ActiveTickets>>() {
            @Override
            protected List<ActiveTickets> call() throws Exception {
                LOGGER.info("Scheduled Service getActiveTickets STARTED");
                RestTemplate restTemplate = new RestTemplate();
                restTemplate.setErrorHandler(new RestErrorHandler());

                List<ActiveTickets> response = restTemplate.getForObject(ACTIVE_TICKETS_URL, List.class);

                LOGGER.info(response.toString);
                return response;
            }
        };
    }
};

}

My Model Object: ActiveTickets.java

package com.ronintech.bayTrans.model;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.beans.property.SimpleStringProperty;

@JsonIgnoreProperties(ignoreUnknown = true)
public class ActiveTickets {

private SimpleLongProperty id;
private SimpleLongProperty ticketId;
private SimpleStringProperty dateArrived;
private SimpleStringProperty timeArrived;
private SimpleIntegerProperty deviceId;

public ActiveTickets(long id,
                     long ticketId,
                     String dateArrived,
                     String timeArrived,
                     int deviceId) {
    this.id = new SimpleLongProperty(id);
    this.ticketId = new SimpleLongProperty(ticketId);
    this.dateArrived = new SimpleStringProperty(dateArrived);
    this.timeArrived = new SimpleStringProperty(timeArrived);
    this.deviceId = new SimpleIntegerProperty(deviceId);
}

public long getId() {
    return id.get();
}

public SimpleLongProperty idProperty() {
    return id;
}

public long getTicketId() {
    return ticketId.get();
}

public SimpleLongProperty ticketIdProperty() {
    return ticketId;
}

public String getDateArrived() {
    return dateArrived.get();
}

public SimpleStringProperty dateArrivedProperty() {
    return dateArrived;
}

public String getTimeArrived() {
    return timeArrived.get();
}

public SimpleStringProperty timeArrivedProperty() {
    return timeArrived;
}

public int getDeviceId() {
    return deviceId.get();
}

public SimpleIntegerProperty deviceIdProperty() {
    return deviceId;
}

public void setId(long id) {
    this.id.set(id);
}

public void setTicketId(long ticketId) {
    this.ticketId.set(ticketId);
}

public void setDateArrived(String dateArrived) {
    this.dateArrived.set(dateArrived);
}

public void setTimeArrived(String timeArrived) {
    this.timeArrived.set(timeArrived);
}

public void setDeviceId(int deviceId) {
    this.deviceId.set(deviceId);
}

@Override
public String toString() {
    return "ActiveTickets{" +
            "ticketId=" + ticketId +
            ", dateArrived=" + dateArrived +
            ", timeArrived=" + timeArrived +
            '}';
}
}

The response from the RestTemplate call is:

00:43:06.901 [Thread-9] INFO com.ronintech.bayTrans.ui.main.HomeSceneController - [{id=2, ticketId=1230717013545, dateArrived=23-07-17, timeArrived=01:35:45, deviceId=1}, {id=3, ticketId=1230717013552, dateArrived=23-07-17, timeArrived=01:35:52, deviceId=1}, {id=4, ticketId=1230717013556, dateArrived=23-07-17, timeArrived=01:35:56, deviceId=1}]

The log after setting the ObservableList to TableView:

activeTicketsTable.setItems(activeTicketsList);

00:43:07.159 [JavaFX Application Thread] INFO com.ronintech.bayTrans.ui.main.HomeSceneController - [{id=2, ticketId=1230717013545, dateArrived=23-07-17, timeArrived=01:35:45, deviceId=1}, {id=3, ticketId=1230717013552, dateArrived=23-07-17, timeArrived=01:35:52, deviceId=1}, {id=4, ticketId=1230717013556, dateArrived=23-07-17, timeArrived=01:35:56, deviceId=1}]

The log shows that the TableView data is being set on the Main UI Thread, but the table is still empty

TableView

I'm not sure if and what I'm doing wrong. Please help if someone knows how to solve this.

Thanks in advance.

EDIT

My TableView structure in the FXML:

<TableView fx:id="activeTicketsTable" prefHeight="800.0" prefWidth="800.0" tableMenuButtonVisible="true">
    <columns>
        <TableColumn fx:id="ticketIdCol" prefWidth="75.0" text="Ticket ID"/>
        <TableColumn fx:id="dateArrivedCol" prefWidth="75.0" text="Date Arrived"/>
        <TableColumn fx:id="timeArrivedCol" prefWidth="75.0" text="Time Arrived"/>
    </columns>
    <columnResizePolicy>
        <TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
    </columnResizePolicy>
</TableView>

UPDATE

It seems the marshaling is not happening correctly.

This is the debug trace of ObservableList activeTicketsList:

activeTicketsList

The List has been set as an ArrayList of LinkedHashMaps'

Whereas when I manually make an ObservableList:

private final ObservableList<ActiveTickets> data = FXCollections.observableArrayList(
        new ActiveTickets(1,1234,"date1","time1",1),
        new ActiveTickets(2,5678,"date2","time2",2)
);

"data" is properly set as an ArrayList of Objects of class ActiveTickets

data

How can do make sure the JSON -> Object marshaling is done properly in RestTemplate?

VRN.dev
  • 45
  • 1
  • 6
  • The screenshot shows that there are actually items in the table (an empty table has a placeholder in place of the rows). So all the logs and screenshots are consistent with the cell value factory not being set on the columns. The ones you show in the code look correct; my best guess here would be that somehow the table column instances on which you're calling `setCellValueFactory(...)` are not the correct table column instances, somehow. – James_D Jul 24 '17 at 02:08
  • Yes the table is being set with the list from provided by Service but it looks like the JSON->Object marshaling is going wrong. Any tips on how to fix this? – VRN.dev Jul 24 '17 at 07:56
  • See if https://stackoverflow.com/questions/8108887/generics-with-spring-resttemplate helps. Your `ActiveTickets` class almost certainly needs a no-argument constructor too. – James_D Jul 24 '17 at 11:49
  • It's just like what James_D said, add a no-argument constructor, and use either `ParameterizedTypeReference` or simply marshal to array instead of list. You need to do both steps for this to work. – Jai Jul 25 '17 at 01:24

1 Answers1

0

I'm not sure if this is actually the problem, but generally my REST client projects do not marshal directly to a list (it might be Jackson's problem). This is what I would do:

ActiveTickets[] response = restTemplate.getForObject(ACTIVE_TICKETS_URL, ActiveTickets[].class);

return Arrays.asList(response);

Note that you would still return a list at the endpoint; you do not need to change that.

Update

Read this for another way to do this.

Community
  • 1
  • 1
Jai
  • 8,165
  • 2
  • 21
  • 52
  • Tried this but the Service goes to FAILED state. 11:45:51.658 [JavaFX Application Thread] ERROR com.ronintech.bayTrans.ui.main.HomeSceneController - service task FAILED. But I agree with the premise, there must be some problem in the marshaling from JSON -> Object – VRN.dev Jul 24 '17 at 07:47
  • @VRN.dev does the stacktrace says anything on where and what went wrong? – Jai Jul 24 '17 at 08:12
  • @VRN.dev btw your `ActiveTickets` probably need a default empty constructor. – Jai Jul 24 '17 at 08:18