2

I'm creating simple JavaFX application. I want my model layer to be completely independent from JavaFX - no StringProperty, IntegerProperty and etc. as fields. I want it to be POJO. Main reason to do so is that I want it to be Serializable. I've created DataRepository - simple CRUD-like interface and some implementations of it, so I can at anytime change where I store my data - XML file, SQLite database or anything else. I also have to somehow connect my data storage with JavaFX (to display its content in TableView), so I decided to create my implementation of ObservableList which wraps my repository. My question is - is there any other way? ObservableList contains about 30 methods to implement and it looks like I'm doing something wrong.

My (simplified) model:

public class Movie implements Serializable {

    private String title;
    private String director;

    public Movie() {

    }

    public Movie(String title, String director) {
        this.title = title;
        this.director = director;
    }

    // Getters and setters, equals etc...
}

MovieRepository:

public interface MovieRepository {

    public void add(Movie movie);

    public void remove(String title);

    public void remove(int index);

    public Movie get(String title);

    public Movie get(int index);

    public List<Movie> getAll();
}

Controller for my main view:

public class MainController {

    @FXML
    private TableView<Movie> movieTable;
    @FXML
    private TableColumn<Movie, String> movieTitleColumn;
    @FXML
    private Label titleLabel;

    private MovieRepository movies = new DBMovieRepository(); //MovieRepository implementation which uses SQLite DB to store data
    private MainApp app;

    @FXML
    private void initialize() {
        movieTable.setItems(new ObservableMovies(movies));
        // ObservableMovies is my implementation of ObservableList
        // It basically wraps methods from MovieRepository 
        // and notifies listeners
        showMovieDetails(null);

        movieTitleColumn.setCellValueFactory(cellData -> new ReadOnlyStringWrapper(cellData.getValue().getTitle()));
        movieTable.getSelectionModel().selectedItemProperty()
        .addListener((observable, oldValue, newValue) -> showMovieDetails(newValue));
    }

    private void showMovieDetails(Movie movie) {
        if(movie != null) {
            titleLabel.setText(movie.getTitle());
        } else {
            titleLabel.setText("");
        }
    }

    @FXML
    private void handleNew() {
        Movie movie = new Movie();
        app.showNewMovieDialog(movie);
        movieTable.getItems().add(movie);
    }

    public void setApp(MainApp app) {
        this.app = app;
    }
}
Sebastian Osiński
  • 2,894
  • 3
  • 22
  • 34
  • 1
    Why don't just mark your properties as `transient`? Then you just need to wrap them around you values and it will be both JavaFX compilant and `Serializable`. Also I didn't understand, why you would implement your own `ObversableList`. There are methods in `FXCollections` to create those from plain `java.util.Collections`. But if you really need to implement it, you can inherit from `ObservableListBase`. – Zhedar Oct 31 '15 at 14:50

2 Answers2

3

You have a couple of options here (maybe more), which are covered in other questions on this site. However, for convenience, I'll summarize them here too.

1. Use JavaFX Properties and make the class Serializable

You can do this with a custom serialized form. Make the JavaFX properties transient and implement readObject and writeObject to store the values they wrap:

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Objects;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class Movie implements Serializable {

    private transient StringProperty title = new SimpleStringProperty();
    private transient StringProperty director = new SimpleStringProperty();

    public Movie() {

    }

    public Movie(String title, String director) {
        setTitle(title);
        setDirector(director);
    }



    @Override
    public int hashCode() {
        return Objects.hash(getDirector(), getTitle());
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;

        Movie other = (Movie) obj;
        return Objects.equals(getTitle(), other.getTitle()) 
                && Objects.equals(getDirector(), other.getDirector());

    }

    public final StringProperty titleProperty() {
        return this.title;
    }

    public final String getTitle() {
        return this.titleProperty().get();
    }

    public final void setTitle(final String title) {
        this.titleProperty().set(title);
    }

    public final StringProperty directorProperty() {
        return this.director;
    }

    public final String getDirector() {
        return this.directorProperty().get();
    }

    public final void setDirector(final String director) {
        this.directorProperty().set(director);
    }

    private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {
        s.defaultReadObject();
        title = new SimpleStringProperty((String) s.readObject());
        director = new SimpleStringProperty((String) s.readObject());
    }

    private void writeObject(ObjectOutputStream s) throws IOException {
        s.defaultWriteObject();
        s.writeObject(getTitle());
        s.writeObject(getDirector());
    }

} 

