Search code examples
javafxioobservablelist

JavaFX: How to read and write an ObservableList to a file?


I'm new to programming, what i want to do is read/write data that are stored in a ObservableList, the data are from Student class which are Strings (firstName and lastName), and also i have a TableView to display the data.

Here is my code:

Student Class:

import java.io.Serializable;
import javafx.beans.property.SimpleStringProperty;

public class Student implements Serializable {
    private SimpleStringProperty fname;
    private SimpleStringProperty lname;

    Student() {
        this("","");
    }

    Student(String fn, String ln) {
       this.fname = new SimpleStringProperty(fn);
       this.lname = new SimpleStringProperty(ln);
    }


    public void setFirstName(String f) {
        fname.set(f);
    }
    public String getFirstName() {
        return fname.get();
    }

    public void setLastName(String l) {
        lname.set(l);
    }
    public String getLastName() {
        return lname.get();
    }


    @Override
    public String toString() {
        return String.format("%s %s", getFirstName(), getLastName());
    }
}

And here is my code for inputing data using TextFields:

    @FXML
    ObservableList<Student> data = FXCollections.observableArrayList();

    //Just to input the data
    @FXML
    private void handleButtonAction(ActionEvent event) {

        if(!"".equals(txtFirstName.getText()) && !"".equals(txtLastName.getText())){
            data.add(
                    new Student(txtFirstName.getText(),
                                txtLastName.getText()
            ));
        }

        txtFirstName.clear();
        txtLastName.clear();

        // System.out.println(data);
    }

And here is the problem...

Reading/Writing the ObservableList:

    @FXML
    private void HandleMenuSaveAction(ActionEvent event) {
         try {
            FileOutputStream f = new FileOutputStream(new File("saveStudentList.txt"));
            ObjectOutputStream o = new ObjectOutputStream(f);

            o.writeObject(data);
            o.close();
            f.close();

            System.out.println("File Saved Successfully.");

        } catch (FileNotFoundException ex) {
            System.err.println("Save: File not found.");
        } catch (IOException ex) {
            System.err.println("Save: Error initializing stream.");
            ex.printStackTrace();
        } 
    }

    @FXML
    private void HandleMenuLoadAction(ActionEvent event) {
         try {
            FileInputStream fi = new FileInputStream(new File("saveStudentList.txt"));
            ObjectInputStream oi = new ObjectInputStream(fi);

            data = (ObservableList) oi.readObject();

            System.out.println(data.toString());

            //Refresh the Table everytime we load data

            oi.close();
            fi.close();


        } catch (FileNotFoundException ex) {
            System.err.println("Load: File not found.");
        } catch (IOException ex) {
            System.err.println("Load: Error initializing stream.");
        } catch (ClassNotFoundException ex) {
            ex.printStackTrace();
        }
    }

This is causing me java.io.NotSerializableException, Does anyone have an idea how to change my code so that it works?


Solution

  • implement custom serialisation for the Student object (see https://stackoverflow.com/a/7290812/2991525) and copy the contents of the ObservableList to a ArrayList to create a serializable list:

    public class Student implements Serializable {
    
        private void writeObject(ObjectOutputStream out)
                throws IOException {
            out.writeObject(getFirstName());
            out.writeObject(getLastName());
        }
    
        private void readObject(ObjectInputStream in)
                throws IOException, ClassNotFoundException {
            fname = new SimpleStringProperty((String) in.readObject());
            lname = new SimpleStringProperty((String) in.readObject());
        }
    
        ...
    }
    

    (De)serialisation example

    ObservableList<Student> students = FXCollections.observableArrayList();
    
    for(int i = 0; i < 100; i++) {
        students.add(new Student("Mark"+i, "Miller"+i));
    }
    
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    
    // write list
    try (ObjectOutputStream oos = new ObjectOutputStream(bos)) {
        oos.writeObject(new ArrayList<>(students));
    }
    
    students = null; // make sure the old reference is no longer available
    
    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    
    // read list
    try (ObjectInputStream ois = new ObjectInputStream(bis)){
        students = FXCollections.observableList((List<Student>) ois.readObject());
    }
    
    System.out.println(students);