I recently had the opportunity to create an MVP completely in SwiftUI. I decided to choose Realm as it’s backing data layer and had to figure out how to fit it into SwiftUIs data life-cycle. When researching how to accomplish this I didn’t find what I was looking for. So here is an article I wish I had. I’m going to cover my four layers from Realm to DataObserver to DataStore and then finally to SwiftUI.
Realm Objects and Maps
The foundation of my solution ended up with duplication of data models, one for the backing realm object, and one for the presented user-interface. Usually, duplication of code is frowned upon, but like every rule, there is a time and place to break it. Having two different data models allows you to have more complex data types and enums that might otherwise not be supported with Realm. It also removes the Realm dependency from the user interface, making it easy to change or augment the backing data without much change to the UI layer. The main drawback to this approach is creating manual maps between the two models and if not updated the same every time, can lead to missing data.
While figuring out my data stack I caught myself updating both the models while forgetting to update the mapping, I would then try to test the UI and be confused why data was not showing as expected. I eventually would realize the error of my ways and go back to update the maps. Needless to say, the duplicated models and mapping can be a pain in the ass sometimes.
The RealmConvertible protocol is the underpinning of the whole mappable data models. This protocol is what stores the reference to the RealmType and also it’s mapped data function and initializations.
Only the UI data model layer needs explicit conformance and once it conforms it’s ready to be used in the DataObservable class.
DataObservable does all the heavy lifting by watching the mapped realm model results for changes then updating and mapping the models to the observed publishers.
While you could plug the DataObservable straight into SwiftUI, it doesn’t allow for data that may have multiple relationships. My todo example doesn’t need the DataStore object, but for anything that has data relationships, it’s a must. For example, you may have a product model with relations to a seller model, a related product list, and user models as part of a review model. This is very dynamic to your specific needs and how you structured the data models, but encapsulating all related data into a single DataStore is very handy when it comes to clean code on the UI layer.
As you can see we’re left with a very clean UI struct that utilizes the data store as a property wrapped @EnvironmentObject for todo data. We pass a realmBinding as defined in the RealmConvertible protocol to the cell.
Now any data that is changed in the cell will be saved as the binding updates. Every time a character is typed or removed the data will be persisted instantly. You could quit the app in the middle of typing a todo and data will be saved up to the last thing typed.
Hopefully you found this article useful in deciding how you can fit Realm into your SwiftUI projects. You can view and download the source code for this project on GitHub.