Is this in the same application run using the same function call both times?
Usually you’d see this difference depending on when you add your listener. The empty TRACK node gets added with the Edit::insertTrack call and then TrackList::createNewObject gets called to initialise it as an audio track.
You best bet is to do anything you need to asynchronously as the track will be guaranteed to have been created at that point.
That’s not really possible as the Tracks will be initialised via ValueTree::Listeners as well. As the order of ValueTree::Listener callbacks isn’t well defined, you can’t know if your listener will be called before the Tracktion Engine one or not.
So if you listen to the state, you need to use an juce::AsyncUpdater to find the object with that state asynchronously. Then it’s fully initialised and safe to use. E.g. you can use this function: Track* findTrackForState (const Edit&, const juce::ValueTree&);
In Waveform what we tend to do for UI is just have a class that listens to the state and then triggers an async update if something changes e.g. a track is added/removed/moved or a clip is added/removed or a clip position is moved. Then depending on what thing happened, in the async callback we can update our track UI or reposition clip components etc. That saves updating the whole UI every time something changes.