Search code examples
javafxduplicatesuniqueobservablelist

Preventing duplicate entries in ObservableList


I can't seem to get it right. I have a listbox with category items. I also have a data model that defines the ID, Name, Lastname, Category.. and an ObservableList that holds the data. I'm trying to update the existing object in case the user click on the same item in the listbox and change name, lastname.

Here is my code:

public class FXMLDocController implements Initializable {

    ObservableList<String> listitems = FXCollections.observableArrayList(
                                        "Visual Basic", "ASP.net", "JavaFX");
    ObservableList<Persons> personData = FXCollections.observableArrayList();
    Persons pp = new Persons();

    private Label label;
    @FXML
    private TextField txtName;
    @FXML
    private TextField txtLastName;
    @FXML
    private Button btnSave;
    @FXML
    private TextArea txtArea;
    @FXML
    private ListView<String> listview = new ListView<String>();
    @FXML
    private Button btnTest;
    @FXML
    private Label lblCategory;
    @FXML
    private Label lblIndex;



    @Override
    public void initialize(URL url, ResourceBundle rb) {
        listview.setItems(listitems);

    }    

    @FXML
    private void handleSave(ActionEvent event) {

        String category = lblCategory.getText();
        boolean duplicate = false;
        //Add data. Check first if personData is empty

                if (personData.isEmpty()){
                    pp = new Persons(Integer.valueOf(lblIndex.getText()),category,txtName.getText(),txtLastName.getText());
                    personData.add(pp);
                }else{
                    for(int i = 0 ; i<personData.size() ; i ++){
                         if(Integer.toString(personData.get(i).getID()).equals(lblIndex.getText())){
                            duplicate = true;
                         }else{ 
                            duplicate = false;

                        }
                    }
                    System.out.println(duplicate);
                    if (duplicate == false){
                        pp = new Persons(Integer.valueOf(lblIndex.getText()),category,txtName.getText(),txtLastName.getText());
                        personData.add(pp);
                    }else{
                        System.out.println("Duplicate");
                    // Do Update later. 

                    }

                }

        //Show data to Test

            System.out.println("-- START OF LIST --");
        for (Persons person : personData){

            System.out.println(person.getID() + " " + person.getCategory()+ " " + person.getName() + " " + person.getLastname() + " " );
        }
        System.err.println(" ");
    }


    @FXML
    private void handleListClick(MouseEvent event) {
        lblCategory.setText(listview.getSelectionModel().getSelectedItem());
        lblIndex.setText(String.valueOf(listview.getSelectionModel().getSelectedIndex()));
    }
}

