Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Realm with SwiftUI

Tags:

swiftui

realm

I have been trying to figure out how to use Realm with SwiftUI. The problem is that SwiftUI and Realm both have a List type. When you import SwiftUI into your Realm model to make the class a BindableObject and try to create a Realm List property there is an error.

Is it possible to use an instance of the Realm object model and make it a BindableObject in SwiftUI?

like image 792
K. Law Avatar asked Jun 19 '19 23:06

K. Law


People also ask

Should I use Core Data or realm?

Core Data is incredibly fast if you consider what it does under the hood to do its magic. But Realm is faster, much faster. Realm was built with performance in mind and that shows. As Marcus Zarra once said in a presentation, you don't choose Core Data for its speed.

What is realm Mongodb?

Realm is an embedded, object-oriented database that lets you build real-time, offline-first applications. Its SDKs also provide access to Atlas App Services, a secure backend that can sync data between devices, authenticate and manage users, and run serverless JavaScript functions.


2 Answers

Sure, it's very simple, use the module identifier as prefix like this :

let members = RealmSwift.List<Member>()

Now to the second part of your question. It's easy to encapsulate a Realm object (or list, or resultset) in an BindableObject :

final class DBData: BindableObject  {

    let didChange = PassthroughSubject<DBData, Never>()

    private var notificationTokens: [NotificationToken] = []    
    var posts = Post.all

    init() {
        // Observe changes in the underlying model
        self.notificationTokens.append(posts.observe { _ in
            self.didChange.send(self)
        })

        self.notificationTokens.append(Message.all.observe { _ in
            self.didChange.send(self)
        })
    }
}

If you "link" a DBData instance to a SwiftUI View by either using @ObjectBinding or @EnvironmentObject the UI will be refreshed and the new value for posts (in our example here) will be available each time the underlying realm changes.

like image 152
Bogdan Farca Avatar answered Nov 17 '22 13:11

Bogdan Farca


@JustinMiller I created a ChannelsData class that I use to listen for changes in my chat channels from a Realm collection. I then update the UI by making the ChannelsData an @EnvironmentObject in my view. Here is what works for me in Xcode 11 GM Seed:

final class ChannelsData: ObservableObject {
    @Published var channels: [Channel]
    private var channelsToken: NotificationToken?

// Grab channels from Realm, and then activate a Realm token to listen for changes.
init() {
    let realm = try! Realm()
    channels = Array(realm.objects(Channel.self)) // Convert Realm results object to Array
    activateChannelsToken()
}

private func activateChannelsToken() {
    let realm = try! Realm()
    let channels = realm.objects(Channel.self)
    channelsToken = channels.observe { _ in
        // When there is a change, replace the old channels array with a new one.
        self.channels = Array(channels) 
    }
}

deinit {
    channelsToken?.invalidate()
}

And then I use an @EnvironmentObject to grab the channels for my view:

struct ChannelsContainerView: View {

    @EnvironmentObject var channelsData: ChannelsData

    var body: some View {
        List(channelsData.channels.indexed(), id: \.1.id) { index, _ in
            NavigationLink(destination: ChatView()) {
                ChannelRow(channel: self.$channelsData.channels[index])
            }
        }
    }
}

Don't worry about the indexed() function in the List. But if you're curious, it comes from Majid's clever approach to creating flexible SwiftUI data storage classes here: https://mecid.github.io/2019/09/04/modeling-app-state-using-store-objects-in-swiftui/

And if you're coming into the view from another view, be sure to add .environmentObject(ChannelsData()) to your view link (and also in your Previews) or it won't work.

like image 39
Joe Sweeney Avatar answered Nov 17 '22 15:11

Joe Sweeney