Skip to content

(DOCSP-12165): Android phase 2 tutorial with bluehawk/text #479

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class LoginActivity : AppCompatActivity() {
}

private fun onLoginSuccess() {
// successful login ends this activity, bringing the user back to the task activity
// successful login ends this activity, bringing the user back to the project activity
finish()
}

Expand Down Expand Up @@ -68,8 +68,10 @@ class LoginActivity : AppCompatActivity() {

if (createUser) {
// register a user using the Realm App we created in the TaskTracker class
// :code-block-start: create-user
// :hide-start:
taskApp.emailPassword.registerUserAsync(username, password) {
// re-enable the buttons after user registration completes
// re-enable the buttons after user registration returns a result
createUserButton.isEnabled = true
loginButton.isEnabled = true
if (!it.isSuccess) {
Expand All @@ -81,10 +83,16 @@ class LoginActivity : AppCompatActivity() {
login(false)
}
}
// :replace-with:
//// TODO: Register a new user with the supplied username and password when the "Create" button is pressed.
// :hide-end:
// :code-block-end:
} else {
// :code-block-start: login-user
// :hide-start:
val creds = Credentials.emailPassword(username, password)
taskApp.loginAsync(creds) {
// re-enable the buttons after
// re-enable the buttons after user login returns a result
loginButton.isEnabled = true
createUserButton.isEnabled = true
if (!it.isSuccess) {
Expand All @@ -93,6 +101,10 @@ class LoginActivity : AppCompatActivity() {
onLoginSuccess()
}
}
// :replace-with:
//// TODO: Log in with the supplied username and password when the "Log in" button is pressed.
// :hide-end:
// :code-block-end:
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ class MemberActivity : AppCompatActivity() {
.setCancelable(true)
.setPositiveButton("Add User") { dialog, _ ->
dialog.dismiss()
// :code-block-start: add-new-member-to-project
// :hide-start:
val functionsManager: Functions = taskApp.getFunctions(user)
functionsManager.callFunctionAsync(
"addTeamMember",
Expand All @@ -65,12 +67,17 @@ class MemberActivity : AppCompatActivity() {
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()
}
}
// :replace-with:
//// TODO: Add the new team member to the project by calling the `addTeamMember` Realm Function through `taskApp`.
// :hide-end:
// :code-block-end:
}
.setNegativeButton("Cancel") { dialog, _ ->
dialog.cancel()
Expand All @@ -89,11 +96,14 @@ class MemberActivity : AppCompatActivity() {
}

private fun setUpRecyclerView() {
// :code-block-start: get-team-members
// :hide-start:
val functionsManager: Functions = taskApp.getFunctions(user)
// get team members by calling a Realm Function which returns a list of members
functionsManager.callFunctionAsync("getMyTeamMembers", ArrayList<String>(), ArrayList::class.java) { result ->
if (result.isSuccess) {
Log.v(TAG(), "team members value: ${result.get()}")
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)
Expand All @@ -104,5 +114,16 @@ class MemberActivity : AppCompatActivity() {
Log.e(TAG(), "failed to get team members with: " + result.error)
}
}
// :replace-with:
//// TODO: Call the `getMyTeamMembers` function to get a list of team members, then display them in a RecyclerView with the following code:
//// 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))
// :hide-end:
// :code-block-end:
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ class ProjectActivity : AppCompatActivity() {
// if no user is currently logged in, start the login activity so the user can authenticate
startActivity(Intent(this, LoginActivity::class.java))
} else {
// :code-block-start: set-up-user-realm
// :hide-start:
// 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()
Expand All @@ -51,28 +53,43 @@ class ProjectActivity : AppCompatActivity() {
setUpRecyclerView(realm)
}
})
// :replace-with:
//// TODO: initialize a connection to a realm containing the user's User object
// :hide-end:
// :code-block-end:
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_project)

recyclerView = findViewById(R.id.project_list)
}

// :code-block-start: on-stop-close-realm
// :hide-start:
override fun onStop() {
super.onStop()
user.run {
userRealm?.close()
}
}
// :replace-with:
//// TODO: always ensure that the user realm closes when the activity ends via the onStop lifecycle method
// :hide-end:
// :code-block-end:

// :code-block-start: on-destroy-close-realm
// :hide-start:
override fun onDestroy() {
super.onDestroy()
userRealm?.close()
recyclerView.adapter = null
}
// :replace-with:
//// TODO: always ensure that the user realm closes when the activity ends via the onDestroy lifecycle method
// :hide-end:
// :code-block-end:

override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.activity_task_menu, menu)
Expand Down Expand Up @@ -101,8 +118,15 @@ class ProjectActivity : AppCompatActivity() {

private fun setUpRecyclerView(realm: Realm) {
// query for a user object in our user realm, which should only contain our user object
// :code-block-start: fetch-synced-user-safely
// :hide-start:
val syncedUsers : RealmResults<User> = realm.where<User>().sort("_id").findAll()
val syncedUser : User? = syncedUsers.getOrNull(0) // since there might be no user objects in the results, default to "null"
// :replace-with:
//// TODO: query the realm to get a copy of the currently logged in user's User object (or null, if the trigger didn't create it yet)
//var syncedUser : User? = null
// :hide-end:
// :code-block-end:

// if a user object exists, create the recycler view and the corresponding adapter
if (syncedUser != null) {
Expand All @@ -122,11 +146,17 @@ class ProjectActivity : AppCompatActivity() {
// if the user object doesn't yet exist (that is, if there are no users in the user realm), call this function again when it is created
Log.i(TAG(), "User object not yet initialized, waiting for initialization via Trigger before displaying projects.")
// change listener on a query for our user object lets us know when the user object has been created by the auth trigger
// :code-block-start: user-init-change-listener
// :hide-start:
val changeListener = OrderedRealmCollectionChangeListener<RealmResults<User>> { results, changeSet ->
Log.i(TAG(), "User object initialized, displaying project list.")
setUpRecyclerView(realm)
}
syncedUsers?.addChangeListener(changeListener)
syncedUsers.addChangeListener(changeListener)
// :replace-with:
//// TODO: set up a change listener that will set up the recycler view once our trigger initializes the user's User object
// :hide-end:
// :code-block-end:
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ class TaskActivity : AppCompatActivity() {

// display the name of the project in the action bar via the title member variable of the Activity
title = projectName

// :code-block-start: set-up-project-realm
// :hide-start:
val config = SyncConfiguration.Builder(user!!, partition)
.build()

Expand All @@ -57,15 +60,26 @@ class TaskActivity : AppCompatActivity() {
setUpRecyclerView(realm, user, partition)
}
})
// :replace-with:
//// TODO: initialize a connection to a realm containing all of the Tasks in this project
// :hide-end:
// :code-block-end:
}
}

// :code-block-start: on-stop-close-realm
// :hide-start:
override fun onStop() {
super.onStop()
user.run {
projectRealm.close()
}
}
// :replace-with:
//// TODO: always ensure that the project realm closes when the activity ends via the onStop lifecycle method
// :hide-end:
// :code-block-end:


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand All @@ -81,11 +95,17 @@ class TaskActivity : AppCompatActivity() {
.setCancelable(true)
.setPositiveButton("Create") { dialog, _ -> run {
dialog.dismiss()
// :code-block-start: add-new-task-to-project
// :hide-start:
val task = Task(input.text.toString())
// all realm writes need to occur inside of a transaction
projectRealm.executeTransactionAsync { realm ->
realm.insert(task)
}
// :replace-with:
//// TODO: Add a new task to the project by inserting into the realm when the user clicks "create" for a new task.
// :hide-end:
// :code-block-end:
}
}
.setNegativeButton("Cancel") { dialog, _ -> dialog.cancel()
Expand All @@ -98,19 +118,32 @@ class TaskActivity : AppCompatActivity() {
}
}

// :code-block-start: on-destroy-close-realm
// :hide-start:
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()
}
// :replace-with:
//// TODO: always ensure that the project realm closes when the activity ends via the onDestroy lifecycle method
// :hide-end:
// :code-block-end:


private fun setUpRecyclerView(realm: Realm, user: User?, partition: String) {
// a recyclerview requires an adapter, which feeds it items to display.
// Realm provides RealmRecyclerViewAdapter, which you can extend to customize for your application
// pass the adapter a collection of Tasks from the realm
// sort this collection so that the displayed order of Tasks remains stable across updates
// :code-block-start:
// :hide-start:
adapter = TaskAdapter(realm.where<Task>().sort("_id").findAll(), user!!, partition)
// :replace-with:
//// TODO: Query the realm for Task objects, sorted by a stable order that remains consistent between runs.
// :hide-end:
// :code-block-end:
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = adapter
recyclerView.setHasFixedSize(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,16 @@ class TaskTracker : Application() {

override fun onCreate() {
super.onCreate()
// :code-block-start: initialize-realm-and-create-app
// :hide-start:
Realm.init(this)
taskApp = App(
AppConfiguration.Builder(BuildConfig.MONGODB_REALM_APP_ID)
.build())
// :replace-with:
//// TODO: Initialize the Realm SDK and create the App object we will use to communicate with the Realm backend.
// :hide-end:
// :code-block-end:

// Enable more logging in debug mode
if (BuildConfig.DEBUG) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ internal class MemberAdapter(private val data: ArrayList<Member>, private val us
dialogBuilder.setMessage("Are you sure you want to remove this user from the project?")
.setCancelable(true)
.setPositiveButton("Remove User") { dialog, _ ->
// :code-block-start: remove-user-from-project
// :hide-start:
val functionsManager: Functions = taskApp.getFunctions(user)
functionsManager.callFunctionAsync("removeTeamMember",
listOf(obj.name), Document::class.java) { result ->
Expand All @@ -58,6 +60,12 @@ internal class MemberAdapter(private val data: ArrayList<Member>, private val us
}
}
}
// :replace-with:
//// TODO: Call the `removeTeamMember` Realm Function through `taskApp` to remove the selected user from the project.
//// When the function completes, remember to dismiss the dialog.
//// If the function successfully removes the team member, remove the team member from the displayed data and notify the Adapter that an item has been removed.
// :hide-end:
// :code-block-end:
}
.setNegativeButton("Cancel") { dialog, _ ->
dialog.cancel()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ internal class ProjectAdapter(data: RealmList<Project>, var user: User) : RealmR
holder.itemView.setOnClickListener {
run {
// when a user clicks on a project, bring them to the task view for that project
var intent : Intent = Intent(parent.context, TaskActivity::class.java)
val intent : Intent = Intent(parent.context, TaskActivity::class.java)
intent.putExtra(PARTITION_EXTRA_KEY, obj?.partition)
intent.putExtra(PROJECT_NAME_EXTRA_KEY, obj?.name)
parent.context.startActivity(intent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import org.bson.types.ObjectId
* TaskAdapter: extends the Realm-provided RealmRecyclerViewAdapter to provide data for a RecyclerView to display
* Realm objects on screen to a user.
*/
internal class TaskAdapter(data: OrderedRealmCollection<Task>, val user: io.realm.mongodb.User, val partition: String) : RealmRecyclerViewAdapter<Task, TaskAdapter.TaskViewHolder?>(data, true) {
internal class TaskAdapter(data: OrderedRealmCollection<Task>, val user: io.realm.mongodb.User, private val partition: String) : RealmRecyclerViewAdapter<Task, TaskAdapter.TaskViewHolder?>(data, true) {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TaskViewHolder {
val itemView: View = LayoutInflater.from(parent.context).inflate(R.layout.task_view, parent, false)
Expand Down Expand Up @@ -83,6 +83,8 @@ internal class TaskAdapter(data: OrderedRealmCollection<Task>, val user: io.real
}

private fun changeStatus(status: TaskStatus, _id: ObjectId?) {
// :code-block-start: change-task-status
// :hide-start:
// 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)
Expand All @@ -98,9 +100,18 @@ internal class TaskAdapter(data: OrderedRealmCollection<Task>, val user: io.real
}
// always close realms when you are done with them!
realm.close()
// :replace-with:
//// TODO: Change the status of the specified Task object in the project realm.
//// Step 1: Connect to the project realm using the `partition` member variable of the adapter.
//// Step 2: Query the realm for the Task with the specified _id value.
//// Step 3: Set the `statusEnum` property of the Task to the specified status value.
// :hide-end:
// :code-block-end:
}

private fun removeAt(id: ObjectId) {
// :code-block-start: delete-task
// :hide-start:
// 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)
Expand All @@ -116,6 +127,13 @@ internal class TaskAdapter(data: OrderedRealmCollection<Task>, val user: io.real
}
// always close realms when you are done with them!
realm.close()
// :replace-with:
//// TODO: Delete the specified Task object from the project realm.
//// Step 1: Connect to the project realm using the `partition` member variable of the adapter.
//// Step 2: Query the realm for the Task with the specified _id value.
//// Step 3: Delete the Task from the project realm.
// :hide-end:
// :code-block-end:
}

internal inner class TaskViewHolder(view: View) : RecyclerView.ViewHolder(view) {
Expand Down
2 changes: 1 addition & 1 deletion tutorial/kotlin-android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ buildscript {
}
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.0'
classpath 'com.android.tools.build:gradle:4.0.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "io.realm:realm-gradle-plugin:10.0.0-BETA.8"
}
Expand Down
Binary file not shown.
Loading