Skip to content

Open a Realm, Call a Function #519

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Oct 8, 2020
Merged

Open a Realm, Call a Function #519

merged 14 commits into from
Oct 8, 2020

Conversation

MongoCaleb
Copy link
Collaborator

@MongoCaleb MongoCaleb requested a review from nirinchev October 6, 2020 16:37
Copy link
Contributor

@nathan-contino-mongo nathan-contino-mongo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with a couple of small nits.

var result = await
user.Functions.CallAsync("sum", 2, 40);

// result.ToInt32() == 42
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me, this comment looks a little out of place in the code-snippet on this page. Could we maybe store the result in a variable and print it out instead?


Open a Local (Non-Synced) Realm
-------------------------------
When opening a local (non-synced) {+realm+}, you pass a ``RealmConfiguration``
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
When opening a local (non-synced) {+realm+}, you pass a ``RealmConfiguration``
When opening a local (non-synced) {+realm+}, pass a ``RealmConfiguration``

extraneous word, I would probably just trim this.

Close a Realm
-------------
There is no need to manually close a {+realm+} with the .NET SDK. When a {+realm+}
goes out of scope and is removed from memory, the {+realm+} is closed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great. I wish the other SDKs did this 😢

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not true unfortunately. The Realm instance implements IDisposable (see https://realm.io/docs/dotnet/latest/#closing-realm-instances) so the general recommendation is to open a Realm instance with a using, unless that instance is going to be long lived (e.g. on the main thread) and you need to continuously read/write data to it as the user interacts with the app. It's true that we have hooks into GC to do that for you, but the issue is that the majority of the memory associated with a Realm instance is native memory which GC doesn't know about. This means that it may take a long time between an instance going out of scope and it being collected because the GC sees it as a very lightweight object and may decide not to run.

Copy link
Collaborator

@nirinchev nirinchev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a few small corrections, but looks good overall.


In the above example, the code shows how to open the {+realm+} *asynchronously*
by calling ``GetInstanceAsync``. You can also open a {+realm+} *synchronously*
by calling ``GetInstance``, but this may lead to temporary data inconsistencies
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that's very clear - there are no data inconsistencies associated with opening a Realm file synchronously and I don't believe we have strong recommendations against it. In fact, opening a Realm asynchronously is only possible if the device is online and it kind of defeats one of the major selling points for Realm.

I believe the general recommendation we have is to use GetInstanceAsync the first time a user logs in as it's likely that they're connected at this point, but switch to GetInstance after that to ensure offline capabilities.

user = app.LogInAsync(Credentials.EmailPassword("[email protected]", "foobar")).Result;
// :code-block-start: open-synced-realm
config = new SyncConfiguration("My Project", user);
Realm realm = await Realm.GetInstanceAsync(config);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned before, let's use var instead of the type name here as it improves readability. It's also consistent with the Swift and Kotlin docs where let and val are used throughout the documentation.


.. note::

If you specify a local path, that path must already exist on the device.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only the case when opening the realm with IsReadOnly = true, which is probably fairly rare. If you're opening a Realm in a read/write mode, the SDK will create the file if it didn't exist before that.

Close a Realm
-------------
There is no need to manually close a {+realm+} with the .NET SDK. When a {+realm+}
goes out of scope and is removed from memory, the {+realm+} is closed.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not true unfortunately. The Realm instance implements IDisposable (see https://realm.io/docs/dotnet/latest/#closing-realm-instances) so the general recommendation is to open a Realm instance with a using, unless that instance is going to be long lived (e.g. on the main thread) and you need to continuously read/write data to it as the user interacts with the app. It's true that we have hooks into GC to do that for you, but the issue is that the majority of the memory associated with a Realm instance is native memory which GC doesn't know about. This means that it may take a long time between an instance going out of scope and it being collected because the GC sees it as a very lightweight object and may decide not to run.

{
// :code-block-start: callfunc
var result = await
user.Functions.CallAsync("sum", 2, 40);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably a good idea to showcase the generic overload of CallAsync so that we don't have to work with the raw BsonValue. I believe this would look like var result = await user.Function.CallAsync<int>("sum", 2, 40);

Copy link
Collaborator

@nirinchev nirinchev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Final nits and clarifications! Thanks for bearing with me and my obsessiveness 🙏

conf.py Outdated
@@ -109,6 +109,7 @@
'swift-sdk': ('https://docs.mongodb.com/realm-sdks/swift/10.0.0-rc.1/%s', ''),
'objc-sdk': ('https://docs.mongodb.com/realm-sdks/objc/10.0.0-rc.1/%s', ''),
'js-sdk': ('https://docs.mongodb.com/realm-sdks/js/latest/%s', ''),
'dotnet-sdk': ('https://docs.mongodb.com/realm-sdks/js/latest/%s', ''),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be updated to .../dotnet/latest/...?

{
Name = "Do this thing",
Status = TaskStatus.Open.ToString()
Status = dotnet.TaskStatus.Open.ToString()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably don't want to use the namespace here?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it conflicts with something, you can add

using TaskStatus = dotnet.TaskStatus;

next to your using statements to help the compiler resolve the correct type.

{
// :code-block-start: scope
config = new SyncConfiguration("My Project", user);
using (var realm = await Realm.GetInstanceAsync(config))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can showcase the new using syntax from C# 8 here and simplify the code a little:

config = new SyncConfiguration(...);
using var realm = await Realm.GetInstanceAsync(...);
var allTasks = realm.All<RealmTask>();


.. note::
The ``CallAsync()`` method returns a raw ``BsonValue``, which must be cast to the
expected type, unless you use one of the generic overloads. In the example above,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[super-nit] There's only one generic overload for CallAsync.


.. note::

The first time a user logs on to your realm app, you should open the realm
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would use a less compelling verb - i.e. The first time a user logs on to your realm app, it is recommended to open the realm *asynchronously*.

And on a separate note - before the acquisition, the branding instructions stated that we should capitalize Realm when talking about the files - e.g. "Can you send me your Realm file", "To open a synchronized Realm, just pass in a SyncConfiguration with a logged in user", etc. Not sure if that has changed, but might want to sync up with the docs/marketing teams to ensure we're consistent in the usage.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

@nirinchev nirinchev Oct 8, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I hadn't seen this, but super helpful - I just need to untrain my brain to uppercase the local file 😅 But I guess the realm in your realm app needs to be uppercase then?


Scoping the Realm
-----------------
The Realm instance implements ``IDisposable``, and you should dispose of a realm
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would reword this slightly to make it less forceful:

The Realm class implements ``IDisposable`` to clean up the native resources it uses. It is recommended
to always dispose of Realm instances after use, especially on background threads. The simplest way is to
declare the variable with a `using` or wrap the code that interacts with a Realm in a `using (...)` statement:

Also, might be worth adding a note that disposing of a Realm instance will invalidate all objects associated with that instance, so if the developer needs to use them throughout the lifetime of the app - e.g. for databinding purposes on the main thread - it's safe not to dispose of the Realm. The rule of thumb is "don't dispose on the main thread, dispose on background ones".

@MongoCaleb MongoCaleb merged commit 74f163e into master Oct 8, 2020
@MongoCaleb MongoCaleb deleted the clientguide branch October 8, 2020 21:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants