Java Quiz Player

Home Examples

A Reminder App using JavaFX

April 15, 2017

1. Overview

This is an example reminder application built using JavaFX. This app has functions to add, update, delete reminders and notify when a reminder is due. The reminder data is stored in HSQL relational database. The app accesses the database using Spring JDBC (Java Database Connectivity).

Java SE 8, JavaFX 8, Spring 4.3.5 and HSQLDB 2.3.2 are used to build the app.

The finished app's screenshot is as follows:

GUI image

The app's creation (a.k.a. code) is explained in following sections:

The Java source code for the finished application can be downloaded from the Download section below.

2. The GUI

The app has a main window. This has a table with all the reminders and their details. New reminders can be created. A reminder can be selected from the table, edited or deleted. There are buttons to create, update or delete reminders. The reminder info is entered and edited in a reminder dialog.

The reminders are grouped and the predefined group names are listed in the app's main window; and on selecting a group the reminders related to that group are displayed in the table.

The app's GUI is built using the JavaFX 8; this is part of Java SE 8.

2.1. Main Window

The controls (or widgets) in the main window are grouped in two javafx.scene.layout.VBoxes (a layout where controls are placed vertically).

The first VBox has the reminder group list and a calendar. The list is a javafx.scene.control.ListView and the calendar a LocalDatePicker (this is from JFXtras library, https://jfxtras.org/). The LocalDatePicker allows the user to select a date on which a new reminder is to be created or just browse through the calendar.

The second VBox contains a set of javafx.scene.control.Button controls for new, update and delete reminder functions, the javafx.scene.control.TableView in which the reminders are listed, and a javafx.scene.text.Text control for status messages.

The two VBoxes are added to a javafx.scene.layout.HBox (controls are placed horizontally). This completes the GUI layout for the app's main window.

2.2. Reminder Dialog

The reminder's info is entered or updated in a javafx.scene.control.Dialog. This dialog is opened when a user clicks the new or update buttons in the main window. The following are the screenshots for the new and update reminder dialogs respectively:

GUI image GUI image

In the dialog, the reminder name is entered in a javafx.scene.control.TextField, the priority is checked in a javafx.scene.control.CheckBox and the notes is entered in a javafx.scene.control.TextArea. The javafx.scene.control.DatePicker control allows the user to select a reminder date from a calendar popup. There is a JFXtras's (https://jfxtras.org/) LocalTimePicker control to set the time by sliding the hour and minute knobs.

In case of updating an existing reminder, there is an additional completed CheckBox in the dialog.

2.3. Reminder Notification

The app displays an alert dialog whenever a reminder is due. The alert dialog is a modal javafx.scene.control.Alert control.

GUI image

2.4. The App's GUI Class

The app's main GUI is built in the AppGui.java class. This has code to build, configure and layout the controls, and populate the appropriate controls with the data from the database. The following sections has the detailed description.

This class defines the event handlers for the add/update/delete buttons, the reminder group list selection listener, reminders table configuration and the initiating the reminders task to find due reminders.

This class has reference to various classes which provide functionality like data access, build other GUI components like the reminder entry dialog and the reminders task function.

Link to view source code: AppGui.java

3. The App

3.1. The Reminder

A reminder has the following attributes: name with 5 to 25 characters, notes with up to 200 characters, date, time, priority flag and completed flag. The reminder is defined in the Reminder.java class.

A reminder is unique for a given name and date. The newly created reminder date and time must be in future.

The reminder's date and time fields are captured in the app as java.time.LocalDate and LocalTime respectively. The LocalTime has a precision of nanoseconds (10:23:45.27891161) by default. But, the app stores the time truncated to minutes (10:23). The following code snippet shows how it's done.

LocalTime truncatedToMinutes =
    LocalTime.now().truncatedTo(java.time.temporal.ChronoUnit.MINUTES));

Link to view source code: Reminder.java

3.2. Reminder Group List

This is a read-only ListView in the main window with reminder groups as items. The groups are Reminders, Today, Overdue, Completed and Priority. On selecting a group item in the list the filtered reminders are shown in the table. The default item is Reminders and its selection shows all reminders in the database.

GUI image

The reminder groups are defined as an enum ReminderGroup. The enum, in addition to the constant definitions, has a list of reminder group string values (for example, PRIORITY constant has a string value of "Priority") which are used to populate the reminder group list.

