ValueTree-backed objects and ComponentBuilder

This is a question related to @drowaudio’s ADC 2017 talk on ValueTree-backed apps.

I’m starting work on a mid/large sized audio app for my final-final university project, and plan to use a giant ValueTree as my model/controller to store the entire app state and generate the view (as detailed in the talk).

My big question is, how does ComponentBuilder fit into the realm of the talk? It seems like it does a lot of the same things as the ValueTreeItem class in the talk’s example code, with slightly less control since it doesn’t use direct ValueTree::Listener callbacks, instead using the updateComponentFromState method(?).

Any enlightenment on how these pieces fit together or a more concrete example of when to use and when not to use ComponentBuilder would be much appreciated.

EDIT: Doh, just ran across Is anyone using the ComponentBuilder class?

Guess I’ll make my own ValueTree management classes then - which makes sense I suppose because not every ValueTree backed thing will necessarily be a Component - sometimes you just want a ValueTree backed object for interfacing with the ValueTree.

To be honest I would steer clear of the ComponentBuilder. It’s more hassle than it’s worth as you’ll spend your whole time writing property binders and actions. Using a ValueTreeObjectList is far more flexible and probably quicker in the end.

1 Like

Is ValueTreeObjectList some internal Tracktion type or something? I don’t see reference to it anywhere…

We developed it at Tracktion but there’s a version on my GitHub: https://github.com/drowaudio/presentations/blob/master/ADC%202017%20-%20Using%20JUCE%20ValueTrees%20and%20Modern%20C%2B%2B%20to%20Build%20Large%20Scale%20Applications/Examples/shared/drow_ValueTreeObjectList.h

The main reason it never made it in to JUCE is that there’s several potential versions you might want of it. The one I’ve posted is probably the most generic, it gives you full control over the object lifetime (so you can use reference counting if you like) and has callbacks for objects being added/removed. I can see use cases for automatically managed lists (i.e. an OwnedArray<ObjectType> or std::vector<std::unique_ptr<ObjectType>> backed version) and even value typed lists (i.e. Array<ObjectType> or std::vector<ObjectType>). Then there’s the level of thread safety you want to involve etc.

It’s also fairly lightweight so easily copyable for the projects you need it in.

I’d just copy the one on my GitHub perhaps adapting it to suit your own needs.

5 Likes

Wow cool! These give me a great starting point. I’m also taking a look at the actual Tracktion project format for additional inspiration/guidance on how to structure things. Looking forward to sharing my code once it’s done.

Good stuff. Don’t take the Tracktion project format as the absolute best way to do things. There’s some legacy support in there (that format has largely been around for over 10 years, way before ValueTree existed).

Hi, I was wondering if what was said back then is still true today ? Like, is the ValueTreeObjectList still a better way to deal with that kind of stuff ? I assume it is since I haven’t found indications of a major update to the ComponentBuilder or anything related in JUCE release notes (between Jan 2018 - now).

I am just starting with JUCE so trying to go for the best / up-to-date practices, and there’s a portion of my app for which the ValueTreeObjectList seemed completely relevant so I used that and ignored the ComponentBuilder.

To the best of my knowledge, this is still very much up-to-date advice. ValueTree is very useful and very popular, and building wrappers around ValueTree is, IMO, the best way to use them.

As Dave said, it might be best to take a look at how his ValueTreeObjectList works and then build your own. IIRC, his original class handles its own resources manually, including calling delete, and it’s probably better to use smart pointers.

My experience has been that it’s best to use one of juce’s own Array classes to handle the resources instead of std::vector. This is because ValueTree::Listener functions will usually give you indices, and you can map these directly onto the juce::Array classes very easily. If you use std::vector, you have to mess around with iterators, which in this case is a bit more tedious.

So the main options are
juce::OwnedArray<MyClass>
juce::ReferenceCountedArray<MyClass>
juce::Array<std::unique_ptr<MyClass>>
juce::Array<std::shared_ptr<MyClass>>

personally I prefer the last two, but it really depends on your use case.

2 Likes

Alright, thanks a lot, super clear !