Interested in speaking at MongoDB World 2022? Click here to become a speaker.
HomeLearnHow-toOpen Synced Realms in SwiftUI using @Auto/AsyncOpen

Open Synced Realms in SwiftUI using @Auto/AsyncOpen

Updated: Aug 25, 2021 |

Published: Aug 25, 2021

  • Mobile
  • Realm
  • Swift
  • ...

By Diana Perez Afanador

Rate this article

#Introduction

We’re very happy to announce that v10.12.0 of the Realm Cocoa SDK includes our two new property wrappers @AutoOpen and @AsyncOpen for asynchronous opening of a realm for Realm Sync users. This new feature, which is a response to your community feedback, aligns with our goal to make our developer experience better and more effortless, integrating it with SwiftUI, and removing boilerplate code.

Up until now, the standard approach for opening a realm for any sync user is to call Realm.asyncOpen() using a user’s sync configuration, then publish the opened Realm to the view:

1enum AsyncOpenState {
2 case waiting
3 case inProgress(Progress)
4 case open(Realm)
5 case error(Error)
6}
7
8struct AsyncView: View {
9 @State var asyncOpenState: AsyncOpenState = .waiting
10
11 var body: some View {
12 switch asyncOpenState {
13 case .waiting:
14 ProgressView()
15 .onAppear(perform: initAsyncOpen)
16 case .inProgress(let progress):
17 ProgressView(progress)
18 case .open(let realm):
19 ContactsListView()
20 .environment(\.realm, realm)
21 case .error(let error):
22 ErrorView(error: error)
23 }
24 }
25
26 func initAsyncOpen() {
27 let app = App(id: "appId")
28 guard let currentUser = app.currentUser else { return }
29 let realmConfig = currentUser.configuration(partitionValue: "myPartition")
30 Realm.asyncOpen(configuration: realmConfig,
31 callbackQueue: DispatchQueue.main) { result in
32 switch result {
33 case .success(let realm):
34 asyncOpenState = .open(realm)
35 case .failure(let error):
36 asyncOpenState = .error(error)
37 }
38 }.addProgressNotification { syncProgress in
39 let progress = Progress(totalUnitCount: Int64(syncProgress.transferredBytes))
40 progress.completedUnitCount = Int64(syncProgress.transferredBytes)
41 asyncOpenState = .inProgress(progress)
42 }
43 }
44}

With @AsyncOpen and @AutoOpen, we are reducing development time and boilerplate, making it easier, faster, and cleaner to implement Realm.asyncOpen(). @AsyncOpen and @AutoOpen give the user the possibility to cover two common use cases in synced apps.

#Prerequisites

#@AsyncOpen

With the @AsyncOpen property wrapper, we have the same behavior as using Realm.asyncOpen(), but with a much more natural API for SwiftUI developers. Using this property wrapper prevents your app from trying to fetch the Realm file if there is no network connection, and it will only return a realm when it's synced with MongoDB Realm data. If there is no internet connection, then @AsyncOpen< will throw an error.

Let’s take, for example, a game app, which the user can play both on an iPhone and iPad. Having the data not updated would result in losing track of the current status of the player. In this case, it’s very important to have our data updated with any latest changes. This is the perfect use case for @AsyncOpen.

This property wrapper's API gives you the flexibility to optionally specify a MongoDB Realm AppId. If no AppId is provided, and you’ve only used one ID within your App, then that will be used. You can also provide a timeout for your asynchronous operation:

1@AsyncOpen(appId: "appId",
2 partitionValue: "myPartition",
3 configuration: Realm.Configuration(objectTypes: [SwiftPerson.self])
4 timeout: 20000)
5var asyncOpen

Adding it to your SwiftUI App is as simple as declaring it in your view and have your view react to the state of the sync operation:

  • Display a progress view while downloading or waiting for a user to be logged in.
  • Display an error view if there is a failure during sync.
  • Navigate to a new view after our realm is opened

Once the synced realm has been successfully opened, you can pass it to another view (embedded or via a navigation link):

1struct AsyncOpenView: View {
2 @AsyncOpen(appId: "appId",
3 partitionValue: "myPartition",
4 configuration: Realm.Configuration(objectTypes: [SwiftPerson.self])
5 timeout: 20000)
6 var asyncOpen
7
8 var body: some View {
9 VStack {
10 switch asyncOpen {
11 case .connecting:
12 ProgressView()
13 case .waitingForUser:
14 ProgressView("Waiting for user to logged in...")
15 case .open(let realm):
16 ListView()
17 .environment(\.realm, realm)
18 case .error(let error):
19 ErrorView(error: error)
20 case .progress(let progress):
21 ProgressView(progress)
22 }
23 }
24 }
25}

If you have been using Realm.asyncOpen() in your current SwiftUI App and want to maintain the same behavior, you may want to migrate to @AsyncOpen. It will simplify your code and make it more intuitive.

#@AutoOpen

@AutoOpen should be used when you want to work with the synced realm file even when there is no internet connection.

Let’s take, for example, Apple’s Notes app, which tries to sync your data if there is internet access and shows you all the notes synced from other devices. If there is no internet connection, then Notes shows you your local (possibly stale) data. This use case is perfect for the @AutoOpen property wrapper. When the user recovers a network connection, Realm will sync changes in the background, without the need to add any extra code.

The syntax for using @AutoOpen is the same as for @AsyncOpen:

1struct AutoOpenView: View {
2 @AutoOpen(appId: "appId",
3 partitionValue: "myPartition",
4 configuration: Realm.Configuration(objectTypes: [SwiftPerson.self])
5 timeout: 10000)
6 var autoOpen
7
8 var body: some View {
9 VStack {
10 switch autoOpen {
11 case .connecting:
12 ProgressView()
13 case .waitingForUser:
14 ProgressView("Waiting for user to logged in...")
15 case .open(let realm):
16 ContactView()
17 .environment(\.realm, realm)
18 case .error(let error):
19 ErrorView(error: error)
20 case .progress(let progress):
21 ProgressView(progress)
22 }
23 }
24 }
25}

#One Last Thing…

We added a new key to our set of Environment Values: a “partition value” environment key which is used by our new property wrappers @AsyncOpen and @AutoOpen to dynamically inject a partition value when it's derived and not static. For example, in the case of using the user id as a partition value, you can pass this environment value to the view where @AsyncOpen or @AutoOpen are used:

1AsyncView()
2 .environment(\.partitionValue, user.id!)

#Conclusion

With these property wrappers, we continue to better integrate Realm into your SwiftUI apps. With the release of this feature, and more to come, we want to make it easier for you to incorporate our SDK and sync functionality into your apps, no matter whether you’re using UIKit or SwiftUI.

We are excited for our users to test these new features. Please share any feedback or ideas for new features in our community forum.

Documentation on both of these property wrappers can be found in our docs.

Rate this article
MongoDB logo
© 2021 MongoDB, Inc.

About

  • Careers
  • Legal Notices
  • Privacy Notices
  • Security Information
  • Trust Center
© 2021 MongoDB, Inc.