I have a javafx project. In the main view i have a listview with some tags item and a delete button. The whole project is connected to a postgresql db. The tags are stored as ( user_id, name, color). When i click the delete button, the tag is deleted from the db, but i get this error "No results were returned by the query." while i have items in db and the list is updated. Why do i keep getting this error?
public class TagsController extends SQLConnection implements Initializable {
@FXML
public Button forest, shop, timeline, tags, rewards, settings, friends, button;
@FXML
public VBox menu, vbox;
@FXML
public Label gold;
@FXML
public ListView<Tag> listView;
public Stage stage;
public Scene scene;
ObservableList<Tag> list = FXCollections.observableArrayList();
List<Tag> listOfTags;
{
try {
listOfTags = new ArrayList<>(listOfTags());
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
User user = new User();
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
//set the gold value
gold.setText(String.valueOf(getGold(user.getId())));
//set the image of the menu button
InputStream in = getClass().getResourceAsStream("/images/menu.png");
Image image = new Image(in);
ImageView imageView = new ImageView(image);
button.setGraphic(imageView);
button.setMaxSize(40, 40);
button.setMinSize(40, 40);
button.setContentDisplay(ContentDisplay.TOP);
imageView.fitWidthProperty().bind(button.widthProperty());
imageView.setPreserveRatio(true);
//hide the menu buttons
forest.setVisible(false);
shop.setVisible(false);
timeline.setVisible(false);
tags.setVisible(false);
rewards.setVisible(false);
settings.setVisible(false);
friends.setVisible(false);
menu.setVisible(false);
listView.setItems(list);
listView.setCellFactory(param -> new ListCell<>() {
private final Button deleteButton = new Button();
@Override
protected void updateItem(Tag tag, boolean empty) {
super.updateItem(tag, empty);
if (empty || tag == null) {
setText(null);
setGraphic(null);
} else {
setText(tag.getName());
setGraphic(deleteButton);
setStyle("-fx-background-color: #deaef4; -fx-padding: 20px; -fx-border-width: 1px; -fx-border-color: #cccccc; -fx-font-family: System Italic; -fx-font-size: 19; -fx-text-fill: #f5f599;");
InputStream in1 = getClass().getResourceAsStream("/images/bin.png");
Image image1 = new Image(in1);
ImageView imageView1 = new ImageView(image1);
imageView1.setFitHeight(40);
imageView1.setFitWidth(40);
deleteButton.setGraphic(imageView1);
deleteButton.setStyle("-fx-background-color: #deaef4;");
deleteButton.setOnAction(actionEvent -> {
removeTag(user.getId(), tag.getName());
list.remove(listView.getSelectionModel().getSelectedItem());
listView.getItems().remove(tag);
refreshTags();
});
}
}
});
refreshTags();
}
private void refreshTags(){
listView.getItems().removeAll();
list.removeAll();
try {
Connection connection = connection();
Statement statement = connection.createStatement();
String query = "SELECT * FROM tags WHERE user_id = " + user.getId();
ResultSet resultSet = statement.executeQuery(query);
while (resultSet.next()) {
listView.getItems().add(new Tag(
user.getId(),
resultSet.getString("name"),
resultSet.getString("color"))
);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
You seem to be working too hard. The point of using
ObservableListis that when you add or delete to that collection, the subscribed listener objects will be informed of the change. So there is no need for you to replacing all elements. Just delete the selectedTagobject from the observable list, and theListViewwill update itself automatically. See example code below.As discussed in the Comments, your code needs to be re-organized.
To make your code more comprehensible, learn to think in terms of separation of concerns. Each piece of code should have one single responsibility.
So… Code for interacting with the user should not know anything about SQL and databases. Code for managing the lifecycle of your app (launching, exiting, etc.) should not know the details of the forms appearing in your user-interface. And so on.
Let's write a revamped example. Here we define a simple
Tagclass as a record.In your app, the key re-org is separating the database work from the user-interface (UI) work. In the Java world, we commonly seen this done via the Repository pattern. Define an interface with features needed by your app for interacting with a database.
In our case with this example, we need just two operations:
Tagobjects.Tagobject.So our interface looks like this:
One advantage of using an interface here is that we can write a simple hard-coded implementation using an in-memory collection of
Tagobjects. No need to get distracted by messy SQL code now. We can develop our UI without any database involvement.Now we can build our UI to use this hard-coded repository. Our entire UI for displaying and deleting tags is contained in its own class. This class extends one of the JavaFX layout managers.
Our class here fetches
Tagobjects from the repository. ThoseTagobjects are then transferred to an observable list. We use that observable list to back aListViewwidget.This UI class will need to interact with an instance of
Repository. We pass an instance to the UI class constructor. This is one way of doing dependency injection, supplying a needed resource or service without the consuming class from having to know how to get it or where it came from.Notice how the Delete button uses the
Repositoryobject to:ListViewTagobject of the selected row.As a nicety, we enable/disable our Delete button as the user selects/deselects a row. Notice how we add a selection listener to make that happen.
Look at how we respond to a button click. First we delete the matching object from our repository. The crucial part is how that deletion method returns a boolean to indicate success or failure. If successful, only then do we delete from our observable list. After deleting from the observable list, the
ListViewobject automatically updates its display to omit the removed item.And we need to harness all this code to make an app. We do this here by writing a class named
Appthat extends from the JavaFXApplicationclass. ThisAppclass manages the lifecycle of a JavaFX app, by overriding methods such asstartandstop.Notice how we instantiate a particular implementation of
Repository. Then we instantiate our layout, aTagListEditorLayoutobject, and wrap into aScene(viewport of window) placed in aStage(window).Now we can run our app. Select a row to enable the Delete button.
Hit the Delete button to try our logic. You should see the selected row disappear as we delete the
Tagobject from the observable list.Great! It all works. But what you really wanted was database access. So we need another implementation of
Repositoryto work with a database. Here we use the H2 Database Engine, for convenience.And change our
Appclass to use this alternativeRepositoryimplementation.At this point, we can more easily debug any database problems. We know our app worked perfectly well without any database. So if we experience any problems, we know it comes from our database work. (Indeed, we could even write separate code to test our database access code without using our UI at all. But for now, let's try our UI code.)
Notice in code above that we use a
DataSourceobject to store our database login credentials, such as user name, user password, the name of the database, the server address, and so on. UsingDataSourcegives you flexibility later, letting you externalize these details outside your source code. For example, aDataSourceobject can be retrieved from a naming/directory service such as an LDAP server. This way, you don't need to recompile your app just for a password change.Here's class to mimic retrieving a
DataSourceobject.Of course we need to also set up a database. So write a class for that. This class creates the database, creates our
tag_table, and populates that tables with some example data.In real work, I would use a database migration tool like Flyway or Liquibase to run SQL scripts to create the tables and pre-populate rows.
And, voilà, we have fully functioning database-backed list editor.
Later, in further development, if you started having problems, you could always switch back to the list-backed repository to eliminate the database from your debugging considerations.
For completeness, here is my
POM&module-info.java.