Hi Jim, I see what you mean.
There are surely situations where you want to save the state persistently.
But you can easily save data persistently as a side-effect of the ViewModel's StateReducer (by adding a call to the Repository), only where it makes sense, while keeping the AppState as the UI's single source of truth consistently.
Making a distinction between the AppState and the Repository, doesn't mean duplicating the data, but just referencing the data. Because in fact the AppState would hold data retrieved from the Repository (look at the setCityData() state reducer, in the example above). The AppState conveniently adds computed properties (with “getters”) to format the Repository data (which is the backing property object) according to how it should be displayed on the UI layer. Also consider that the architecture follows an MVI / unidirectional data flow, so it wouldn't use any pattern such as two-way synchronisation.
Even if most of the AppState data comes from the Repository, the distinction between the two structures still holds.
The Repository is part of the DataLayer and should provide “_unformatted_” data, while the AppState is part of the ViewModel and should provide the “_UI format_”, via the computed properties.
Moreover, the AppState can have properties that are not coming from the Repository, for example an “isLoading” flag, to show a loading screen.
On the other hand, the Repository can hold data that is not needed by the UI (and by the AppState). For example, caching timestamps.
I believe the traditional mechanism of having the DB as the Repo's single source of truth made sense in a non-multiplatform world, where you could not afford to write a peculiar caching mechanism, as it would have to be replicated for each single platform.
But now with MultiPlatform, we can really be incredibly flexible and remove the rigidity of having the DB as the SSoT. Because, finally, we can just write the client business logic once and use it on all platforms.
The ViewModel should just call the Repository for the data it needs, without knowing where it's coming from: it could come from the runtime cache, or from the DB, or from a webservice. It's the Repository which decides all, with great flexibility, depending on its sourcing/caching logic for that specific type of information.
App development becomes incredibly beautiful with a neat separation of concerns between the ViewModel and the DataLayer. The structure is very clear and easily understood by anyone in the team. You can have a developer that just focuses on the ViewModel and on defining which is the exact data to provide to the UI, with the correct formatting (to be used by both JetpackCompose and SwiftUI). And then a DataLayer developer that defines the Repository functions to be called by the ViewModel, connecting to the different datasources (DB, Webservices, GraphQL, Firestore, SharedPref, runtime objects, platform services, files, etc.), with great flexibility to setup a very smart caching mechanisms, surely much smarter than a dumb DB.