I'm using Realm in my SwiftUI project to store some of my data.
Currently I store there ConversationObject
and SettingObject
types.
Each of objects have it's corresponding Repository
(e.g. ConversationsRepository):
final class ConversationsRepository: ConversationsRepositoryProtocol {
@Inject private var realmManager: RealmManaging
init() {}
func getConversations() -> AnyPublisher<[Conversation], Error> {
realmManager.objects(ofType: ConversationObject.self)
.map { $0.map({ $0.toModel }) }
.eraseToAnyPublisher()
}
[...]
}
which utilises generic RealmManager
class RealmManager: RealmManaging {
let realm = try? Realm()
private let realmQueue: DispatchQueue = DispatchQueue(label: "realmQueue")
private let updatePolicy: Realm.UpdatePolicy = .modified
private enum RealmError: Error {
case realmConstructionError
}
private init() {}
static let shared: RealmManaging = {
RealmManager()
}()
func objects<T: Object>(ofType: T.Type) -> AnyPublisher<[T], Error> {
Future { promise in
self.realmQueue.async {
do {
let realm = try Realm()
if realm.isInWriteTransaction || realm.isPerformingAsynchronousWriteOperations {
try realm.commitWrite()
}
let objects = realm.objects(T.self)
promise(Result.success(objects.compactMap { $0 }))
} catch {
promise(Result.failure(error))
}
}
}
.eraseToAnyPublisher()
}
[...]
}
In my project every View
struct ChatView: View {
@StateObject private var chatViewModel: ChatViewModel = ChatViewModel()
[...]
}
has it's ViewModel which manages Repository data and passes it to corresponding View
final class ChatViewModel: ObservableObject {
@Inject private var conversationsRepository: ConversationsRepositoryProtocol
@Published var currentConversation: Conversation?
@Published var conversations: [Conversation] = []
private var cancelBag: Set<AnyCancellable> = Set<AnyCancellable>()
init() {
fetchConversations()
}
func fetchConversations() {
conversationsRepository.getConversations()
.receive(on: DispatchQueue.main)
.sink { _ in
} receiveValue: { [weak self] fetchedConversations in
guard let self = self,
!fetchedConversations.isEmpty else {
self?.addNewConversation()
return
}
self.conversations = fetchedConversations
self.sortConversations()
self.currentConversation = self.conversations.last
}
.store(in: &cancelBag)
}
}
My problem is I have ChatView which displays data for ConversationObject collection
, but I have SettingsView in which I can delete all ConversationObjects
. Both views' viewmodels use ConversationRepository
to do their stuff. But I want the data in ChatViewModel reload when I delete ConversationObject
data in SettingsView
I want to create observers that I could use in various ViewModels so that they could have latest data for my Realm collections and react to changes mutating their views.
I know that I could just simply use Realm object inside my ViewModels and create Realm Notification Token
there, but I want to avoid using Realm object explicitly in my ViewModels, would prefer to do indirectly by calling some repository function which later calls generic RealmManager
function, unless it is not possible.
I have decided to go with Realm's built-in features that are created especially for SwiftUI. Therefore in every view which ViewModel's should have Realm's data I implemented
@ObservedResults(ConversationObject.self) var conversationsObjects
It is then passed to ViewModel with modifier
.onChange(of: conversationsObjects) { _, updatedConversationsObjects in
chatViewModel.getConversations(from: updatedConversationsObjects)
}
Later, the method getConversations
converts it from Realm Object to my other structure that is assigned to @Published var
and this data is displayed on screen
The same technique is applied to SettingsObjects, SettingsView and SettingsViewModel respectively. By using this approach I was able to get automatically refreshing Realm data I want.