Search code examples
javaserializationjavafxserializableobservablelist

How to implement Serializable for my project to have persistence?


My main method:

public class ToDoList extends Application{

public static void main(String[] args) {
    launch(args);
}

public void start(Stage primaryStage) throws Exception {

    Pane pane = FXMLLoader.load(getClass().getResource("ToDoList.fxml"));

    Scene scene = new Scene(pane);
    scene.getStylesheets().add(getClass().getResource("ToDoListStyle.css").toExternalForm());
    primaryStage.setScene(scene);
    primaryStage.setTitle("Plan yourself");
    primaryStage.show();
}

}

Then a part of controller:

    ObservableList<EventsBean> dataList = FXCollections.observableArrayList();

@Override
public void initialize(URL location, ResourceBundle resources) {
    System.out.println("The pane loaded");

    List<String> myList;
    try {
        myList = Files.lines(Paths.get("src/com/todolist/EventsList.txt")).collect(Collectors.toList());
        eventsSelector.setItems(FXCollections.observableArrayList(myList));
    } catch (IOException e) {
        System.out.println("Don t find file");
    }

    removeCol.setCellFactory(CheckBoxTableCell.forTableColumn(removeCol));
    eventCol.setCellValueFactory(new PropertyValueFactory<EventsBean, String>("event"));
    dateCol.setCellValueFactory(new PropertyValueFactory<EventsBean, LocalDate>("date"));
    doneCol.setCellValueFactory(new PropertyValueFactory<EventsBean, String>("done"));
    doneCol.setCellFactory(TextFieldTableCell.<EventsBean>forTableColumn());
    doneCol.setOnEditCommit((CellEditEvent<EventsBean, String> t) -> {
        ((EventsBean) t.getTableView().getItems().get(t.getTablePosition().getRow()))
                .setDone(t.getNewValue());
    });
    observationCol.setCellValueFactory(new PropertyValueFactory<EventsBean, String>("observation"));
    removeCol.setCellValueFactory(cellData -> cellData.getValue().selectedProperty());
    observationCol.setCellFactory(TextFieldTableCell.<EventsBean>forTableColumn());
    observationCol.setOnEditCommit((CellEditEvent<EventsBean, String> t) -> {
        ((EventsBean) t.getTableView().getItems().get(t.getTablePosition().getRow()))
                .setObservation(t.getNewValue());
    });
    observationCol.setSortable(false);

    eventsTable.setItems(dataList);
    eventsTable.setEditable(true);

    bttnAddEvent.setOnAction((ActionEvent e) -> {
        try {
            text = eventsSelector.getValue().toString();
            dataList.add(new EventsBean(text, isoDate, "",  "", false));
        } catch (Exception e1) {
            System.out.println("Nothing selected");
        }
    });

    bttnRemove.setOnAction((ActionEvent e) -> {
        ObservableList<EventsBean> dataListToRemove = FXCollections.observableArrayList();
        for (EventsBean bean : dataList) {
            if (bean.getSelected()) {
                dataListToRemove.add(bean);
            }
        }
        dataList.removeAll(dataListToRemove);

        // Below code it is for delete a focused row
        // EventsBean selectedItem = eventsTable.getSelectionModel().getSelectedItem();
        // eventsTable.getItems().remove(selectedItem);
    });

}

Then the EventsBean:

public class EventsBean {


private SimpleStringProperty event;
private SimpleObjectProperty<LocalDate> date;
private SimpleStringProperty done;
private SimpleStringProperty observation;
private SimpleBooleanProperty selected;

public EventsBean(String event, LocalDate date, String done, String observation, boolean selected) {
    this.event = new SimpleStringProperty(event);
    this.date = new SimpleObjectProperty<LocalDate>(date);
    this.done = new SimpleStringProperty(done);
    this.observation = new SimpleStringProperty(observation);
    this.selected = new SimpleBooleanProperty(selected);
}
// Getters and Setters ...

I want to serialize the data in the table. The serializable object I think is dataList from ObservableList. My problem is where to implement the Serializable interface? This are the best solution in my case for persistence?

Thank you in advance!


Solution

  • You should first ask yourself if you really want to use object serialization here. It might be better to use a different persistence approach: e.g. use a JSON marshaling library such as GSON. Most such libraries just use an object's get and set methods to find the data, so would likely work with your EventsBean class with little additional code.

    If you do want to use Java object serialization, you need to make the EventsBean class Serializable. Normally this is straightforward, but the problem you will run into is that the JavaFX Properties classes do not implement Serializable, so naïvely making EventsBean implement Serializable and attempting to serialize instances of it will generate runtime exceptions. You need to use custom serialization to do this.

    First, make the JavaFX properties transient, so the default serialization mechanism does not attempt to serialize them, and then define custom readObject and writeObject methods to define how the objects should be serialized:

    public class EventsBean implements Serializable {
    
    
        private transient SimpleStringProperty event;
        private transient SimpleObjectProperty<LocalDate> date;
        private transient SimpleStringProperty done;
        private transient SimpleStringProperty observation;
        private transient SimpleBooleanProperty selected;
    
        // constructors...
    
        // example get/set/property methods:
        public StringProperty eventProperty() {
            return event ;
        }
    
        public final String getEvent() {
            return eventProperty().get();
        }
    
        public final void setEvent(String event) {
            eventProperty().set(event);
        }
    
        // etc. for other properties...
    
    
        // custom serialization:
        private void writeObject(ObjectOutputStream s) throws IOException {
            s.defaultWriteObject();
            s.writeObject(getEvent()); // write event as a plain String
            s.writeObject(getDate()); 
            s.writeObject(getDone());
            s.writeObject(getObservation());
            s.writeBoolean(isSelected());
        }
    
        // custom deserialization:
        private void readObject(ObjectInputStream s) throws IOException {
            s.defaultReadObject();
            this.event = new SimpleStringProperty((String)s.readObject());
            this.data = new SimpleObjectProperty<>((LocalDate)s.readObject());
            this.done = new SimpleStringProperty((String)s.readObject());
            this.observation = new SimpleStringProperty((String)s.readObject());
            this.selected = new SimpleBooleanProperty(s.readBoolean());
        }
    }
    

    Finally, you also need to note that ObservableList implementations are typically not serializable, so you need to serialize a "regular" list in order to serialize the data, i.e.:

    ObjectOutputStream s = ... ;
    s.writeObject(new ArrayList<EventsBean>(dataList));
    

    and to read it back:

    ObjectInputStream s = ... ;
    dataList.setAll((List<EventsBean>)s.readObject());