Javafx observable list of values that are themselves observable?

right now I have the following:

ObservableList<Person> personsList;

and my UI for displaying the person list is tied to personsList.

Person is something like below:

class Person {
    Name name
    // other details
    List<SomeItem> list;
}

// Item is immutable, but SomeItem can mutate by setting and getting the Items
class SomeItem {
    Item item
    Item item2
}

The issue is SomeItem is mutable, so I would want any changes to SomeItem be propagated to the original personsList.

How would I achieve something like that?? Based on googling I kind of have the following modifications, but I am not sure if they work!

class Person {
    Name name
    // other details
    List<SomeItem> list;  // <-- change to ObservableListValue<SomeItem>
}

// Item is immutable, but SomeItem can mutate by setting and getting the Items
class SomeItem {
    Item item   // <-- change to ObservablePropertyBase<Item>
    Item item2  // <-- change to ObservablePropertyBase<Item>
}

From what I understand, this changes would make SomeItem report when any ObservablePropertyBase<Item> changes, and then ObservableListValue<SomeItem> would propagate this changes up, which would be caught by personsList?

Edit: Question 2: Is it possible to force refresh personsList? Lets say I make an overall update to a specific SomeItem, then I can refresh the entire personsList?

Answer

You can fire Change events by creating an ObservableList with an extractor.

Here is an example:

Name.java:

public class Name {
   
    private final String name;
   
    public Name(String name) {
        this.name = name;
    }
   
    public final String getName() {
        return name;
    }

}

Item.java:

public class Item {
    
    private final String name;
    
    public Item(String name) {
        this.name = name;
    }
   
    public final String getName() {
        return name;
    }
    
    @Override
    public String toString() {
        return name;
    }

}

SomeItem.java:

public class SomeItem {
    
    private final ObjectProperty<Item> item1 = new SimpleObjectProperty<>(this, "item1");
    private final ObjectProperty<Item> item2 = new SimpleObjectProperty<>(this, "item2");
    
    public SomeItem(Item item1, Item item2) {
        this.item1.set(item1);
        this.item2.set(item2);
    }
    
    public final ObjectProperty<Item> item1Property() {
        return item1;
    }
    
    public final Item getItem1() {
        return item1.get();
    }
    
    public final void setItem1(Item item) {
        item1.set(item);
    }
    
    public final ObjectProperty<Item> item2Property() {
        return item2;
    }
    
    public final Item getItem2() {
        return item2.get();
    }
    
    public final void setItem2(Item item) {
        item2.set(item);
    }
    
    @Override
    public String toString() {
        return "[" + item1.get() + ", " + item2.get() + "]";
    }
    
}

Person.java:

public class Person {
    
    private final Name name;
    
    private final ObservableList<SomeItem> someItems = FXCollections.observableArrayList(someItem -> 
                    new Observable[]{someItem.item1Property(), someItem.item2Property()});
    
    public Person(Name name, SomeItem... someItems) {
        this.name = name;
        this.someItems.addAll(someItems);
    }
    
    public final Name getName() {
        return name;
    }
    
    public final ObservableList<SomeItem> getSomeItems() {
        return someItems;
    }
    
    @Override
    public String toString() {
        return "[name=" + name.getName() + ", someItems=" + someItems + "]";
    }

}

App.java:

public class App extends Application {

    @Override
    public void start(Stage stage) {

        SomeItem someItem1 = new SomeItem(new Item("item1"), new Item("item2"));
        SomeItem someItem2 = new SomeItem(new Item("item3"), new Item("item4"));     
        SomeItem someItem3 = new SomeItem(new Item("item5"), new Item("item6"));
        SomeItem someItem4 = new SomeItem(new Item("item7"), new Item("item8"));
    
        Person person1 = new Person(new Name("person1"), someItem1, someItem2);
        Person person2 = new Person(new Name("person2"), someItem3, someItem4);

        ObservableList<Person> persons = FXCollections.observableArrayList(person -> 
            new Observable[]{person.someItemsProperty()});
    
        persons.addAll(person1, person2);

        persons.addListener((ListChangeListener<Person>) c -> {
            while (c.next()) {
                if (c.wasUpdated()) {
                    System.out.println("Updated persons:");
                    IntStream.range(c.getFrom(), c.getTo())
                            .mapToObj(index -> "Person at index " + index + " was updated to: " + c.getList().get(index))
                            .forEach(System.out::println);
                }
            }
        });

        // Update items to trigger change event for testing
        someItem1.setItem1(new Item("item1Updated"));
        someItem4.setItem2(new Item("item8Updated"));

    }

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

}

Output:

Updated persons:
Person at index 0 was updated to: [name=person1, someItems=[[item1Updated, item2], [item3, item4]]]
Updated persons:
Person at index 1 was updated to: [name=person2, someItems=[[item5, item6], [item7, item8Updated]]]