If I click multiple times on one item on the listbox it works well.. but if for example I click on Visual Basic, then ASP.net the go back to Visual Basic it still accepts it. :( Need advice and help. Please

Here is the code based on Phil's suggestion

@FXML
private void handleSave(ActionEvent event) {

    String category = lblCategory.getText();
    boolean duplicate = false;
    int x = 0;
    //Add data

           Persons newPerson = new Persons(Integer.valueOf(lblIndex.getText()), category, txtName.getText(), txtLastName.getText());
             if (!personData.contains(newPerson)) {
                personData.add(newPerson);

             }else{
                 System.out.println("Duplicate!");
             }


    //Show data

        System.out.println("-- START OF LIST --");
    for (Persons person : personData){

        System.out.println(person.getID() + " " + person.getCategory()+ " " + person.getName() + " " + person.getLastname() + " " );
    }

}

here's my Persons Class

public class Persons {
private SimpleIntegerProperty id;
private SimpleStringProperty name;
private SimpleStringProperty lastname;
private SimpleStringProperty category;



public Persons(){}
public Persons(int id, String category, String name, String lastname){
    this.id = new SimpleIntegerProperty(id);
    this.name = new SimpleStringProperty(name);
    this.lastname = new SimpleStringProperty(lastname);
    this.category = new SimpleStringProperty(category);


}
public Persons(String name, String lastname){
    this.name = new SimpleStringProperty(name);
    this.lastname = new SimpleStringProperty(lastname);
}

@Override
public boolean equals(Object o){
    if (o == this) return true;
    if (!(o instanceof Persons)){
        return false;
    }
    Persons persons = (Persons) o;

    return persons.id.equals(id) &&
            persons.name.equals(name) && 
            persons.lastname.equals(lastname) &&
            persons.category.equals(category);

}


//SETTERS
public void setID(int id) {
    this.id = new SimpleIntegerProperty(id);
}
public void setName(String name) {
    this.name = new SimpleStringProperty(name);
}
public void setLastname(String lastname) {
    this.lastname = new SimpleStringProperty(lastname);
}
public void setCategory(String category) {
    this.category = new SimpleStringProperty(category);
}

//GETTERS

public int getID() {
    return id.getValue();
}

public String getName() {
    return name.getValue();
}

public String getLastname() {
    return lastname.getValue();
}
public String getCategory(){
    return category.getValue();
}

// PROPERTIES

public SimpleIntegerProperty idProperty(){
    return this.id;
}
public SimpleStringProperty nameProperty(){
    return this.name;
}
public SimpleStringProperty lastnameProperty(){
    return this.lastname;
}
public SimpleStringProperty categoryProperty(){
    return this.category;
}

}


Solution

  • Ok, this looks good. Just one little thing in your equals implementation:

    @Override
    public boolean equals(Object o) {
        if (o == this) return true;
        if (!(o instanceof Persons)) {
            return false;
        }
        Persons persons = (Persons) o;
    
        // persons.id.equals() leads to the default implementation in Object 
        // --> instead use this one.
        // The Property classes have their own isEqualTo method
        // with get(), you will get your simple boolean from the returned BooleanBinding
        return persons.id.isEqualTo(id).get() &&
            persons.name.isEqualTo(name).get() &&
            persons.lastname.isEqualTo(lastname).get() &&
            persons.category.isEqualTo(category).get();
    
    }
    

    The default equals implementation from Object just compares if it is the same instance. And we are creating a new instance and check with contains(...) if the list contains the Persons.

    Here is the whole code I used to test it:

    public class Main extends Application {
    
        private ObservableList<String> listitems = FXCollections.observableArrayList("Visual Basic", "ASP.net", "JavaFX");
        private ObservableList<Persons> personData = FXCollections.observableArrayList();
    
        private TextField txtName = new TextField();
        private TextField txtLastName = new TextField();
        private Button btnSave = new Button("save");
        private ListView<String> listview = new ListView<>();
        private Label lblCategory = new Label();
        private Label lblIndex = new Label();
    
        public static void main(String[] args) {
            launch(args);
        }
    
        @Override
        public void start(Stage stage) {
            listview.setItems(listitems);
            listview.setOnMouseClicked(this::handleListClick);
            btnSave.setOnAction(this::handleSave);
    
            VBox vb = new VBox(new HBox(5, new Label("Index:"), lblIndex),
                new HBox(5, new Label("Category:"), lblCategory),
                new HBox(5, new Label("Name:"), txtName),
                new HBox(5, new Label("Last name:"), txtLastName)
            );
    
            BorderPane bp = new BorderPane();
            bp.setLeft(listview);
            bp.setCenter(vb);
            bp.setRight(btnSave);
    
            Scene scene = new Scene(bp, 600, 400);
            stage.setScene(scene);
            stage.show();
        }
    
    
        private void handleSave(ActionEvent event) {
            Persons newPerson = new Persons(Integer.valueOf(lblIndex.getText()), lblCategory.getText(), txtName.getText(), txtLastName.getText());
            if (!personData.contains(newPerson)) {
                personData.add(newPerson);
            } else {
                System.out.println("Duplicate!");
            }
    
            System.out.println("-- START OF LIST --");
            for (Persons person : personData) {
                System.out.println(person);
            }
    
        }
    
        private void handleListClick(MouseEvent event) {
            System.out.println("click");
            lblCategory.setText(listview.getSelectionModel().getSelectedItem());
            lblIndex.setText(String.valueOf(listview.getSelectionModel().getSelectedIndex()));
        }
    
    
        public class Persons {
            SimpleIntegerProperty id;
            SimpleStringProperty name;
            SimpleStringProperty lastname;
            SimpleStringProperty category;
    
            Persons(int id, String category, String name, String lastname) {
                this.id = new SimpleIntegerProperty(id);
                this.name = new SimpleStringProperty(name);
                this.lastname = new SimpleStringProperty(lastname);
                this.category = new SimpleStringProperty(category);
            }
    
            @Override
            public boolean equals(Object o) {
                if (o == this) return true;
                if (!(o instanceof Persons)) {
                    return false;
                }
                Persons persons = (Persons) o;
    
                // persons.id.equals() leads to the default implementation in Object
                // --> instead use this one.
                // The Property classes have their own isEqualTo method
                // with get(), you will get your simple boolean from the returned BooleanBinding
                return persons.id.isEqualTo(id).get() &&
                    persons.name.isEqualTo(name).get() &&
                    persons.lastname.isEqualTo(lastname).get() &&
                    persons.category.isEqualTo(category).get();
    
            }
    
            @Override
            public String toString() {
                return "Persons{" +
                    "id=" + id +
                    ", name=" + name +
                    ", lastname=" + lastname +
                    ", category=" + category +
                    '}';
            }
        }
    
    }