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
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:
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
How can do make sure the JSON -> Object marshaling is done properly in RestTemplate?