So I created this little App with a search bar and a table view. When the screen initializes, I use tableView.setItems() to add a fixed list of items to my table. Later, when the user performs a query, a filtered list is generated and I call tableView.setItems() again. When the list is being rendered, my code calls methods against getRowTable().getItem() to add behaviour to a button in each row. Everything is rendered correctly, however, it seems that updateItem() is being called more times than it should, and getRowTable().getItem() returns a null reference making the program throw an exception.
What is causing this? Code below.
App.java
public class App extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
URL url = getClass().getResource("/com/lib/fxml/search-movies.fxml");
try {
Parent root = FXMLLoader.load(url);
Scene scene = new Scene(root, 1024, 768);
primaryStage.setScene(scene);
primaryStage.show();
} catch (IOException exception) {
exception.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
SearchMovies.java
public class SearchMovies {
@FXML TextField searchField;
@FXML TableView<Movie> tableView;
@FXML TableColumn<Movie, String> titleColumn;
@FXML TableColumn<Movie, String> directorColumn;
@FXML TableColumn<Movie, Void> likeColumn;
private ArrayList<Movie> movieList = new ArrayList<>(Arrays.asList(
new Movie("Persona", "Ingmar Bergman"),
new Movie("City of God", "Fernando Meirelles"),
new Movie("Pulp Fiction", "Quentin Tarantino"),
new Movie("Gone Girl", "David Fincher"),
new Movie("Fight Club", "David Fincher"),
new Movie("Perfect Blue", "Satoshi Kon"),
new Movie("Prisoners", "Denis Villeneuve"),
new Movie("Sin City", "Frank Miller"),
new Movie("Mean Girls", "Mark Waters"),
new Movie("Breakfast Club", "John Hughes"),
new Movie("Sixteen Candles", "John Hughes"),
new Movie("Reservoir Dogs", "Quentin Tarantino")
));
private ObservableList<Movie> observableMovieList = FXCollections.observableArrayList(movieList);
@FXML
public void initialize() {
titleColumn.setCellValueFactory(item -> item.getValue().titleProperty());
directorColumn.setCellValueFactory(item -> item.getValue().directorProperty());
likeColumn.setCellFactory(col -> new TableCell<Movie, Void>() {
private final Button button = new Button("LIKE");
@Override
public void updateItem(Void item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
} else {
Movie movie = getTableRow().getItem();
button.setOnAction(e -> System.out.println("Someone liked " + movie.getTitle() + " " + movie.getDirector()));
System.out.println("Rendering row for " + movie.getTitle() + " by " + movie.getDirector());
setGraphic(button);
}
}
});
tableView.setItems(observableMovieList);
}
@FXML
public void search() {
String query = searchField.getText();
if (query == null || query.isBlank() || query.isEmpty()) {
return;
}
observableMovieList = FXCollections.observableArrayList(filterMovies(query));
tableView.setItems(observableMovieList);
}
private ArrayList<Movie> filterMovies(String query) {
ArrayList<Movie> filteredMovies = new ArrayList<>();
for (Movie movie : movieList) {
if (movie.getTitle().contains(query) || movie.getDirector().contains(query)) {
filteredMovies.add(movie);
}
}
return filteredMovies;
}
private class Movie {
private final StringProperty title;
private final StringProperty director;
public Movie(String title, String director) {
this.title = new SimpleStringProperty(title);
this.director = new SimpleStringProperty(director);
}
public String getTitle() {
return title.get();
}
public StringProperty titleProperty() {
return title;
}
public void setTitle(String title) {
this.title.set(title);
}
public String getDirector() {
return director.get();
}
public StringProperty directorProperty() {
return director;
}
public void setDirector(String director) {
this.director.set(director);
}
}
}
search-movies.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane prefHeight="610.0" prefWidth="1024.0" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.lib.controllers.SearchMovies">
<children>
<TextField fx:id="searchField" prefHeight="49.0" prefWidth="734.0" AnchorPane.leftAnchor="25.0" AnchorPane.rightAnchor="192.0" AnchorPane.topAnchor="35.0" />
<Button mnemonicParsing="false" onAction="#search" prefHeight="49.0" prefWidth="155.0" text="SEARCH" AnchorPane.leftAnchor="844.0" AnchorPane.rightAnchor="25.0" AnchorPane.topAnchor="35.0" />
<TableView fx:id="tableView" layoutX="50.0" layoutY="120.0" prefHeight="627.0" prefWidth="974.0" AnchorPane.bottomAnchor="21.0" AnchorPane.leftAnchor="25.0" AnchorPane.rightAnchor="25.0" AnchorPane.topAnchor="120.0">
<columns>
<TableColumn fx:id="titleColumn" prefWidth="75.0" text="Title" />
<TableColumn fx:id="directorColumn" prefWidth="433.0" text="Director" />
<TableColumn fx:id="likeColumn" prefWidth="215.0" text="Like" />
</columns>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
</columnResizePolicy>
</TableView>
</children>
</AnchorPane>