The reminder group list is created and populated as follows in the AppGui:

ListView<String> list = new ListView<>(ReminderGroup.getAsFormattedStrings());

The enum also maintains a Map collection of the string key and enum constant values for lookup. There are corresponding getter methods to access the list and the map data respectively.

Link to view source code: ReminderGroup.java

3.3. Local Date Picker

The calendar in the app is built using the LocalDatePicker from JFXtras.

GUI image

The date selected from this control is retrieved and is used in reminder dialog for new reminder entry. In case of no selection (or unselected) the selectedDate value will be null. In such case the selectedDate is set to today's date in this app.

The following code snippet shows the date picker creation and getting the date from it:

LocalDatePicker picker = new LocalDatePicker();
LocalDate selectedDate = picker.getLocalDate();

3.4. Reminders Table

The reminders are listed in a table. The TableView with the reminder info has all the columns for a reminder, except the notes text. The date and time columns are formatted as dd.MMM.yyyy and HH:mm respectively. The priority and completed information is rendered as CheckBoxes. The table is not editable.

The table columns can be re-positioned, resized or sorted; these are the default settings. A javafx.scene.control.Tooltip shows the reminder notes (up to first 100 characters of it) when the mouse pointer is hovered over a row.

GUI image

The table is built and populated with all the reminders at the start of the app in the AppGui as shown in the code snippet:

TableView<Reminder> table = new TableView<>();
table.setItems(dataAccess.getAllReminders());

Whenever a reminder is added, updated or deleted the database is updated and the table is refreshed with updates. The later section Reminder Functions has details.

3.4.1. Date and Time Column Formatting

The reminder date is stored as a LocalDate in the app. The date column is formatted and shown as, for example 24.Mar.2017.

The formatting is achieved by setting the javafx.scene.control.TableColumn's cell factory to provide the custom formatting the date value. A cell factory is responsible for rendering the data contained within a javafx.scene.control.TableCell for a table column. The cell factory is a javafx.util.CallBack's call() method which accepts the table column and returns a formatted cell. CallBack is a functional interface.

The following code snippet shows the date column definition and the cell configuration:

TableColumn<Reminder, LocalDate> dateCol = new TableColumn<>("Date");
dateCol.setCellFactory(column -> cellFactories.getTableCellWithDateFormatting());

The getTableCellWithDateFormatting() method of TableViewRowAndCellFactories.java returns a customized TableCell which formats the LocalDate value of the reminder. The date is formatted using the java.time.format.DateTimeFormatter:

localDate.format(DateTimeFormatter.ofPattern("dd.MMM.yyyy"))

The reminder time is defined as LocalTime. The reminder time table column is formatted in a similar way by customizing the column's cell factory; the time value is displayed for example as 10:25.

Link to view source code: TableViewRowAndCellFactories.java

3.4.2. Rendering CheckBox in Table Column

The boolean value in the priority and completed columns are rendered as a CheckBox. The boxes are checked or unchecked depending the values true or false respectively. This rendering is achieved by setting the TableColumn's cell factory to provide the custom rendering.

completedCol.setCellFactory(column -> {
    CheckBoxTableCell<Reminder, Boolean> cell = new CheckBoxTableCell<>();
    cell.setAlignment(Pos.CENTER);
    return cell;
});

The cell factory is a CallBack's call method which returns a javafx.scene.control.cell.CheckBoxTableCell which is aligned at the center of the column. The CheckBoxTableCell is a class containing a TableCell implementation that draws a CheckBox inside the cell.

3.4.3. Table Row Tooltip

A javafx.scene.control.Tooltip shows the row's reminder note text. This is achieved by setting the row factory for the table with a customized javafx.scene.control.TableRow.

table.setRowFactory(tableView -> cellFactories.getTooltipTableRow());

The row factory is a CallBack's call() method's return value, a customized TableRow to show the tooltip with notes value. cellFactories is an instance of TableViewRowAndCellFactories.java.

Link to view source code: TableViewRowAndCellFactories.java

4. Reminder Functions

At the app's start all the reminders stored in the database are retrieved and are listed in the table.