2. Use a POJO with "bound properties".

See JavaBean wrapping with JavaFX Properties for details. In brief:

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

public class Movie {

    private String title ;
    private String director ;
    private final PropertyChangeSupport propertySupport ;

    public Movie(String title, String director) {
        this.title = title ;
        this.director = director ;
        this.propertySupport = new PropertyChangeSupport(this);
    }

    public Movie() {
        this("", "");
    }

    public String getTitle() {
        return title ;
    }

    public String setTitle(String title) {
        String oldTitle = this.title ;
        this.title = title ;
        propertySupport.firePropertyChange("title", oldTitle, title);
    }

    // similarly for director...

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        propertySupport.addPropertyChangeListener(listener);
    }

    // hashCode and equals...
}

For wanting to wrap your repository as an observable list, instead wrap it with a repository implementation that uses an observable list:

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;


public class ObservableMovieRepository implements MovieRepository {

    private final MovieRepository repository ;
    private final ObservableList<Movie> movieList;


    public ObservableMovieRepository(MovieRepository repository) {
        this.repository = repository ;
        this.movieList = FXCollections.observableArrayList(repository.getAll());
    }

    @Override
    public void add(Movie movie) {
        repository.add(movie);
        movieList.add(movie);
    }

    @Override
    public void remove(String title) {
        Movie movie = get(title);
        repository.remove(title);
        movieList.remove(title);
    }

    @Override
    public void remove(int index) {
        repository.remove(index);
        movieList.remove(index);
    }

    @Override
    public Movie get(String title) {
        return repository.get(title);
    }

    @Override
    public Movie get(int index) {
        return movieList.get(index);
    }

    @Override
    public ObservableList<Movie> getAll() {
        return movieList ;
    }

}

This uses the standard ObservableList implementation that copies an existing list on creation, and the implementation keeps that list in sync with the list in the wrapped repository. Now your UI code can do

ObservableMovieRepository movies = new ObservableMovieRepository(new DBMovieRepository());

// ...

movieTable.setItems(movies.getAll());

With the Movie class above, you would just do

movieTitleColumn.setCellValueFactory(cellData -> cellData.getValue().titleProperty());

If you use the POJO version you can do

movieTitleColumn.setCellValueFactory(cellData -> {
    try {
        return new JavaBeanStringPropertyBuilder()
            .bean(cellData.getValue())
            .name("title")
            .build();
    } catch (Exception e) { throw new RuntimeException(e); }
}
Community
  • 1
  • 1
James_D
  • 201,275
  • 16
  • 291
  • 322
  • I wanted my `DataRepository` to be a 'black-box' - when asked, it just spits out the data needed for the application, but rest the application should not know how data is stored (as I said before, it could be read from XML or database or antyhing else). So I assumed that I have to have some layer which will connect my data source with gui/controller and `ObservableList` looked like good way to go. – Sebastian Osiński Oct 31 '15 at 16:04
  • 1
    You probably need to post some code to show what you mean. I still don't see why you wouldn't just use the provided implementation. – James_D Oct 31 '15 at 16:13
  • Ok, I will try to come up with some minimal working example and I will post it in edit the question – Sebastian Osiński Oct 31 '15 at 16:14
0

There seem to be multiple question in here, so I'm not really sure, if I understood you correctly, but I will try to split it up a bit.

I want my model layer to be completely independent from JavaFX - no StringProperty, IntegerProperty and etc. as fields. I want it to be POJO.

You could mark your properties as transient. Then you just need to wrap them around your values and it will be both JavaFX compliant and Serializable. You just have to propagate changes back to your backing attributes.

I also have to somehow connect my data storage with JavaFX (to display its content in TableView), so I decided to create my implementation of ObservableList which wraps my repository. My question is - is there any other way?

Very limited information on this and I really don't know, why you would need to create your own implementation of ObservableList, but to keep it POJO, you could maintain plain java.util.Collections in your bean and provide transient ObservableLists, which you can create on creation by wrapping your java.util.Lists in your POJO. You can find those methods in the FXCollections utility class.

ObservableList contains about 30 methods to implement and it looks like I'm doing something wrong.

If you really need to implement it, you can inherit from ObservableListBase.

Zhedar
  • 3,480
  • 1
  • 21
  • 44