A very early GRDBCombine package is available on GitHub - groue/GRDBCombine: GRDB ❤️ Combine. It comes with a very simple demo app which uses DatabasePublisher
, and the DatabasePublished
property wrapper, in order to keep UI in sync with the database content.
This is pretty experimental code. Known issues:
- Only use it from the main thread
- The API for defining the observed database values needs some love.
Many thanks to @Tony_Parker and folks in the Foundation team!
7 Likes
Wow, good job Apple 
The ViewModel fueled by DatabasePublished can trivially be turned into an EnvironmentObject
that feeds a SwiftUI View, with free tableView animations 
The demo app has been upgraded in the GRDBCombine repo.
In the end, what do we have? A pile of layers that click very well together:
- GRDB
-
Record Types that can read and write in the database.
- The Query Interface that can generate SQL when you don't want to.
-
ValueObservation that can observe the results of one or several database requests. Observed values are guaranteed to be fetched from a consistent database state, that's what's super cool, and super important, with Value Observation.
- GRDBCombine
-
DatabasePublishers.Value, a publisher that you build from a ValueObservation.
-
DatabasePublished, a property wrapper that embeds a DatabasePublisher and keeps a property up-to-date with database content, ready to feed a SwiftUI view.
4 Likes
Very nice! The outer struct / inner class is the pattern we use for nearly all the operators in Combine as well. Thanks for sharing.
1 Like
Thanks Tony. Your validation of the initial path is much welcomed :-)
GRDBCombine is almost ready, with some inline doc, improved robustness and scheduling control, and an upgraded demo app which embeds all the good practices I could think of.
On a side note, I wish to thank our friends from Point-Free (@stephencelis, maybe?) . Their article How to Control the World provides a nice way to inject dependencies at a static level, which the DatabasePublished
property wrapper requires:
class HallOfFameViewModel: BindableObject {
// Here the database needs to be injected somehow.
@DatabasePublished(Players.hallOfFame(maxPlayerCount: 10))
private var hallOfFame: Result<Players.HallOfFame, Error>
...
}
// Here is how:
enum Players {
static func hallOfFame(maxPlayerCount: Int)
-> DatabasePublishers.Value<HallOfFame>
{
// --------------------------------------v 👍
return DatabasePublishers.Value(..., in: Current.database())
}
}
This pattern allows the hallOfFame publisher to be tested outside of the application, with an in-memory SQLite database for example.