There are a couple things wrong with your code. The most relevant being that you use FXCollections.observableArrayList(Collection). This Javadoc of this method states:
Creates a new observable array list and adds a content of collection col to it.
In other words, it copies the Collection into a new ObservableList that is backed by an ArrayList. Any updates to the original Collection will never even be added to the ObservableList. You should be using FXCollections.observableList(List) if you want the passed List to be the backing List.
The Javadoc for FXCollections.observableList(List) (emphasis mine):
Constructs an ObservableList that is backed by the specified list. Mutation operations on the ObservableList instance will be reported to observers that have registered on that instance.
Note that mutation operations made directly to the underlying list are not reported to observers of any ObservableList that wraps it.
This Javadoc hints at the second issue. Unless you are doing differently in code you haven't posted it appears you add the elements to the ArrayList field (named allParts). Because of this the ObservableList is never aware anything changes and thus no change events are fired. The firing of change events is coded in the ObservableList. If you want to be notified of changes you must only access the list via the ObservableList that wraps the ArrayList.
In this case, your code would still work (when you call tableView.refresh()) if it wasn't for the fact you also wrap your ObservableList in a FilteredList. A FilteredList creates a "view" of the backing ObservableList. If the backing ObservableList never fires any changes the FilteredList is never notified of any changes which means it never knows to update this "view". This means when you add elements to the ArrayList these new elements are "outside the view" (if you were to replace an element that is "inside the view" the change would be visible).
You can see this behavior with the following code:
import java.util.*;
import javafx.collections.*;
import javafx.collections.transformation.*;
public class Main {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Hello");
list.add("World");
ObservableList<String> obsList = FXCollections.observableList(list);
FilteredList<String> filteredList = new FilteredList<>(obsList);
System.out.println("INITIAL STATE");
System.out.printf("\tList: %s%n", list);
System.out.printf("\tFilteredList: %s%n", filteredList);
list.add("Goodbye");
list.add("World");
System.out.println("AFTER ADDING ELEMENTS");
System.out.printf("\tList: %s%n", list);
System.out.printf("\tFilteredList: %s%n", filteredList);
list.set(0, "Foo");
list.set(1, "Bar");
System.out.println("AFTER REPLACING ELEMENTS");
System.out.printf("\tList: %s%n", list);
System.out.printf("\tFilteredList: %s%n", filteredList);
}
}
Which prints out:
INITIAL STATE
List: [Hello, World]
FilteredList: [Hello, World]
AFTER ADDING ELEMENTS
List: [Hello, World, Goodbye, World]
FilteredList: [Hello, World]
AFTER REPLACING ELEMENTS
List: [Foo, Bar, Goodbye, World]
FilteredList: [Foo, Bar]
Taking all this into account the easiest way to fix your code would be to make allParts an ObservableList. Otherwise you must take care to only use the ObservableList that you created around the ArrayList.
Edit
You also mention that, "If I leave the search textfield blank and hit the search button, the 4th part shows up". I want to address this. Here is your code with explanations why the new Part shows up in the TableView when you hit search:
/*
* I extracted the FXCollections.observableArrayList(Collection) out of the FilteredList
* constructor to more easily see what is going on.
*/
/*
* You create a **new** ObservableList using FXCollections.observableArrayList(Collection). This basically
* creates a *snapshot* of the List returnd by getAllParts() as it currently is. At this point the 4th
* Part is in that returned List. This means the newly created ObservableList will also contian the new
* Part (since observableArrayList(Collection) copies the data). However, the *old* ObservableList that
* was already set on the TableView *does not* contain that 4th Part.
*/
ObservableList<Part> parts = FXCollections.observableArrayList(inventory.getAllParts());
// You create a FilteredList that performs no filtering around "parts". Note
// here that a Predicate that always returns true is equivalent to passing null
// as the Predicate.
FilteredList<Part> filteredData = new FilteredList<>(parts, p -> true);
// Get the search criteria
String newValue = txtPartSearch.getText();
filteredData.setPredicate(part -> {
if (newValue == null || newValue.isEmpty()) {
return true; // don't filter if there is no search criteria
// since the newValue will be null or blank in this
// case no Parts are filtered
}
// filter based on lower-case names and search critiera
String lowerCaseFilter = newValue.toLowerCase();
if (part.getName().toLowerCase().contains(lowerCaseFilter)) {
return true;
}
// else filter by ID
return Integer.toString(part.getPartID()).equals(lowerCaseFilter);
});
// Wrap the FilteredList in a SortedList and bind the comparatorProperty to
// the comparatorProperty of the TableView (allows sorting by column).
SortedList<Part> sortedData = new SortedList<>(filteredData);
sortedData.comparatorProperty().bind(tblParts.comparatorProperty());
// Set the sortedData to the TableView
tblParts.setItems(sortedData);
So, the fundamental reason why when you search you see the new Part show up is because you are creating a new ObservableList every time you search. This new ObservableList has the most recent state of the getAllParts() List. Also, as I already mentioned in the comments, your edit is basically doing the same thing as your sorting code. Since you do:
inventory.addPart(new Part(6,"Test",5.23,4,2,8));
tblParts.setItems(FXCollections.observableArrayList(inventory.getAllParts()));
which adds the Part before creating the ObservableList. Again, FXCollections.observableArrayList(Collection) takes a snapshot of the Collection which, when the method is called here, contains that new Part. If you were to flip the code to:
tblParts.setItems(FXCollections.observableArrayList(inventory.getAllParts()));
inventory.addPart(new Part(6, "Test", 5.23, 4, 2, 8));
then the TableView's items property will not contain the new Part. However, the allParts ArrayList in inventory will.