This is a really interesting question, if for no other reason than there are multiple correct answers, and even within those answers, things can get messy. MaterialApp, for instance, will create a Material Theme widget, which in turn creates a CupertinoTheme, but using a Material-based set of values for the data. :)
I can point to a few apps that have used different approaches.
Flutterfolio from gSkinner, for instance, just uses a MaterialApp widget and hacks the theme quite a bit. They're going for an interface that respects certain navigational paradigms and input methods, but otherwise doesn't use a ton of "native-looking" UI components. That means they don't explicitly need a CupertinoApp at any point in the hierarchy.
When we did the
Building for iOS talk at I/O a couple years ago,
the code we built took the opposite approach, and created a MaterialApp for Android and a CupertinoApp for iOS. As a result, the app could do some cool tricks like use flat navigation on iOS, and drawer-based, stacked navigation on Android. Finding the right themedata to use for a font, though, ended up being a challenge. I had to build an InheritedWidget that would look for *either* a Theme or CupertinoTheme widget above it in the tree, grab its TextTheme data, and then pull particular values out, which it then shared with widgets below.
All-in-all, I think either approach can work as long as you're very clear with yourself about which parts of your app you want to adapt to the platform and which parts you don't. If you get that right, you can build widgets at those decision points to branch according to platform, and then have other widgets capable of bringing those branches back together.
This is a topic that's only going to become more important, BTW, so I'd be interested to hear what other folks think!
-Andrew