Reminders are created, updated or deleted. There are three buttons: new, update and delete. The javafx.event.EventHandlers handle the events of the three button actions respectively. Additionally, selecting a reminder row in the table and double-clicking it triggers the update function. The button click and the mouse double-click action event handlers are defined in the AppGui class.

GUI image

4.1. New Reminder

The new button click action triggers creating a new reminder.

newBtn.setOnAction(actionEvent -> newReminderRoutine());

The newReminderRoutine() method has code to show a dialog to enter the new reminder details and then insert the reminder data in the database. The following code snippet is from the newReminderRoutine():

Dialog<Reminder> dialog = reminderDialog.create(reminderDate);		
Optional<Reminder> result = dialog.showAndWait();

The reminderDate is the date selected from the LocalDatePicker control in the app. The new reminder dialog is displayed as follows:

GUI image

The dialog is created and returned from ReminderDialog.java class's create() method. This class builds the dialog with the controls and their attributes like size, tooltips, initial values and validation criteria.

The dialog's showAndWait() method displays the dialog and waits for user response (click Okay button or click 'x' to cancel). As the Okay button is pressed the dialog's data are validated and in case of not valid data a modal alert dialog displays an appropriate message (as shown below).

GUI image

If the entered data is valid, a new Reminder instance is built and is returned as a java.util.Optional<Reminder> value. The Reminder's info is added to the database and the app's table is refreshed to show the newly created reminder.

In the following code snippet, result is the Optional<Reminder>.

if (result.isPresent()) {
    dataAccess.addReminder(result.get());
    refreshTable();
    ...
}

Link to view source code: ReminderDialog.java

4.2. Update Reminder

The update action is triggered by either clicking the update button or double-clicking the reminder row in the table. The update dialog is similar to that of the new function, except that it has an additional control - the completed check box.

GUI image

The mouse double-click event is set for the table: table.setOnMousePressed() method accepts a function to be called when a mouse button is pressed on this node.

