An InheritedWidget might be the best and cleaner way because:
- it gives you the advantage of scoping your variable (I know, you need them globe, so that's not your concern, for now).
- you can rebuild it under some circumstances via the updateShouldNotify. When rebuilding anInheritedWidgetyou rebuild all the widgets that are referencing its properties.
- it's cleaner than global variables, because you have more control over them and you can also implement some "global" methods that leverage some private variables. It's still a class, after all.
- the access has O(1)complexity since Flutter indexesInheritedWidgetimplementation, allowing efficient access through theancestorInheritedElementForWidgetOfExactTypemethod fromcontext.
That said, SharedPreferences is a big no since you'd be doing a lot of disk I/O, which has a lot worse performance than operating in RAM. Plus, you'd need a lot of boilerplate to manage the temporary nature of your variables.
As for global variables, I think they don't give you the same agility of an InheritedWidget and I don't think that they're a good practice.
Talking about InheritedWidget, for "dynamic" stuff you might not use it plain, since its variables should be immutable (the class is marked as immutable, so all of your members should be final). 
A clean way to work around this is well explained here. Basically the AppState are your global variables.
EDIT as @Rémi Rousselet pointed out, telling that ancestorInheritedElementForWidgetOfExactType has O(1) complexity is a bit of a stretch, since the lookup is performed on an HashMap, which has O(1) complexity for the best case, O(n) in the worst case. 
It's still an efficient way since it doesn't traverse the whole widget tree looking for it.