https://kotlinlang.org logo
#compose
Title
# compose
d

Daniele B

02/23/2021, 6:18 PM
I realized there is a lot of confusion about the concept of “*Single Source Of Truth*”. In traditional Android development, we used to talk about SSoT in the context of the Repository. Can we please agree, and probably make it clear, that the SSoT in the context of JetpackCompose (and DeclarativeUIs), has nothing to do with the Repository (and the DataLayer), but only with the ViewModel? I keep hearing developers saying that the SSoT of an app is the DB, even in the context of JetpackCompose. How does that make sense? I am attaching a diagram, where I try to highlight the difference between AppState (which is part of the ViewModel) and Repository (which is part of the DataLayer), with particular reference to the D-KMP architecture. Any comment is very welcome.
j

Javier

02/23/2021, 6:25 PM
IMO repository can't be the SOT because it has a lot of sources. One of that sources should be the SOT.
And it is not related about declarative UIs, or UIs, in general
d

Daniele B

02/23/2021, 6:48 PM
There are actually information required by the UI which have nothing to do with the DataLayer. Let’s give an example: On a JetpackCompose application you have a bottom navigation menu. Where would you want to store the property of the selected menu item? I guess in the AppState. Would you also want to store it in the Repository? In the context of MVI, the data repository is just one of the 3 inputs needed by the state reducers to create a new state. The other two are: - user inputs - old app state
The are contexts where state reducers create a new state, without even needing data from the datarepository. Look at the “SetLoading()” state reducer below.
j

jim

02/23/2021, 6:53 PM
There is no one-size-fits-all to that question. For example, suppose a user is in the middle of entering a comment into their comment box, but hasn't hit submit yet, is that UI state? Maybe. Certainly most users would prefer that the not-yet-submitted comment not get lost if the app crashes and needs to be restarted, or if get confused if they open the app in another window, or even on another device. There is a very valid argument that all temporary comments should generally be synchronized with the server, depending of course on the specifics of your app and use case. And one could make a similar argument for bottom navigation, depending on the app. For instance, a quiz app or survey app might want to synchronize the navigation page such that a user can't effectively "see/edit old values" (even on other devices logged into the same account) after they've proceeded to the next tab. Such decisions are very much up to the app developer, and need to be considered on a case-by-case basis. But as a general rule of thumb, I think people reach for local view-only state far too often (probably because it's easier than writing a new REST target), when they probably should be synchronizing that data with a server if they really thought about the possible user considerations and use cases.
💯 2
a

Afzal Najam

02/23/2021, 6:53 PM
If I understand correctly, the argument about single source of truth applies to each piece of data, not all pieces of data. For each piece of data, there should be a single source of truth, whether that's the db, or the ViewModel, or even the
remember {}
(where is that stored? In the composition?) depends on what the data is being used for.
7
j

jim

02/23/2021, 6:54 PM
Yes, exactly.
The principle of "single source of truth" as a whole applies across the entire stack (not just view models). For any piece of information, there should be a single source of truth.
4
d

Daniele B

02/23/2021, 8:12 PM
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.
This is the diagram of the D-KMP architecture, which describes the relationship between the ViewModel and the DataLayer:
One further consideration: in a multi-platform strategy, where you want to minimize the amount of per-platform code, it makes sense to limit the use of
remember{}
as much as possible, because any UI local state object would have to be replicated on both JetpackCompose and SwiftUI. This is also the reason why it's convenient to have a shared ViewModel, holding the AppState as the Single Source of Truth for the UI layer.
j

Javier

02/24/2021, 10:11 AM
So using exposing a unique Flow of a
data/sealed class
as state from your ViewModel to the UI which contains all the logic of what is happening in the UI, and based on that, decide, for example, if a switch is on/off, etc.
So every composable is stateless?
d

Daniele B

02/24/2021, 10:59 AM
Yes, each composable is stateless and just reflects the AppState properties. The AppState is a data class, which is propagated from the ViewModel to the UI Layer via StateFlow.
j

Joost Klitsie

02/25/2021, 2:43 PM
Well you can have different kinds of SSOT's. For example, your repository layer is probably the SSOT (in some way) for the raw data that is in your application. Then your viewmodel will most likely observe that, and transform that information into a view state. The view model is the SSOT for the view state. Having the view model be the SSOT for the raw data would be a violation, as you can use the same raw data across multiple components (all backed by their own view model)
d

Daniele B

02/26/2021, 10:55 PM
In the D-KMP Architecture, the ViewModel doesn't "observe" the Repository. The relationship between the two is that the state reducers (which are part of the ViewModel) deliberately call Repository functions to retrieve any data needed by the AppState.
j

Joost Klitsie

02/27/2021, 3:45 PM
Well if you in the end keep the data in the viewmodel it seems likely to me that it will be duplicated and you will not have a proper ssot. Saying: I have an overview of objects, I have a viewmodel for the overview. Then I have a detail view, which has its own viewmodel and an edit view, which has its own viewmodel. I would use an observable application scoped layer to feed these 3 screens the exact same object(s). If this layer is some reducer or repository doesn't matter. Adjusting it in 1 screen would then automatically update the others. If the info would only be stored in the viewmodels, you will have issues synchronising changes.
BTW I was curious: what does the D stand for in D-KMP? :)
d

Daniele B

03/01/2021, 3:02 AM
D in D-KMP stands for "Declarative UI"
In the example you presented, the state reducers would still save any data (meant to be persistent) to the Repository. And when presenting a screen, the state reducers would still call a Repository function to get the data. But the "Single Source of Truth" for the UI would still be the AppState, which doesn't just hold data coming from the Repository, but also other data that doesn't need to be saved persistently.
2 Views