table.setOnMousePressed((MouseEvent me) -> {
    if ((me.isPrimaryButtonDown()) && (me.getClickCount() == 2)) {
        updateReminderRoutine();
    ...

The updateReminderRoutine() method is common for the update button press action event and mouse double-click event; the following is the code snippet from the method:

Reminder rem = table.getSelectionModel().getSelectedItem();
int ix = dataAccess.getAllReminders().indexOf(rem);
...
Dialog<Reminder> dialog = reminderDialog.create(rem);
Optional<Reminder> result = dialog.showAndWait();
		
if (result.isPresent()) {
    dataAccess.updateReminder(ix, result.get());
    refreshTable();
    ...

The updated reminder information is shown in the refreshed table in the app.

4.3. Delete Reminder

The delete button when clicked prompts a confirm alert to delete the selected reminder and on confirmation deletes the reminder from the app's table and the database.

The following code snippets show the delete button configuration and the deleteReminderRoutine() method which is triggered from clicking the delete button respectively.

Button delBtn = new Button("Delete");
delBtn.setOnAction(actionEvent -> deleteReminderRoutine());
Reminder rem = table.getSelectionModel().getSelectedItem();
Alert confirmAlert = getConfirmAlertForDelete(rem);
Optional<ButtonType> result = confirmAlert.showAndWait();
if ((result.isPresent()) && (result.get() == ButtonType.OK)) {
    dataAccess.deleteReminder(rem);
    ...

4.4. Reminder Notification Mechanism

The reminders when due are shown in an alert dialog.

GUI image

The notification mechanism is implemented using the Timer and TimerTask API of java.util package. Timer is a facility for threads to schedule tasks for future execution in a background thread. Tasks may be scheduled for one-time execution, or for repeated execution at regular intervals.

The task is defined by extending a TimerTask. This is an abstract class which implements Runnable; the run() method has code for the action of this task.

There are two tasks defined for reminder notification. The first one is run once at the start of the app; this checks for all the overdue reminders and shows them in reminder alerts.

The next task is scheduled to run at a regular interval, every minute for the duration of the app. This checks for the reminder due at that minute and shows them in the alerts. It is possible to show multiple reminders at the same time.

The tasks are initiated at the start of the app in the AppGui as shown in the following code snippet.

Timer timer = new Timer();
CheckRemindersTask tasks = new CheckRemindersTask();
timer.scheduleAtFixedRate(tasks, zeroDelay, periodOneMinute);

4.4.1. Reminders Task

The class CheckRemindersTask.java extends the TimerTask and overrides the run() abstract method. The run() method has code to check for due or overdue reminders and display the reminder alert.

In the following code snippet the getDueRems() method retrieves all the due reminders based on the supplied predicate. Initially, the predicate value is set as: Predicate<Reminder> predicate = ReminderPredicates.OVER_DUE; this notifies all the due reminders until the start of the app, all overdue reminders.

public void run() {
    List<Reminder> dueRems = getDueRems();
    showNotifications(dueRems);
    predicate = ReminderPredicates.DUE_NOW;
}

private List<Reminder> getDueRems() {
    ObservableList<Reminder> rems = dataClass.getAllReminders();
    return rems.stream()
        .filter(predicate)
        .collect(Collectors.toList());
}

After the first run of the task, the predicate is reset to ReminderPredicates.DUE_NOW and is run at a regular interval.

Link to view source code: CheckRemindersTask.java

4.5. Reminder Predicates

A java.util.function.Predicate<T> is a functional interface and represents a predicate (a boolean-valued function) of one argument (T is the input type).

This app uses predicates to filter reminders in two functions: (i) To display filtered reminders based on selecting a reminder group, and (ii) in the reminder notification task to get due reminders. These predicates are defined in a class ReminderPredicates.java and are accessed as public static members. The following is a code snippet from the class:

public static final Predicate<Reminder> COMPLETED = r ->
        (r.getCompleted() == true);
public static final Predicate<Reminder> PRIORITY = r ->
        (r.getPriority() == true);
public static final Predicate<Reminder> TODAYS = r ->
        r.getDate().isEqual(LocalDate.now());
private static final Predicate<Reminder> TIME_NOW = r ->
        r.getTime().equals(LocalTime.now().truncatedTo(ChronoUnit.MINUTES));
public static final Predicate<Reminder> DUE_NOW =
        TODAYS.and(TIME_NOW).and(COMPLETED.negate());
...

Link to view source code: ReminderPredicates.java

4.6. Data Access

The class DataAccess.java provides access to the data in the app. There are methods to get reminders, add, update or delete reminders. These methods are used with the app functions.

This class acts as a data access layer and interacts with the database access code. This class also maintains all the reminders in a collection.

The following code snippet shows the reference to the database access class DatabaseJdbcAccess, the local reminder store, the read and insert methods.

private DatabaseJdbcAccess dbAccess;
private ObservableList<Reminder> remData;

public ObservableList<Reminder> getAllReminders() {
    return remData;
}
public void addReminder(Reminder rem) {
    remData.add(rem);
    dbAccess.addReminder(rem);
}

Link to view source code: DataAccess.java

4.6.1. Data for Reminder Group

In the app, when an item (group) is selected in the reminder group list, the corresponding group's reminders are shown in the app's table. A group's reminders are arrived at in the getTableDataForGroup() method of DataAccess class.

The following is the code snippet from the javafx.beans.value.ChangeListener's changed() method of reminder group ListView.

String groupStr = list.getItems().get(ix); // ix = selected list item's index
ReminderGroup group = ReminderGroup.getGroup(groupStr);
table.setItems(DataAccess.getTableDataForGroup(group));

The following is the code snippet from the DataAccess's getTableDataForGroup() method:

public ObservableList<Reminder> getTableDataForGroup(ReminderGroup group) {
    Predicate<Reminder> remPredicate = null;
    switch (group) {
        case PRIORITY:
            remPredicate = ReminderPredicates.PRIORITY;
            break;
        case TODAY:
            remPredicate = ReminderPredicates.TODAYS;
            break;
        ...
        default:
            remPredicate = ReminderPredicates.ALL;
    }
    List<Reminder> result = remData.stream()
                                    .filter(remPredicate)
                                    .collect(Collectors.toList());
    return FXCollections.observableList(result);
}

5. Database

The HSQL database is used for storing the reminders data. HSQLDB (HyperSQL DataBase) is a SQL relational database software written in Java and runs in a JVM. It is a small, fast, multithreaded and transactional database engine and supports embedded as well as server modes. It includes a JDBC driver.

5.1. Download and Install

Download the database software from the download link at the website http://hsqldb.org/. In this app, the HSQLDB version 2.3.2 is used. The downloaded file is a ZIP file. Extract the ZIP file into any directory of your choice. The ZIP file is extracted into a folder hsqldb-2.3.2\hsqldb. This is the home (or install) directory.

This completes the installation. The installed database has user documentation, JDBC driver, database executables and utility programs. The install directory has /doc and /lib directories.

The doc directory has the user guides. The lib directory has the following JAR files used commonly:

5.2. Create Reminders Database

The GUI database access tool is used to create and access the database. From the Windows DOS command prompt run this:

> java -cp hsqldb.jar org.hsqldb.util.DatabaseManagerSwing

This opens a Connect GUI dialog as shown below. Note that the hsqldb.jar file is to be in the classpath.

GUI image
Enter or select the following information into the dialog:

NOTE on URL's filepath: The filepath can be specified as a relative or an absolute path. The relative path is relative to the current directory; for example jdbc:hsqldb:file:db\remindersDB in the URL creates a directory called db and the remindersDB database in it. An example with absolute path is jdbc:hsqldb:file:D:\jqp\examples\db\remindersDB.

Click Ok. This creates a database named remindersDB in the specified directory. This also opens the HSQL Database Manager window. The window has areas showing the database structure, SQL entry and result details. The window is shown below in the following section.

IMPORTANT: The Driver, URL, User and Password information entered above in Connect dialog must be used to configure the DataSource object in the app (details follow in later section - Database Access using Spring JDBC).

5.3. Create Reminders Table

In the HSQL Database Manager window enter the following SQL DDL script and execute it.

CREATE TABLE REMINDERS_TABLE (
    name VARCHAR(50) NOT NULL,
    notes VARCHAR(500) NOT NULL,
    date DATE NOT NULL,
    time TIME NOT NULL,
    priority BOOLEAN NOT NULL,
    completed BOOLEAN NOT NULL,
        PRIMARY KEY (name, date))
);

This creates the reminders data table. The newly created table can be viewed in the database structure area as seen in the picture below.

GUI image

IMPORTANT: Note that this step need to be completed before running the app. The app's code assumes that the reminders database and the table are created.

6. Spring Configuration

The Spring Framework (https://spring.io/) is an open source application framework for Java. The framework's core (core-module) features are used with this application. Spring's JDBC data access module is used to work with the database access.

The software can be download from this link: http://repo.spring.io/release/org/springframework/spring/. Find the release you need and download the 'dist' ZIP file; this has the JAR files and the apidocs.

The application uses the 4.3.5.RELEASE version of Spring.

6.1. Spring Java Configuration

The Spring container takes the responsibility of creating the beans in the application and co-ordinating the relationships between those objects via dependency injection. In this app the bean configuration is specified using Java-based configuration.

The annotations used are @Configuration and @Bean of org.springframework.context.annotation package. Annotating a class with @Configuration indicates that its primary purpose is a source of bean definitions. The @Bean annotation is used to indicate that a method instantiates and configures a new object to be managed by the Spring container.

The app defines two configuration classes: AppConfig.java and DatabaseJdbcConfig.java. The AppConfig is the primary configuration and imports database related bean definitions from the DatabaseJdbcConfig. The configurations use constructor-based dependency injection.

The following is a code snippet from the AppConfig configuration class:

@Configuration
@Import(DatabaseJdbcConfig.class)
public class AppConfig {
    @Bean public AppGui appGui(DataAccess dataAccess,
            CheckRemindersTask checkRemindersTask, ReminderDialog reminderDialog) {
        return new AppGui(dataAccess, checkRemindersTask, reminderDialog);
    }
    @Bean public ReminderDialog reminderDialog(DataAccess dataAccess) {
        return new ReminderDialog(dataAccess);
    }
    ...

Link to view source code: AppConfig.java

6.2. Database Access using Spring JDBC

The app uses Spring JDBC to connect and access the reminders database. The API used in this app are org.springframework.jdbc.core.JdbcTemplate and RowMapper. These are used for the create, update, delete and read database operations.

The classes related to the database access and their dependencies are configured in a Spring's Java-configuration class DatabaseJdbcConfig. The configuration defines and produces the beans including java.sql.DataSource, JdbcTemplate and the DatabaseJdbcAccess.

The following code snippet shows the configuration of DataSource in the DatabaseJdbcConfig:

@Configuration public class DatabaseJdbcConfig {
    private static final String JDBC_DRIVER = "org.hsqldb.jdbc.JDBCDriver";
    /* ifexists=true connection property disallows creating a new database.*/
    private static final String CONNECTION_URL =
        "jdbc:hsqldb:file:db/remindersDB;ifexists=true;";
    @Bean
    public DataSource dataSource() {
        SingleConnectionDataSource ds = new SingleConnectionDataSource();
        ds.setDriverClassName(JDBC_DRIVER);
        ds.setUrl(CONNECTION_URL);
        ds.setUsername(""); ds.setPassword("");
        return ds;
    } ...

Note that in the above code the driver, URL, user and password are same as the ones used in creating the reminders database, in Database section.

In the above code snippet the DataSource is an implementation of org.springframework.jdbc.datasource.SingleConnectionDataSource. This wraps a single JDBC java.sql.Connection which is not closed after use. In this app, the database shutdown routine in DatabaseJdbcAccess uses the open connection to close the database.

Link to view source code: DatabaseJdbcConfig.java

6.2.1. Database Jdbc Access

The DatabaseJdbcAccess.java class has methods for querying and updating the reminders data in the database using the Spring JDBC. The following code snippet shows the functions to query all and insert the reminders respectively:

public List<Reminder> getAllReminders() {
    return jdbcTemplate.query("SELECT * FROM REMINDERS_TABLE",
        new RowMapper<Reminder>() {
            @Override public Reminder mapRow(ResultSet rs, int rowNum)
                    throws SQLException {
                Reminder r = new Reminder();
                r.setName(rs.getString("name"));
                r.setNotes(rs.getString("notes"));
                r.setDate(rs.getDate("date").toLocalDate());
                ...
                return r;
            }
        ...
public void addReminder(Reminder r) {
    jdbcTemplate.update("INSERT INTO REMINDERS_TABLE  VALUES (?, ?, ?, ?, ?, ?)",
        r.getName(),
        r.getNotes(),
        ...
        r.getCompleted());
}

Link to view source code: DatabaseJdbcAccess.java

6.2.2. Date and Time Data Conversion

The reminder's date and time are stored in the reminders database table of HSQLDB are of type Date and Time respectively. These are compatible with the java.sql.Date and Time respectively.

The app captures the reminder date and time as java.time.LocalDate and LocalTime respectively. The LocalDate to database's Date and vice-versa conversion is done using the following java.sql.Date class's methods, for example:

LocalDate input = LocalDate.now();
Date date = Date.valueOf(input);
LocalDate localDate = date.toLocalDate();

The java.sql.Time class has methods with similar function: valueOf() and toLocalTime().

6.3. Database Exception Handling

The app handles the database exceptions. Spring's org.springframework.dao.DataAccessException, an unchecked exception, is thrown by the database access JDBC API.

The app's database access is for getting all reminders, adding, updating and deleting reminders. The DataAccessException is handled in the app's GUI layer, in the AppGui class. Whenever there is a database related exception an error alert is displayed with a message and then the app is closed.

7. Application Starter

The AppStarter.java launches the reminders app.

The class extends javafx.application.Application and overrides its start() abstract method, which is the main entry point for the JavaFX application. The start method has code to load the Spring's application context and show the GUI.

@Override public void start(Stage primaryStage) {
    context = new AnnotationConfigApplicationContext(AppConfig.class);
    AppGui appGui = context.getBean(AppGui.class);
    Parent mainView = appGui.getView();
    primaryStage.setScene(new Scene(mainView, 925, 450));
    primaryStage.show();
}

This class also has code that runs at the application close, to destroy any open resources. The Application class's overridden stop() method has code to close the Spring's application context, shutdown the database and cancel the reminders task timer.

@Override public void stop() {
    context.getBean(DatabaseJdbcAccess.class).shutdownDatabase();
    context.getBean(AppGui.class).getTimer().cancel();
    context.close();
}

Link to view source code: AppStarter.java

A screenshot of the reminder app:

GUI image

8. Download

Download the Java source code for the example: reminder-app.zip

9. Useful Links

Return to top


Comments

Comments are welcome. Please note that comments are moderated.
Email to: info(at)javaquizplayer(dot)com.