Skip to Content
Course content

214: Building a Simple Todo App with JavaFX

Click on the "Edit" button in the top corner of the screen to edit your slide content.

By now, you've got the basics of Java down, and you're likely itching to build something you can actually see and interact with. JavaFX is great for this, but there is a trap that almost every developer falls into the first time they build a GUI: the "Everything-in-the-Start-Method" pattern. It feels efficient at first, but it's a recipe for a maintenance nightmare.

The Temptation of the Single-Class App

When you start building a Todo app, your instinct will be to instantiate your TextField, your Button, and your VBox right inside the start() method. You'll probably write a lambda for the button click that grabs the text from the field and manually adds a new Label to the layout. It looks like this:

public void start(Stage primaryStage) {
    TextField input = new TextField();
    VBox todoList = new VBox();
    Button addBtn = new Button("Add");

    addBtn.setOnAction(e -> {
        String task = input.getText();
        todoList.getChildren().add(new Label(task));
        input.clear();
    });

    primaryStage.setScene(new Scene(new VBox(input, addBtn, todoList)));
    primaryStage.show();
}

I'll be honest: for a three-field demo, this is fine. But the moment you want to add a "Delete" button to each task, or a way to save your list to a file, this approach collapses. You're mixing your data (the task string), your business logic (adding a task), and your view (the Label) into one giant block of code. If you want to change how a task looks—say, adding a checkbox—you have to dig into the event handler of a button that is buried inside your main application setup.

When the UI Logic Chokes the Application

The real problem here is that your "Todo item" doesn't actually exist as an object; it only exists as a Label in a VBox. This is a classic mistake. You've coupled your data directly to the UI component. If you ever decided to switch from a VBox to a ListView, you'd have to rewrite every single line of your logic because your "data" is literally a piece of the user interface.

As a colleague, I'd tell you: stop thinking about Labels and start thinking about Tasks. You need a way to track the state of a todo item independently of how it is drawn on the screen. If your app crashes or you want to implement an "Undo" feature, you can't do that if your only record of the task is a UI element sitting in a container.

Decoupling State from Layout

The professional way to handle this is to separate your data model from your view. First, create a simple TodoItem class. Then, instead of manually adding nodes to a pane, use an ObservableList. This is where JavaFX gets powerful. An ObservableList can be bound to a UI component, meaning when you update the list in Java, the UI updates itself automatically.

Here is how I would structure this. I'd create a TodoItem POJO to hold the description and completion status, and then use a ListView coupled with a CellFactory. This separates what a task is from how it's rendered.

public class TodoItem {
    private final StringProperty description = new SimpleStringProperty();
    public TodoItem(String text) { description.set(text); }
    public StringProperty descriptionProperty() { return description; }
    public String getDescription() { return description.get(); }
}

// In your main app:
ObservableList<TodoItem> masterData = FXCollections.observableArrayList();
ListView<TodoItem> listView = new ListView<>(masterData);

// Tell the ListView how to display the TodoItem object
listView.setCellFactory(lv -> new ListCell<>() {
    @Override
    protected void updateItem(TodoItem item, boolean empty) {
        super.updateItem(item, empty);
        setText(empty ? null : item.getDescription());
    }
});

addBtn.setOnAction(e -> {
    masterData.add(new TodoItem(input.getText()));
    input.clear();
});

Now, the addBtn doesn't know that a ListView exists. It only knows that it's adding a TodoItem to a list. If you decide tomorrow that you want to display your tasks in a table or a grid instead of a list, you only change the UI component—your logic for adding and managing tasks remains untouched. That's the difference between a script that works and a piece of software that lasts.




📋 Practical Task

Exercise: Implement Task Deletion in the Decoupled Todo App

Using the decoupled architecture from the lesson (where tasks are stored in an ObservableList<TodoItem> and displayed via a ListView), implement a "Remove Selected" feature.

Your goal is to add a "Delete" button to the layout. When clicked, it should identify which TodoItem is currently selected in the ListView and remove that object from the ObservableList. Ensure that the UI updates automatically upon removal and that the application does not crash if the user clicks "Delete" without having an item selected.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.