The problem with demos
- connect to a Realm with UserA
- display an object from that Realm on the screen
- take a second device
- connect to the same Realm with UserA
- display the same object from that Realm on the screen
- make changes to the object being displayed on your first device
- see how the value almost instantly updates on the second device as well
PermissionOffer and PermissionOfferResponse
The final piece of the puzzle is getting that token to an other user. For this I just used the ZXing library to generate a QR code from that token. The other user can then easily from within our app scan the code so that we get the token and do our little handshaking.
Show me the code!
The inviting side of the story
To create an invite, I’ve built a InivitaionHelper class that has a static method CreateInviteAsync.
public static Task<MyInvitation> CreateInviteAsync(string realmUrl) { var tcs = new TaskCompletionSource<MyInvitation>(); var permissionOffer = new PermissionOffer(realmUrl, mayRead: true, mayWrite: true, mayManage: false, expiresAt: DateTimeOffset.UtcNow.AddDays(7)); var managementRealm = User.Current.GetManagementRealm(); managementRealm.Write(() => { managementRealm.Add(permissionOffer); }); permissionOffer.WhenPropertyChanged(nameof(PermissionOffer.Status), (po) => po.Status == ManagementObjectStatus.Success, (po) => { tcs.TrySetResult(CreateInvite(po)); }); return tcs.Task; } private static MyInvitation CreateInvite(PermissionOffer po) { return new MyInvitation(po.Token); }
This method will basically do all the boilerplate code for generating the PermissionOffer and eventually will return us a MyInvitation object. This object is just a wrapper around the Token that has been generated by Realm. I’ve made this wrapper class so I could add some meta data to the invitation if I wanted.
When the PermissionOffer object is written to the Management Realm of the current user, Realm will do its thing in the background and will update the PermissionOffer object once it’s done: the status of the object will be set to ‘Success’ and the Token property will be filled in. Fortunately, almost all Realm objects implement INotifyPropertyChanged, so we can determine exactly when Realm has processed everything and thus has updated the values on the PermissionOffer object.
For these kinds of things, I’ve written an extension method WhenPropertyChanged for objects that implement the INotifyPropertyChanged interface:
public static void WhenPropertyChanged<T>(this T obj, string property, Predicate<T> predicate, Action<T> action) where T : INotifyPropertyChanged { obj.PropertyChanged += (sender, e) => { if (e.PropertyName == property && predicate((T)sender)) action((T)sender); }; }
The action (last parameter) gets executed when the provided property changed and the given predicated returns true. With this extension method, I can easily write the following:
permissionOffer.WhenPropertyChanged(nameof(PermissionOffer.Status), (po) => po.Status == ManagementObjectStatus.Success, (po) => { tcs.TrySetResult(CreateInvite(po)); } );
So WHEN the Status property value changes, AND the value of the Status property is ‘Success‘, we can new up an instance of MyInvitation from the token of the PermissionOffer object. I Set the result of my TaskCompletionSource to this newly created MyInvitation object. This way, I can elegantly call this method like this:
MyInvitation invite = await InvitationHelper.CreateInviteAsync(ApplicationConfig.REALM_URL);
I just love it when I can abstract code away so elegantly like this, don’t you?
Finally, on the inviting side of the story, the only thing left to do, is generating a QR code. The QR code will be our MyInvitation object serialized as a Json string.
To display a QR code, I’ve used the ZXing libary. This library makes it a breeze to write and read QR codes (and others). Just install it through NuGet (ZXing.Net.Mobile.Forms) and you’re good to go.
On my InvitePage, I just put a ZXingBarcodeImageView control:
<ZXing:ZXingBarcodeImageView x:Name="BarCodeView" HorizontalOptions="Fill" VerticalOptions="Fill" HeightRequest="300" WidthRequest="300" Margin="10" BarcodeFormat="QR_CODE" />
In the code-behind of my Invite page, I’ve got a constructor that takes a MyInvitation object. This parameter gets serialized by using Newtonsoft.Json and this serialized content is set as the BarcodeValue of my ZXingBarcodeImageView control.
public InvitePage(MyInvitation invite) : this(Newtonsoft.Json.JsonConvert.SerializeObject(invite)) { } public InvitePage(string content) { InitializeComponent(); BarCodeView.BarcodeOptions.Height = 300; BarCodeView.BarcodeOptions.Width = 300; BarCodeView.BarcodeValue = content; }
The invitee side of the story
We can then deserialize this string to a MyInvitation object.
public static async Task<MyInvitation> ScanInviteAsync() { var scanner = new MobileBarcodeScanner(); var result = await scanner.Scan(); if (result != null) { try { return JsonConvert.DeserializeObject<MyInvitation>(result.Text); } catch (Exception) { //ToDo } } return null;
In the static InvitationHelper class that I used earlier, I’ve also added an AcceptInvitationAsync method. Analogous to the CreateInviteAsync method, this method does all the boilerplate stuff to accept the PermissionOffer. From my MyInvitation object, I can get the Token from the PermissionOffer and new up a PermissionOfferResponse object which I can then save to the user’s ManagementRealm. Once I’ve saved this, again Realm will do its thing and so the handshaking is done.
public static Task<Realm> AcceptInvitationAsync(MyInvitation invitation) { var tcs = new TaskCompletionSource<Realm>(); var offerResponse = new PermissionOfferResponse(invitation.Token); var managementRealm = User.Current.GetManagementRealm(); managementRealm.Write(() => { managementRealm.Add(offerResponse); }); offerResponse.WhenPropertyChanged( nameof(PermissionOfferResponse.Status), (por) => por.Status == ManagementObjectStatus.Success, (por) => { if (por.RealmUrl != null) { var configuration = new SyncConfiguration(User.Current, new Uri(por.RealmUrl)); tcs.TrySetResult(Realm.GetInstance(configuration)); } }); return tcs.Task; }
Here I use the same WhenPropertyChanged extension method that I’ve used earlier to listen for the PermissionOfferResponse object’s Status property to be set to Success. Once that’s done, I can create a SyncConfiguration object by using the RealmUrl I got from the PermissionOfferResponse. With this SyncConfiguration object I can create an instance of a Realm object. The resulting Realm object will point to the Realm data store userA shared.
Both UserA and UserB can now read from and write to this Realm data store. On both clients the data is kept in sync in real-time!
Demo
NOTE
When adding the XZing library to you app, make sure you do the following:
On Android, before LoadApplication:
ZXing.Net.Mobile.Forms.Android.Platform.Init();
MobileBarcodeScanner.Initialize(Application);
On iOS, before LoadApplication:
ZXing.Net.Mobile.Forms.iOS.Platform.Init();
and add the following to the Info.plist:
<key>NSCameraUsageDescription</key>
<string>The app would like to use your camera</string>
August 8, 2017 at 1:14 pm
Nice one! I think you’ll be happy to know that a lot of the boilerplate (around Task-ifying the changes) can be eliminated by using the new permission API:
“`
var token = User.Current.OfferPermissionsAsync(realmUrl, AccessLevel.Write, DateTimeOffset.UtcNow.AddDays(7));
// Accepting
var realmUrl = User.Current.AcceptPermissionOfferAsync(invitation.Token);
“`
Here’s the API reference for the User where the new APIs reside: https://realm.io/docs/xamarin/1.5.0/api/reference/Realms.Sync.User.html
August 8, 2017 at 1:17 pm
Thanks for your input! I wasn’t aware of the updated API… Thanks for sharing!