From c5f31d30704c484b12d73ee5f1c82ecf68e82821 Mon Sep 17 00:00:00 2001 From: Chris Bush Date: Fri, 2 Oct 2020 10:39:29 -0400 Subject: [PATCH] Tutorials with examples (#505) * Add generated code blocks * Update literalincludes WIP * Add more literalincludes * Kotlin WIP * Add languages --- .../steps-tutorial-android-kotlin.yaml | 71 ++++++++++------ source/includes/steps-tutorial-ios-swift.yaml | 83 ++++++++++++------- .../includes/steps-tutorial-react-native.yaml | 56 ++++++++----- source/tutorial/android-kotlin.txt | 3 +- source/tutorial/generated/.gitignore | 3 + .../AuthProvider.codeblock.open-user-realm.js | 24 ++++++ .../final/AuthProvider.codeblock.sign-in.js | 7 ++ .../final/AuthProvider.codeblock.sign-out.js | 10 +++ .../final/AuthProvider.codeblock.sign-up.js | 5 ++ ...thProvider.codeblock.user-realm-cleanup.js | 19 +++++ .../LoginActivity.codeblock.create-user.kt | 13 +++ .../LoginActivity.codeblock.login-user.kt | 11 +++ .../ManageTeam.codeblock.add-team-member.js | 10 +++ .../final/ManageTeam.codeblock.get-team.js | 10 +++ ...ManageTeam.codeblock.remove-team-member.js | 10 +++ ...Controller.codeblock.add-team-member.swift | 5 ++ ...troller.codeblock.fetch-team-members.swift | 30 +++++++ ...troller.codeblock.remove-team-member.swift | 5 ++ ...ity.codeblock.add-new-member-to-project.kt | 18 ++++ ...mberActivity.codeblock.get-team-members.kt | 16 ++++ ...pter.codeblock.remove-user-from-project.kt | 15 ++++ .../Models.codeblock.project-model.swift | 9 ++ .../final/Models.codeblock.task-model.swift | 25 ++++++ .../final/Models.codeblock.user-model.swift | 9 ++ ...vity.codeblock.fetch-synced-user-safely.kt | 2 + ...tivity.codeblock.on-destroy-close-realm.kt | 5 ++ ...tActivity.codeblock.on-stop-close-realm.kt | 6 ++ ...ectActivity.codeblock.set-up-user-realm.kt | 12 +++ ...ity.codeblock.user-init-change-listener.kt | 5 ++ ...Controller.codeblock.cell-for-row-at.swift | 10 +++ ...ntroller.codeblock.did-select-row-at.swift | 15 ++++ ...ontroller.codeblock.invalidate-token.swift | 4 + ...r.codeblock.log-out-button-did-click.swift | 15 ++++ ....codeblock.number-of-rows-in-section.swift | 16 ++++ ...codeblock.user-in-realm-notification.swift | 8 ++ ...eneDelegate.codeblock.initialize-app.swift | 1 + ...ivity.codeblock.add-new-task-to-project.kt | 5 ++ ...ck.fetch-tasks-for-project-sorted-by-id.kt | 1 + ...tivity.codeblock.on-destroy-close-realm.kt | 6 ++ ...kActivity.codeblock.on-stop-close-realm.kt | 6 ++ ...Activity.codeblock.set-up-project-realm.kt | 11 +++ ...askAdapter.codeblock.change-task-status.kt | 15 ++++ .../TaskAdapter.codeblock.delete-task.kt | 15 ++++ ...em.codeblock.define-task-status-actions.js | 24 ++++++ ...deblock.initialize-realm-and-create-app.kt | 4 + .../final/TasksProvider.codeblock.clean-up.js | 9 ++ .../TasksProvider.codeblock.create-task.js | 13 +++ .../TasksProvider.codeblock.delete-task.js | 8 ++ ...ksProvider.codeblock.open-project-realm.js | 17 ++++ ...TasksProvider.codeblock.set-task-status.js | 18 ++++ .../TasksViewController.codeblock..swift | 5 ++ ...oller.codeblock.add-button-did-click.swift | 8 ++ ...TasksViewController.codeblock.deinit.swift | 4 + ...ViewController.codeblock.delete-task.swift | 12 +++ .../TasksViewController.codeblock.init.swift | 45 ++++++++++ ...iewController.codeblock.is-own-tasks.swift | 4 + ...ller.codeblock.populate-action-sheet.swift | 27 ++++++ ...comeViewController.codeblock.sign-in.swift | 45 ++++++++++ ...comeViewController.codeblock.sign-up.swift | 22 +++++ .../code/final/config.codeblock.realmAppID.js | 1 + .../getRealmApp.codeblock.get-realm-app.js | 16 ++++ .../code/final/index.codeblock.getRealm.js | 6 ++ .../code/final/index.codeblock.openRealm.js | 10 +++ .../final/projects.codeblock.getProjects.js | 7 ++ .../final/schemas.codeblock.projectSchema.js | 8 ++ .../final/schemas.codeblock.taskSchema.js | 11 +++ .../final/schemas.codeblock.userSchema.js | 10 +++ .../code/final/tasks.codeblock.createTask.js | 36 ++++++++ .../code/final/tasks.codeblock.deleteTask.js | 25 ++++++ .../code/final/tasks.codeblock.getTask.js | 19 +++++ .../code/final/tasks.codeblock.getTasks.js | 6 ++ .../code/final/tasks.codeblock.modifyTask.js | 13 +++ .../final/team.codeblock.addTeamMember.js | 17 ++++ .../final/team.codeblock.getTeamMembers.js | 10 +++ .../final/team.codeblock.removeTeamMember.js | 20 +++++ .../code/final/users.codeblock.newRealmApp.js | 1 + .../code/final/users.codeblock.userLogin.js | 34 ++++++++ source/tutorial/nodejs-cli.txt | 35 ++++---- tutorial/rn/schemas.js | 5 ++ 79 files changed, 1085 insertions(+), 95 deletions(-) create mode 100644 source/tutorial/generated/.gitignore create mode 100644 source/tutorial/generated/code/final/AuthProvider.codeblock.open-user-realm.js create mode 100644 source/tutorial/generated/code/final/AuthProvider.codeblock.sign-in.js create mode 100644 source/tutorial/generated/code/final/AuthProvider.codeblock.sign-out.js create mode 100644 source/tutorial/generated/code/final/AuthProvider.codeblock.sign-up.js create mode 100644 source/tutorial/generated/code/final/AuthProvider.codeblock.user-realm-cleanup.js create mode 100644 source/tutorial/generated/code/final/LoginActivity.codeblock.create-user.kt create mode 100644 source/tutorial/generated/code/final/LoginActivity.codeblock.login-user.kt create mode 100644 source/tutorial/generated/code/final/ManageTeam.codeblock.add-team-member.js create mode 100644 source/tutorial/generated/code/final/ManageTeam.codeblock.get-team.js create mode 100644 source/tutorial/generated/code/final/ManageTeam.codeblock.remove-team-member.js create mode 100644 source/tutorial/generated/code/final/ManageTeamViewController.codeblock.add-team-member.swift create mode 100644 source/tutorial/generated/code/final/ManageTeamViewController.codeblock.fetch-team-members.swift create mode 100644 source/tutorial/generated/code/final/ManageTeamViewController.codeblock.remove-team-member.swift create mode 100644 source/tutorial/generated/code/final/MemberActivity.codeblock.add-new-member-to-project.kt create mode 100644 source/tutorial/generated/code/final/MemberActivity.codeblock.get-team-members.kt create mode 100644 source/tutorial/generated/code/final/MemberAdapter.codeblock.remove-user-from-project.kt create mode 100644 source/tutorial/generated/code/final/Models.codeblock.project-model.swift create mode 100644 source/tutorial/generated/code/final/Models.codeblock.task-model.swift create mode 100644 source/tutorial/generated/code/final/Models.codeblock.user-model.swift create mode 100644 source/tutorial/generated/code/final/ProjectActivity.codeblock.fetch-synced-user-safely.kt create mode 100644 source/tutorial/generated/code/final/ProjectActivity.codeblock.on-destroy-close-realm.kt create mode 100644 source/tutorial/generated/code/final/ProjectActivity.codeblock.on-stop-close-realm.kt create mode 100644 source/tutorial/generated/code/final/ProjectActivity.codeblock.set-up-user-realm.kt create mode 100644 source/tutorial/generated/code/final/ProjectActivity.codeblock.user-init-change-listener.kt create mode 100644 source/tutorial/generated/code/final/ProjectsViewController.codeblock.cell-for-row-at.swift create mode 100644 source/tutorial/generated/code/final/ProjectsViewController.codeblock.did-select-row-at.swift create mode 100644 source/tutorial/generated/code/final/ProjectsViewController.codeblock.invalidate-token.swift create mode 100644 source/tutorial/generated/code/final/ProjectsViewController.codeblock.log-out-button-did-click.swift create mode 100644 source/tutorial/generated/code/final/ProjectsViewController.codeblock.number-of-rows-in-section.swift create mode 100644 source/tutorial/generated/code/final/ProjectsViewController.codeblock.user-in-realm-notification.swift create mode 100644 source/tutorial/generated/code/final/SceneDelegate.codeblock.initialize-app.swift create mode 100644 source/tutorial/generated/code/final/TaskActivity.codeblock.add-new-task-to-project.kt create mode 100644 source/tutorial/generated/code/final/TaskActivity.codeblock.fetch-tasks-for-project-sorted-by-id.kt create mode 100644 source/tutorial/generated/code/final/TaskActivity.codeblock.on-destroy-close-realm.kt create mode 100644 source/tutorial/generated/code/final/TaskActivity.codeblock.on-stop-close-realm.kt create mode 100644 source/tutorial/generated/code/final/TaskActivity.codeblock.set-up-project-realm.kt create mode 100644 source/tutorial/generated/code/final/TaskAdapter.codeblock.change-task-status.kt create mode 100644 source/tutorial/generated/code/final/TaskAdapter.codeblock.delete-task.kt create mode 100644 source/tutorial/generated/code/final/TaskItem.codeblock.define-task-status-actions.js create mode 100644 source/tutorial/generated/code/final/TaskTracker.codeblock.initialize-realm-and-create-app.kt create mode 100644 source/tutorial/generated/code/final/TasksProvider.codeblock.clean-up.js create mode 100644 source/tutorial/generated/code/final/TasksProvider.codeblock.create-task.js create mode 100644 source/tutorial/generated/code/final/TasksProvider.codeblock.delete-task.js create mode 100644 source/tutorial/generated/code/final/TasksProvider.codeblock.open-project-realm.js create mode 100644 source/tutorial/generated/code/final/TasksProvider.codeblock.set-task-status.js create mode 100644 source/tutorial/generated/code/final/TasksViewController.codeblock..swift create mode 100644 source/tutorial/generated/code/final/TasksViewController.codeblock.add-button-did-click.swift create mode 100644 source/tutorial/generated/code/final/TasksViewController.codeblock.deinit.swift create mode 100644 source/tutorial/generated/code/final/TasksViewController.codeblock.delete-task.swift create mode 100644 source/tutorial/generated/code/final/TasksViewController.codeblock.init.swift create mode 100644 source/tutorial/generated/code/final/TasksViewController.codeblock.is-own-tasks.swift create mode 100644 source/tutorial/generated/code/final/TasksViewController.codeblock.populate-action-sheet.swift create mode 100644 source/tutorial/generated/code/final/WelcomeViewController.codeblock.sign-in.swift create mode 100644 source/tutorial/generated/code/final/WelcomeViewController.codeblock.sign-up.swift create mode 100644 source/tutorial/generated/code/final/config.codeblock.realmAppID.js create mode 100644 source/tutorial/generated/code/final/getRealmApp.codeblock.get-realm-app.js create mode 100644 source/tutorial/generated/code/final/index.codeblock.getRealm.js create mode 100644 source/tutorial/generated/code/final/index.codeblock.openRealm.js create mode 100644 source/tutorial/generated/code/final/projects.codeblock.getProjects.js create mode 100644 source/tutorial/generated/code/final/schemas.codeblock.projectSchema.js create mode 100644 source/tutorial/generated/code/final/schemas.codeblock.taskSchema.js create mode 100644 source/tutorial/generated/code/final/schemas.codeblock.userSchema.js create mode 100644 source/tutorial/generated/code/final/tasks.codeblock.createTask.js create mode 100644 source/tutorial/generated/code/final/tasks.codeblock.deleteTask.js create mode 100644 source/tutorial/generated/code/final/tasks.codeblock.getTask.js create mode 100644 source/tutorial/generated/code/final/tasks.codeblock.getTasks.js create mode 100644 source/tutorial/generated/code/final/tasks.codeblock.modifyTask.js create mode 100644 source/tutorial/generated/code/final/team.codeblock.addTeamMember.js create mode 100644 source/tutorial/generated/code/final/team.codeblock.getTeamMembers.js create mode 100644 source/tutorial/generated/code/final/team.codeblock.removeTeamMember.js create mode 100644 source/tutorial/generated/code/final/users.codeblock.newRealmApp.js create mode 100644 source/tutorial/generated/code/final/users.codeblock.userLogin.js diff --git a/source/includes/steps-tutorial-android-kotlin.yaml b/source/includes/steps-tutorial-android-kotlin.yaml index 306b759814..72c29a44f5 100644 --- a/source/includes/steps-tutorial-android-kotlin.yaml +++ b/source/includes/steps-tutorial-android-kotlin.yaml @@ -9,11 +9,11 @@ content: | .. code-block:: console - $ git clone git@github.com:mongodb-university/realm-tutorial-kotlin-android.git + git clone git@github.com:mongodb-university/realm-tutorial-android-kotlin.git .. important:: - The realm-tutorial-kotlin-android repository contains two branches: + The realm-tutorial-android-kotlin repository contains two branches: ``final`` and ``start``. The ``final`` branch is a finished version of the app as it should look *after* you complete this tutorial. To walk through this tutorial, please check out the ``start`` @@ -21,7 +21,7 @@ content: | .. code-block:: console - $ git checkout start + git checkout start --- title: Open the Project in Android Studio @@ -38,9 +38,9 @@ content: | 3. In the file navigator opened by Android Studio, navigate to the directory where, in the previous step, you cloned the - ``realm-tutorial-kotlin-android`` repository. + ``realm-tutorial-android-kotlin`` repository. - 4. Select the ``realm-tutorial-kotlin-android`` folder. + 4. Select the ``realm-tutorial-android-kotlin`` folder. 5. Click "Open". @@ -216,7 +216,7 @@ content: | fields for email and password entry, as well as buttons to either register a user account or login to an existing account. We need to implement the logic to handle user login and user account creation. - + You'll find this logic in the ``login()`` method, where a boolean value called ``createuser`` controls where the method submits user credentials to create a new account or to login to an existing @@ -224,14 +224,16 @@ content: | First, let's implement the logic that registers a new user: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/LoginActivity.codeblock.create-user.kt + :language: kotlin Now, implement the logic to log in with an existing user. Once logged in successfully, call the ``onLoginSuccess()`` method, which closes the ``LoginActivity`` and resumes the calling activity (typically ``ProjectActivity``): - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/LoginActivity.codeblock.login-user.kt + :language: kotlin Don't forget to call the ``onLoginFailed()`` method in the event of a login or account creation failure with a message describing the error. @@ -252,7 +254,8 @@ content: | the user's {+realm+} in the ``else`` block of the ``onStart()`` method of ``ProjectActivity``: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/ProjectActivity.codeblock.set-up-user-realm.kt + :language: kotlin Next, we need to query the {+realm+} to get a copy of the ``User`` object containing the user's list of projects. Because each user @@ -263,7 +266,8 @@ content: | object when the user creates an account. Add the code that queries for the user object: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/ProjectActivity.codeblock.user-init-change-listener.kt + :language: kotlin Because it can take a few seconds for the trigger to create this object after a login, we should handle the case where the user object @@ -272,14 +276,19 @@ content: | watch the {+realm+} for changes and only set up the project's Recycler View once the trigger runs: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/ProjectActivity.codeblock.fetch-synced-user-safely.kt + :language: kotlin Finally, we need to guarantee that ``ProjectActivity`` always closes the user {+realm+} when the app closes or the user logs out. To accomplish this, add logic that calls the ``realm.close()`` method when ``ProjectActivity`` finishes or stops: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/ProjectActivity.codeblock.on-destroy-close-realm.kt + :language: kotlin + + .. literalinclude:: /tutorial/generated/code/final/ProjectActivity.codeblock.on-stop-close-realm.kt + :language: kotlin --- title: Implement the Tasks List @@ -294,7 +303,8 @@ content: | the user who owns the project). We'll begin by initializing a connection to this {+realm+} when the activity starts: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/TaskActivity.codeblock.set-up-project-realm.kt + :language: kotlin Next, we'll query the realm for the list of tasks belonging to this project. Fortunately the query isn't too complicated: since every task @@ -307,21 +317,27 @@ content: | list of tasks, pass the ``RealmResult`` to the ``TaskAdapter`` and set that adapter as the ``RecyclerView's`` adapter: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/TaskActivity.codeblock.fetch-tasks-for-project-sorted-by-id.kt + :language: kotlin - ``TaskActivity`` needs allow users to create a new task in the - project. To handle this, write logic in the floating action button's - ``setPositiveButton()`` callback that creates a new task based on the - user's input in ``inputText`` and adds that task to the {+realm+}: + ``TaskActivity`` needs to allow users to create a new task in the project. To + handle this, write logic in the floating action button's + ``setPositiveButton()`` callback that creates a new task based on the user's + input in ``inputText`` and adds that task to the {+realm+}: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/TaskActivity.codeblock.add-new-task-to-project.kt + :language: kotlin Finally, we need to guarantee that ``TaskActivity`` always closes the user {+realm+} when the app closes or the user logs out. To accomplish this, add logic that calls the ``realm.close()`` method when ``TaskActivity`` finishes or stops: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/TaskActivity.codeblock.on-destroy-close-realm.kt + :language: kotlin + + .. literalinclude:: /tutorial/generated/code/final/TaskActivity.codeblock.on-stop-close-realm.kt + :language: kotlin --- title: Add Logic to Update and Delete Tasks to the TaskAdapter @@ -347,13 +363,15 @@ content: | Don't forget to read and write from the {+realm+} within a transaction! - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/TaskAdapter.codeblock.change-task-status.kt + :language: kotlin The logic that deletes a task is similar to the logic that updates a task, but it removes the task from the {+realm+} instead of updating any properties: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/TaskAdapter.codeblock.delete-task.kt + :language: kotlin --- title: Implement the Manage Team View @@ -380,7 +398,8 @@ content: | access {+service-short+} Functions through the function manager found in your project-global {+service-short+} app: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/MemberActivity.codeblock.get-team-members.kt + :language: kotlin Similar to ``TaskActivity``, we'll use the floating action button in ``MemberActivity`` to add users to the project. To handle this, write @@ -396,7 +415,8 @@ content: | backend when the ``addTeamMember()`` {+service-short+} Function successfully adds a team member: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/MemberActivity.codeblock.add-new-member-to-project.kt + :language: kotlin --- title: Handle Team Member Removals in MemberAdapter @@ -416,7 +436,8 @@ content: | position in the dataset and the UI should automatically stop displaying the removed team member: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/MemberAdapter.codeblock.remove-user-from-project.kt + :language: kotlin --- title: Run and Test diff --git a/source/includes/steps-tutorial-ios-swift.yaml b/source/includes/steps-tutorial-ios-swift.yaml index 48037a8765..d543b93839 100644 --- a/source/includes/steps-tutorial-ios-swift.yaml +++ b/source/includes/steps-tutorial-ios-swift.yaml @@ -81,7 +81,7 @@ content: | ID to the SceneDelegate.swift file. The SceneDelegate.swift file declares a global Realm App instance: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/SceneDelegate.codeblock.initialize-app.swift :language: swift Change the value of ``id`` to your Realm app ID, which you can :ref:`find in @@ -95,16 +95,19 @@ content: | this app. First, implement the User model, which contains information about the user's membership in different projects: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/Models.codeblock.user-model.swift + :language: swift Next, implement the Project embedded object that represents a Project that a User is a member of: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/Models.codeblock.project-model.swift + :language: swift Finally, let's turn the Task class into a Realm object model: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/Models.codeblock.task-model.swift + :language: swift --- title: Enable Authentication @@ -117,23 +120,26 @@ content: | First, let's implement the ``signUp()`` method to register a new user: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/WelcomeViewController.codeblock.sign-up.swift + :language: swift Now, implement the ``signIn()`` method to log in with an existing user. Once logged in successfully, open the user realm and navigate to the ProjectsViewController: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/WelcomeViewController.codeblock.sign-in.swift + :language: swift --- title: Implement the Projects List ref: implement-the-projects-list content: | - The WelcomeViewController has opened the user realm for us and passed it in to - this controller. This is a good practice, as it allows the realm to fully - download before transitioning to the next screen. If the realm failed to open - for some reason, the error could be handled on the original screen and not - have to undo a transition. + Open the ProjectsViewController.swift file, which is where we present the user + with a list of projects they are a member of. The WelcomeViewController has + opened the user realm for us and passed it in to this controller. This is a + good practice, as it allows the realm to fully download before transitioning + to the next screen. If the realm failed to open for some reason, the error + could be handled on the original screen and not have to undo a transition. We have designed our app so that the user realm will only ever contain at most one object: that is, the user custom data object. Because an authentication @@ -141,14 +147,16 @@ content: | immediately after creating an account. So, we should watch the realm itself for changes: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/ProjectsViewController.codeblock.user-in-realm-notification.swift + :language: swift Let's provide a way for a user to log out and get back to the WelcomeViewController. The ``viewDidLoad()`` method hooks up the Log Out button at the top of the view to the ``logOutButtonDidClick()`` method. We can implement ``logOutButtonDidClick()`` as follows: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/ProjectsViewController.codeblock.log-out-button-did-click.swift + :language: swift Since the ProjectsViewController implements the UITableViewDelegate protocol for its own list, let's implement these methods. First, implement the @@ -157,13 +165,15 @@ content: | their own project, we always return at least 1 even though the user object may not be available yet: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/ProjectsViewController.codeblock.number-of-rows-in-section.swift + :language: swift Next, implement the ``tableView(_:cellForRowAt:)`` to fill out the project information for each cell. Again, if the user object has not loaded yet, we still know the user's own project is always in the list: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/ProjectsViewController.codeblock.cell-for-row-at.swift + :language: swift Finally, implement the ``tableView(_:didSelectRowAt:)`` method to handle what happens when the user clicks a project in the list. As we opened the user @@ -171,7 +181,8 @@ content: | ProjectsViewController, we'll open the project realm before navigating to the TasksViewController: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/ProjectsViewController.codeblock.did-select-row-at.swift + :language: swift --- title: Implement the Tasks List @@ -184,13 +195,15 @@ content: | use ``RealmResults`` instead of a standard Swift array. Let's convert that property now: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/TasksViewController.codeblock..swift + :language: swift We can initialize the ``tasks`` property with a query on the project realm passed in from the ProjectsViewController. Once we have the live tasks collection, we can observe that for changes: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/TasksViewController.codeblock.init.swift + :language: swift Calls to ``observe`` Realm objects return a notificationToken. Always retain the notificationToken as long as you want the observation to complete. When @@ -198,7 +211,8 @@ content: | -- be sure to invalidate the token. The ``deinit`` method is a good place to do so: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/TasksViewController.codeblock.deinit.swift + :language: swift The TasksViewController already populates the UI using the Tasks in the list. Check out the ``tableView(_:numberOfRowsInSection:)`` and @@ -211,25 +225,29 @@ content: | the ``addButtonDidClick()`` method. We can implement the Task creation in this method: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/TasksViewController.codeblock.add-button-did-click.swift + :language: swift When the user selects a Task in the list, we present them with an action sheet to allow them to update the Task's status. Complete the ``tableView(_:didSelectRowAt:)`` method implementation as follows: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/TasksViewController.codeblock.populate-action-sheet.swift + :language: swift To handle swipes to delete a Task, we implement the ``tableView(_:commit:forRowAt:)`` method: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/TasksViewController.codeblock.delete-task.swift + :language: swift Finally, a user can manage the team of their own project. When setting up the UI, the TasksViewController uses the ``isOwnTasks()`` method to decide whether to add the "Manage Team" action to the action bar. We can implement that as follows: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/TasksViewController.codeblock.is-own-tasks.swift + :language: swift --- title: Implement the Manage Team View @@ -246,7 +264,8 @@ content: | ``getMyTeamMembers`` function that returns a list of team members and refresh the list upon successful return: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/ManageTeamViewController.codeblock.fetch-team-members.swift + :language: swift The ManageTeamViewController wires up the add button and swipe to delete functionality to the ``addTeamMember()`` and ``removeTeamMember()`` methods, @@ -256,13 +275,15 @@ content: | can use the ``onTeamMemberOperationComplete()`` method as a completion handler: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/ManageTeamViewController.codeblock.add-team-member.swift + :language: swift The ``removeTeamMember()`` method calls the ``removeTeamMember`` Realm function and also uses the ``onTeamMemberOperationComplete()`` method as a completion handler: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/ManageTeamViewController.codeblock.remove-team-member.swift + :language: swift The ``onTeamMemberOperationComplete()`` method presents any errors to the user and refreshes the member list. @@ -275,16 +296,16 @@ content: | Click the :guilabel:`Run` button in Xcode. If the app builds successfully, here are some things you can try in the app: - - Create a user with email first@example.com + - Create a user with email *first@example.com* - Explore the app, then log out or launch a second instance of the app on another device or simulator - Create another user with email second@example.com - - Navigate to second@example.com's project + - Navigate to *second@example.com*'s project - Add, update, and remove some tasks - Click "Manage Team" - - Add first@example.com to your team - - Log out and log in as first@example.com + - Add *first@example.com* to your team + - Log out and log in as *first@example.com* - See two projects in the projects list - - Navigate to second@example.com's project + - Navigate to *second@example.com*'s project - Collaborate by adding, updating, and removing some new tasks .. admonition:: Reminder diff --git a/source/includes/steps-tutorial-react-native.yaml b/source/includes/steps-tutorial-react-native.yaml index 59f12f9ac1..670ec7ec77 100644 --- a/source/includes/steps-tutorial-react-native.yaml +++ b/source/includes/steps-tutorial-react-native.yaml @@ -124,7 +124,7 @@ content: | Navigate to the getRealmApp.js file and complete the ``getRealmApp()`` function implementation: - .. literalinclude:: FIXME + .. literalinclude:: /tutorial/generated/code/final/getRealmApp.codeblock.get-realm-app.js :language: javascript Be sure to enter your own Realm app ID, which you can :ref:`find in the Realm @@ -152,16 +152,19 @@ content: | First, let's implement the login functionality that a descendant component can pass an email and password to in order to log in: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/AuthProvider.codeblock.sign-in.js + :language: javascript Next, implement the user registration functionality by using the email/password provider of the Realm app: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/AuthProvider.codeblock.sign-up.js + :language: javascript Next, implement the log out functionality: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/AuthProvider.codeblock.sign-out.js + :language: javascript Finally, the AuthProvider must access the user custom data object for the logged-in user in order to provide the list of Projects that the user is a @@ -179,13 +182,15 @@ content: | available, read its memberOf field to populate the available Project data for any descendant of the provider: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/AuthProvider.codeblock.open-user-realm.js + :language: javascript Finally, the Realm SDK requires you to close any realm you open when you are finished with it. You can return a cleanup function from the effect, which will close the realm any time the user changes or the component is unmounted: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/AuthProvider.codeblock.user-realm-cleanup.js + :language: javascript Check out the App.js, view/WelcomeView.js, and view/ProjectView.js files to see how they use the AuthProvider's signIn, signUp, and signOut functions and @@ -201,7 +206,8 @@ content: | their schema. Navigate to the schemas.js file to complete the Task class's schema definition: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/schemas.codeblock.react-native-task-schema.js + :language: javascript --- title: Implement the TasksProvider @@ -221,26 +227,31 @@ content: | In the effect block, open the realm. Once opened, attach an observer that will update the Tasks list when any changes come in: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/TasksProvider.codeblock.open-project-realm.js + :language: javascript Again, Realm requires you to close any realm you open once you are done with it. We can do that by returning a cleanup function from the effect: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/TasksProvider.codeblock.clean-up.js + :language: javascript A user can create, update, and delete Tasks in a Project that they are a member of. Implement the Task creation functionality like so: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/TasksProvider.codeblock.create-task.js + :language: javascript We limit updates to changing the status of the Task. In a write block, you can assign to the Task object's members to have it persisted to the database: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/TasksProvider.codeblock.set-task-status.js + :language: javascript Finally, implement the Task deletion functionality: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/TasksProvider.codeblock.delete-task.js + :language: javascript Check out the views/TasksView.js and components/TaskItem.js files to see how they use the provider's task data and functionality via the ``useTasks()`` @@ -257,17 +268,20 @@ content: | Navigate to components/ManageTeam.js. First, we need to fetch the list of current team members: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/ManageTeam.codeblock.get-team.js + :language: javascript Next, call the addTeamMember function on the backend to implement add team member functionality: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/ManageTeam.codeblock.add-team-member.js + :language: javascript Finally, call the removeTeamMember function on the backend to implement remove team member functionality: - .. literalinclude:: FIXME FIXME FIXME + .. literalinclude:: /tutorial/generated/code/final/ManageTeam.codeblock.remove-team-member.js + :language: javascript title: Run and Test ref: run-and-test @@ -275,16 +289,16 @@ content: | Congratulations! Now that you have completed the code, you can run the app and check functionality. Here are some things you can try in the app: - - Create a user with email first@example.com + - Create a user with email *first@example.com* - Explore the app, then log out or launch a second instance of the app on another device or simulator - - Create another user with email second@example.com - - Navigate to second@example.com's project + - Create another user with email *second@example.com* + - Navigate to *second@example.com*'s project - Add, update, and remove some tasks - Click "Manage Team" - - Add first@example.com to your team - - Log out and log in as first@example.com + - Add *first@example.com* to your team + - Log out and log in as *first@example.com* - See two projects in the projects list - - Navigate to second@example.com's project + - Navigate to *second@example.com*'s project - Collaborate by adding, updating, and removing some new tasks .. admonition:: Reminder diff --git a/source/tutorial/android-kotlin.txt b/source/tutorial/android-kotlin.txt index 82b7faa59f..91828afef5 100644 --- a/source/tutorial/android-kotlin.txt +++ b/source/tutorial/android-kotlin.txt @@ -48,6 +48,7 @@ The backend app is set up with an email/password authentication provider that we can use to let users sign up and log in. Now, we need to define sync rules and enable sync so that sync clients can read and write objects. + .. include:: /includes/steps/task-tracker-enable-sync.rst Set Up the Mobile App @@ -57,7 +58,7 @@ Set Up the Mobile App :class: note We host this tutorial application's :github:`complete and ready-to-compile - source code ` on + source code ` on GitHub. Just follow the instructions in ``README.md`` to get started. Don't forget to update the ``build.gradle`` file with your App ID, which you can find in the {+ui+}. diff --git a/source/tutorial/generated/.gitignore b/source/tutorial/generated/.gitignore new file mode 100644 index 0000000000..e4d3b4bd01 --- /dev/null +++ b/source/tutorial/generated/.gitignore @@ -0,0 +1,3 @@ +steps/ +code/start/ + diff --git a/source/tutorial/generated/code/final/AuthProvider.codeblock.open-user-realm.js b/source/tutorial/generated/code/final/AuthProvider.codeblock.open-user-realm.js new file mode 100644 index 0000000000..d329a6b1b2 --- /dev/null +++ b/source/tutorial/generated/code/final/AuthProvider.codeblock.open-user-realm.js @@ -0,0 +1,24 @@ +const config = { + sync: { + user, + partitionValue: `user=${user.id}`, + }, +}; + +// Open a realm with the logged in user's partition value in order +// to get the projects that the logged in user is a member of +Realm.open(config).then((userRealm) => { + realmRef.current = userRealm; + const users = userRealm.objects("User"); + + users.addListener(() => { + // The user custom data object may not have been loaded on + // the server side yet when a user is first registered. + if (users.length === 0) { + setProjectData([myProject]); + } else { + const { memberOf } = users[0]; + setProjectData([...memberOf]); + } + }); +}); \ No newline at end of file diff --git a/source/tutorial/generated/code/final/AuthProvider.codeblock.sign-in.js b/source/tutorial/generated/code/final/AuthProvider.codeblock.sign-in.js new file mode 100644 index 0000000000..fc0d957f16 --- /dev/null +++ b/source/tutorial/generated/code/final/AuthProvider.codeblock.sign-in.js @@ -0,0 +1,7 @@ +// The signIn function takes an email and password and uses the +// emailPassword authentication provider to log in. +const signIn = async (email, password) => { + const creds = Realm.Credentials.emailPassword(email, password); + const newUser = await app.logIn(creds); + setUser(newUser); +}; \ No newline at end of file diff --git a/source/tutorial/generated/code/final/AuthProvider.codeblock.sign-out.js b/source/tutorial/generated/code/final/AuthProvider.codeblock.sign-out.js new file mode 100644 index 0000000000..1d2dd11fff --- /dev/null +++ b/source/tutorial/generated/code/final/AuthProvider.codeblock.sign-out.js @@ -0,0 +1,10 @@ +// The signOut function calls the logOut function on the currently +// logged in user +const signOut = () => { + if (user == null) { + console.warn("Not logged in, can't log out!"); + return; + } + user.logOut(); + setUser(null); +}; \ No newline at end of file diff --git a/source/tutorial/generated/code/final/AuthProvider.codeblock.sign-up.js b/source/tutorial/generated/code/final/AuthProvider.codeblock.sign-up.js new file mode 100644 index 0000000000..6df035ccf3 --- /dev/null +++ b/source/tutorial/generated/code/final/AuthProvider.codeblock.sign-up.js @@ -0,0 +1,5 @@ +// The signUp function takes an email and password and uses the +// emailPassword authentication provider to register the user. +const signUp = async (email, password) => { + await app.emailPasswordAuth.registerUser(email, password); +}; \ No newline at end of file diff --git a/source/tutorial/generated/code/final/AuthProvider.codeblock.user-realm-cleanup.js b/source/tutorial/generated/code/final/AuthProvider.codeblock.user-realm-cleanup.js new file mode 100644 index 0000000000..18be641a6b --- /dev/null +++ b/source/tutorial/generated/code/final/AuthProvider.codeblock.user-realm-cleanup.js @@ -0,0 +1,19 @@ + return () => { + // cleanup function + const userRealm = realmRef.current; + if (userRealm) { + userRealm.close(); + realmRef.current = null; + setProjectData([]); // set project data to an empty array (this prevents the array from staying in state on logout) + } + }; +}, [user]); + +// :code-block-start: sign-in +// The signIn function takes an email and password and uses the +// emailPassword authentication provider to log in. +const signIn = async (email, password) => { + const creds = Realm.Credentials.emailPassword(email, password); + const newUser = await app.logIn(creds); + setUser(newUser); +}; \ No newline at end of file diff --git a/source/tutorial/generated/code/final/LoginActivity.codeblock.create-user.kt b/source/tutorial/generated/code/final/LoginActivity.codeblock.create-user.kt new file mode 100644 index 0000000000..f7e067566e --- /dev/null +++ b/source/tutorial/generated/code/final/LoginActivity.codeblock.create-user.kt @@ -0,0 +1,13 @@ +taskApp.emailPassword.registerUserAsync(username, password) { + // re-enable the buttons after user registration returns a result + createUserButton.isEnabled = true + loginButton.isEnabled = true + if (!it.isSuccess) { + onLoginFailed("Could not register user.") + Log.e(TAG(), "Error: ${it.error}") + } else { + Log.i(TAG(), "Successfully registered user.") + // when the account has been created successfully, log in to the account + login(false) + } +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/LoginActivity.codeblock.login-user.kt b/source/tutorial/generated/code/final/LoginActivity.codeblock.login-user.kt new file mode 100644 index 0000000000..7f3bfaddb9 --- /dev/null +++ b/source/tutorial/generated/code/final/LoginActivity.codeblock.login-user.kt @@ -0,0 +1,11 @@ +val creds = Credentials.emailPassword(username, password) +taskApp.loginAsync(creds) { + // re-enable the buttons after user login returns a result + loginButton.isEnabled = true + createUserButton.isEnabled = true + if (!it.isSuccess) { + onLoginFailed(it.error.message ?: "An error occurred.") + } else { + onLoginSuccess() + } +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/ManageTeam.codeblock.add-team-member.js b/source/tutorial/generated/code/final/ManageTeam.codeblock.add-team-member.js new file mode 100644 index 0000000000..c6c1e8a49d --- /dev/null +++ b/source/tutorial/generated/code/final/ManageTeam.codeblock.add-team-member.js @@ -0,0 +1,10 @@ +// addTeamMember calls the backend function addTeamMember to add a +// team member to the logged in user's project +const addTeamMember = async () => { + try { + await user.functions.addTeamMember(newTeamMember); + getTeam(); + } catch (err) { + Alert.alert("An error occurred while adding a team member", err.message); + } +}; \ No newline at end of file diff --git a/source/tutorial/generated/code/final/ManageTeam.codeblock.get-team.js b/source/tutorial/generated/code/final/ManageTeam.codeblock.get-team.js new file mode 100644 index 0000000000..d891b3f15f --- /dev/null +++ b/source/tutorial/generated/code/final/ManageTeam.codeblock.get-team.js @@ -0,0 +1,10 @@ +// getTeam calls the backend function getMyTeamMembers to retrieve the +// team members of the logged in user's project +const getTeam = async () => { + try { + const teamMembers = await user.functions.getMyTeamMembers([]); + setTeamMemberList(teamMembers); + } catch (err) { + Alert.alert("An error occurred while getting team members", err); + } +}; \ No newline at end of file diff --git a/source/tutorial/generated/code/final/ManageTeam.codeblock.remove-team-member.js b/source/tutorial/generated/code/final/ManageTeam.codeblock.remove-team-member.js new file mode 100644 index 0000000000..fcacf63a8d --- /dev/null +++ b/source/tutorial/generated/code/final/ManageTeam.codeblock.remove-team-member.js @@ -0,0 +1,10 @@ +// removeTeamMember calls the backend function removeTeamMember to remove a +// team member from the logged in user's project +const removeTeamMember = async (email) => { + try { + await user.functions.removeTeamMember(email); + getTeam(); + } catch (err) { + Alert.alert("An error occurred while removing a team member", err); + } +}; \ No newline at end of file diff --git a/source/tutorial/generated/code/final/ManageTeamViewController.codeblock.add-team-member.swift b/source/tutorial/generated/code/final/ManageTeamViewController.codeblock.add-team-member.swift new file mode 100644 index 0000000000..8d3b261a55 --- /dev/null +++ b/source/tutorial/generated/code/final/ManageTeamViewController.codeblock.add-team-member.swift @@ -0,0 +1,5 @@ +func addTeamMember(email: String) { + print("Adding member: \(email)") + activityIndicator.startAnimating() + app.currentUser()!.functions.addTeamMember([AnyBSON(email)!], self.onTeamMemberOperationComplete) +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/ManageTeamViewController.codeblock.fetch-team-members.swift b/source/tutorial/generated/code/final/ManageTeamViewController.codeblock.fetch-team-members.swift new file mode 100644 index 0000000000..dc3319a68b --- /dev/null +++ b/source/tutorial/generated/code/final/ManageTeamViewController.codeblock.fetch-team-members.swift @@ -0,0 +1,30 @@ +// Calls a Realm function to fetch the team members and adds them to the list +func fetchTeamMembers() { + // Start loading indicator + activityIndicator.startAnimating() + app.currentUser()!.functions.getMyTeamMembers([]) { [weak self](result, error) in + DispatchQueue.main.sync { + guard self != nil else { + // This can happen if the view is dismissed + // before the operation completes + print("Team members list no longer needed."); + return + } + // Stop loading indicator + self!.activityIndicator.stopAnimating() + guard error == nil else { + print("Fetch team members failed: \(error!.localizedDescription)") + return + } + print("Fetch team members complete.") + + // Convert documents to members array + self!.members = result!.arrayValue!.map({ (bson) in + return Member(document: bson!.documentValue!) + }) + + // Notify UI of changed data + self!.tableView.reloadData() + } + } +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/ManageTeamViewController.codeblock.remove-team-member.swift b/source/tutorial/generated/code/final/ManageTeamViewController.codeblock.remove-team-member.swift new file mode 100644 index 0000000000..4c0652bf22 --- /dev/null +++ b/source/tutorial/generated/code/final/ManageTeamViewController.codeblock.remove-team-member.swift @@ -0,0 +1,5 @@ +func removeTeamMember(email: String) { + print("Removing member: \(email)") + activityIndicator.startAnimating() + app.currentUser()!.functions.removeTeamMember([AnyBSON(email)!], self.onTeamMemberOperationComplete) +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/MemberActivity.codeblock.add-new-member-to-project.kt b/source/tutorial/generated/code/final/MemberActivity.codeblock.add-new-member-to-project.kt new file mode 100644 index 0000000000..95fe99ab9b --- /dev/null +++ b/source/tutorial/generated/code/final/MemberActivity.codeblock.add-new-member-to-project.kt @@ -0,0 +1,18 @@ +val functionsManager: Functions = taskApp.getFunctions(user) +functionsManager.callFunctionAsync( + "addTeamMember", + listOf(input.text.toString()), + Document::class.java +) { result -> + if (result.isSuccess) { + Log.v( + TAG(), + "Attempted to add team member. Result: ${result.get()}" + ) + // rebuild the list of members to display the newly-added member + setUpRecyclerView() + } else { + Log.e(TAG(), "failed to add team member with: " + result.error) + Toast.makeText(this, result.error.errorMessage, Toast.LENGTH_LONG).show() + } +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/MemberActivity.codeblock.get-team-members.kt b/source/tutorial/generated/code/final/MemberActivity.codeblock.get-team-members.kt new file mode 100644 index 0000000000..ae9d30d4f9 --- /dev/null +++ b/source/tutorial/generated/code/final/MemberActivity.codeblock.get-team-members.kt @@ -0,0 +1,16 @@ +val functionsManager: Functions = taskApp.getFunctions(user) +// get team members by calling a Realm Function which returns a list of members +functionsManager.callFunctionAsync("getMyTeamMembers", ArrayList(), ArrayList::class.java) { result -> + if (result.isSuccess) { + Log.v(TAG(), "successfully fetched team members. Number of team members: ${result.get().size}") + // The `getMyTeamMembers` function returns team members as Document objects. Convert them into Member objects so the MemberAdapter can display them. + this.members = ArrayList(result.get().map { item -> Member(item as Document) }) + adapter = MemberAdapter(members, user!!) + recyclerView.layoutManager = LinearLayoutManager(this) + recyclerView.adapter = adapter + recyclerView.setHasFixedSize(true) + recyclerView.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.VERTICAL)) + } else { + Log.e(TAG(), "failed to get team members with: " + result.error) + } +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/MemberAdapter.codeblock.remove-user-from-project.kt b/source/tutorial/generated/code/final/MemberAdapter.codeblock.remove-user-from-project.kt new file mode 100644 index 0000000000..4555972915 --- /dev/null +++ b/source/tutorial/generated/code/final/MemberAdapter.codeblock.remove-user-from-project.kt @@ -0,0 +1,15 @@ +val functionsManager: Functions = taskApp.getFunctions(user) +functionsManager.callFunctionAsync("removeTeamMember", + listOf(obj.name), Document::class.java) { result -> + run { + dialog.dismiss() + if (result.isSuccess) { + Log.v(TAG(), "removed team member: ${result.get()}") + data.removeAt(position) + notifyItemRemoved(position) + } else { + Log.e(TAG(), "failed to remove team member with: " + result.error) + Toast.makeText(parent.context, result.error.toString(), Toast.LENGTH_LONG).show() + } + } +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/Models.codeblock.project-model.swift b/source/tutorial/generated/code/final/Models.codeblock.project-model.swift new file mode 100644 index 0000000000..2b700b4ef5 --- /dev/null +++ b/source/tutorial/generated/code/final/Models.codeblock.project-model.swift @@ -0,0 +1,9 @@ +class Project: EmbeddedObject { + @objc dynamic var name: String? = nil + @objc dynamic var partition: String? = nil + convenience init(partition: String, name: String) { + self.init() + self.partition = partition + self.name = name + } +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/Models.codeblock.task-model.swift b/source/tutorial/generated/code/final/Models.codeblock.task-model.swift new file mode 100644 index 0000000000..2a65f5cbb3 --- /dev/null +++ b/source/tutorial/generated/code/final/Models.codeblock.task-model.swift @@ -0,0 +1,25 @@ +class Task: Object { + @objc dynamic var _id: ObjectId = ObjectId.generate() + @objc dynamic var _partition: String = "" + @objc dynamic var name: String = "" + @objc dynamic var owner: String? = nil + @objc dynamic var status: String = "" + override static func primaryKey() -> String? { + return "_id" + } + + var statusEnum: TaskStatus { + get { + return TaskStatus(rawValue: status) ?? .Open + } + set { + status = newValue.rawValue + } + } + + convenience init(partition: String, name: String) { + self.init() + self._partition = partition + self.name = name + } +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/Models.codeblock.user-model.swift b/source/tutorial/generated/code/final/Models.codeblock.user-model.swift new file mode 100644 index 0000000000..238da638bf --- /dev/null +++ b/source/tutorial/generated/code/final/Models.codeblock.user-model.swift @@ -0,0 +1,9 @@ +class User: Object { + @objc dynamic var _id: String = "" + @objc dynamic var _partition: String = "" + @objc dynamic var name: String = "" + let memberOf = RealmSwift.List() + override static func primaryKey() -> String? { + return "_id" + } +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/ProjectActivity.codeblock.fetch-synced-user-safely.kt b/source/tutorial/generated/code/final/ProjectActivity.codeblock.fetch-synced-user-safely.kt new file mode 100644 index 0000000000..327a43ef1c --- /dev/null +++ b/source/tutorial/generated/code/final/ProjectActivity.codeblock.fetch-synced-user-safely.kt @@ -0,0 +1,2 @@ +val syncedUsers : RealmResults = realm.where().sort("_id").findAll() +val syncedUser : User? = syncedUsers.getOrNull(0) // since there might be no user objects in the results, default to "null" \ No newline at end of file diff --git a/source/tutorial/generated/code/final/ProjectActivity.codeblock.on-destroy-close-realm.kt b/source/tutorial/generated/code/final/ProjectActivity.codeblock.on-destroy-close-realm.kt new file mode 100644 index 0000000000..2cf05cb58b --- /dev/null +++ b/source/tutorial/generated/code/final/ProjectActivity.codeblock.on-destroy-close-realm.kt @@ -0,0 +1,5 @@ +override fun onDestroy() { + super.onDestroy() + userRealm?.close() + recyclerView.adapter = null +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/ProjectActivity.codeblock.on-stop-close-realm.kt b/source/tutorial/generated/code/final/ProjectActivity.codeblock.on-stop-close-realm.kt new file mode 100644 index 0000000000..242dc4aba2 --- /dev/null +++ b/source/tutorial/generated/code/final/ProjectActivity.codeblock.on-stop-close-realm.kt @@ -0,0 +1,6 @@ +override fun onStop() { + super.onStop() + user.run { + userRealm?.close() + } +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/ProjectActivity.codeblock.set-up-user-realm.kt b/source/tutorial/generated/code/final/ProjectActivity.codeblock.set-up-user-realm.kt new file mode 100644 index 0000000000..90e3532a50 --- /dev/null +++ b/source/tutorial/generated/code/final/ProjectActivity.codeblock.set-up-user-realm.kt @@ -0,0 +1,12 @@ +// configure realm to use the current user and the partition corresponding to the user's project +val config = SyncConfiguration.Builder(user!!, "user=${user!!.id}") + .build() + +// Sync all realm changes via a new instance, and when that instance has been successfully created connect it to an on-screen list (a recycler view) +Realm.getInstanceAsync(config, object: Realm.Callback() { + override fun onSuccess(realm: Realm) { + // since this realm should live exactly as long as this activity, assign the realm to a member variable + this@ProjectActivity.userRealm = realm + setUpRecyclerView(realm) + } +}) \ No newline at end of file diff --git a/source/tutorial/generated/code/final/ProjectActivity.codeblock.user-init-change-listener.kt b/source/tutorial/generated/code/final/ProjectActivity.codeblock.user-init-change-listener.kt new file mode 100644 index 0000000000..c1aba8915b --- /dev/null +++ b/source/tutorial/generated/code/final/ProjectActivity.codeblock.user-init-change-listener.kt @@ -0,0 +1,5 @@ +val changeListener = OrderedRealmCollectionChangeListener> { results, changeSet -> + Log.i(TAG(), "User object initialized, displaying project list.") + setUpRecyclerView(realm) +} +syncedUsers.addChangeListener(changeListener) \ No newline at end of file diff --git a/source/tutorial/generated/code/final/ProjectsViewController.codeblock.cell-for-row-at.swift b/source/tutorial/generated/code/final/ProjectsViewController.codeblock.cell-for-row-at.swift new file mode 100644 index 0000000000..00d64a0cac --- /dev/null +++ b/source/tutorial/generated/code/final/ProjectsViewController.codeblock.cell-for-row-at.swift @@ -0,0 +1,10 @@ +func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") ?? UITableViewCell(style: .default, reuseIdentifier: "Cell") + cell.selectionStyle = .none + + // User data may not have loaded yet. You always have your own project. + let projectName = userData?.memberOf[indexPath.row].name ?? "My Project" + cell.textLabel?.text = projectName + + return cell +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/ProjectsViewController.codeblock.did-select-row-at.swift b/source/tutorial/generated/code/final/ProjectsViewController.codeblock.did-select-row-at.swift new file mode 100644 index 0000000000..eb0ed15be1 --- /dev/null +++ b/source/tutorial/generated/code/final/ProjectsViewController.codeblock.did-select-row-at.swift @@ -0,0 +1,15 @@ +func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let user = app.currentUser()! + let project = userData?.memberOf[indexPath.row] ?? Project(partition: "project=\(user.id!)", name: "My Project") + + Realm.asyncOpen(configuration: user.configuration(partitionValue: project.partition!)) { [weak self] (realm, error) in + guard error == nil else { + fatalError("Failed to open realm: \(error!)") + } + + self?.navigationController?.pushViewController( + TasksViewController(realm: realm!, title: "\(project.name!)'s Tasks"), + animated: true + ); + } +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/ProjectsViewController.codeblock.invalidate-token.swift b/source/tutorial/generated/code/final/ProjectsViewController.codeblock.invalidate-token.swift new file mode 100644 index 0000000000..ef5a58fb9a --- /dev/null +++ b/source/tutorial/generated/code/final/ProjectsViewController.codeblock.invalidate-token.swift @@ -0,0 +1,4 @@ +deinit { + // Always invalidate any notification tokens when you are done with them. + notificationToken?.invalidate() +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/ProjectsViewController.codeblock.log-out-button-did-click.swift b/source/tutorial/generated/code/final/ProjectsViewController.codeblock.log-out-button-did-click.swift new file mode 100644 index 0000000000..ccc656c3ab --- /dev/null +++ b/source/tutorial/generated/code/final/ProjectsViewController.codeblock.log-out-button-did-click.swift @@ -0,0 +1,15 @@ +@objc func logOutButtonDidClick() { + let alertController = UIAlertController(title: "Log Out", message: "", preferredStyle: .alert); + alertController.addAction(UIAlertAction(title: "Yes, Log Out", style: .destructive, handler: { + alert -> Void in + print("Logging out..."); + app.currentUser()?.logOut() { (error) in + DispatchQueue.main.sync { + print("Logged out!"); + self.navigationController?.popViewController(animated: true) + } + } + })) + alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + self.present(alertController, animated: true, completion: nil) +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/ProjectsViewController.codeblock.number-of-rows-in-section.swift b/source/tutorial/generated/code/final/ProjectsViewController.codeblock.number-of-rows-in-section.swift new file mode 100644 index 0000000000..2b3f8c3553 --- /dev/null +++ b/source/tutorial/generated/code/final/ProjectsViewController.codeblock.number-of-rows-in-section.swift @@ -0,0 +1,16 @@ +func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + // You always have at least one project (your own) + return userData?.memberOf.count ?? 1 +} + +// :code-block-start: cell-for-row-at +func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") ?? UITableViewCell(style: .default, reuseIdentifier: "Cell") + cell.selectionStyle = .none + + // User data may not have loaded yet. You always have your own project. + let projectName = userData?.memberOf[indexPath.row].name ?? "My Project" + cell.textLabel?.text = projectName + + return cell +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/ProjectsViewController.codeblock.user-in-realm-notification.swift b/source/tutorial/generated/code/final/ProjectsViewController.codeblock.user-in-realm-notification.swift new file mode 100644 index 0000000000..931e56ceaf --- /dev/null +++ b/source/tutorial/generated/code/final/ProjectsViewController.codeblock.user-in-realm-notification.swift @@ -0,0 +1,8 @@ +// There should only be one user in my realm - that is myself +let usersInRealm = userRealm.objects(User.self) + +notificationToken = usersInRealm.observe { [weak self, usersInRealm] (changes) in + self?.userData = usersInRealm.first + guard let tableView = self?.tableView else { return } + tableView.reloadData() +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/SceneDelegate.codeblock.initialize-app.swift b/source/tutorial/generated/code/final/SceneDelegate.codeblock.initialize-app.swift new file mode 100644 index 0000000000..d7d5536263 --- /dev/null +++ b/source/tutorial/generated/code/final/SceneDelegate.codeblock.initialize-app.swift @@ -0,0 +1 @@ +let app = App(id: "tasktracker-qczfq") \ No newline at end of file diff --git a/source/tutorial/generated/code/final/TaskActivity.codeblock.add-new-task-to-project.kt b/source/tutorial/generated/code/final/TaskActivity.codeblock.add-new-task-to-project.kt new file mode 100644 index 0000000000..124c434690 --- /dev/null +++ b/source/tutorial/generated/code/final/TaskActivity.codeblock.add-new-task-to-project.kt @@ -0,0 +1,5 @@ +val task = Task(input.text.toString()) +// all realm writes need to occur inside of a transaction +projectRealm.executeTransactionAsync { realm -> + realm.insert(task) +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/TaskActivity.codeblock.fetch-tasks-for-project-sorted-by-id.kt b/source/tutorial/generated/code/final/TaskActivity.codeblock.fetch-tasks-for-project-sorted-by-id.kt new file mode 100644 index 0000000000..582bec3bb4 --- /dev/null +++ b/source/tutorial/generated/code/final/TaskActivity.codeblock.fetch-tasks-for-project-sorted-by-id.kt @@ -0,0 +1 @@ +adapter = TaskAdapter(realm.where().sort("_id").findAll(), user!!, partition) \ No newline at end of file diff --git a/source/tutorial/generated/code/final/TaskActivity.codeblock.on-destroy-close-realm.kt b/source/tutorial/generated/code/final/TaskActivity.codeblock.on-destroy-close-realm.kt new file mode 100644 index 0000000000..306933ba23 --- /dev/null +++ b/source/tutorial/generated/code/final/TaskActivity.codeblock.on-destroy-close-realm.kt @@ -0,0 +1,6 @@ +override fun onDestroy() { + super.onDestroy() + recyclerView.adapter = null + // if a user hasn't logged out when the activity exits, still need to explicitly close the realm + projectRealm.close() +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/TaskActivity.codeblock.on-stop-close-realm.kt b/source/tutorial/generated/code/final/TaskActivity.codeblock.on-stop-close-realm.kt new file mode 100644 index 0000000000..bca338c53c --- /dev/null +++ b/source/tutorial/generated/code/final/TaskActivity.codeblock.on-stop-close-realm.kt @@ -0,0 +1,6 @@ +override fun onStop() { + super.onStop() + user.run { + projectRealm.close() + } +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/TaskActivity.codeblock.set-up-project-realm.kt b/source/tutorial/generated/code/final/TaskActivity.codeblock.set-up-project-realm.kt new file mode 100644 index 0000000000..102b7e46ba --- /dev/null +++ b/source/tutorial/generated/code/final/TaskActivity.codeblock.set-up-project-realm.kt @@ -0,0 +1,11 @@ +val config = SyncConfiguration.Builder(user!!, partition) + .build() + +// Sync all realm changes via a new instance, and when that instance has been successfully created connect it to an on-screen list (a recycler view) +Realm.getInstanceAsync(config, object: Realm.Callback() { + override fun onSuccess(realm: Realm) { + // since this realm should live exactly as long as this activity, assign the realm to a member variable + this@TaskActivity.projectRealm = realm + setUpRecyclerView(realm, user, partition) + } +}) \ No newline at end of file diff --git a/source/tutorial/generated/code/final/TaskAdapter.codeblock.change-task-status.kt b/source/tutorial/generated/code/final/TaskAdapter.codeblock.change-task-status.kt new file mode 100644 index 0000000000..253aa93bec --- /dev/null +++ b/source/tutorial/generated/code/final/TaskAdapter.codeblock.change-task-status.kt @@ -0,0 +1,15 @@ +// need to create a separate instance of realm to issue an update, since this event is +// handled by a background thread and realm instances cannot be shared across threads +val config = SyncConfiguration.Builder(user, partition) + .build() + +// Sync all realm changes via a new instance, and when that instance has been successfully created connect it to an on-screen list (a recycler view) +val realm: Realm = Realm.getInstance(config) +// execute Transaction (not async) because changeStatus should execute on a background thread +realm.executeTransaction { + // using our thread-local new realm instance, query for and update the task status + val item = it.where().equalTo("_id", _id).findFirst() + item?.statusEnum = status +} +// always close realms when you are done with them! +realm.close() \ No newline at end of file diff --git a/source/tutorial/generated/code/final/TaskAdapter.codeblock.delete-task.kt b/source/tutorial/generated/code/final/TaskAdapter.codeblock.delete-task.kt new file mode 100644 index 0000000000..e8a94c4fea --- /dev/null +++ b/source/tutorial/generated/code/final/TaskAdapter.codeblock.delete-task.kt @@ -0,0 +1,15 @@ +// need to create a separate instance of realm to issue an update, since this event is +// handled by a background thread and realm instances cannot be shared across threads +val config = SyncConfiguration.Builder(user, partition) + .build() + +// Sync all realm changes via a new instance, and when that instance has been successfully created connect it to an on-screen list (a recycler view) +val realm: Realm = Realm.getInstance(config) +// execute Transaction (not async) because remoteAt should execute on a background thread +realm.executeTransaction { + // using our thread-local new realm instance, query for and delete the task + val item = it.where().equalTo("_id", id).findFirst() + item?.deleteFromRealm() +} +// always close realms when you are done with them! +realm.close() \ No newline at end of file diff --git a/source/tutorial/generated/code/final/TaskItem.codeblock.define-task-status-actions.js b/source/tutorial/generated/code/final/TaskItem.codeblock.define-task-status-actions.js new file mode 100644 index 0000000000..17312580e5 --- /dev/null +++ b/source/tutorial/generated/code/final/TaskItem.codeblock.define-task-status-actions.js @@ -0,0 +1,24 @@ +if (task.status !== "" && task.status !== Task.STATUS_OPEN) { + actions.push({ + title: "Mark Open", + action: () => { + setTaskStatus(task, Task.STATUS_OPEN); + }, + }); +} +if (task.status !== Task.STATUS_IN_PROGRESS) { + actions.push({ + title: "Mark In Progress", + action: () => { + setTaskStatus(task, Task.STATUS_IN_PROGRESS); + }, + }); +} +if (task.status !== Task.STATUS_COMPLETE) { + actions.push({ + title: "Mark Complete", + action: () => { + setTaskStatus(task, Task.STATUS_COMPLETE); + }, + }); +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/TaskTracker.codeblock.initialize-realm-and-create-app.kt b/source/tutorial/generated/code/final/TaskTracker.codeblock.initialize-realm-and-create-app.kt new file mode 100644 index 0000000000..d28a536a97 --- /dev/null +++ b/source/tutorial/generated/code/final/TaskTracker.codeblock.initialize-realm-and-create-app.kt @@ -0,0 +1,4 @@ +Realm.init(this) +taskApp = App( + AppConfiguration.Builder(BuildConfig.MONGODB_REALM_APP_ID) + .build()) \ No newline at end of file diff --git a/source/tutorial/generated/code/final/TasksProvider.codeblock.clean-up.js b/source/tutorial/generated/code/final/TasksProvider.codeblock.clean-up.js new file mode 100644 index 0000000000..c9fa48ccad --- /dev/null +++ b/source/tutorial/generated/code/final/TasksProvider.codeblock.clean-up.js @@ -0,0 +1,9 @@ +return () => { + // cleanup function + const projectRealm = realmRef.current; + if (projectRealm) { + projectRealm.close(); + realmRef.current = null; + setTasks([]); + } +}; \ No newline at end of file diff --git a/source/tutorial/generated/code/final/TasksProvider.codeblock.create-task.js b/source/tutorial/generated/code/final/TasksProvider.codeblock.create-task.js new file mode 100644 index 0000000000..228319bf6b --- /dev/null +++ b/source/tutorial/generated/code/final/TasksProvider.codeblock.create-task.js @@ -0,0 +1,13 @@ +const createTask = (newTaskName) => { + const projectRealm = realmRef.current; + projectRealm.write(() => { + // Create a new task in the same partition -- that is, in the same project. + projectRealm.create( + "Task", + new Task({ + name: newTaskName || "New Task", + partition: projectPartition, + }) + ); + }); +}; \ No newline at end of file diff --git a/source/tutorial/generated/code/final/TasksProvider.codeblock.delete-task.js b/source/tutorial/generated/code/final/TasksProvider.codeblock.delete-task.js new file mode 100644 index 0000000000..f968fdf9e7 --- /dev/null +++ b/source/tutorial/generated/code/final/TasksProvider.codeblock.delete-task.js @@ -0,0 +1,8 @@ +// Define the function for deleting a task. +const deleteTask = (task) => { + const projectRealm = realmRef.current; + projectRealm.write(() => { + projectRealm.delete(task); + setTasks([...projectRealm.objects("Task").sorted("name")]); + }); +}; \ No newline at end of file diff --git a/source/tutorial/generated/code/final/TasksProvider.codeblock.open-project-realm.js b/source/tutorial/generated/code/final/TasksProvider.codeblock.open-project-realm.js new file mode 100644 index 0000000000..a7b9235737 --- /dev/null +++ b/source/tutorial/generated/code/final/TasksProvider.codeblock.open-project-realm.js @@ -0,0 +1,17 @@ +const config = { + sync: { + user: user, + partitionValue: projectPartition, + }, +}; +// open a realm for this particular project +Realm.open(config).then((projectRealm) => { + realmRef.current = projectRealm; + + const syncTasks = projectRealm.objects("Task"); + let sortedTasks = syncTasks.sorted("name"); + setTasks([...sortedTasks]); + sortedTasks.addListener(() => { + setTasks([...sortedTasks]); + }); +}); \ No newline at end of file diff --git a/source/tutorial/generated/code/final/TasksProvider.codeblock.set-task-status.js b/source/tutorial/generated/code/final/TasksProvider.codeblock.set-task-status.js new file mode 100644 index 0000000000..c077843b5e --- /dev/null +++ b/source/tutorial/generated/code/final/TasksProvider.codeblock.set-task-status.js @@ -0,0 +1,18 @@ +const setTaskStatus = (task, status) => { + // One advantage of centralizing the realm functionality in this provider is + // that we can check to make sure a valid status was passed in here. + if ( + ![ + Task.STATUS_OPEN, + Task.STATUS_IN_PROGRESS, + Task.STATUS_COMPLETE, + ].includes(status) + ) { + throw new Error(`Invalid status: ${status}`); + } + const projectRealm = realmRef.current; + + projectRealm.write(() => { + task.status = status; + }); +}; \ No newline at end of file diff --git a/source/tutorial/generated/code/final/TasksViewController.codeblock..swift b/source/tutorial/generated/code/final/TasksViewController.codeblock..swift new file mode 100644 index 0000000000..7f5e5d3fc2 --- /dev/null +++ b/source/tutorial/generated/code/final/TasksViewController.codeblock..swift @@ -0,0 +1,5 @@ +let tableView = UITableView() +let partitionValue: String +let realm: Realm +var notificationToken: NotificationToken? +let tasks: Results \ No newline at end of file diff --git a/source/tutorial/generated/code/final/TasksViewController.codeblock.add-button-did-click.swift b/source/tutorial/generated/code/final/TasksViewController.codeblock.add-button-did-click.swift new file mode 100644 index 0000000000..70204acd0e --- /dev/null +++ b/source/tutorial/generated/code/final/TasksViewController.codeblock.add-button-did-click.swift @@ -0,0 +1,8 @@ +// Create a new Task with the text that the user entered. +let task = Task(partition: self.partitionValue, name: textField.text ?? "New Task") + +// Any writes to the Realm must occur in a write block. +try! self.realm.write { + // Add the Task to the Realm. That's it! + self.realm.add(task) +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/TasksViewController.codeblock.deinit.swift b/source/tutorial/generated/code/final/TasksViewController.codeblock.deinit.swift new file mode 100644 index 0000000000..ef5a58fb9a --- /dev/null +++ b/source/tutorial/generated/code/final/TasksViewController.codeblock.deinit.swift @@ -0,0 +1,4 @@ +deinit { + // Always invalidate any notification tokens when you are done with them. + notificationToken?.invalidate() +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/TasksViewController.codeblock.delete-task.swift b/source/tutorial/generated/code/final/TasksViewController.codeblock.delete-task.swift new file mode 100644 index 0000000000..7bdae20339 --- /dev/null +++ b/source/tutorial/generated/code/final/TasksViewController.codeblock.delete-task.swift @@ -0,0 +1,12 @@ +func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { + guard editingStyle == .delete else { return } + + // User can swipe to delete items. + let task = tasks[indexPath.row] + + // All modifications to a realm must happen in a write block. + try! realm.write { + // Delete the Task. + realm.delete(task) + } +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/TasksViewController.codeblock.init.swift b/source/tutorial/generated/code/final/TasksViewController.codeblock.init.swift new file mode 100644 index 0000000000..8fccddb3ee --- /dev/null +++ b/source/tutorial/generated/code/final/TasksViewController.codeblock.init.swift @@ -0,0 +1,45 @@ +required init(realm: Realm, title: String) { + + // Ensure the realm was opened with sync. + guard let syncConfiguration = realm.configuration.syncConfiguration else { + fatalError("Sync configuration not found! Realm not opened with sync?"); + } + + self.realm = realm + + // Partition value must be of string type. + partitionValue = syncConfiguration.partitionValue!.stringValue! + + // Access all tasks in the realm, sorted by _id so that the ordering is defined. + tasks = realm.objects(Task.self).sorted(byKeyPath: "_id") + + super.init(nibName: nil, bundle: nil) + + self.title = title + + // Observe the tasks for changes. Hang on to the returned notification token. + notificationToken = tasks.observe { [weak self] (changes) in + guard let tableView = self?.tableView else { return } + switch changes { + case .initial: + // Results are now populated and can be accessed without blocking the UI + tableView.reloadData() + case .update(_, let deletions, let insertions, let modifications): + // Query results have changed, so apply them to the UITableView. + tableView.performBatchUpdates({ + // It's important to be sure to always update a table in this order: + // deletions, insertions, then updates. Otherwise, you could be unintentionally + // updating at the wrong index! + tableView.deleteRows(at: deletions.map({ IndexPath(row: $0, section: 0) }), + with: .automatic) + tableView.insertRows(at: insertions.map({ IndexPath(row: $0, section: 0) }), + with: .automatic) + tableView.reloadRows(at: modifications.map({ IndexPath(row: $0, section: 0) }), + with: .automatic) + }) + case .error(let error): + // An error occurred while opening the Realm file on the background worker thread + fatalError("\(error)") + } + } +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/TasksViewController.codeblock.is-own-tasks.swift b/source/tutorial/generated/code/final/TasksViewController.codeblock.is-own-tasks.swift new file mode 100644 index 0000000000..68582d0436 --- /dev/null +++ b/source/tutorial/generated/code/final/TasksViewController.codeblock.is-own-tasks.swift @@ -0,0 +1,4 @@ +// Returns true if these are the user's own tasks. +func isOwnTasks() -> Bool { + return partitionValue == "project=\(app.currentUser()!.id!)" +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/TasksViewController.codeblock.populate-action-sheet.swift b/source/tutorial/generated/code/final/TasksViewController.codeblock.populate-action-sheet.swift new file mode 100644 index 0000000000..60dec80faf --- /dev/null +++ b/source/tutorial/generated/code/final/TasksViewController.codeblock.populate-action-sheet.swift @@ -0,0 +1,27 @@ +// If the task is not in the Open state, we can set it to open. Otherwise, that action will not be available. +// We do this for the other two states -- InProgress and Complete. +if (task.statusEnum != .Open) { + actionSheet.addAction(UIAlertAction(title: "Open", style: .default) { _ in + // Any modifications to managed objects must occur in a write block. + // When we modify the Task's state, that change is automatically reflected in the realm. + try! self.realm.write { + task.statusEnum = .Open + } + }) +} + +if (task.statusEnum != .InProgress) { + actionSheet.addAction(UIAlertAction(title: "Start Progress", style: .default) { _ in + try! self.realm.write { + task.statusEnum = .InProgress + } + }) +} + +if (task.statusEnum != .Complete) { + actionSheet.addAction(UIAlertAction(title: "Complete", style: .default) { _ in + try! self.realm.write { + task.statusEnum = .Complete + } + }) +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/WelcomeViewController.codeblock.sign-in.swift b/source/tutorial/generated/code/final/WelcomeViewController.codeblock.sign-in.swift new file mode 100644 index 0000000000..2411bfd1b4 --- /dev/null +++ b/source/tutorial/generated/code/final/WelcomeViewController.codeblock.sign-in.swift @@ -0,0 +1,45 @@ +@objc func signIn() { + print("Log in as user: \(email!)"); + setLoading(true); + + app.login(credentials: Credentials(email: email!, password: password!)) { [weak self](maybeUser, error) in + // Completion handlers are not necessarily called on the UI thread. + // This call to DispatchQueue.main.sync ensures that any changes to the UI, + // namely disabling the loading indicator and navigating to the next page, + // are handled on the UI thread: + DispatchQueue.main.sync { + self!.setLoading(false); + guard error == nil else { + // Auth error: user already exists? Try logging in as that user. + print("Login failed: \(error!)"); + self!.errorLabel.text = "Login failed: \(error!.localizedDescription)" + return + } + + guard let user = maybeUser else { + fatalError("Invalid user object?") + } + + print("Login succeeded!"); + + self!.setLoading(true); + + // Get a configuration to open the synced realm. + var configuration = user.configuration(partitionValue: "user=\(user.id!)") + // Only allow User objects in this partition. + configuration.objectTypes = [User.self, Project.self] + // Open the realm asynchronously so that it downloads the remote copy before + // opening the local copy. + Realm.asyncOpen(configuration: configuration) { [weak self](userRealm, error) in + self!.setLoading(false); + guard error == nil else { + fatalError("Failed to open realm: \(error!)") + } + + // For the second phase of the tutorial, go to the Projects management page. + // This is where you can manage permissions and collaborators. + self!.navigationController!.pushViewController(ProjectsViewController(userRealm: userRealm!), animated: true); + } + } + }; +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/WelcomeViewController.codeblock.sign-up.swift b/source/tutorial/generated/code/final/WelcomeViewController.codeblock.sign-up.swift new file mode 100644 index 0000000000..c60550d484 --- /dev/null +++ b/source/tutorial/generated/code/final/WelcomeViewController.codeblock.sign-up.swift @@ -0,0 +1,22 @@ +@objc func signUp() { + setLoading(true); + app.emailPasswordAuth().registerUser(email: email!, password: password!, completion: { [weak self](error) in + // Completion handlers are not necessarily called on the UI thread. + // This call to DispatchQueue.main.sync ensures that any changes to the UI, + // namely disabling the loading indicator and navigating to the next page, + // are handled on the UI thread: + DispatchQueue.main.sync { + self!.setLoading(false); + guard error == nil else { + print("Signup failed: \(error!)") + self!.errorLabel.text = "Signup failed: \(error!.localizedDescription)" + return + } + print("Signup successful!") + + // Registering just registers. Now we need to sign in, but we can reuse the existing email and password. + self!.errorLabel.text = "Signup successful! Signing in..." + self!.signIn() + } + }) +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/config.codeblock.realmAppID.js b/source/tutorial/generated/code/final/config.codeblock.realmAppID.js new file mode 100644 index 0000000000..ac27ba8a76 --- /dev/null +++ b/source/tutorial/generated/code/final/config.codeblock.realmAppID.js @@ -0,0 +1 @@ +exports.realmAppId = "TODO"; \ No newline at end of file diff --git a/source/tutorial/generated/code/final/getRealmApp.codeblock.get-realm-app.js b/source/tutorial/generated/code/final/getRealmApp.codeblock.get-realm-app.js new file mode 100644 index 0000000000..c58d9f0ed6 --- /dev/null +++ b/source/tutorial/generated/code/final/getRealmApp.codeblock.get-realm-app.js @@ -0,0 +1,16 @@ +// Returns the shared instance of the Realm app. +export function getRealmApp() { + if (app === undefined) { + const appId = "tasktracker-qczfq"; // Set Realm app ID here. + const appConfig = { + id: appId, + timeout: 10000, + app: { + name: "default", + version: "0", + }, + }; + app = new Realm.App(appConfig); + } + return app; +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/index.codeblock.getRealm.js b/source/tutorial/generated/code/final/index.codeblock.getRealm.js new file mode 100644 index 0000000000..91373ad289 --- /dev/null +++ b/source/tutorial/generated/code/final/index.codeblock.getRealm.js @@ -0,0 +1,6 @@ +async function getRealm(partitionKey) { + if (realms[partitionKey] == undefined) { + realms[partitionKey] = openRealm(partitionKey); + } + return realms[partitionKey]; +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/index.codeblock.openRealm.js b/source/tutorial/generated/code/final/index.codeblock.openRealm.js new file mode 100644 index 0000000000..1de735e4ba --- /dev/null +++ b/source/tutorial/generated/code/final/index.codeblock.openRealm.js @@ -0,0 +1,10 @@ +async function openRealm(partitionKey) { + const config = { + schema: [schemas.TaskSchema, schemas.UserSchema, schemas.ProjectSchema], + sync: { + user: users.getAuthedUser(), + partitionValue: partitionKey, + }, + }; + return Realm.open(config); +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/projects.codeblock.getProjects.js b/source/tutorial/generated/code/final/projects.codeblock.getProjects.js new file mode 100644 index 0000000000..9b7320a93b --- /dev/null +++ b/source/tutorial/generated/code/final/projects.codeblock.getProjects.js @@ -0,0 +1,7 @@ +async function getProjects() { + const realm = await index.getRealm(`user=${users.getAuthedUser().id}`); + const currentUser = users.getAuthedUser().id; + const user = realm.objectForPrimaryKey("User", currentUser); + const projects = user.memberOf; + return projects; +}; \ No newline at end of file diff --git a/source/tutorial/generated/code/final/schemas.codeblock.projectSchema.js b/source/tutorial/generated/code/final/schemas.codeblock.projectSchema.js new file mode 100644 index 0000000000..e6f620e2a1 --- /dev/null +++ b/source/tutorial/generated/code/final/schemas.codeblock.projectSchema.js @@ -0,0 +1,8 @@ +const ProjectSchema = { + name: 'Project', + embedded: true, + properties: { + name: 'string?', + partition: 'string?', + }, +}; \ No newline at end of file diff --git a/source/tutorial/generated/code/final/schemas.codeblock.taskSchema.js b/source/tutorial/generated/code/final/schemas.codeblock.taskSchema.js new file mode 100644 index 0000000000..a2e8a8dd00 --- /dev/null +++ b/source/tutorial/generated/code/final/schemas.codeblock.taskSchema.js @@ -0,0 +1,11 @@ +const TaskSchema = { + name: 'Task', + properties: { + _id: 'objectId', + _partition: 'string', + name: 'string', + owner: 'string?', + status: 'string', + }, + primaryKey: '_id', +}; \ No newline at end of file diff --git a/source/tutorial/generated/code/final/schemas.codeblock.userSchema.js b/source/tutorial/generated/code/final/schemas.codeblock.userSchema.js new file mode 100644 index 0000000000..4b9ed927e4 --- /dev/null +++ b/source/tutorial/generated/code/final/schemas.codeblock.userSchema.js @@ -0,0 +1,10 @@ +const UserSchema = { + name: 'User', + properties: { + _id: 'string', + _partition: 'string', + memberOf: 'Project[]', + name: 'string', + }, + primaryKey: '_id', +}; \ No newline at end of file diff --git a/source/tutorial/generated/code/final/tasks.codeblock.createTask.js b/source/tutorial/generated/code/final/tasks.codeblock.createTask.js new file mode 100644 index 0000000000..d4df39356e --- /dev/null +++ b/source/tutorial/generated/code/final/tasks.codeblock.createTask.js @@ -0,0 +1,36 @@ +exports.createTask = async (partition) => { + const realm = await index.getRealm(partition); + try { + output.header("*** CREATE NEW TASK ***"); + const task = await inquirer.prompt([ + { + type: "input", + name: "name", + message: "What is the task text?", + }, + { + type: "rawlist", + name: "status", + message: "What is the task status?", + choices: ["Open", "In Progress", "Closed"], + default: function () { + return "Open"; + }, + }, + ]); + let result; + realm.write(() => { + result = realm.create("Task", { + _id: new bson.ObjectID(), + _partition: partition, + name: task.name, + status: task.status.replace(/\s/g, ''), // Removes space from "In Progress", + }); + }); + + output.header("New task created"); + output.result(JSON.stringify(result, null, 2)); + } catch (err) { + output.error(JSON.stringify(err)); + } +}; \ No newline at end of file diff --git a/source/tutorial/generated/code/final/tasks.codeblock.deleteTask.js b/source/tutorial/generated/code/final/tasks.codeblock.deleteTask.js new file mode 100644 index 0000000000..0b34ccbaba --- /dev/null +++ b/source/tutorial/generated/code/final/tasks.codeblock.deleteTask.js @@ -0,0 +1,25 @@ +exports.deleteTask = async (partition) => { + const realm = await index.getRealm(partition); + output.header("DELETE A TASK"); + const answers = await inquirer.prompt([ + { + type: "input", + name: "id", + message: "What is the task ID (_id)?", + }, + { + type: "confirm", + name: "confirm", + message: "Are you sure you want to delete this task?", + }, + ]); + + if (answers.confirm) { + let task = realm.objectForPrimaryKey("Task", new bson.ObjectID(answers.id)); + realm.write(() => { + realm.delete(task); + output.result("Task deleted."); + }); + return; + } +}; \ No newline at end of file diff --git a/source/tutorial/generated/code/final/tasks.codeblock.getTask.js b/source/tutorial/generated/code/final/tasks.codeblock.getTask.js new file mode 100644 index 0000000000..f19c4efbdb --- /dev/null +++ b/source/tutorial/generated/code/final/tasks.codeblock.getTask.js @@ -0,0 +1,19 @@ +exports.getTask = async (partition) => { + const realm = await index.getRealm(partition); + try { + const task = await inquirer.prompt([ + { + type: "input", + name: "id", + message: "What is the task ID (_id)?", + }, + ]); + let result = realm.objectForPrimaryKey("Task", new bson.ObjectID(task.id)); + if (result !== undefined) { + output.header("Here is the task you requested:"); + output.result(JSON.stringify(result, null, 2)); + } + } catch (err) { + output.error(JSON.stringify(err)); + } +}; \ No newline at end of file diff --git a/source/tutorial/generated/code/final/tasks.codeblock.getTasks.js b/source/tutorial/generated/code/final/tasks.codeblock.getTasks.js new file mode 100644 index 0000000000..9f68e6475b --- /dev/null +++ b/source/tutorial/generated/code/final/tasks.codeblock.getTasks.js @@ -0,0 +1,6 @@ +exports.getTasks = async (partition) => { + const realm = await index.getRealm(partition); + const tasks = realm.objects("Task"); + output.header("MY TASKS:"); + output.result(JSON.stringify(tasks, null, 2)); +}; \ No newline at end of file diff --git a/source/tutorial/generated/code/final/tasks.codeblock.modifyTask.js b/source/tutorial/generated/code/final/tasks.codeblock.modifyTask.js new file mode 100644 index 0000000000..41f03933a0 --- /dev/null +++ b/source/tutorial/generated/code/final/tasks.codeblock.modifyTask.js @@ -0,0 +1,13 @@ +async function modifyTask(answers, partition) { + const realm = await index.getRealm(partition); + let task; + try { + realm.write(() => { + task = realm.objectForPrimaryKey("Task", new bson.ObjectID(answers.id)); + task[answers.key] = answers.value; + }); + return JSON.stringify(task, null, 2); + } catch (err) { + return output.error(err); + } +} \ No newline at end of file diff --git a/source/tutorial/generated/code/final/team.codeblock.addTeamMember.js b/source/tutorial/generated/code/final/team.codeblock.addTeamMember.js new file mode 100644 index 0000000000..71eeb09a28 --- /dev/null +++ b/source/tutorial/generated/code/final/team.codeblock.addTeamMember.js @@ -0,0 +1,17 @@ +exports.addTeamMember = async () => { + try { + output.header("*** ADD A TEAM MEMBER ***"); + const currentUser = users.getAuthedUser(); + const { email } = await inquirer.prompt([ + { + type: "input", + name: "email", + message: "What is the new team member's email address?", + }, + ]); + await currentUser.functions.addTeamMember(email); + output.result("The user was added to your team."); + } catch (err) { + output.error(err.message); + } +}; \ No newline at end of file diff --git a/source/tutorial/generated/code/final/team.codeblock.getTeamMembers.js b/source/tutorial/generated/code/final/team.codeblock.getTeamMembers.js new file mode 100644 index 0000000000..21c371d8dc --- /dev/null +++ b/source/tutorial/generated/code/final/team.codeblock.getTeamMembers.js @@ -0,0 +1,10 @@ +exports.getTeamMembers = async () => { + const currentUser = users.getAuthedUser(); + try { + const teamMembers = await currentUser.functions.getMyTeamMembers(); + output.result(JSON.stringify(teamMembers, null, 2)); + } + catch (err) { + output.error(JSON.stringify(err)); + } +}; \ No newline at end of file diff --git a/source/tutorial/generated/code/final/team.codeblock.removeTeamMember.js b/source/tutorial/generated/code/final/team.codeblock.removeTeamMember.js new file mode 100644 index 0000000000..459f4717bd --- /dev/null +++ b/source/tutorial/generated/code/final/team.codeblock.removeTeamMember.js @@ -0,0 +1,20 @@ +exports.removeTeamMember = async () => { + const currentUser = users.getAuthedUser(); + const teamMembers = await currentUser.functions.getMyTeamMembers(); + const teamMemberNames = teamMembers.map(t => t.name); + try { + output.header("*** REMOVE A TEAM MEMBER ***"); + const { selectedTeamMember } = await inquirer.prompt([ + { + type: "rawlist", + name: "selectedTeamMember", + message: "Which team member do you want to remove?", + choices: [...teamMemberNames, new inquirer.Separator()], + }, + ]); + let result = await currentUser.functions.removeTeamMember(selectedTeamMember); + output.result("The user was removed from your team."); + } catch (err) { + output.error(JSON.stringify(err)); + } +}; \ No newline at end of file diff --git a/source/tutorial/generated/code/final/users.codeblock.newRealmApp.js b/source/tutorial/generated/code/final/users.codeblock.newRealmApp.js new file mode 100644 index 0000000000..e214b2fbd8 --- /dev/null +++ b/source/tutorial/generated/code/final/users.codeblock.newRealmApp.js @@ -0,0 +1 @@ +const app = new Realm.App(appConfig); \ No newline at end of file diff --git a/source/tutorial/generated/code/final/users.codeblock.userLogin.js b/source/tutorial/generated/code/final/users.codeblock.userLogin.js new file mode 100644 index 0000000000..117922af3b --- /dev/null +++ b/source/tutorial/generated/code/final/users.codeblock.userLogin.js @@ -0,0 +1,34 @@ +async function logIn() { + const input = await inquirer.prompt([ + { + type: "input", + name: "email", + message: "Email:", + }, + { + type: "password", + name: "password", + message: "Password:", + mask: "*", + }, + ]); + + try { + const credentials = Realm.Credentials.emailPassword( + input.email, + input.password + ); + + const user = await app.logIn(credentials); + if (user) { + output.result("You have successfully logged in as " + app.currentUser.id); + return main.mainMenu(); + } else { + output.error("There was an error logging you in"); + return logIn(); + } + } catch (err) { + output.error(JSON.stringify(err, null, 2)); + return logIn(); + } +} \ No newline at end of file diff --git a/source/tutorial/nodejs-cli.txt b/source/tutorial/nodejs-cli.txt index 3f101eaa79..e655aab39e 100644 --- a/source/tutorial/nodejs-cli.txt +++ b/source/tutorial/nodejs-cli.txt @@ -81,7 +81,7 @@ We've already put together a task tracker CLI application that has most of the code you'll need. You can clone the client application repository directly from GitHub: -.. code-block:: shell +.. code-block:: console git clone git@github.com:mongodb-university/realm-tutorial.git @@ -91,14 +91,14 @@ directly from GitHub: you complete this tutorial. To walk through this tutorial, please check out the ``todo`` branch: - .. code-block:: shell + .. code-block:: console git checkout todo In your terminal, run the following commands to navigate to the task tracker client application and install its dependencies: -.. code-block:: shell +.. code-block:: console cd realm-tutorial/node-cli npm install @@ -166,7 +166,8 @@ To get the app working with your backend, you first need to add your Realm App I to the ``config.js`` file. The ``config.js`` module exports a single property, ``realmAppId``, which is currently set to "TODO": -.. literalinclude:: /tutorial/node-cli/bluehawk-gen/code/start/config.codeblock.realmAppID.js +.. literalinclude:: /tutorial/generated/code/final/config.codeblock.realmAppID.js + :language: javascript Change this value to your Realm App ID. @@ -180,14 +181,14 @@ open a {+realm+}. In ``index.js``, find the ``openRealm`` function. Replace the line with a line of code that opens a {+realm+} and assigns it to the ``realm`` property. It will look like this: -.. literalinclude:: /tutorial/node-cli/bluehawk-gen/code/final/index.codeblock.openRealm.js +.. literalinclude:: /tutorial/generated/code/final/index.codeblock.openRealm.js :emphasize-lines: 9 Now that you have implemented the ``openRealm`` function, you will now need to complete the code that retrieves the {+realm+}. In ``index.js``, find the ``getRealm`` function. It will look like this: -.. literalinclude:: /tutorial/node-cli/bluehawk-gen/code/final/index.codeblock.getRealm.js +.. literalinclude:: /tutorial/generated/code/final/index.codeblock.getRealm.js :emphasize-lines: 3 At this point, your app is pointing to your backend and opens a connection @@ -206,7 +207,7 @@ credential and passes it to the Realm Find the the ``logIn`` function and add the following code to create a ``emailPassword`` credential and call the ``logIn()`` method. -.. literalinclude:: tutorial/bluehawk-gen/code/final/users.codeblock.userLogin.js +.. literalinclude:: /tutorial/generated/code/final/users.codeblock.userLogin.js :emphasize-lines: 17, 18, 19, 22 F. Implement the CRUD methods @@ -226,7 +227,7 @@ In ``tasks.js``: To get all objects, call the :js-sdk:`objects() ` method and pass in the name of the collection: - .. literalinclude:: tutorial/bluehawk-gen/final/tasks.codeblock.getTasks.js + .. literalinclude:: /tutorial/generated/code/final/tasks.codeblock.getTasks.js :emphasize-lines: 3 @@ -236,7 +237,7 @@ In ``tasks.js``: we call the :js-sdk:`objectForPrimaryKey() ` function to get a task by its Id. - .. literalinclude:: tutorial/bluehawk-gen/final/tasks.codeblock.getTask.js + .. literalinclude:: /tutorial/generated/code/final/tasks.codeblock.getTask.js :emphasize-lines: 11 @@ -249,7 +250,7 @@ In ``tasks.js``: the :js-sdk:`create() ` function, passing in all of the required properties: - .. literalinclude:: tutorial/bluehawk-gen/final/tasks.codeblock.createTask.js + .. literalinclude:: /tutorial/generated/code/final/tasks.codeblock.createTask.js :emphasize-lines: 23, 24, 25, 26, 27, 28 @@ -269,7 +270,7 @@ In ``tasks.js``: We'll first call the ``objectForPrimaryKey`` method to get the specific we want to delete and then the :js-sdk:`delete() ` function on that task: - .. literalinclude:: tutorial/bluehawk-gen/code/final/tasks.codeblock.deleteTask.js + .. literalinclude:: /tutorial/generated/code/final/tasks.codeblock.deleteTask.js :emphasize-lines: 18, 20 @@ -281,7 +282,7 @@ In ``tasks.js``: call to a Realm API to change an object. Rather, you change the *local* object and Sync ensures the object is updated on the server. - .. literalinclude:: /tutorial/node-cli/bluehawk-gen/code/final/tasks.codeblock.modifyTask.js + .. literalinclude:: /tutorial/generated/code/final/tasks.codeblock.modifyTask.js :emphasize-lines: 6, 7 @@ -297,7 +298,7 @@ In ``projects.js``: To get all projects the user is a part of, we'll have to use the ``objectForPrimaryKey`` method to get the current user and then access the current user's ``memberOf`` property. - .. literalinclude:: /tutorial/node-cli/bluehawk-gen/final/projects.codeblock.getProjects.js + .. literalinclude:: /tutorial/generated/code/final/projects.codeblock.getProjects.js :emphasize-lines: 4, 5 G. Use Realm Functions @@ -313,7 +314,7 @@ implement the calls to {+service-short+}. To get all team members, call the ``getMyTeamMembers`` {+service-short+} function using the ``User.functions`` method. - .. literalinclude:: /tutorial/node-cli/bluehawk-gen/final/team.codeblock.getTeamMembers.js + .. literalinclude:: /tutorial/generated/code/final/team.codeblock.getTeamMembers.js :emphasize-lines: 4 * ``addTeamMember`` @@ -322,7 +323,7 @@ implement the calls to {+service-short+}. to call the ``addTeamMember`` {+service-short+} function and pass it the ``email`` parameter. - .. literalinclude:: /tutorial/node-cli/bluehawk-gen/final/team.codeblock.addTeamMember.js + .. literalinclude:: /tutorial/generated/code/final/team.codeblock.addTeamMember.js :emphasize-lines: 12 * ``removeTeamMember`` @@ -331,7 +332,7 @@ implement the calls to {+service-short+}. remove from their project. You will need to call the ``removeTeamMember`` {+service-short+} function and pass it the ``email`` parameter. - .. literalinclude:: /tutorial/node-cli/bluehawk-gen/final/team.codeblock.removeTeamMember.js + .. literalinclude:: /tutorial/generated/code/final/team.codeblock.removeTeamMember.js :emphasize-lines: 15 @@ -346,7 +347,7 @@ Once you have completed the code, you should run the app and check functionality #. Run the following commands to install all of the dependencies and start the app: - .. code-block:: sh + .. code-block:: console npm install node index.js diff --git a/tutorial/rn/schemas.js b/tutorial/rn/schemas.js index 03242bacf0..2d95acbefb 100644 --- a/tutorial/rn/schemas.js +++ b/tutorial/rn/schemas.js @@ -22,6 +22,8 @@ class Task { static STATUS_OPEN = "Open"; static STATUS_IN_PROGRESS = "InProgress"; static STATUS_COMPLETE = "Complete"; + // :code-block-start: react-native-task-schema + // :hide-start: static schema = { name: "Task", properties: { @@ -32,6 +34,9 @@ class Task { }, primaryKey: "_id", }; + // :replace-with: + //// TODO: implement schema + // :hide-end: } export { Task };