From c52ac04cab8e62049106c84af6b435e8546f113d Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 15 Jan 2019 15:10:58 +0100 Subject: [PATCH 1/5] Prepare next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 3f055cf770..7ca1081f54 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.9.1-SNAPSHOT \ No newline at end of file +5.10.0-SNAPSHOT \ No newline at end of file From 11d0732ce2b55e283436a6eedbbbed74632ddb77 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 15 Jan 2019 16:05:11 +0100 Subject: [PATCH 2/5] Add missing bug information --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61423ce1fa..de2b580084 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ * Added better checks for detecting corrupted files, both before and after the file is written to disk. ### Fixed -* [ObjectServer] Native errors sometimes mapped to the wrong Java ErrorCode. [#6364](https://github.com/realm/realm-java/issues/6364) +* [ObjectServer] Native errors sometimes mapped to the wrong Java ErrorCode. (Issue [#6364](https://github.com/realm/realm-java/issues/6364), since 2.0.0) * [ObjectServer] Query-based Sync queries involving LIMIT, limited the result before permissions were evaluated. This could sometimes result in the wrong number of elements being returned. * Removed Java 8 bytecode. Resulted in errors like `D8: Invoke-customs are only supported starting with Android O (--min-api 26)` if not compiled with Java 8. (Issue [#6300](https://github.com/realm/realm-java/issues/6300), since 5.8.0). From c26dd2d4ed952923475112f978876631e1d89fef Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 5 Feb 2019 10:07:28 +0100 Subject: [PATCH 3/5] Convert ObjectServerExample to Kotlin and use data binding (#6413) --- Dockerfile | 25 +- examples/objectServerExample/README.md | 8 +- examples/objectServerExample/build.gradle | 22 +- .../objectserver/CounterActivity.java | 222 ------------------ .../examples/objectserver/CounterActivity.kt | 177 ++++++++++++++ .../examples/objectserver/LoginActivity.java | 140 ----------- .../examples/objectserver/LoginActivity.kt | 113 +++++++++ .../{MyApplication.java => MyApplication.kt} | 25 +- .../objectserver/model/CRDTCounter.java | 42 ---- .../objectserver/model/CRDTCounter.kt | 38 +++ .../src/main/res/layout/activity_counter.xml | 90 +++---- .../src/main/res/layout/activity_login.xml | 113 ++++----- .../src/main/res/values/strings.xml | 2 + realm/build.gradle | 2 +- 14 files changed, 491 insertions(+), 528 deletions(-) delete mode 100644 examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java create mode 100644 examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.kt delete mode 100644 examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java create mode 100644 examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.kt rename examples/objectServerExample/src/main/java/io/realm/examples/objectserver/{MyApplication.java => MyApplication.kt} (62%) delete mode 100644 examples/objectServerExample/src/main/java/io/realm/examples/objectserver/model/CRDTCounter.java create mode 100644 examples/objectServerExample/src/main/java/io/realm/examples/objectserver/model/CRDTCounter.kt diff --git a/Dockerfile b/Dockerfile index ddcc4b4c7e..2b66ca9912 100644 --- a/Dockerfile +++ b/Dockerfile @@ -46,16 +46,23 @@ RUN cd /opt && \ rm -f android-tools-linux.zip # Grab what's needed in the SDK -RUN mkdir "${ANDROID_HOME}/licenses" && \ - echo -e "\n8933bad161af4178b1185d1a37fbf41ea5269c55" > "${ANDROID_HOME}/licenses/android-sdk-license" RUN sdkmanager --update -# Accept all licenses -RUN yes y | sdkmanager --licenses -RUN sdkmanager 'platform-tools' -RUN sdkmanager 'build-tools;28.0.3' -RUN sdkmanager 'extras;android;m2repository' -RUN sdkmanager 'platforms;android-27' -RUN sdkmanager 'cmake;3.6.4111459' + +# Accept licenses before installing components, no need to echo y for each component +# License is valid for all the standard components in versions installed from this file +# Non-standard components: MIPS system images, preview versions, GDK (Google Glass) and Android Google TV require separate licenses, not accepted there +RUN yes | sdkmanager --licenses + +# SDKs +# Please keep these in descending order! +# The `yes` is for accepting all non-standard tool licenses. +# Please keep all sections in descending order! +RUN yes | sdkmanager \ + 'platform-tools' \ + 'build-tools;28.0.3' \ + 'extras;android;m2repository' \ + 'platforms;android-27' \ + 'cmake;3.6.4111459' # Install the NDK RUN mkdir /opt/android-ndk-tmp && \ diff --git a/examples/objectServerExample/README.md b/examples/objectServerExample/README.md index d3c8558940..f6630def1b 100644 --- a/examples/objectServerExample/README.md +++ b/examples/objectServerExample/README.md @@ -10,11 +10,15 @@ injected into the build configuration. To use a different ObjectServer, simply put the server IP Address into the `build.gradle`, as indicated in the comments, on the lines like this: - buildConfigField "String", "OBJECT_SERVER_IP", "\"${host}\"" + def rosUrl = "" For instance: - buildConfigField "String", "OBJECT_SERVER_IP", "192.168.0.1" + def rosUrl = "https://myinstance.us1.cloud.realm.io" + +or: + + def rosUrl = "http://127.0.0.1:9080" To read more about the Realm Object Server and how to deploy it, see https://realm.io/news/introducing-realm-mobile-platform/ diff --git a/examples/objectServerExample/build.gradle b/examples/objectServerExample/build.gradle index ff0d0d5ccd..44535c85a7 100644 --- a/examples/objectServerExample/build.gradle +++ b/examples/objectServerExample/build.gradle @@ -1,4 +1,19 @@ +buildscript { + ext.kotlin_version = '1.3.20' + repositories { + google() + jcenter() + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + apply plugin: 'com.android.application' +apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' apply plugin: 'realm-android' android { @@ -13,6 +28,10 @@ android { versionName "1.0" } + dataBinding { + enabled = true + } + buildTypes { // Go to https://cloud.realm.io and copy the URL to your instance. Insert it below. // It will look something like "https://test.us1.cloud.realm.io" @@ -45,6 +64,5 @@ dependencies { implementation 'com.android.support:appcompat-v7:27.1.1' implementation 'com.android.support:design:27.1.1' implementation 'me.zhanghai.android.materialprogressbar:library:1.3.0' - implementation 'com.jakewharton:butterknife:8.8.1'//TODO:Can be refactored with Native Android Data Binding - annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'//TODO:Can be refactored with Native Android Data Binding + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java deleted file mode 100644 index 26d129cd2d..0000000000 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.realm.examples.objectserver; - -import android.content.Intent; -import android.graphics.PorterDuff; -import android.os.Bundle; -import android.support.annotation.ColorRes; -import android.support.v7.app.AppCompatActivity; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.TextView; - -import java.util.Locale; -import java.util.concurrent.atomic.AtomicBoolean; - -import javax.annotation.Nonnull; - -import butterknife.BindView; -import butterknife.ButterKnife; -import butterknife.OnClick; -import io.realm.OrderedCollectionChangeSet; -import io.realm.OrderedRealmCollectionChangeListener; -import io.realm.Progress; -import io.realm.ProgressListener; -import io.realm.ProgressMode; -import io.realm.Realm; -import io.realm.RealmResults; -import io.realm.SyncConfiguration; -import io.realm.SyncManager; -import io.realm.SyncSession; -import io.realm.SyncUser; -import io.realm.examples.objectserver.model.CRDTCounter; -import me.zhanghai.android.materialprogressbar.MaterialProgressBar; - -public class CounterActivity extends AppCompatActivity { - - private final ProgressListener downloadListener = new ProgressListener() { - @Override - public void onChange(@Nonnull Progress progress) { - downloadingChanges.set(!progress.isTransferComplete()); - runOnUiThread(updateProgressBar); - } - }; - private final ProgressListener uploadListener = new ProgressListener() { - @Override - public void onChange(@Nonnull Progress progress) { - uploadingChanges.set(!progress.isTransferComplete()); - runOnUiThread(updateProgressBar); - } - }; - private final Runnable updateProgressBar = new Runnable() { - @Override - public void run() { - updateProgressBar(downloadingChanges.get(), uploadingChanges.get()); - } - }; - - private final AtomicBoolean downloadingChanges = new AtomicBoolean(false); - private final AtomicBoolean uploadingChanges = new AtomicBoolean(false); - - private Realm realm; - private SyncSession session; - private SyncUser user; - - @BindView(R.id.text_counter) TextView counterView; - @BindView(R.id.progressbar) MaterialProgressBar progressBar; - private RealmResults counters; // Keep strong reference to counter to keep change listeners alive. - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_counter); - ButterKnife.bind(this); - } - - @Override - protected void onStart() { - super.onStart(); - user = getLoggedInUser(); - if (user == null) { return; } - - // Create a RealmConfiguration for our user - SyncConfiguration config = user.createConfiguration(BuildConfig.REALM_URL) - .initialData(new Realm.Transaction() { - @Override - public void execute(@Nonnull Realm realm) { - realm.createObject(CRDTCounter.class, user.getIdentity()); - } - }) - .build(); - - // This will automatically sync all changes in the background for as long as the Realm is open - realm = Realm.getInstance(config); - - counterView.setText("-"); - counters = realm.where(CRDTCounter.class).equalTo("name", user.getIdentity()).findAllAsync(); - counters.addChangeListener(new OrderedRealmCollectionChangeListener>() { - @Override - public void onChange(RealmResults counters, OrderedCollectionChangeSet changeSet) { - if (counters.isValid() && !counters.isEmpty()) { - CRDTCounter counter = counters.first(); - counterView.setText(String.format(Locale.US, "%d", counter.getCount())); - } else { - counterView.setText("-"); - } - } - }); - - // Setup progress listeners for indeterminate progress bars - session = SyncManager.getSession(config); - session.addDownloadProgressListener(ProgressMode.INDEFINITELY, downloadListener); - session.addUploadProgressListener(ProgressMode.INDEFINITELY, uploadListener); - } - - @Override - protected void onStop() { - super.onStop(); - if (session != null) { - session.removeProgressListener(downloadListener); - session.removeProgressListener(uploadListener); - session = null; - } - closeRealm(); - user = null; - counters = null; - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.menu_counter, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch(item.getItemId()) { - case R.id.action_logout: - closeRealm(); - user.logOut(); - user = getLoggedInUser(); - return true; - - default: - return super.onOptionsItemSelected(item); - } - } - - @OnClick(R.id.upper) - public void incrementCounter() { - adjustCounter(1); - } - - @OnClick(R.id.lower) - public void decrementCounter() { - adjustCounter(-1); - } - - private void updateProgressBar(boolean downloading, boolean uploading) { - @ColorRes int color = android.R.color.black; - int visibility = View.VISIBLE; - if (downloading && uploading) { - color = R.color.progress_both; - } else if (downloading) { - color = R.color.progress_download; - } else if (uploading) { - color = R.color.progress_upload; - } else { - visibility = View.GONE; - } - progressBar.getIndeterminateDrawable().setColorFilter(getResources().getColor(color), PorterDuff.Mode.SRC_IN); - progressBar.setVisibility(visibility); - } - - private void adjustCounter(final int adjustment) { - // A synchronized Realm can get written to at any point in time, so doing synchronous writes on the UI - // thread is HIGHLY discouraged as it might block longer than intended. Use only async transactions. - realm.executeTransactionAsync(new Realm.Transaction() { - @Override - public void execute(@Nonnull Realm realm) { - CRDTCounter counter = realm.where(CRDTCounter.class).findFirst(); - if (counter != null) { - counter.incrementCounter(adjustment); - } - } - }); - } - - private SyncUser getLoggedInUser() { - SyncUser user = null; - - try { user = SyncUser.current(); } - catch (IllegalStateException ignore) { } - - if (user == null) { - startActivity(new Intent(this, LoginActivity.class)); - } - - return user; - } - - private void closeRealm() { - if (realm != null && !realm.isClosed()) { - realm.close(); - } - } -} diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.kt b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.kt new file mode 100644 index 0000000000..bc069f88c6 --- /dev/null +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.kt @@ -0,0 +1,177 @@ +/* + * Copyright 2019 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.examples.objectserver + +import android.content.Intent +import android.databinding.DataBindingUtil +import android.graphics.PorterDuff +import android.os.Bundle +import android.support.annotation.ColorRes +import android.support.v7.app.AppCompatActivity +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.widget.TextView +import io.realm.* +import io.realm.examples.objectserver.databinding.ActivityCounterBinding +import io.realm.examples.objectserver.model.CRDTCounter +import io.realm.kotlin.createObject +import io.realm.kotlin.syncSession +import io.realm.kotlin.where +import io.realm.log.RealmLog +import me.zhanghai.android.materialprogressbar.MaterialProgressBar +import java.util.* +import java.util.concurrent.atomic.AtomicBoolean + +class CounterActivity : AppCompatActivity() { + + private lateinit var binding: ActivityCounterBinding + + private val downloadListener = ProgressListener { progress -> + downloadingChanges.set(!progress.isTransferComplete) + runOnUiThread(updateProgressBar) + } + private val uploadListener = ProgressListener { progress -> + uploadingChanges.set(!progress.isTransferComplete) + runOnUiThread(updateProgressBar) + } + private val updateProgressBar = Runnable { updateProgressBar(downloadingChanges.get(), uploadingChanges.get()) } + + private val downloadingChanges = AtomicBoolean(false) + private val uploadingChanges = AtomicBoolean(false) + + private lateinit var realm: Realm + private lateinit var session: SyncSession + private var user: SyncUser? = null + + private lateinit var counterView: TextView + private lateinit var progressBar: MaterialProgressBar + private lateinit var counters: RealmResults // Keep strong reference to counter to keep change listeners alive. + + private val loggedInUser: SyncUser? + get() { + var user: SyncUser? = null + + try { + user = SyncUser.current() + } catch (e: IllegalStateException) { + RealmLog.warn(e); + } + + if (user == null) { + startActivity(Intent(this, LoginActivity::class.java)) + } + + return user + } + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = DataBindingUtil.setContentView(this, R.layout.activity_counter) + counterView = binding.textCounter + progressBar = binding.progressbar + binding.upper.setOnClickListener { adjustCounter(1) } + binding.lower.setOnClickListener { adjustCounter(-1) } + } + + override fun onStart() { + super.onStart() + user = loggedInUser + val user = user + if (user != null) { + // Create a RealmConfiguration for our user + val config = user.createConfiguration(BuildConfig.REALM_URL) + .initialData { realm -> realm.createObject(user.identity) } + .build() + + // This will automatically sync all changes in the background for as long as the Realm is open + realm = Realm.getInstance(config) + + counterView.text = "-" + counters = realm.where().equalTo("name", user.identity).findAllAsync() + counters.addChangeListener { counters, _ -> + if (counters.isValid && !counters.isEmpty()) { + val counter = counters.first() + counterView.text = String.format(Locale.US, "%d", counter!!.count) + } else { + counterView.text = "-" + } + } + + // Setup progress listeners for indeterminate progress bars + session = realm.syncSession + session.run { + addDownloadProgressListener(ProgressMode.INDEFINITELY, downloadListener) + addUploadProgressListener(ProgressMode.INDEFINITELY, uploadListener) + } + } + } + + override fun onStop() { + super.onStop() + user?.run { + session.run { + removeProgressListener(downloadListener) + removeProgressListener(uploadListener) + } + realm.close() + } + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.menu_counter, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.action_logout -> { + realm.close() + val user = user + if (user != null) { + user.logOut() + this.user = loggedInUser + } + true + } + + else -> super.onOptionsItemSelected(item) + } + } + + private fun updateProgressBar(downloading: Boolean, uploading: Boolean) { + @ColorRes val color = when { + downloading && uploading -> R.color.progress_both + downloading -> R.color.progress_download + uploading -> R.color.progress_upload + else -> android.R.color.black + } + progressBar.indeterminateDrawable.setColorFilter(resources.getColor(color), PorterDuff.Mode.SRC_IN) + progressBar.visibility = if (color == android.R.color.black) View.GONE else View.VISIBLE + } + + private fun adjustCounter(adjustment: Int) { + // A synchronized Realm can get written to at any point in time, so doing synchronous writes on the UI + // thread is HIGHLY discouraged as it might block longer than intended. Use only async transactions. + realm.executeTransactionAsync { realm -> + val counter = realm.where().findFirst() + counter?.incrementCounter(adjustment.toLong()) + } + } + +} diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java deleted file mode 100644 index d16b593802..0000000000 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.realm.examples.objectserver; - -import android.app.ProgressDialog; -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.view.View; -import android.widget.Button; -import android.widget.EditText; -import android.widget.Toast; - -import javax.annotation.Nonnull; - -import butterknife.BindView; -import butterknife.ButterKnife; -import io.realm.SyncCredentials; -import io.realm.ObjectServerError; -import io.realm.SyncUser; - - -public class LoginActivity extends AppCompatActivity { - @BindView(R.id.input_username) EditText username; - @BindView(R.id.input_password) EditText password; - @BindView(R.id.button_login) Button loginButton; - @BindView(R.id.button_create) Button createUserButton; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_login); - ButterKnife.bind(this); - loginButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - login(false); - } - }); - createUserButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - login(true); - } - }); - } - - public void login(boolean createUser) { - if (!validate()) { - onLoginFailed("Invalid username or password"); - return; - } - - createUserButton.setEnabled(false); - loginButton.setEnabled(false); - - final ProgressDialog progressDialog = new ProgressDialog(LoginActivity.this); - progressDialog.setIndeterminate(true); - progressDialog.setMessage("Authenticating..."); - progressDialog.show(); - - String username = this.username.getText().toString(); - String password = this.password.getText().toString(); - - SyncCredentials creds = SyncCredentials.usernamePassword(username, password, createUser); - SyncUser.Callback callback = new SyncUser.Callback() { - @Override - public void onSuccess(@Nonnull SyncUser user) { - progressDialog.dismiss(); - onLoginSuccess(); - } - - @Override - public void onError(@Nonnull ObjectServerError error) { - progressDialog.dismiss(); - String errorMsg; - switch (error.getErrorCode()) { - case UNKNOWN_ACCOUNT: - errorMsg = "Account does not exists."; - break; - case INVALID_CREDENTIALS: - errorMsg = "User name and password does not match"; - break; - default: - errorMsg = error.toString(); - } - onLoginFailed(errorMsg); - } - }; - - SyncUser.logInAsync(creds, BuildConfig.REALM_AUTH_URL, callback); - } - - @Override - public void onBackPressed() { - // Disable going back to the MainActivity - moveTaskToBack(true); - } - - public void onLoginSuccess() { - loginButton.setEnabled(true); - createUserButton.setEnabled(true); - finish(); - } - - public void onLoginFailed(String errorMsg) { - loginButton.setEnabled(true); - createUserButton.setEnabled(true); - Toast.makeText(getBaseContext(), errorMsg, Toast.LENGTH_LONG).show(); - } - - public boolean validate() { - boolean valid = true; - String email = username.getText().toString(); - String password = this.password.getText().toString(); - - if (email.isEmpty()) { - valid = false; - } - - if (password.isEmpty()) { - valid = false; - } - - return valid; - } -} diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.kt b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.kt new file mode 100644 index 0000000000..1038e795a8 --- /dev/null +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.kt @@ -0,0 +1,113 @@ +/* + * Copyright 2019 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.examples.objectserver + +import android.app.ProgressDialog +import android.databinding.DataBindingUtil +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import android.widget.Button +import android.widget.EditText +import android.widget.Toast +import io.realm.ErrorCode +import io.realm.ObjectServerError +import io.realm.SyncCredentials +import io.realm.SyncUser +import io.realm.examples.objectserver.databinding.ActivityLoginBinding + +class LoginActivity : AppCompatActivity() { + + private lateinit var username: EditText + private lateinit var password: EditText + private lateinit var loginButton: Button + private lateinit var createUserButton: Button + + lateinit private var binding: ActivityLoginBinding + + public override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = DataBindingUtil.setContentView(this, R.layout.activity_login) + username = binding.inputUsername + password = binding.inputPassword + loginButton = binding.buttonLogin + createUserButton = binding.buttonCreate + + loginButton.setOnClickListener { login(false) } + createUserButton.setOnClickListener { login(true) } + } + + private fun login(createUser: Boolean) { + if (!validate()) { + onLoginFailed("Invalid username or password") + return + } + + binding.buttonCreate.isEnabled = false + binding.buttonLogin.isEnabled = false + + val progressDialog = ProgressDialog(this@LoginActivity) + progressDialog.isIndeterminate = true + progressDialog.setMessage("Authenticating...") + progressDialog.show() + + val username = this.username.text.toString() + val password = this.password.text.toString() + + val creds = SyncCredentials.usernamePassword(username, password, createUser) + val callback = object : SyncUser.Callback { + override fun onSuccess(user: SyncUser) { + progressDialog.dismiss() + onLoginSuccess() + } + + override fun onError(error: ObjectServerError) { + progressDialog.dismiss() + val errorMsg: String = when (error.errorCode) { + ErrorCode.UNKNOWN_ACCOUNT -> getString(R.string.login_error_unknown_account) + ErrorCode.INVALID_CREDENTIALS -> getString(R.string.login_error_invalid_credentials) + else -> error.toString() + } + onLoginFailed(errorMsg) + } + } + + SyncUser.logInAsync(creds, BuildConfig.REALM_AUTH_URL, callback) + } + + override fun onBackPressed() { + // Disable going back to the MainActivity + moveTaskToBack(true) + } + + private fun onLoginSuccess() { + loginButton.isEnabled = true + createUserButton.isEnabled = true + finish() + } + + private fun onLoginFailed(errorMsg: String) { + loginButton.isEnabled = true + createUserButton.isEnabled = true + Toast.makeText(baseContext, errorMsg, Toast.LENGTH_LONG).show() + } + + private fun validate(): Boolean = when { + username.text.toString().isEmpty() -> false + password.text.toString().isEmpty() -> false + else -> true + } +} diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.kt similarity index 62% rename from examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java rename to examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.kt index 417fd9d9f5..dc7360fb48 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016 Realm Inc. + * Copyright 2019 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,24 +14,23 @@ * limitations under the License. */ -package io.realm.examples.objectserver; +package io.realm.examples.objectserver -import android.app.Application; -import android.util.Log; +import android.app.Application +import android.util.Log -import io.realm.Realm; -import io.realm.log.RealmLog; +import io.realm.Realm +import io.realm.log.RealmLog -public class MyApplication extends Application { +class MyApplication : Application() { - @Override - public void onCreate() { - super.onCreate(); - Realm.init(this, "ObjectServerExample/" + BuildConfig.VERSION_NAME); + override fun onCreate() { + super.onCreate() + Realm.init(this, "ObjectServerExample/" + BuildConfig.VERSION_NAME) - // Enable full log output when debugging + // Enable more if (BuildConfig.DEBUG) { - RealmLog.setLevel(Log.DEBUG); + RealmLog.setLevel(Log.DEBUG) } } } diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/model/CRDTCounter.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/model/CRDTCounter.java deleted file mode 100644 index 0bca8fd53c..0000000000 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/model/CRDTCounter.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.realm.examples.objectserver.model; - -import io.realm.MutableRealmInteger; -import io.realm.RealmObject; -import io.realm.annotations.PrimaryKey; -import io.realm.annotations.Required; - -/** - * A named, conflict-free replicated data-type. - */ -public class CRDTCounter extends RealmObject { - @PrimaryKey - private String name; - - @Required - public final MutableRealmInteger counter = MutableRealmInteger.valueOf(0L); - - // Required for Realm - public CRDTCounter() {} - - public CRDTCounter(String name) { this.name = name; } - - public String getName() { return name; } - - public long getCount() { return counter.get().longValue(); } - public void incrementCounter(long delta) { counter.increment(delta); } -} diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/model/CRDTCounter.kt b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/model/CRDTCounter.kt new file mode 100644 index 0000000000..6078bb0512 --- /dev/null +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/model/CRDTCounter.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2019 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.realm.examples.objectserver.model + +import io.realm.MutableRealmInteger +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey +import io.realm.annotations.Required + +open class CRDTCounter : RealmObject() { + + @PrimaryKey + var name: String = "" + + @Required + private val counter = MutableRealmInteger.valueOf(0L) + + val count: Long + get() = this.counter.get()!!.toLong() + + fun incrementCounter(delta: Long) { + counter.increment(delta) + } + +} diff --git a/examples/objectServerExample/src/main/res/layout/activity_counter.xml b/examples/objectServerExample/src/main/res/layout/activity_counter.xml index a1300b2123..215c9fb47f 100644 --- a/examples/objectServerExample/src/main/res/layout/activity_counter.xml +++ b/examples/objectServerExample/src/main/res/layout/activity_counter.xml @@ -1,49 +1,51 @@ - - - + + + + + + android:layout_height="match_parent"> - - + android:layout_height="match_parent" + android:orientation="vertical"> + + + + + - + + - - - - - - - - + android:layout_height="wrap_content" + android:layout_gravity="top" + android:indeterminate="true" + android:visibility="visible" + app:mpb_progressStyle="horizontal" /> + + + + diff --git a/examples/objectServerExample/src/main/res/layout/activity_login.xml b/examples/objectServerExample/src/main/res/layout/activity_login.xml index 375cdbca98..29f078ebc2 100644 --- a/examples/objectServerExample/src/main/res/layout/activity_login.xml +++ b/examples/objectServerExample/src/main/res/layout/activity_login.xml @@ -1,68 +1,75 @@ - + - + - + - + android:orientation="vertical" + android:paddingLeft="24dp" + android:paddingTop="56dp" + android:paddingRight="24dp"> - + + - + android:layout_marginTop="8dp" + android:layout_marginBottom="8dp"> - + + - - + android:layout_marginTop="8dp" + android:layout_marginBottom="8dp"> - + + + + + + + + + + - - - diff --git a/examples/objectServerExample/src/main/res/values/strings.xml b/examples/objectServerExample/src/main/res/values/strings.xml index b4f90b3676..dbf98e52eb 100644 --- a/examples/objectServerExample/src/main/res/values/strings.xml +++ b/examples/objectServerExample/src/main/res/values/strings.xml @@ -7,4 +7,6 @@ Create account and login Login Logout + Account does not exist. + User name and password do not match. diff --git a/realm/build.gradle b/realm/build.gradle index 53ffc2de3a..9ab569ac19 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -38,7 +38,7 @@ allprojects { def projectDependencies = new Properties() projectDependencies.load(new FileInputStream("${rootDir}/../dependencies.list")) project.ext.minSdkVersion = 9 - project.ext.compileSdkVersion = 26 + project.ext.compileSdkVersion = 27 project.ext.buildToolsVersion = projectDependencies.get("ANDROID_BUILD_TOOLS") group = 'io.realm' From 002c1ac4ba589b08a6168b4431cb62105d96eb7e Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 28 Feb 2019 09:13:36 +0100 Subject: [PATCH 4/5] Add new ObjectServer example --- dependencies.list | 1 - examples/build.gradle | 2 +- examples/gradle.properties | 2 +- .../README.md | 45 ++++ .../build.gradle | 55 ++++ .../lint.xml | 0 .../src/main/AndroidManifest.xml | 35 +++ .../objectserver/activitytracker/Constants.kt | 35 +++ .../activitytracker/MyApplication.kt | 41 +++ .../activitytracker/RealmBaseAdapter.java | 160 +++++++++++ .../objectserver/activitytracker/model/App.kt | 46 ++++ .../model/entities/Activity.kt | 25 ++ .../activitytracker/model/entities/Booking.kt | 56 ++++ .../activitytracker/model/entities/Guest.kt | 18 ++ .../activitytracker/model/entities/Order.kt | 45 ++++ .../model/entities/Timeslot.kt | 50 ++++ .../activitytracker/ui/BaseActivity.kt | 62 +++++ .../activitytracker/ui/BaseViewModel.kt | 35 +++ .../ui/RealmRecyclerViewAdapter.java | 248 ++++++++++++++++++ .../activitytracker/ui/SingleLiveEvent.java | 76 ++++++ .../activitylist/ActivityRecyclerAdapter.kt | 49 ++++ .../ui/activitylist/SelectActivityActivity.kt | 99 +++++++ .../activitylist/SelectActivityViewModel.kt | 158 +++++++++++ .../ui/bookingslist/BookingsListActivity.kt | 111 ++++++++ .../ui/bookingslist/BookingsListViewModel.kt | 166 ++++++++++++ .../bookingslist/BookingsRecyclerAdapter.kt | 50 ++++ .../ui/checkin/CheckinActivity.kt | 100 +++++++ .../ui/checkin/CheckinViewModel.kt | 151 +++++++++++ .../ui/checkin/TimeslotAdapter.kt | 53 ++++ .../activitytracker/ui/login/LoginActivity.kt | 119 +++++++++ .../ui/orderlist/OrdersActivity.kt | 79 ++++++ .../ui/orderlist/OrdersRecyclerAdapter.kt | 57 ++++ .../ui/orderlist/OrdersViewModel.kt | 150 +++++++++++ .../ui/shared/ObservableExt.kt | 39 +++ .../drawable-v24/ic_launcher_foreground.xml | 34 +++ .../res/drawable/ic_launcher_background.xml | 170 ++++++++++++ .../src/main/res/drawable/logo.png | Bin .../res/layout/activity_bookings_list.xml | 63 +++++ .../src/main/res/layout/activity_checkin.xml | 144 ++++++++++ .../res/layout/activity_excursion_list.xml | 37 +++ .../src/main/res/layout/activity_login.xml | 76 ++++++ .../main/res/layout/activity_order_list.xml | 37 +++ .../main/res/layout/item_bookings_list.xml | 40 +++ .../main/res/layout/item_excursion_list.xml | 40 +++ .../src/main/res/layout/item_order_list.xml | 65 +++++ .../src/main/res/layout/list.xml | 14 + .../src/main/res/layout/spinner_item.xml | 13 + .../res/layout/spinner_item_drop_down.xml | 15 ++ .../src/main/res/menu/menu_items.xml | 31 +++ .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 16078 bytes .../src/main/res/values/colors.xml | 13 + .../src/main/res/values/dimens.xml | 6 + .../src/main/res/values/strings.xml | 27 ++ .../src/main/res/values/styles.xml | 30 +++ .../README.md | 0 .../build.gradle | 0 examples/objectServerSimpleExample/lint.xml | 10 + .../src/main/AndroidManifest.xml | 0 .../examples/objectserver/CounterActivity.kt | 0 .../examples/objectserver/LoginActivity.kt | 0 .../examples/objectserver/MyApplication.kt | 0 .../objectserver/model/CRDTCounter.kt | 0 .../ic_exit_to_app_white_24dp.png | Bin .../ic_exit_to_app_white_24dp.png | Bin .../src/main/res/drawable/button_counter.xml | 0 .../src/main/res/drawable/logo.png | Bin 0 -> 16078 bytes .../src/main/res/layout/activity_counter.xml | 0 .../src/main/res/layout/activity_login.xml | 0 .../src/main/res/menu/menu_counter.xml | 0 .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 4906 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2968 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 7076 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 11165 bytes .../src/main/res/values-w820dp/dimens.xml | 0 .../src/main/res/values/dimens.xml | 0 .../src/main/res/values/realm_colors.xml | 0 .../src/main/res/values/strings.xml | 0 .../src/main/res/values/styles.xml | 0 examples/settings.gradle | 3 +- 83 files changed, 3282 insertions(+), 4 deletions(-) create mode 100644 examples/objectServerActivityTrackerExample/README.md create mode 100644 examples/objectServerActivityTrackerExample/build.gradle rename examples/{objectServerExample => objectServerActivityTrackerExample}/lint.xml (100%) create mode 100644 examples/objectServerActivityTrackerExample/src/main/AndroidManifest.xml create mode 100644 examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/Constants.kt create mode 100644 examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/MyApplication.kt create mode 100644 examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/RealmBaseAdapter.java create mode 100644 examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/model/App.kt create mode 100644 examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/model/entities/Activity.kt create mode 100644 examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/model/entities/Booking.kt create mode 100644 examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/model/entities/Guest.kt create mode 100644 examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/model/entities/Order.kt create mode 100644 examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/model/entities/Timeslot.kt create mode 100644 examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/BaseActivity.kt create mode 100644 examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/BaseViewModel.kt create mode 100644 examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/RealmRecyclerViewAdapter.java create mode 100644 examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/SingleLiveEvent.java create mode 100644 examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/activitylist/ActivityRecyclerAdapter.kt create mode 100644 examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/activitylist/SelectActivityActivity.kt create mode 100644 examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/activitylist/SelectActivityViewModel.kt create mode 100644 examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/bookingslist/BookingsListActivity.kt create mode 100644 examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/bookingslist/BookingsListViewModel.kt create mode 100644 examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/bookingslist/BookingsRecyclerAdapter.kt create mode 100644 examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/checkin/CheckinActivity.kt create mode 100644 examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/checkin/CheckinViewModel.kt create mode 100644 examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/checkin/TimeslotAdapter.kt create mode 100644 examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/login/LoginActivity.kt create mode 100644 examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/orderlist/OrdersActivity.kt create mode 100644 examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/orderlist/OrdersRecyclerAdapter.kt create mode 100644 examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/orderlist/OrdersViewModel.kt create mode 100644 examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/shared/ObservableExt.kt create mode 100644 examples/objectServerActivityTrackerExample/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 examples/objectServerActivityTrackerExample/src/main/res/drawable/ic_launcher_background.xml rename examples/{objectServerExample => objectServerActivityTrackerExample}/src/main/res/drawable/logo.png (100%) create mode 100644 examples/objectServerActivityTrackerExample/src/main/res/layout/activity_bookings_list.xml create mode 100644 examples/objectServerActivityTrackerExample/src/main/res/layout/activity_checkin.xml create mode 100644 examples/objectServerActivityTrackerExample/src/main/res/layout/activity_excursion_list.xml create mode 100644 examples/objectServerActivityTrackerExample/src/main/res/layout/activity_login.xml create mode 100644 examples/objectServerActivityTrackerExample/src/main/res/layout/activity_order_list.xml create mode 100644 examples/objectServerActivityTrackerExample/src/main/res/layout/item_bookings_list.xml create mode 100644 examples/objectServerActivityTrackerExample/src/main/res/layout/item_excursion_list.xml create mode 100644 examples/objectServerActivityTrackerExample/src/main/res/layout/item_order_list.xml create mode 100644 examples/objectServerActivityTrackerExample/src/main/res/layout/list.xml create mode 100644 examples/objectServerActivityTrackerExample/src/main/res/layout/spinner_item.xml create mode 100644 examples/objectServerActivityTrackerExample/src/main/res/layout/spinner_item_drop_down.xml create mode 100644 examples/objectServerActivityTrackerExample/src/main/res/menu/menu_items.xml rename examples/{objectServerExample => objectServerActivityTrackerExample}/src/main/res/mipmap-hdpi/ic_launcher.png (100%) rename examples/{objectServerExample => objectServerActivityTrackerExample}/src/main/res/mipmap-mdpi/ic_launcher.png (100%) rename examples/{objectServerExample => objectServerActivityTrackerExample}/src/main/res/mipmap-xhdpi/ic_launcher.png (100%) rename examples/{objectServerExample => objectServerActivityTrackerExample}/src/main/res/mipmap-xxhdpi/ic_launcher.png (100%) create mode 100755 examples/objectServerActivityTrackerExample/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 examples/objectServerActivityTrackerExample/src/main/res/values/colors.xml create mode 100644 examples/objectServerActivityTrackerExample/src/main/res/values/dimens.xml create mode 100644 examples/objectServerActivityTrackerExample/src/main/res/values/strings.xml create mode 100644 examples/objectServerActivityTrackerExample/src/main/res/values/styles.xml rename examples/{objectServerExample => objectServerSimpleExample}/README.md (100%) rename examples/{objectServerExample => objectServerSimpleExample}/build.gradle (100%) create mode 100644 examples/objectServerSimpleExample/lint.xml rename examples/{objectServerExample => objectServerSimpleExample}/src/main/AndroidManifest.xml (100%) rename examples/{objectServerExample => objectServerSimpleExample}/src/main/java/io/realm/examples/objectserver/CounterActivity.kt (100%) rename examples/{objectServerExample => objectServerSimpleExample}/src/main/java/io/realm/examples/objectserver/LoginActivity.kt (100%) rename examples/{objectServerExample => objectServerSimpleExample}/src/main/java/io/realm/examples/objectserver/MyApplication.kt (100%) rename examples/{objectServerExample => objectServerSimpleExample}/src/main/java/io/realm/examples/objectserver/model/CRDTCounter.kt (100%) rename examples/{objectServerExample => objectServerSimpleExample}/src/main/res/drawable-xxhdpi/ic_exit_to_app_white_24dp.png (100%) rename examples/{objectServerExample => objectServerSimpleExample}/src/main/res/drawable-xxxhdpi/ic_exit_to_app_white_24dp.png (100%) rename examples/{objectServerExample => objectServerSimpleExample}/src/main/res/drawable/button_counter.xml (100%) create mode 100755 examples/objectServerSimpleExample/src/main/res/drawable/logo.png rename examples/{objectServerExample => objectServerSimpleExample}/src/main/res/layout/activity_counter.xml (100%) rename examples/{objectServerExample => objectServerSimpleExample}/src/main/res/layout/activity_login.xml (100%) rename examples/{objectServerExample => objectServerSimpleExample}/src/main/res/menu/menu_counter.xml (100%) create mode 100755 examples/objectServerSimpleExample/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100755 examples/objectServerSimpleExample/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100755 examples/objectServerSimpleExample/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100755 examples/objectServerSimpleExample/src/main/res/mipmap-xxhdpi/ic_launcher.png rename examples/{objectServerExample => objectServerSimpleExample}/src/main/res/values-w820dp/dimens.xml (100%) rename examples/{objectServerExample => objectServerSimpleExample}/src/main/res/values/dimens.xml (100%) rename examples/{objectServerExample => objectServerSimpleExample}/src/main/res/values/realm_colors.xml (100%) rename examples/{objectServerExample => objectServerSimpleExample}/src/main/res/values/strings.xml (100%) rename examples/{objectServerExample => objectServerSimpleExample}/src/main/res/values/styles.xml (100%) diff --git a/dependencies.list b/dependencies.list index b45dd5b76d..a7df5cebee 100644 --- a/dependencies.list +++ b/dependencies.list @@ -18,4 +18,3 @@ ndkVersion=r10e BUILD_INFO_EXTRACTOR_GRADLE=4.7.5 GRADLE_BINTRAY_PLUGIN=1.8.4 - diff --git a/examples/build.gradle b/examples/build.gradle index 5e91e876da..3ed8391b72 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -1,6 +1,6 @@ def projectDependencies = new Properties() projectDependencies.load(new FileInputStream("${rootDir}/../dependencies.list")) -project.ext.sdkVersion = 27 +project.ext.sdkVersion = 28 project.ext.minSdkVersion = 15 project.ext.buildTools = projectDependencies.get("ANDROID_BUILD_TOOLS") diff --git a/examples/gradle.properties b/examples/gradle.properties index 94789e069a..d5b415dc3b 100644 --- a/examples/gradle.properties +++ b/examples/gradle.properties @@ -11,4 +11,4 @@ android.enableD8=true android.enableBuildScriptClasspathCheck=false # See https://developer.android.com/studio/build/optimize-your-build#configuration_on_demand -org.gradle.configureondemand=false +org.gradle.configureondemand=false \ No newline at end of file diff --git a/examples/objectServerActivityTrackerExample/README.md b/examples/objectServerActivityTrackerExample/README.md new file mode 100644 index 0000000000..d059179f23 --- /dev/null +++ b/examples/objectServerActivityTrackerExample/README.md @@ -0,0 +1,45 @@ +# Object Server Example + +This project contains a demo app demonstrating how you can build a real world app using modern +Android components and frameworks together with Realm Sync. + +This demo app covers the use case of managing people who have booked an activity during a vacation. + +It makes it possible for the person in charge of the activity to handle checkins as well. + + +## Requirements + +Two Query-based Realms must exist on the server. These must be called: + +* `/demo1` +* `/demo2` + +They can be created using an Admin user through Realm Studio. + +## Build project + +1) Edit `io.realm.examples.objectserver.advanced.Constants` and set the proper URL to the Realm Sync server +2) Compile and install the app `./gradlew clean installDebug` + +## Technical Details + +The app is built using the following frameworks/libraries: + +* Kotlin 1.3 +* Databinding +* Android Architecture Components: LiveData and ViewModel +* RxJava +* Realm Java + +For the UI parts the project uses package-by-feature instead of package-by-layer, so e.g. everything +related to the Excursion selection screen should be in the `io.realm.examples.objectserver.advanced.ui.excursionlist` +package. + +The project uses an MVVM architecture. All logic related to controlling the UI should be in the +various `*ViewModel` classes. These classes expose data using `LiveData` which are consumed by +the `Activity` using data binding. + +As this demo is relatively simple in scope, all business logic are contained within the view model +classes, this also includes all usages of Realm. + diff --git a/examples/objectServerActivityTrackerExample/build.gradle b/examples/objectServerActivityTrackerExample/build.gradle new file mode 100644 index 0000000000..f2ca3ac585 --- /dev/null +++ b/examples/objectServerActivityTrackerExample/build.gradle @@ -0,0 +1,55 @@ +buildscript { + ext.kotlin_version = '1.3.21' + repositories { + google() + jcenter() + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' +apply plugin: 'realm-android' + +android { + compileSdkVersion rootProject.sdkVersion + buildToolsVersion rootProject.buildTools + + defaultConfig { + applicationId 'io.realm.examples.objectserver.advanced' + targetSdkVersion rootProject.sdkVersion + minSdkVersion rootProject.minSdkVersion + versionCode 1 + versionName "1.0" + } + + dataBinding { + enabled = true + } +} + +realm { + syncEnabled = true +} + +def lifecycle_version = "2.0.0" +dependencies { + implementation 'com.android.support.constraint:constraint-layout:1.1.3' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + + implementation 'io.reactivex.rxjava2:rxjava:2.2.4' + implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' + implementation 'androidx.appcompat:appcompat:1.0.2' + implementation 'androidx.cardview:cardview:1.0.0' + implementation 'com.google.android.material:material:1.1.0-alpha03' + + implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" + kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" + implementation "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycle_version" + implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version" +} diff --git a/examples/objectServerExample/lint.xml b/examples/objectServerActivityTrackerExample/lint.xml similarity index 100% rename from examples/objectServerExample/lint.xml rename to examples/objectServerActivityTrackerExample/lint.xml diff --git a/examples/objectServerActivityTrackerExample/src/main/AndroidManifest.xml b/examples/objectServerActivityTrackerExample/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..e0556cc96f --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/AndroidManifest.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/Constants.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/Constants.kt new file mode 100644 index 0000000000..22494b3c95 --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/Constants.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2019 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.examples.objectserver.advanced + +internal object Constants { + /** + * Realm Cloud Users: + * Replace INSTANCE_ADDRESS with the hostname of your cloud instance + * e.g., "mycoolapp.us1.cloud.realm.io" + * + * ROS On-Premises Users + * Replace the INSTANCE_ADDRESS with the fully qualified version of + * address of your ROS server, e.g.: INSTANCE_ADDRESS = "192.168.1.65:9080" and "http://" + INSTANCE_ADDRESS + "/auth" + * (remember to use 'http/realm' instead of 'https/realms' if you didn't setup SSL on ROS yet) + */ + private val INSTANCE_ADDRESS = "cmelchior.us1.cloud.realm.io" + val AUTH_URL = "https://$INSTANCE_ADDRESS/auth" + val STANDARD_REALM_URL = "realms://$INSTANCE_ADDRESS/demo1" // Query-based Realm + val ORDERS_REALM_URL = "realms://$INSTANCE_ADDRESS/demo2" // Query-based Realm + +} diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/MyApplication.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/MyApplication.kt new file mode 100644 index 0000000000..eab6cf6203 --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/MyApplication.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2019 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.examples.objectserver.advanced + +import android.app.Application +import io.realm.examples.objectserver.advanced.model.App + +import io.realm.Realm +import io.realm.SyncUser + +class MyApplication : Application() { + override fun onCreate() { + super.onCreate() + Realm.init(this) + + // Initialize the current Realm setup if a user is found + // This is required if we user should be allowed to + // start in the middle of the app after a crash. + + // Make sure that calling Realm.getDefaultInstance() will throw + Realm.removeDefaultConfiguration() + + SyncUser.current()?.let { + App.configureRealms(it) + } + } +} diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/RealmBaseAdapter.java b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/RealmBaseAdapter.java new file mode 100644 index 0000000000..db310c83c4 --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/RealmBaseAdapter.java @@ -0,0 +1,160 @@ +/* + * Copyright 2019 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.examples.objectserver.advanced; + +import android.widget.BaseAdapter; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import io.realm.OrderedRealmCollection; +import io.realm.RealmChangeListener; +import io.realm.RealmList; +import io.realm.RealmModel; +import io.realm.RealmResults; + +/** + * The RealmBaseAdapter class is an abstract utility class for binding UI elements to Realm data, much like an + * {@link android.widget.CursorAdapter}. + *

+ * This adapter will automatically handle any updates to its data and call {@link #notifyDataSetChanged()} as + * appropriate. + *

+ * The RealmAdapter will stop receiving updates if the Realm instance providing the {@link io.realm.RealmResults} is + * closed. Trying to access Realm objects will at this point also result in a {@code IllegalStateException}. + */ +public abstract class RealmBaseAdapter extends BaseAdapter { + @Nullable + protected OrderedRealmCollection adapterData; + private final RealmChangeListener> listener; + + public RealmBaseAdapter(@Nullable OrderedRealmCollection data) { + if (data != null && !data.isManaged()) + throw new IllegalStateException("Only use this adapter with managed list, " + + "for un-managed lists you can just use the BaseAdapter"); + this.adapterData = data; + this.listener = new RealmChangeListener>() { + @Override + public void onChange(OrderedRealmCollection results) { + notifyDataSetChanged(); + } + }; + + if (isDataValid()) { + //noinspection ConstantConditions + addListener(data); + } + } + + private void addListener(@NonNull OrderedRealmCollection data) { + if (data instanceof RealmResults) { + RealmResults results = (RealmResults) data; + //noinspection unchecked + results.addChangeListener((RealmChangeListener) listener); + } else if (data instanceof RealmList) { + RealmList list = (RealmList) data; + //noinspection unchecked + list.addChangeListener((RealmChangeListener) listener); + } else { + throw new IllegalArgumentException("RealmCollection not supported: " + data.getClass()); + } + } + + private void removeListener(@NonNull OrderedRealmCollection data) { + if (data instanceof RealmResults) { + RealmResults results = (RealmResults) data; + //noinspection unchecked + results.removeChangeListener((RealmChangeListener) listener); + } else if (data instanceof RealmList) { + RealmList list = (RealmList) data; + //noinspection unchecked + list.removeChangeListener((RealmChangeListener) listener); + } else { + throw new IllegalArgumentException("RealmCollection not supported: " + data.getClass()); + } + } + + /** + * Returns how many items are in the data set. + * + * @return the number of items. + */ + @Override + public int getCount() { + //noinspection ConstantConditions + return isDataValid() ? adapterData.size() : 0; + } + + /** + * Get the data item associated with the specified position in the data set. + * + * @param position Position of the item whose data we want within the adapter's + * data set. + * @return The data at the specified position. + */ + @Override + @Nullable + public T getItem(int position) { + //noinspection ConstantConditions + return isDataValid() ? adapterData.get(position) : null; + } + + /** + * Get the row value associated with the specified position in the list. Note that item IDs are not stable so you + * cannot rely on the item ID being the same after {@link #notifyDataSetChanged()} or + * {@link #updateData(OrderedRealmCollection)} has been called. + * + * @param position The position of the item within the adapter's data set whose row value we want. + * @return The value of the item at the specified position. + */ + @Override + public long getItemId(int position) { + // TODO: find better solution once we have unique IDs + return position; + } + + /** + * Updates the data associated with the Adapter. + * + * Note that RealmResults and RealmLists are "live" views, so they will automatically be updated to reflect the + * latest changes. This will also trigger {@code notifyDataSetChanged()} to be called on the adapter. + * + * This method is therefore only useful if you want to display data based on a new query without replacing the + * adapter. + * + * @param data the new {@link OrderedRealmCollection} to display. + */ + @SuppressWarnings("WeakerAccess") + public void updateData(@Nullable OrderedRealmCollection data) { + if (listener != null) { + if (isDataValid()) { + //noinspection ConstantConditions + removeListener(adapterData); + } + if (data != null && data.isValid()) { + addListener(data); + } + } + + this.adapterData = data; + notifyDataSetChanged(); + } + + private boolean isDataValid() { + return adapterData != null && adapterData.isValid(); + } +} \ No newline at end of file diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/model/App.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/model/App.kt new file mode 100644 index 0000000000..8e7f4b3ca2 --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/model/App.kt @@ -0,0 +1,46 @@ +package io.realm.examples.objectserver.advanced.model + +import io.realm.examples.objectserver.advanced.Constants +import io.realm.examples.objectserver.advanced.model.entities.Guest +import io.realm.examples.objectserver.advanced.model.entities.Order +import io.realm.examples.objectserver.advanced.model.entities.Activity +import io.realm.RealmConfiguration +import io.realm.SyncUser +import io.realm.kotlin.where + +/** + * App / Business logic that doesn't naturally fit elsewhere + */ +class App { + + + + + companion object { + + lateinit var REALM1_CONFIG: RealmConfiguration + lateinit var REALM2_CONFIG: RealmConfiguration + + fun configureRealms(user: SyncUser) { + REALM1_CONFIG = user + .createConfiguration(Constants.STANDARD_REALM_URL) + .initialData { + // Initial subscriptions + it.where().subscribe("guests") + it.where().subscribe("activities") + it.where().subscribe("orders") + } + .build() + + REALM2_CONFIG = user + .createConfiguration(Constants.ORDERS_REALM_URL) + .initialData { + // Initial subscriptions + it.where().subscribe("orders") + } + .build() + + + } + } +} \ No newline at end of file diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/model/entities/Activity.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/model/entities/Activity.kt new file mode 100644 index 0000000000..878ac0d481 --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/model/entities/Activity.kt @@ -0,0 +1,25 @@ +package io.realm.examples.objectserver.advanced.model.entities + +import io.realm.RealmList +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey +import java.util.* + +inline class ActivityId(val value: String) { + constructor(): this(UUID.randomUUID().toString()) +} + +/** + * This object describes a specific activity. An activity can happen multiple times during a day, + * each of these are described as a [TimeSlot] + * + * Guests will book a slot for a given [TimeSlot], not on the [Activity] which just contains + * the high-level details. + */ +open class Activity: RealmObject() { + @PrimaryKey + var id: ActivityId = ActivityId() + var name:String = "" + + var timeslots: RealmList = RealmList() +} \ No newline at end of file diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/model/entities/Booking.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/model/entities/Booking.kt new file mode 100644 index 0000000000..a2eb423b14 --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/model/entities/Booking.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2019 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.examples.objectserver.advanced.model.entities + +import io.realm.RealmObject +import io.realm.RealmResults +import io.realm.annotations.Ignore +import io.realm.annotations.LinkingObjects +import io.realm.annotations.PrimaryKey +import java.util.* + +inline class BookingId(val value: String) { + constructor(): this(UUID.randomUUID().toString()) +} + +/** + * Booking object representing both normal and adhoc bookings. + */ +open class Booking: RealmObject() { + + @PrimaryKey + var id: BookingId = BookingId() + + var guest: Guest? = null + var checkedIn: Boolean = false + var adhoc: Boolean = false + var selected: Boolean = false; // UI-state. Storing here for now for simplicity + + // Should always contain one element, the parent TimeSlot + @LinkingObjects("bookings") + private val timeslots: RealmResults? = null + + @Ignore + var offering: TimeSlot = TimeSlot() + get() = if (timeslots != null) timeslots.first()!! else throw IllegalStateException("Only available on managed objects") + private set + + @Ignore + var excursion: Activity = Activity() + get() = if (timeslots != null) timeslots.first()!!.activity else throw IllegalStateException("Only available on managed objects") + private set +} \ No newline at end of file diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/model/entities/Guest.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/model/entities/Guest.kt new file mode 100644 index 0000000000..3edf36f526 --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/model/entities/Guest.kt @@ -0,0 +1,18 @@ +package io.realm.examples.objectserver.advanced.model.entities + +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey +import java.util.* + +inline class GuestId(val value: String) { + constructor(): this(UUID.randomUUID().toString()) +} + +/** + * Object describing a Guest that can book activities. + */ +open class Guest: RealmObject() { + @PrimaryKey + var id: GuestId = GuestId() + var name: String = "" +} \ No newline at end of file diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/model/entities/Order.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/model/entities/Order.kt new file mode 100644 index 0000000000..20fe1a0247 --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/model/entities/Order.kt @@ -0,0 +1,45 @@ +package io.realm.examples.objectserver.advanced.model.entities + +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey +import java.util.* + +inline class OrderId(val value: String) { + constructor(): this(UUID.randomUUID().toString()) +} + +/** + * This object represents a drink order in the bar. + * + */ +open class Order: RealmObject() { + + @PrimaryKey + var id: TimeSlotId = TimeSlotId() + + var name: String = "" + var createdAt: Date = Date() + var from: String = "" + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Order + + if (id != other.id) return false + if (name != other.name) return false + if (createdAt != other.createdAt) return false + if (from != other.from) return false + + return true + } + + override fun hashCode(): Int { + var result = id.hashCode() + result = 31 * result + name.hashCode() + result = 31 * result + createdAt.hashCode() + result = 31 * result + from.hashCode() + return result + } +} \ No newline at end of file diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/model/entities/Timeslot.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/model/entities/Timeslot.kt new file mode 100644 index 0000000000..6c46d3a8af --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/model/entities/Timeslot.kt @@ -0,0 +1,50 @@ +package io.realm.examples.objectserver.advanced.model.entities + +import io.realm.RealmList +import io.realm.RealmObject +import io.realm.RealmResults +import io.realm.annotations.Ignore +import io.realm.annotations.LinkingObjects +import io.realm.annotations.PrimaryKey +import java.util.* + + +inline class TimeSlotId(val value: String) { + constructor(): this(UUID.randomUUID().toString()) +} + +/** + * An offering describes an Activity happening at a specific time. + * [Guest]s will book a time on a specific [TimeSlot] + */ +open class TimeSlot: RealmObject() { + @PrimaryKey + var id: TimeSlotId = TimeSlotId() + + var time: Date = Date() + var totalNumberOfSlots: Int = 0 + var bookings: RealmList = RealmList() + + @LinkingObjects("timeslots") + val activities: RealmResults? = null + + // Ignore fields cannot be queried + // Should always contain one element, the parent Activity. This is only true for managed + // objects. + @Ignore + var activity: Activity = Activity() + get() = if (activities != null) activities.first()!! else throw IllegalStateException("Only available on managed objects") + private set + + fun remaining(): Int { + return bookings.size - bookings.where().equalTo("checkedIn", true).count().toInt() + } + + fun checkedIn(): Int { + return bookings.where().equalTo("checkedIn", true).count().toInt() + } + + fun availableSlots(): Int { + return totalNumberOfSlots - bookings.size + } +} diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/BaseActivity.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/BaseActivity.kt new file mode 100644 index 0000000000..ccf8153690 --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/BaseActivity.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2019 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.examples.objectserver.advanced.ui + +import android.content.Intent +import android.view.Menu +import android.view.MenuItem +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import io.realm.examples.objectserver.advanced.R +import io.realm.examples.objectserver.advanced.ui.login.LoginActivity +import io.realm.SyncUser + +open class BaseActivity: AppCompatActivity() { + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.menu_items, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when(item.itemId) { + R.id.action_logout -> { + for (user in SyncUser.all().values) { + // Normally there is only one user present, but in case of + // development, you might run into multiple users being + // logged in at the same time. So for simplicity, just + // log out all users. + user.logOut() + } + val intent = Intent(this, LoginActivity::class.java) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + startActivity(intent) + return true + } + else -> { + return super.onOptionsItemSelected(item) + } + } + } + + // Credit: http://www.albertgao.xyz/2018/04/13/how-to-add-additional-parameters-to-viewmodel-via-kotlin/ + protected inline fun viewModelFactory(crossinline f: () -> VM) = + object : ViewModelProvider.Factory { + override fun create(aClass: Class):T = f() as T + } +} \ No newline at end of file diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/BaseViewModel.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/BaseViewModel.kt new file mode 100644 index 0000000000..63b87b4bf8 --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/BaseViewModel.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2019 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.examples.objectserver.advanced.ui + +import androidx.lifecycle.ViewModel +import io.realm.examples.objectserver.advanced.model.App +import io.realm.Realm + +/** + * Realm-aware base view model class. + */ +open class BaseViewModel: ViewModel() { + + protected val realm: Realm = Realm.getInstance(App.REALM1_CONFIG) + + override fun onCleared() { + super.onCleared() + realm.close() + } + +} \ No newline at end of file diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/RealmRecyclerViewAdapter.java b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/RealmRecyclerViewAdapter.java new file mode 100644 index 0000000000..b11e0aa635 --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/RealmRecyclerViewAdapter.java @@ -0,0 +1,248 @@ +/* + * Copyright 2019 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package io.realm.examples.objectserver.advanced.ui; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.RecyclerView; + +import io.realm.OrderedCollectionChangeSet; +import io.realm.OrderedRealmCollection; +import io.realm.OrderedRealmCollectionChangeListener; +import io.realm.RealmList; +import io.realm.RealmModel; +import io.realm.RealmResults; + +/** + * The RealmBaseRecyclerAdapter class is an abstract utility class for binding RecyclerView UI elements to Realm data. + *

+ * This adapter will automatically handle any updates to its data and call {@code notifyDataSetChanged()}, + * {@code notifyItemInserted()}, {@code notifyItemRemoved()} or {@code notifyItemRangeChanged()} as appropriate. + *

+ * The RealmAdapter will stop receiving updates if the Realm instance providing the {@link OrderedRealmCollection} is + * closed. + *

+ * If the adapter contains Realm model classes with a primary key that is either an {@code int} or a {@code long}, call + * {@code setHasStableIds(true)} in the constructor and override {@link #getItemId(int)} as described by the Javadoc in that method. + * + * @param type of {@link RealmModel} stored in the adapter. + * @param type of RecyclerView.ViewHolder used in the adapter. + * @see RecyclerView.Adapter#setHasStableIds(boolean) + * @see RecyclerView.Adapter#getItemId(int) + */ +public abstract class RealmRecyclerViewAdapter + extends RecyclerView.Adapter { + + private final boolean hasAutoUpdates; + private final boolean updateOnModification; + private final OrderedRealmCollectionChangeListener listener; + @Nullable + private OrderedRealmCollection adapterData; + + private OrderedRealmCollectionChangeListener createListener() { + return new OrderedRealmCollectionChangeListener() { + @Override + public void onChange(Object collection, OrderedCollectionChangeSet changeSet) { + if (changeSet.getState() == OrderedCollectionChangeSet.State.INITIAL) { + notifyDataSetChanged(); + return; + } + // For deletions, the adapter has to be notified in reverse order. + OrderedCollectionChangeSet.Range[] deletions = changeSet.getDeletionRanges(); + for (int i = deletions.length - 1; i >= 0; i--) { + OrderedCollectionChangeSet.Range range = deletions[i]; + notifyItemRangeRemoved(range.startIndex + dataOffset(), range.length); + } + + OrderedCollectionChangeSet.Range[] insertions = changeSet.getInsertionRanges(); + for (OrderedCollectionChangeSet.Range range : insertions) { + notifyItemRangeInserted(range.startIndex + dataOffset(), range.length); + } + + if (!updateOnModification) { + return; + } + + OrderedCollectionChangeSet.Range[] modifications = changeSet.getChangeRanges(); + for (OrderedCollectionChangeSet.Range range : modifications) { + notifyItemRangeChanged(range.startIndex + dataOffset(), range.length); + } + } + }; + } + + /** + * Returns the number of header elements before the Realm collection elements. This is needed so + * all indexes reported by the {@link OrderedRealmCollectionChangeListener} can be adjusted + * correctly. Failing to override can cause the wrong elements to animate and lead to runtime + * exceptions. + * + * @return The number of header elements in the RecyclerView before the collection elements. Default is {@code 0}. + */ + public int dataOffset() { + return 0; + } + + /** + * This is equivalent to {@code RealmRecyclerViewAdapter(data, autoUpdate, true)}. + * + * @param data collection data to be used by this adapter. + * @param autoUpdate when it is {@code false}, the adapter won't be automatically updated when collection data + * changes. + * @see #RealmRecyclerViewAdapter(OrderedRealmCollection, boolean, boolean) + */ + public RealmRecyclerViewAdapter(@Nullable OrderedRealmCollection data, boolean autoUpdate) { + this(data, autoUpdate, true); + } + + /** + * @param data collection data to be used by this adapter. + * @param autoUpdate when it is {@code false}, the adapter won't be automatically updated when collection data + * changes. + * @param updateOnModification when it is {@code true}, this adapter will be updated when deletions, insertions or + * modifications happen to the collection data. When it is {@code false}, only + * deletions and insertions will trigger the updates. This param will be ignored if + * {@code autoUpdate} is {@code false}. + */ + public RealmRecyclerViewAdapter(@Nullable OrderedRealmCollection data, boolean autoUpdate, + boolean updateOnModification) { + if (data != null && !data.isManaged()) + throw new IllegalStateException("Only use this adapter with managed RealmCollection, " + + "for un-managed lists you can just use the BaseRecyclerViewAdapter"); + this.adapterData = data; + this.hasAutoUpdates = autoUpdate; + this.listener = hasAutoUpdates ? createListener() : null; + this.updateOnModification = updateOnModification; + } + + @Override + public void onAttachedToRecyclerView(final RecyclerView recyclerView) { + super.onAttachedToRecyclerView(recyclerView); + if (hasAutoUpdates && isDataValid()) { + //noinspection ConstantConditions + addListener(adapterData); + } + } + + @Override + public void onDetachedFromRecyclerView(final RecyclerView recyclerView) { + super.onDetachedFromRecyclerView(recyclerView); + if (hasAutoUpdates && isDataValid()) { + //noinspection ConstantConditions + removeListener(adapterData); + } + } + + @Override + public int getItemCount() { + //noinspection ConstantConditions + return isDataValid() ? adapterData.size() : 0; + } + + /** + * Returns the item in the underlying data associated with the specified position. + * + * This method will return {@code null} if the Realm instance has been closed or the index + * is outside the range of valid adapter data (which e.g. can happen if {@link #getItemCount()} + * is modified to account for header or footer views. + * + * Also, this method does not take into account any header views. If these are present, modify + * the {@code index} parameter accordingly first. + * + * @param index index of the item in the original collection backing this adapter. + * @return the item at the specified position or {@code null} if the position does not exists or + * the adapter data are no longer valid. + */ + @SuppressWarnings("WeakerAccess") + @Nullable + public T getItem(int index) { + if (index < 0) { + throw new IllegalArgumentException("Only indexes >= 0 are allowed. Input was: " + index); + } + + // To avoid exception, return null if there are some extra positions that the + // child adapter is adding in getItemCount (e.g: to display footer view in recycler view) + if(adapterData != null && index >= adapterData.size()) return null; + //noinspection ConstantConditions + return isDataValid() ? adapterData.get(index) : null; + } + + /** + * Returns data associated with this adapter. + * + * @return adapter data. + */ + @Nullable + public OrderedRealmCollection getData() { + return adapterData; + } + + /** + * Updates the data associated to the Adapter. Useful when the query has been changed. + * If the query does not change you might consider using the automaticUpdate feature. + * + * @param data the new {@link OrderedRealmCollection} to display. + */ + @SuppressWarnings("WeakerAccess") + public void updateData(@Nullable OrderedRealmCollection data) { + if (hasAutoUpdates) { + if (isDataValid()) { + //noinspection ConstantConditions + removeListener(adapterData); + } + if (data != null) { + addListener(data); + } + } + + this.adapterData = data; + notifyDataSetChanged(); + } + + private void addListener(@NonNull OrderedRealmCollection data) { + if (data instanceof RealmResults) { + RealmResults results = (RealmResults) data; + //noinspection unchecked + results.addChangeListener(listener); + } else if (data instanceof RealmList) { + RealmList list = (RealmList) data; + //noinspection unchecked + list.addChangeListener(listener); + } else { + throw new IllegalArgumentException("RealmCollection not supported: " + data.getClass()); + } + } + + private void removeListener(@NonNull OrderedRealmCollection data) { + if (data instanceof RealmResults) { + RealmResults results = (RealmResults) data; + //noinspection unchecked + results.removeChangeListener(listener); + } else if (data instanceof RealmList) { + RealmList list = (RealmList) data; + //noinspection unchecked + list.removeChangeListener(listener); + } else { + throw new IllegalArgumentException("RealmCollection not supported: " + data.getClass()); + } + } + + private boolean isDataValid() { + return adapterData != null && adapterData.isValid(); + } +} \ No newline at end of file diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/SingleLiveEvent.java b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/SingleLiveEvent.java new file mode 100644 index 0000000000..dec2cd57d6 --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/SingleLiveEvent.java @@ -0,0 +1,76 @@ +/* + * Copyright 2019 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.examples.objectserver.advanced.ui; + +import android.util.Log; + +import androidx.annotation.MainThread; +import androidx.annotation.Nullable; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.Observer; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * A lifecycle-aware observable that sends only new updates after subscription, used for events like + * navigation and Snackbar messages. + *

+ * This avoids a common problem with events: on configuration change (like rotation) an update + * can be emitted if the observer is active. This LiveData only calls the observable if there's an + * explicit call to setValue() or call(). + *

+ * Note that only one observer is going to be notified of changes. + */ +public class SingleLiveEvent extends MutableLiveData { + + private static final String TAG = "SingleLiveEvent"; + + private final AtomicBoolean mPending = new AtomicBoolean(false); + + @MainThread + public void observe(LifecycleOwner owner, final Observer observer) { + + if (hasActiveObservers()) { + Log.w(TAG, "Multiple observers registered but only one will be notified of changes."); + } + + // Observe the internal MutableLiveData + super.observe(owner, new Observer() { + @Override + public void onChanged(@Nullable T t) { + if (mPending.compareAndSet(true, false)) { + observer.onChanged(t); + } + } + }); + } + + @MainThread + public void setValue(@Nullable T t) { + mPending.set(true); + super.setValue(t); + } + + /** + * Used for cases where T is Void, to make calls cleaner. + */ + @MainThread + public void call() { + setValue(null); + } +} \ No newline at end of file diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/activitylist/ActivityRecyclerAdapter.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/activitylist/ActivityRecyclerAdapter.kt new file mode 100644 index 0000000000..647c18e0bb --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/activitylist/ActivityRecyclerAdapter.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2019 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.examples.objectserver.advanced.ui.activitylist + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.realm.examples.objectserver.advanced.databinding.ItemExcursionListBinding +import io.realm.examples.objectserver.advanced.model.entities.Activity +import io.realm.OrderedRealmCollection + + +class ActivityRecyclerAdapter(private val viewModel: SelectActivityViewModel, data: OrderedRealmCollection) + : io.realm.examples.objectserver.advanced.ui.RealmRecyclerViewAdapter(data, true) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val itemBinding = ItemExcursionListBinding.inflate(layoutInflater, parent, false) + return MyViewHolder(itemBinding) + } + + override fun onBindViewHolder(holder: MyViewHolder, position: Int) { + val item = getItem(position) + holder.bind(item!!) + } + + // See https://medium.com/androiddevelopers/android-data-binding-recyclerview-db7c40d9f0e4 + inner class MyViewHolder(private val binding: ItemExcursionListBinding) : RecyclerView.ViewHolder(binding.root) { + fun bind(item: Activity) { + binding.item = item + binding.vm = viewModel + binding.executePendingBindings() + } + } +} diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/activitylist/SelectActivityActivity.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/activitylist/SelectActivityActivity.kt new file mode 100644 index 0000000000..f4aee15684 --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/activitylist/SelectActivityActivity.kt @@ -0,0 +1,99 @@ +/* + * Copyright 2019 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.examples.objectserver.advanced.ui.activitylist + +import android.content.Intent +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import androidx.databinding.DataBindingUtil +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import io.realm.examples.objectserver.advanced.R +import io.realm.examples.objectserver.advanced.databinding.ActivityExcursionListBinding +import io.realm.examples.objectserver.advanced.ui.BaseActivity +import io.realm.examples.objectserver.advanced.ui.checkin.CheckinActivity +import io.realm.examples.objectserver.advanced.ui.orderlist.OrdersActivity + +class SelectActivityActivity : BaseActivity() { + + private lateinit var viewModel: SelectActivityViewModel + private lateinit var binding: ActivityExcursionListBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + viewModel = ViewModelProviders.of(this).get(SelectActivityViewModel::class.java) + binding = DataBindingUtil.setContentView(this, R.layout.activity_excursion_list) + binding.lifecycleOwner = this + binding.viewModel = viewModel + + // Setup UI + setSupportActionBar(findViewById(R.id.toolbar)) + val itemsRecyclerAdapter = ActivityRecyclerAdapter(viewModel, viewModel.excursions()) + val recyclerView = findViewById(R.id.recycler_view) + recyclerView.layoutManager = LinearLayoutManager(this) + recyclerView.adapter = itemsRecyclerAdapter + + // Setup navigation + viewModel.navigateTo.observe(this, Observer { + when(it.first) { + SelectActivityViewModel.NavigationTarget.ExcursionDetails -> { + val intent = Intent(this, CheckinActivity::class.java).apply { + putExtra(CheckinActivity.INTENT_EXTRA_ACTIVIY_ID, it.second.value) + } + startActivity(intent) + } + SelectActivityViewModel.NavigationTarget.Orders -> { + val intent = Intent(this, OrdersActivity::class.java) + startActivity(intent) + } + } + }) + } + + override fun onStart() { + super.onStart() + viewModel.cleanupSubscriptions() + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + super.onCreateOptionsMenu(menu) + menu.apply { + findItem(R.id.action_create_demo_data).isVisible = true + findItem(R.id.action_goto_orders).isVisible = true + } + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when(item.itemId) { + R.id.action_create_demo_data -> { + viewModel.createDemoData() + true + } + R.id.action_goto_orders -> { + viewModel.gotoOrdersSelected() + true + } + else -> { + super.onOptionsItemSelected(item) + } + } + } +} diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/activitylist/SelectActivityViewModel.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/activitylist/SelectActivityViewModel.kt new file mode 100644 index 0000000000..b1f42a4d4a --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/activitylist/SelectActivityViewModel.kt @@ -0,0 +1,158 @@ +/* + * Copyright 2019 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.examples.objectserver.advanced.ui.activitylist + +import androidx.lifecycle.LiveData +import io.realm.examples.objectserver.advanced.model.App +import io.realm.examples.objectserver.advanced.model.entities.* +import io.realm.examples.objectserver.advanced.ui.BaseViewModel +import io.realm.Realm +import io.realm.RealmList +import io.realm.RealmResults +import io.realm.Sort +import io.realm.kotlin.where +import java.util.* + +class SelectActivityViewModel: BaseViewModel() { + + enum class NavigationTarget { + ExcursionDetails, + Orders + } + private val navigationTarget = io.realm.examples.objectserver.advanced.ui.SingleLiveEvent>() + + /** + * Returns the list of all available activities for the day + */ + fun excursions(): RealmResults { + // For now just assume that all activities are available for the day + // If not, the data model might be a bit tricky since Realm Java doesn't support + // sub-queries yet. So finding the Activity objects with timeslots might + // be a bit tricky. See https://github.com/realm/realm-java/issues/1598 and + // https://github.com/realm/realm-java/pull/6116 + return realm.where() + .sort("name", Sort.ASCENDING) + .findAllAsync("activities.today") + } + + /** + * Select a given activity + */ + fun excursionSelected(excursion: Activity) { + navigationTarget.value = Pair(NavigationTarget.ExcursionDetails, excursion.id) + } + + fun gotoOrdersSelected() { + navigationTarget.value = Pair(NavigationTarget.Orders, ActivityId()) + } + + /** + * Navigation event. Used by the Activity to navigationTarget to the selected navigationTarget + */ + val navigateTo : LiveData> + get() = navigationTarget + + + /** + * This method will loop through the users subscriptions and remove those that no longer is + * needed. + */ + fun cleanupSubscriptions() { + realm.executeTransactionAsync { + + // All subscriptions used when searching for guests part of a Booking + // See `BookingsListViewModel.kt#131` + it.getSubscriptions("bookings.*.*.*").deleteAllFromRealm() + + // Right now we keep all subscriptions for Offerings in `CheckinViewModel.kt#37` + // These should only live for a ~day. Until we get time-based subscription support + // in Realm we need to manually remove them. + // For this demo, just keep them, but the date should be encoded in the name + // e.g. `timeslots.dd-mm-yyy` so we can easily identify and remove them here. + // TODO() + } + } + + /** + * Creates a number of randomized demo data + */ + fun createDemoData() { + val random = Random() + realm.executeTransactionAsync { realm -> + // Generate guests + val firstNames = listOf("John", "Jane", "Sean", "Sofia", "Marisa", "Scott", "Peter", "Brian", "Julia") + val lastNames = listOf("Atkinson", "Reynolds", "Gibson", "Glenn", "Holland", "Brooks", "Hope", "McDonalds") + repeat(20) { + val guest = Guest() + guest.name = "${firstNames.random()} ${lastNames.random()}" + realm.insertOrUpdate(guest) + } + val guests = realm.where().findAll() + + // Generate Shore Excursions + val titles = listOf( + "Snorkeling", + "Shark fishing", + "Scuba diving", + "Thrill Waterpark All Day Pass") + for (name in titles) { + val excursion = Activity() + excursion.name = name + excursion.timeslots = RealmList() + + // Generate timeslots + repeat(5) { + val offering = TimeSlot() + offering.totalNumberOfSlots = random.nextInt(20) + 1 // Avoid 0 as number of slots + offering.time = Date() + offering.time.hours = offering.time.hours + it // Ignore overflow into the next day + + // Add bookings to each offering + repeat(random.nextInt(offering.totalNumberOfSlots)) { + val booking = Booking() + booking.checkedIn = random.nextBoolean() + booking.guest = guests.random() + booking.adhoc = (random.nextInt(5) == 0) + offering.bookings.add(booking) + } + excursion.timeslots.add(offering) + } + realm.insertOrUpdate(excursion) + } + + repeat(10) { i -> + val o = Order() + o.name = "Drink $i" + o.from = "Realm1" + o.createdAt = Date(Math.abs(random.nextInt()).toLong()) + realm.insertOrUpdate(o) + } + } + + val realm2 = Realm.getInstance(App.REALM2_CONFIG) + realm2.executeTransactionAsync(Realm.Transaction { realm -> + repeat(10) { i -> + val o = Order() + o.name = "Drink $i" + o.from = "Realm2" + o.createdAt = Date(Math.abs(random.nextInt()).toLong()) + realm.insertOrUpdate(o) + } + }, Realm.Transaction.OnSuccess { realm2.close() }) + } + +} \ No newline at end of file diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/bookingslist/BookingsListActivity.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/bookingslist/BookingsListActivity.kt new file mode 100644 index 0000000000..94f71fa7bd --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/bookingslist/BookingsListActivity.kt @@ -0,0 +1,111 @@ +/* + * Copyright 2019 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.examples.objectserver.advanced.ui.bookingslist + +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import androidx.databinding.DataBindingUtil +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import io.realm.examples.objectserver.advanced.R +import io.realm.examples.objectserver.advanced.databinding.ActivityBookingsListBinding +import io.realm.examples.objectserver.advanced.model.entities.TimeSlotId +import io.realm.examples.objectserver.advanced.ui.BaseActivity +import java.util.* + + +class BookingsListActivity : BaseActivity() { + + companion object { + const val INTENT_EXTRA_OFFERING_ID = "BookingsListActivity.TimeSlotId" + const val INTENT_ACTION_CHECKIN = "BookingsListActivity.Checkin" + const val INTENT_ACTION_CANCEL_CHECKIN = "BookingsListActivity.CancelCheckin" + } + + private lateinit var viewModel: BookingsListViewModel + private lateinit var binding: ActivityBookingsListBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val id = TimeSlotId(intent.getStringExtra(INTENT_EXTRA_OFFERING_ID)) + viewModel = when (intent.action) { + INTENT_ACTION_CHECKIN -> { + ViewModelProviders.of( + this, + viewModelFactory { BookingsListViewModel(id, BookingsListViewModel.Mode.CheckIn) } + ).get(BookingsListViewModel::class.java) + } + INTENT_ACTION_CANCEL_CHECKIN -> { + ViewModelProviders.of( + this, + viewModelFactory { BookingsListViewModel(id, BookingsListViewModel.Mode.CancelCheckIn) } + ).get(BookingsListViewModel::class.java) + } + else -> throw IllegalArgumentException("Unsupported action: ${intent.action}") + } + + binding = DataBindingUtil.setContentView(this, R.layout.activity_bookings_list) + binding.lifecycleOwner = this + binding.viewModel = viewModel + + // Setup UI + setSupportActionBar(binding.toolbar) + viewModel.title().observe(this, Observer { + binding.toolbar.title = it + }) + + binding.textSearch.addTextChangedListener(object: TextWatcher { + private var timer = Timer() + private val DELAY: Long = 200 // milliseconds + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + override fun afterTextChanged(s: Editable) { + timer.cancel() + timer = Timer() + timer.schedule(object: TimerTask() { + override fun run() { + runOnUiThread { + viewModel.setSearchCriteria(binding.textSearch.text.toString()) + } + } + }, DELAY) + } + }) + + val recyclerView = findViewById(R.id.recycler_view) + recyclerView.layoutManager = LinearLayoutManager(this) + viewModel.bookings().observe(this, Observer { + // If the query is updated we need to replace the query result completely instead + // of getting fine-grained animations. This is a bit sub-optimal, but the best we + // can do so far. See https://github.com/realm/realm-java/issues/6216 + val bookingsAdapter = BookingsRecyclerAdapter(viewModel, it) + recyclerView.adapter = bookingsAdapter + }) + + // Setup navigation + viewModel.navigate().observe(this, Observer { + when(it.first) { + // For now, just assume we always return to the Checkin overview screen + BookingsListViewModel.NavigationTarget.CheckinOverview -> finish() + } + }) + } +} diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/bookingslist/BookingsListViewModel.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/bookingslist/BookingsListViewModel.kt new file mode 100644 index 0000000000..54e24058c2 --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/bookingslist/BookingsListViewModel.kt @@ -0,0 +1,166 @@ +/* + * Copyright 2019 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.examples.objectserver.advanced.ui.bookingslist + +import androidx.lifecycle.LiveData +import androidx.lifecycle.LiveDataReactiveStreams +import androidx.lifecycle.MutableLiveData +import io.realm.examples.objectserver.advanced.model.entities.Booking +import io.realm.examples.objectserver.advanced.model.entities.TimeSlot +import io.realm.examples.objectserver.advanced.model.entities.TimeSlotId +import io.realm.examples.objectserver.advanced.ui.BaseViewModel +import io.realm.Case +import io.realm.Realm +import io.realm.RealmResults +import io.realm.Sort +import io.realm.kotlin.where + +class BookingsListViewModel(private val timeslotId: TimeSlotId, private val mode: Mode): BaseViewModel() { + + enum class Mode { + CheckIn, + CancelCheckIn + } + + enum class NavigationTarget { + CheckinOverview, + } + + private val navigateTo = MutableLiveData>() + private val title: MutableLiveData = MutableLiveData() + private val bookings: MutableLiveData> = MutableLiveData() + private var selectedOffering: TimeSlot // Strong reference to keep change listener alive + private val actionText: LiveData + + init { + selectedOffering = realm.where().equalTo("id", timeslotId.value).findFirstAsync() + selectedOffering.addChangeListener { obj -> + title.value = if (obj.isValid) obj.activity.name else "Activity not found" + } + bookings.value = createSearchQuery("") + actionText = LiveDataReactiveStreams.fromPublisher(realm.where() + .equalTo("timeslots.id", timeslotId.value) + .equalTo("selected", true) + .findAllAsync() + .asFlowable() + .map { + when(mode) { + Mode.CheckIn -> "Check-in (${it.count()})" + Mode.CancelCheckIn -> "Cancel check-in (${it.count()})" + } + }) + } + + /** + * Returns the list of all relevant bookings + */ + fun bookings(): LiveData> { + return bookings + } + + /** + * Returns the title of the activity + */ + fun title(): LiveData { + return title + } + + /** + * Observe navigation events from this ViewModel + */ + fun navigate(): LiveData> { + return navigateTo + } + + fun setSearchCriteria(searchString: String) { + bookings.value = createSearchQuery(searchString) + } + + fun toggleSelected(booking: Booking) { + val id = booking.id + realm.executeTransactionAsync { + // We cheat a bit here and save the UI state to the Realm. + // Most likely we don't want to synchronize a temporary state as "selected" + // to the Realm, but it simplifies the logic quite a lot. + // + // Otherwise we will need to combine two streams of data: RealmResults and SelectedBookings + // which is a bit complex for a POC, especially if we also want fine-grained animations. + it.where().equalTo("id", id.value).findFirst()?.let { booking -> + booking.selected = !booking.selected + } + } + } + + /** + * Returns the String used to describe the action performed on selected guests. + */ + fun actionText(): LiveData { + return actionText + } + + /** + * Toggle the checked in state and return to the Check-in overview screen + */ + fun actionSelected() { + val id = timeslotId.value + realm.executeTransactionAsync(Realm.Transaction { + it.where() + .equalTo("timeslots.id", id) + .equalTo("checkedIn", mode != Mode.CheckIn) + .equalTo("selected", true) + .findAll() + .createSnapshot() // Updated to keep objects in query result even after they are updated + .forEach {obj -> + obj.selected = false + obj.checkedIn = (mode == Mode.CheckIn) + } + }, + Realm.Transaction.OnSuccess { + navigateTo.value = Pair(NavigationTarget.CheckinOverview, timeslotId) + }) + } + + // Creates the query + private fun createSearchQuery(nameFilter: String): RealmResults? { + cleanupSubscriptions() + val query = realm.where() + .equalTo("timeslots.id", timeslotId.value) + .equalTo("checkedIn", mode == Mode.CancelCheckIn) + .like("guest.name", "*$nameFilter*", Case.INSENSITIVE) + .sort("guest.name", Sort.ASCENDING) + + // Use a structured approach to naming subscriptions so we can easily find them later + return query.findAllAsync("bookings.${timeslotId.value}.$mode.$nameFilter") // + } + + /** + * This method cleanup all subscriptions related to this screen + * It does so in an optimistic manner, so e.g. crashes or killing the app on this screen + * will cause subscriptions to linger. + * + * Having a large amounts of subscriptions is not a problem for the device, but can have + * negative performance implications for the server. Having some periodic cleanup routine + * is advised to catch stray subscriptions. + */ + private fun cleanupSubscriptions() { + val id = timeslotId.value + realm.executeTransactionAsync { + it.getSubscriptions("bookings.$id.*").deleteAllFromRealm() + } + } + +} \ No newline at end of file diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/bookingslist/BookingsRecyclerAdapter.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/bookingslist/BookingsRecyclerAdapter.kt new file mode 100644 index 0000000000..38a72e28a6 --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/bookingslist/BookingsRecyclerAdapter.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2019 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.examples.objectserver.advanced.ui.bookingslist + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.realm.examples.objectserver.advanced.databinding.ItemBookingsListBinding +import io.realm.examples.objectserver.advanced.model.entities.Booking +import io.realm.examples.objectserver.advanced.ui.RealmRecyclerViewAdapter +import io.realm.OrderedRealmCollection + + +class BookingsRecyclerAdapter(private val viewModel: BookingsListViewModel, data: OrderedRealmCollection) + : io.realm.examples.objectserver.advanced.ui.RealmRecyclerViewAdapter(data, true) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val itemBinding = ItemBookingsListBinding.inflate(layoutInflater, parent, false) + return MyViewHolder(itemBinding) + } + + override fun onBindViewHolder(holder: MyViewHolder, position: Int) { + val item = getItem(position) + holder.bind(item!!) + } + + // See https://medium.com/androiddevelopers/android-data-binding-recyclerview-db7c40d9f0e4 + inner class MyViewHolder(private val binding: io.realm.examples.objectserver.advanced.databinding.ItemBookingsListBinding) : RecyclerView.ViewHolder(binding.root) { + fun bind(item: Booking) { + binding.item = item + binding.vm = viewModel + binding.executePendingBindings() + } + } +} diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/checkin/CheckinActivity.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/checkin/CheckinActivity.kt new file mode 100644 index 0000000000..7de0fc956b --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/checkin/CheckinActivity.kt @@ -0,0 +1,100 @@ +/* + * Copyright 2019 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.examples.objectserver.advanced.ui.checkin + +import android.content.Intent +import android.os.Bundle +import android.view.View +import android.widget.AdapterView +import android.widget.Toast +import androidx.databinding.DataBindingUtil +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders +import io.realm.examples.objectserver.advanced.R +import io.realm.examples.objectserver.advanced.databinding.ActivityCheckinBinding +import io.realm.examples.objectserver.advanced.model.entities.ActivityId +import io.realm.examples.objectserver.advanced.ui.BaseActivity +import io.realm.examples.objectserver.advanced.ui.bookingslist.BookingsListActivity + + +class CheckinActivity : BaseActivity() { + + companion object { + const val INTENT_EXTRA_ACTIVIY_ID = "CheckinActivity.activityId" + } + + private lateinit var viewModel: CheckinViewModel + private lateinit var binding: ActivityCheckinBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val selectedExcursion = ActivityId(intent.getStringExtra(INTENT_EXTRA_ACTIVIY_ID)) + viewModel = ViewModelProviders.of( + this, + viewModelFactory { CheckinViewModel(selectedExcursion) } + ).get(CheckinViewModel::class.java) + binding = DataBindingUtil.setContentView(this, R.layout.activity_checkin) + binding.lifecycleOwner = this + binding.viewModel = viewModel + + // Setup UI + setSupportActionBar(findViewById(R.id.toolbar)) + viewModel.title().observe(this, Observer { + binding.toolbar.title = it + }) + + val timeslotAdapter = TimeslotAdapter(this, viewModel.timeslots()) + binding.timeslots.adapter = timeslotAdapter + binding.timeslots.onItemSelectedListener = object: AdapterView.OnItemSelectedListener { + override fun onNothingSelected(parent: AdapterView<*>?) { + viewModel.selectOffering(null) + } + + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + viewModel.selectOffering(timeslotAdapter.getItem(position)!!) + } + } + + // Setup navigation + viewModel.navigate().observe(this, Observer { target -> + when(target.first) { + CheckinViewModel.NavigationTarget.CheckedInGuests -> { + val intent = Intent(this, BookingsListActivity::class.java).apply { + putExtra(BookingsListActivity.INTENT_EXTRA_OFFERING_ID, target.second.value) + action = BookingsListActivity.INTENT_ACTION_CANCEL_CHECKIN + } + startActivity(intent) + } + CheckinViewModel.NavigationTarget.RemainingGuests -> { + val intent = Intent(this, BookingsListActivity::class.java).apply { + putExtra(BookingsListActivity.INTENT_EXTRA_OFFERING_ID, target.second.value) + action = BookingsListActivity.INTENT_ACTION_CHECKIN + } + startActivity(intent) + } + CheckinViewModel.NavigationTarget.AdhocGuest -> { + Toast.makeText(this, "Not implemented", Toast.LENGTH_SHORT).show() + } + } + }) + + // Select default starting time + if (!timeslotAdapter.isEmpty) { + viewModel.selectOffering(timeslotAdapter.getItem(0)!!) + } + } +} diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/checkin/CheckinViewModel.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/checkin/CheckinViewModel.kt new file mode 100644 index 0000000000..f0e763a398 --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/checkin/CheckinViewModel.kt @@ -0,0 +1,151 @@ +/* + * Copyright 2019 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.examples.objectserver.advanced.ui.checkin + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Transformations +import io.realm.examples.objectserver.advanced.model.entities.* +import io.realm.examples.objectserver.advanced.ui.BaseViewModel +import io.realm.RealmResults +import io.realm.Sort +import io.realm.kotlin.where + +class CheckinViewModel(private val excursionId: ActivityId): BaseViewModel() { + + enum class NavigationTarget { + CheckedInGuests, + RemainingGuests, + AdhocGuest + } + + private val navigationTarget = MutableLiveData>() + private val selectedTimeslot: MutableLiveData = MutableLiveData() + private val title: MutableLiveData = MutableLiveData() + private val selectedExcursion: Activity + + init { + selectedExcursion = realm.where().equalTo("id", excursionId.value).findFirstAsync() + selectedExcursion.addChangeListener { obj -> + title.value = if (obj.isValid) obj.name else "Excursion not found" + } + selectedTimeslot.value = null // Set dummy value to initialize other streams + } + + /** + * Returns the number of timeslots for the day + */ + fun timeslots(): RealmResults { + return realm.where() + .equalTo("activities.id", excursionId.value) + .sort("time", Sort.ASCENDING) + .findAllAsync("timeslots.${excursionId.value}") + } + + /** + * Returns the title of the activity + */ + fun title(): LiveData { + return title + } + + /** + * Returns the number of checked in guests + */ + fun checkedIn(): LiveData { + return Transformations.map(selectedTimeslot) { + it?.checkedIn()?.toString() ?: "-" + } + } + + /** + * Returns the number of guests not checked in yet + */ + fun remaining(): LiveData { + return Transformations.map(selectedTimeslot) { + it?.remaining()?.toString() ?: "-" + } + } + + /** + * Returns a representation of how many slots are still available + */ + fun slotsAvailable(): LiveData { + return Transformations.map(selectedTimeslot) { + if (it != null) { + "${it.availableSlots()}/${it.totalNumberOfSlots}" + } else { + "-/-" + } + } + } + + // Strong reference to prevent changelistener from being GC'ed + private lateinit var allBookings: RealmResults + + fun selectOffering(offering: TimeSlot?) { + selectedTimeslot.value?.removeAllChangeListeners() + + if (offering != null) { + offering.addChangeListener { obj -> + // The offering itself was updated + selectedTimeslot.value = obj + } + + allBookings = offering.bookings.where().findAllAsync() + allBookings.addChangeListener { results -> + // Some of the bookings associated with the offering was updated + // The changelistener for TimeSlot is not triggered if an element in an list is modified + // Only if the list element is added or removed to the list. For that reason we need + // a seperate listener on all the bookings + if (!results.isEmpty()) { + selectedTimeslot.value = results.first()?.offering + } + } + } + + selectedTimeslot.value = offering + } + + /** + * Observe navigation events from this ViewModel + */ + fun navigate(): LiveData> { + return navigationTarget + } + + fun adhocCheckinSelected() { + val offering = selectedTimeslot.value + if (offering != null) { + navigationTarget.value = Pair(NavigationTarget.AdhocGuest, offering.id) + } + } + + fun remainingGuestsSelected() { + val offering = selectedTimeslot.value + if (offering != null) { + navigationTarget.value = Pair(NavigationTarget.RemainingGuests, offering.id) + } + } + + fun checkedInGuestsSelected() { + val offering = selectedTimeslot.value + if (offering != null) { + navigationTarget.value = Pair(NavigationTarget.CheckedInGuests, offering.id) + } + } +} \ No newline at end of file diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/checkin/TimeslotAdapter.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/checkin/TimeslotAdapter.kt new file mode 100644 index 0000000000..c0bb24a334 --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/checkin/TimeslotAdapter.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2019 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.examples.objectserver.advanced.ui.checkin + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import io.realm.examples.objectserver.advanced.R +import io.realm.examples.objectserver.advanced.RealmBaseAdapter +import io.realm.examples.objectserver.advanced.model.entities.TimeSlot +import io.realm.RealmResults + + +class TimeslotAdapter(context: Context, items: RealmResults): RealmBaseAdapter(items) { + + val formatter = android.text.format.DateFormat.getTimeFormat(context) + + override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { + val itemView = LayoutInflater.from(parent?.context).inflate(R.layout.spinner_item, parent, false) + itemView.findViewById(android.R.id.text1).text = formatTime(getItem(position)) + return itemView + } + + override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup?): View { + val itemView = LayoutInflater.from(parent?.context).inflate(R.layout.spinner_item_drop_down, parent, false) + itemView.findViewById(android.R.id.text1).text = formatTime(getItem(position)) + return itemView + } + + private fun formatTime(item: TimeSlot?): String { + if (item == null) { + return "-" + } else { + return formatter.format(item.time) + } + } +} \ No newline at end of file diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/login/LoginActivity.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/login/LoginActivity.kt new file mode 100644 index 0000000000..37669ba81e --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/login/LoginActivity.kt @@ -0,0 +1,119 @@ +/* + * Copyright 2019 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.examples.objectserver.advanced.ui.login + +import android.app.ProgressDialog +import android.content.Intent +import android.os.Bundle +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.AppCompatButton +import androidx.appcompat.widget.AppCompatEditText +import androidx.databinding.DataBindingUtil +import io.realm.examples.objectserver.advanced.Constants +import io.realm.examples.objectserver.advanced.R +import io.realm.examples.objectserver.advanced.databinding.ActivityLoginBinding +import io.realm.examples.objectserver.advanced.model.App +import io.realm.examples.objectserver.advanced.ui.activitylist.SelectActivityActivity +import io.realm.ErrorCode +import io.realm.ObjectServerError +import io.realm.SyncCredentials +import io.realm.SyncUser + +class LoginActivity: AppCompatActivity() { + + private lateinit var username: AppCompatEditText + private lateinit var password: AppCompatEditText + private lateinit var loginButton: AppCompatButton + private lateinit var createUserButton: AppCompatButton + + lateinit private var binding: ActivityLoginBinding + + public override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = DataBindingUtil.setContentView(this, R.layout.activity_login) + username = binding.inputUsername + password = binding.inputPassword + loginButton = binding.buttonLogin + createUserButton = binding.buttonCreate + + loginButton.setOnClickListener { login(false) } + createUserButton.setOnClickListener { login(true) } + } + + private fun login(createUser: Boolean) { + if (!validate()) { + onLoginFailed("Invalid username or password") + return + } + + binding.buttonCreate.isEnabled = false + binding.buttonLogin.isEnabled = false + + val progressDialog = ProgressDialog(this@LoginActivity) + progressDialog.isIndeterminate = true + progressDialog.setMessage("Authenticating...") + progressDialog.show() + + val username = this.username.text.toString() + val password = this.password.text.toString() + + val creds = SyncCredentials.usernamePassword(username, password, createUser) + val callback = object : SyncUser.Callback { + override fun onSuccess(user: SyncUser) { + progressDialog.dismiss() + onLoginSuccess(user) + } + + override fun onError(error: ObjectServerError) { + progressDialog.dismiss() + val errorMsg: String = when (error.errorCode) { + ErrorCode.UNKNOWN_ACCOUNT -> getString(R.string.login_error_unknown_account) + ErrorCode.INVALID_CREDENTIALS -> getString(R.string.login_error_invalid_credentials) + else -> error.toString() + } + onLoginFailed(errorMsg) + } + } + + SyncUser.logInAsync(creds, Constants.AUTH_URL, callback) + } + + override fun onBackPressed() { + moveTaskToBack(true) + } + + private fun onLoginSuccess(user: SyncUser) { + loginButton.isEnabled = true + createUserButton.isEnabled = true + App.configureRealms(user) + val intent = Intent(this, SelectActivityActivity::class.java) + startActivity(intent) + } + + private fun onLoginFailed(errorMsg: String) { + loginButton.isEnabled = true + createUserButton.isEnabled = true + Toast.makeText(baseContext, errorMsg, Toast.LENGTH_LONG).show() + } + + private fun validate(): Boolean = when { + username.text.toString().isEmpty() -> false + password.text.toString().isEmpty() -> false + else -> true + } +} diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/orderlist/OrdersActivity.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/orderlist/OrdersActivity.kt new file mode 100644 index 0000000000..ebd63628ad --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/orderlist/OrdersActivity.kt @@ -0,0 +1,79 @@ +/* + * Copyright 2019 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.examples.objectserver.advanced.ui.orderlist + +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import androidx.databinding.DataBindingUtil +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import io.realm.examples.objectserver.advanced.ui.BaseActivity +import io.realm.examples.objectserver.advanced.R + + +class OrdersActivity : BaseActivity() { + + private lateinit var viewModel: OrdersViewModel + private lateinit var binding: io.realm.examples.objectserver.advanced.databinding.ActivityOrderListBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + viewModel = ViewModelProviders.of(this).get(OrdersViewModel::class.java) + binding = DataBindingUtil.setContentView(this, R.layout.activity_order_list) + binding.lifecycleOwner = this + binding.viewModel = viewModel + + // Setup UI + setSupportActionBar(findViewById(R.id.toolbar)) + val adapter = OrdersRecyclerAdapter(viewModel) + val recyclerView = findViewById(R.id.recycler_view) + recyclerView.layoutManager = LinearLayoutManager(this) + recyclerView.adapter = adapter + viewModel.orders().observe(this, Observer { + adapter.setData(it.first) + val diffResults = it.second + if (diffResults != null) { + diffResults.dispatchUpdatesTo(adapter) + } else { + adapter.notifyDataSetChanged() + } + }) + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + super.onCreateOptionsMenu(menu) + menu.apply { + findItem(R.id.action_create_order).isVisible = true + } + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when(item.itemId) { + R.id.action_create_order -> { + viewModel.createOrder() + true + } + else -> { + super.onOptionsItemSelected(item) + } + } + } +} diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/orderlist/OrdersRecyclerAdapter.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/orderlist/OrdersRecyclerAdapter.kt new file mode 100644 index 0000000000..e1bd4eb1c7 --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/orderlist/OrdersRecyclerAdapter.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2019 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.examples.objectserver.advanced.ui.orderlist + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.realm.examples.objectserver.advanced.databinding.ItemOrderListBinding +import io.realm.examples.objectserver.advanced.model.entities.Order + + +class OrdersRecyclerAdapter(private val viewModel: OrdersViewModel): RecyclerView.Adapter() { + + private var data: List = listOf() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val itemBinding = ItemOrderListBinding.inflate(layoutInflater, parent, false) + return MyViewHolder(itemBinding) + } + + override fun onBindViewHolder(holder: MyViewHolder, position: Int) { + val item = data[position] + holder.bind(item) + } + + override fun getItemCount(): Int { + return data.size + } + + fun setData(data: List) { + this.data = data + } + + // See https://medium.com/androiddevelopers/android-data-binding-recyclerview-db7c40d9f0e4 + inner class MyViewHolder(private val binding: ItemOrderListBinding) : RecyclerView.ViewHolder(binding.root) { + fun bind(item: Order) { + binding.item = item + binding.vm = viewModel + binding.executePendingBindings() + } + } +} diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/orderlist/OrdersViewModel.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/orderlist/OrdersViewModel.kt new file mode 100644 index 0000000000..952eee6afe --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/orderlist/OrdersViewModel.kt @@ -0,0 +1,150 @@ +/* + * Copyright 2019 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.examples.objectserver.advanced.ui.orderlist + +import android.os.HandlerThread +import androidx.lifecycle.LiveData +import androidx.lifecycle.LiveDataReactiveStreams +import androidx.recyclerview.widget.DiffUtil +import io.realm.examples.objectserver.advanced.model.App +import io.realm.examples.objectserver.advanced.model.entities.Order +import io.realm.examples.objectserver.advanced.ui.BaseViewModel +import io.realm.examples.objectserver.advanced.ui.shared.createObservableForRealm +import io.reactivex.BackpressureStrategy +import io.reactivex.Flowable +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.functions.BiFunction +import io.reactivex.schedulers.Schedulers +import io.realm.OrderedCollectionChangeSet +import io.realm.kotlin.where +import java.util.* + +class OrdersViewModel: BaseViewModel() { + + private val worker1 = HandlerThread("HandlerWorker1") + private val worker2 = HandlerThread("HandlerWorker2") + + init { + worker1.start() + worker2.start() + } + + fun createOrder() { + realm.executeTransactionAsync { + val o = Order() + o.name = "Custom drink" + o.from = "Realm1" + o.createdAt = Date(Random().nextInt(1000).toLong()) + it.insertOrUpdate(o) + } + } + + fun orders(): LiveData, DiffUtil.DiffResult?>> { + // Create Streams from each Realm. + // + // Note especially the special `createObservableForRealm` which is required to manage + // the lifecycle of the Realm instance within the stream + // Also, be careful how results from this Realm are used. In our case we keep all + // managed results on the same thread until they have been copied out, after which they + // can be observed on other threads without issues. + // + // We ignore the INITIAL state as that is mostly used for checking the Subscription state + // which isn't required here. So we just wait for any UPDATE state which will follow + // immediately after as the Subscription is either created or updated. This way we also + // skip doing a lof of extra work in the streams not needed. + val worker1Looper = AndroidSchedulers.from(worker1.looper) + val realm1Orders = Flowable.just(App.REALM1_CONFIG) + .observeOn(worker1Looper) + .flatMap { createObservableForRealm(it) } + .flatMap { it.where().findAllAsync().asChangesetObservable().toFlowable(BackpressureStrategy.ERROR) } + .filter { it.changeset?.state != OrderedCollectionChangeSet.State.INITIAL } + .filter { it.collection.isLoaded } + .map { it.collection.realm.copyFromRealm(it.collection) } + .unsubscribeOn(worker1Looper) + + val worker2Looper = AndroidSchedulers.from(worker2.looper) + val realm2Orders = Flowable.just(App.REALM2_CONFIG) + .observeOn(worker2Looper) + .flatMap { createObservableForRealm(it) } + .flatMap { it.where().findAllAsync().asChangesetObservable().toFlowable(BackpressureStrategy.ERROR) } + .filter { it.changeset?.state != OrderedCollectionChangeSet.State.INITIAL } + .filter { it.collection.isLoaded } + .map { it.collection.realm.copyFromRealm(it.collection) } + .unsubscribeOn(worker2Looper) + + // Merge the two Realm streams and calculate the combined fine-grained animations using + // DiffUtil. + // Inspired by https://hellsoft.se/a-nice-combination-of-rxjava-and-diffutil-fe3807186012 + val initialPair: Pair, DiffUtil.DiffResult?> = Pair(listOf(), null) + val mergedEntries = Flowable.combineLatest(realm1Orders, realm2Orders, BiFunction, List, Pair, List>> { orders1, orders2 -> + // Do minimal amount of of work needed to package the lists up so they can be + // used on a background thread. + Pair(orders1, orders2); + }) + .observeOn(Schedulers.computation()) + .map { + // Merge the two lists and sort them according to timestamp in a natural order + ArrayList() + .apply { + addAll(it.first) + addAll(it.second) + } + .sortedWith(Comparator { first, second -> + first.createdAt.compareTo(second.createdAt) + }) + } + .scan(initialPair) { pair, next -> + // Calculate the DiffUtil results + val callback = MyDiffCallback(pair.first, next) + val result: DiffUtil.DiffResult = DiffUtil.calculateDiff(callback); + Pair(next, result); + } + .skip(1) + .subscribeOn(AndroidSchedulers.mainThread()) + + return LiveDataReactiveStreams.fromPublisher(mergedEntries) + } + + override fun onCleared() { + super.onCleared() + worker1.quit() + worker2.quit() + } + + private class MyDiffCallback(private val current: List, private val next: List) : DiffUtil.Callback() { + + override fun getOldListSize(): Int { + return current.size + } + + override fun getNewListSize(): Int { + return next.size + } + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + val currentItem = current[oldItemPosition] + val nextItem = next[newItemPosition] + return currentItem.id == nextItem.id + } + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + val currentItem = current[oldItemPosition] + val nextItem = next[newItemPosition] + return currentItem == nextItem + } + } +} \ No newline at end of file diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/shared/ObservableExt.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/shared/ObservableExt.kt new file mode 100644 index 0000000000..58e50a6557 --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/shared/ObservableExt.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2019 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.examples.objectserver.advanced.ui.shared + +import io.reactivex.BackpressureStrategy +import io.reactivex.Flowable +import io.reactivex.disposables.Disposables +import io.realm.Realm +import io.realm.RealmConfiguration + +// Creates an Observable that wraps the Realm lifecycle +// The Realm will be opened when subscribed to and closed when the stream is disposed. +// +// It isn't possible to add static extension functions to Java classes: https://youtrack.jetbrains.com/issue/KT-11968 +// So this is a free function for now. +fun createObservableForRealm(config: RealmConfiguration): Flowable { + return Flowable.create({ emitter -> + val realm = Realm.getInstance(config) + emitter.setDisposable(Disposables.fromRunnable { + realm.close() + }) + emitter.onNext(realm) + }, BackpressureStrategy.LATEST) +} + diff --git a/examples/objectServerActivityTrackerExample/src/main/res/drawable-v24/ic_launcher_foreground.xml b/examples/objectServerActivityTrackerExample/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000000..c7bd21dbd8 --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/examples/objectServerActivityTrackerExample/src/main/res/drawable/ic_launcher_background.xml b/examples/objectServerActivityTrackerExample/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000000..d5fccc538c --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/objectServerExample/src/main/res/drawable/logo.png b/examples/objectServerActivityTrackerExample/src/main/res/drawable/logo.png similarity index 100% rename from examples/objectServerExample/src/main/res/drawable/logo.png rename to examples/objectServerActivityTrackerExample/src/main/res/drawable/logo.png diff --git a/examples/objectServerActivityTrackerExample/src/main/res/layout/activity_bookings_list.xml b/examples/objectServerActivityTrackerExample/src/main/res/layout/activity_bookings_list.xml new file mode 100644 index 0000000000..5ba8966454 --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/res/layout/activity_bookings_list.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/objectServerActivityTrackerExample/src/main/res/layout/activity_checkin.xml b/examples/objectServerActivityTrackerExample/src/main/res/layout/activity_checkin.xml new file mode 100644 index 0000000000..09c3e47a12 --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/res/layout/activity_checkin.xml @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/objectServerActivityTrackerExample/src/main/res/layout/activity_excursion_list.xml b/examples/objectServerActivityTrackerExample/src/main/res/layout/activity_excursion_list.xml new file mode 100644 index 0000000000..92447272b6 --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/res/layout/activity_excursion_list.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/objectServerActivityTrackerExample/src/main/res/layout/activity_login.xml b/examples/objectServerActivityTrackerExample/src/main/res/layout/activity_login.xml new file mode 100644 index 0000000000..cde250b7de --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/res/layout/activity_login.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/objectServerActivityTrackerExample/src/main/res/layout/activity_order_list.xml b/examples/objectServerActivityTrackerExample/src/main/res/layout/activity_order_list.xml new file mode 100644 index 0000000000..5d5388ce78 --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/res/layout/activity_order_list.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/objectServerActivityTrackerExample/src/main/res/layout/item_bookings_list.xml b/examples/objectServerActivityTrackerExample/src/main/res/layout/item_bookings_list.xml new file mode 100644 index 0000000000..0c9dada7ee --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/res/layout/item_bookings_list.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + diff --git a/examples/objectServerActivityTrackerExample/src/main/res/layout/item_excursion_list.xml b/examples/objectServerActivityTrackerExample/src/main/res/layout/item_excursion_list.xml new file mode 100644 index 0000000000..c3e5b901ab --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/res/layout/item_excursion_list.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + diff --git a/examples/objectServerActivityTrackerExample/src/main/res/layout/item_order_list.xml b/examples/objectServerActivityTrackerExample/src/main/res/layout/item_order_list.xml new file mode 100644 index 0000000000..3a63572bdb --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/res/layout/item_order_list.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/objectServerActivityTrackerExample/src/main/res/layout/list.xml b/examples/objectServerActivityTrackerExample/src/main/res/layout/list.xml new file mode 100644 index 0000000000..784942510e --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/res/layout/list.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/examples/objectServerActivityTrackerExample/src/main/res/layout/spinner_item.xml b/examples/objectServerActivityTrackerExample/src/main/res/layout/spinner_item.xml new file mode 100644 index 0000000000..5bf0e1bdfb --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/res/layout/spinner_item.xml @@ -0,0 +1,13 @@ + + diff --git a/examples/objectServerActivityTrackerExample/src/main/res/layout/spinner_item_drop_down.xml b/examples/objectServerActivityTrackerExample/src/main/res/layout/spinner_item_drop_down.xml new file mode 100644 index 0000000000..83cc33ea7f --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/res/layout/spinner_item_drop_down.xml @@ -0,0 +1,15 @@ + + diff --git a/examples/objectServerActivityTrackerExample/src/main/res/menu/menu_items.xml b/examples/objectServerActivityTrackerExample/src/main/res/menu/menu_items.xml new file mode 100644 index 0000000000..cab3ec9ce8 --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/res/menu/menu_items.xml @@ -0,0 +1,31 @@ +

+ + + + + + + + diff --git a/examples/objectServerExample/src/main/res/mipmap-hdpi/ic_launcher.png b/examples/objectServerActivityTrackerExample/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from examples/objectServerExample/src/main/res/mipmap-hdpi/ic_launcher.png rename to examples/objectServerActivityTrackerExample/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/examples/objectServerExample/src/main/res/mipmap-mdpi/ic_launcher.png b/examples/objectServerActivityTrackerExample/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from examples/objectServerExample/src/main/res/mipmap-mdpi/ic_launcher.png rename to examples/objectServerActivityTrackerExample/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/examples/objectServerExample/src/main/res/mipmap-xhdpi/ic_launcher.png b/examples/objectServerActivityTrackerExample/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from examples/objectServerExample/src/main/res/mipmap-xhdpi/ic_launcher.png rename to examples/objectServerActivityTrackerExample/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/examples/objectServerExample/src/main/res/mipmap-xxhdpi/ic_launcher.png b/examples/objectServerActivityTrackerExample/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from examples/objectServerExample/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to examples/objectServerActivityTrackerExample/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/examples/objectServerActivityTrackerExample/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/examples/objectServerActivityTrackerExample/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100755 index 0000000000000000000000000000000000000000..91826a7567c331b45474d439ca1b15fdd6f86feb GIT binary patch literal 16078 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 + + #3F51B5 + #303F9F + #7986CB + #E8EAF6 + #FF4081 + #FFF + #FFFFFF + #313131 + #A0A0A0 + + diff --git a/examples/objectServerActivityTrackerExample/src/main/res/values/dimens.xml b/examples/objectServerActivityTrackerExample/src/main/res/values/dimens.xml new file mode 100644 index 0000000000..617af6a243 --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/res/values/dimens.xml @@ -0,0 +1,6 @@ + + 16dp + + 16dp + 16dp + diff --git a/examples/objectServerActivityTrackerExample/src/main/res/values/strings.xml b/examples/objectServerActivityTrackerExample/src/main/res/values/strings.xml new file mode 100644 index 0000000000..9de72bb17f --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/res/values/strings.xml @@ -0,0 +1,27 @@ + + Activity Checkin Demo + Logout + Checkin + Activities + + + Username + Password + Account does not exist. + User name and password do not match. + Sign in or register + Sign in + Create demo data + + + Project description + Remaining + Checked-in + Spots Available + Create account and login + Login + Show Orders + Orders + Create Order + + diff --git a/examples/objectServerActivityTrackerExample/src/main/res/values/styles.xml b/examples/objectServerActivityTrackerExample/src/main/res/values/styles.xml new file mode 100644 index 0000000000..4616b4367b --- /dev/null +++ b/examples/objectServerActivityTrackerExample/src/main/res/values/styles.xml @@ -0,0 +1,30 @@ + + + + + + + + + + diff --git a/examples/objectServerExample/README.md b/examples/objectServerSimpleExample/README.md similarity index 100% rename from examples/objectServerExample/README.md rename to examples/objectServerSimpleExample/README.md diff --git a/examples/objectServerExample/build.gradle b/examples/objectServerSimpleExample/build.gradle similarity index 100% rename from examples/objectServerExample/build.gradle rename to examples/objectServerSimpleExample/build.gradle diff --git a/examples/objectServerSimpleExample/lint.xml b/examples/objectServerSimpleExample/lint.xml new file mode 100644 index 0000000000..6a9810cdcb --- /dev/null +++ b/examples/objectServerSimpleExample/lint.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/examples/objectServerExample/src/main/AndroidManifest.xml b/examples/objectServerSimpleExample/src/main/AndroidManifest.xml similarity index 100% rename from examples/objectServerExample/src/main/AndroidManifest.xml rename to examples/objectServerSimpleExample/src/main/AndroidManifest.xml diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.kt b/examples/objectServerSimpleExample/src/main/java/io/realm/examples/objectserver/CounterActivity.kt similarity index 100% rename from examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.kt rename to examples/objectServerSimpleExample/src/main/java/io/realm/examples/objectserver/CounterActivity.kt diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.kt b/examples/objectServerSimpleExample/src/main/java/io/realm/examples/objectserver/LoginActivity.kt similarity index 100% rename from examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.kt rename to examples/objectServerSimpleExample/src/main/java/io/realm/examples/objectserver/LoginActivity.kt diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.kt b/examples/objectServerSimpleExample/src/main/java/io/realm/examples/objectserver/MyApplication.kt similarity index 100% rename from examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.kt rename to examples/objectServerSimpleExample/src/main/java/io/realm/examples/objectserver/MyApplication.kt diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/model/CRDTCounter.kt b/examples/objectServerSimpleExample/src/main/java/io/realm/examples/objectserver/model/CRDTCounter.kt similarity index 100% rename from examples/objectServerExample/src/main/java/io/realm/examples/objectserver/model/CRDTCounter.kt rename to examples/objectServerSimpleExample/src/main/java/io/realm/examples/objectserver/model/CRDTCounter.kt diff --git a/examples/objectServerExample/src/main/res/drawable-xxhdpi/ic_exit_to_app_white_24dp.png b/examples/objectServerSimpleExample/src/main/res/drawable-xxhdpi/ic_exit_to_app_white_24dp.png similarity index 100% rename from examples/objectServerExample/src/main/res/drawable-xxhdpi/ic_exit_to_app_white_24dp.png rename to examples/objectServerSimpleExample/src/main/res/drawable-xxhdpi/ic_exit_to_app_white_24dp.png diff --git a/examples/objectServerExample/src/main/res/drawable-xxxhdpi/ic_exit_to_app_white_24dp.png b/examples/objectServerSimpleExample/src/main/res/drawable-xxxhdpi/ic_exit_to_app_white_24dp.png similarity index 100% rename from examples/objectServerExample/src/main/res/drawable-xxxhdpi/ic_exit_to_app_white_24dp.png rename to examples/objectServerSimpleExample/src/main/res/drawable-xxxhdpi/ic_exit_to_app_white_24dp.png diff --git a/examples/objectServerExample/src/main/res/drawable/button_counter.xml b/examples/objectServerSimpleExample/src/main/res/drawable/button_counter.xml similarity index 100% rename from examples/objectServerExample/src/main/res/drawable/button_counter.xml rename to examples/objectServerSimpleExample/src/main/res/drawable/button_counter.xml diff --git a/examples/objectServerSimpleExample/src/main/res/drawable/logo.png b/examples/objectServerSimpleExample/src/main/res/drawable/logo.png new file mode 100755 index 0000000000000000000000000000000000000000..91826a7567c331b45474d439ca1b15fdd6f86feb GIT binary patch literal 16078 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 O0000 literal 0 HcmV?d00001 diff --git a/examples/objectServerSimpleExample/src/main/res/mipmap-xxhdpi/ic_launcher.png b/examples/objectServerSimpleExample/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100755 index 0000000000000000000000000000000000000000..eb9ece04b26b69f1d98f9294716e8c982a4577b9 GIT binary patch literal 11165 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 vL;xs~^hI0s(5?>@cEQ$g3}{%|s>uHXFw5o4 Date: Thu, 28 Feb 2019 21:52:51 +0100 Subject: [PATCH 5/5] Fix package names + AndroidX support --- .../README.md | 6 ++--- .../build.gradle | 2 +- .../gradle.properties | 2 ++ .../src/main/AndroidManifest.xml | 4 ++-- .../objectserver/activitytracker/Constants.kt | 2 +- .../activitytracker/MyApplication.kt | 4 ++-- .../activitytracker/RealmBaseAdapter.java | 2 +- .../objectserver/activitytracker/model/App.kt | 10 ++++---- .../model/entities/Activity.kt | 2 +- .../activitytracker/model/entities/Booking.kt | 2 +- .../activitytracker/model/entities/Guest.kt | 2 +- .../activitytracker/model/entities/Order.kt | 2 +- .../model/entities/Timeslot.kt | 2 +- .../activitytracker/ui/BaseActivity.kt | 6 ++--- .../activitytracker/ui/BaseViewModel.kt | 4 ++-- .../ui/RealmRecyclerViewAdapter.java | 2 +- .../activitytracker/ui/SingleLiveEvent.java | 2 +- .../activitylist/ActivityRecyclerAdapter.kt | 8 +++---- .../ui/activitylist/SelectActivityActivity.kt | 12 +++++----- .../activitylist/SelectActivityViewModel.kt | 10 ++++---- .../ui/bookingslist/BookingsListActivity.kt | 10 ++++---- .../ui/bookingslist/BookingsListViewModel.kt | 10 ++++---- .../bookingslist/BookingsRecyclerAdapter.kt | 12 +++++----- .../ui/checkin/CheckinActivity.kt | 12 +++++----- .../ui/checkin/CheckinViewModel.kt | 6 ++--- .../ui/checkin/TimeslotAdapter.kt | 8 +++---- .../activitytracker/ui/login/LoginActivity.kt | 12 +++++----- .../ui/orderlist/OrdersActivity.kt | 8 +++---- .../ui/orderlist/OrdersRecyclerAdapter.kt | 6 ++--- .../ui/orderlist/OrdersViewModel.kt | 10 ++++---- .../ui/shared/ObservableExt.kt | 2 +- .../res/layout/activity_bookings_list.xml | 4 ++-- .../src/main/res/layout/activity_checkin.xml | 23 +++++++++++-------- .../res/layout/activity_excursion_list.xml | 4 ++-- .../main/res/layout/activity_order_list.xml | 4 ++-- .../main/res/layout/item_bookings_list.xml | 4 ++-- .../main/res/layout/item_excursion_list.xml | 4 ++-- .../src/main/res/layout/item_order_list.xml | 4 ++-- .../src/main/res/values/strings.xml | 2 +- 39 files changed, 118 insertions(+), 113 deletions(-) create mode 100644 examples/objectServerActivityTrackerExample/gradle.properties diff --git a/examples/objectServerActivityTrackerExample/README.md b/examples/objectServerActivityTrackerExample/README.md index d059179f23..1ebf12f2f5 100644 --- a/examples/objectServerActivityTrackerExample/README.md +++ b/examples/objectServerActivityTrackerExample/README.md @@ -10,7 +10,7 @@ It makes it possible for the person in charge of the activity to handle checkins ## Requirements -Two Query-based Realms must exist on the server. These must be called: +Two Query-based reference Realms must exist on the server. These must be called: * `/demo1` * `/demo2` @@ -19,7 +19,7 @@ They can be created using an Admin user through Realm Studio. ## Build project -1) Edit `io.realm.examples.objectserver.advanced.Constants` and set the proper URL to the Realm Sync server +1) Edit `io.realm.examples.objectserver.activitytracker.Constants` and set the proper URL to the Realm Sync server 2) Compile and install the app `./gradlew clean installDebug` ## Technical Details @@ -33,7 +33,7 @@ The app is built using the following frameworks/libraries: * Realm Java For the UI parts the project uses package-by-feature instead of package-by-layer, so e.g. everything -related to the Excursion selection screen should be in the `io.realm.examples.objectserver.advanced.ui.excursionlist` +related to the Excursion selection screen should be in the `io.realm.examples.objectserver.activitytracker.ui.excursionlist` package. The project uses an MVVM architecture. All logic related to controlling the UI should be in the diff --git a/examples/objectServerActivityTrackerExample/build.gradle b/examples/objectServerActivityTrackerExample/build.gradle index f2ca3ac585..12c60123b7 100644 --- a/examples/objectServerActivityTrackerExample/build.gradle +++ b/examples/objectServerActivityTrackerExample/build.gradle @@ -21,7 +21,7 @@ android { buildToolsVersion rootProject.buildTools defaultConfig { - applicationId 'io.realm.examples.objectserver.advanced' + applicationId 'io.realm.examples.objectserver.activitytracker' targetSdkVersion rootProject.sdkVersion minSdkVersion rootProject.minSdkVersion versionCode 1 diff --git a/examples/objectServerActivityTrackerExample/gradle.properties b/examples/objectServerActivityTrackerExample/gradle.properties new file mode 100644 index 0000000000..dbb7bf70d1 --- /dev/null +++ b/examples/objectServerActivityTrackerExample/gradle.properties @@ -0,0 +1,2 @@ +android.enableJetifier=true +android.useAndroidX=true diff --git a/examples/objectServerActivityTrackerExample/src/main/AndroidManifest.xml b/examples/objectServerActivityTrackerExample/src/main/AndroidManifest.xml index e0556cc96f..cfb004a0fc 100644 --- a/examples/objectServerActivityTrackerExample/src/main/AndroidManifest.xml +++ b/examples/objectServerActivityTrackerExample/src/main/AndroidManifest.xml @@ -1,11 +1,11 @@ + package="io.realm.examples.objectserver.activitytracker"> ) - : io.realm.examples.objectserver.advanced.ui.RealmRecyclerViewAdapter(data, true) { + : io.realm.examples.objectserver.activitytracker.ui.RealmRecyclerViewAdapter(data, true) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { val layoutInflater = LayoutInflater.from(parent.context) diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/activitylist/SelectActivityActivity.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/activitylist/SelectActivityActivity.kt index f4aee15684..b0208b44ae 100644 --- a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/activitylist/SelectActivityActivity.kt +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/activitylist/SelectActivityActivity.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.examples.objectserver.advanced.ui.activitylist +package io.realm.examples.objectserver.activitytracker.ui.activitylist import android.content.Intent import android.os.Bundle @@ -25,11 +25,11 @@ import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import io.realm.examples.objectserver.advanced.R -import io.realm.examples.objectserver.advanced.databinding.ActivityExcursionListBinding -import io.realm.examples.objectserver.advanced.ui.BaseActivity -import io.realm.examples.objectserver.advanced.ui.checkin.CheckinActivity -import io.realm.examples.objectserver.advanced.ui.orderlist.OrdersActivity +import io.realm.examples.objectserver.activitytracker.R +import io.realm.examples.objectserver.activitytracker.databinding.ActivityExcursionListBinding +import io.realm.examples.objectserver.activitytracker.ui.BaseActivity +import io.realm.examples.objectserver.activitytracker.ui.checkin.CheckinActivity +import io.realm.examples.objectserver.activitytracker.ui.orderlist.OrdersActivity class SelectActivityActivity : BaseActivity() { diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/activitylist/SelectActivityViewModel.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/activitylist/SelectActivityViewModel.kt index b1f42a4d4a..5e3eca9bfc 100644 --- a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/activitylist/SelectActivityViewModel.kt +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/activitylist/SelectActivityViewModel.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package io.realm.examples.objectserver.advanced.ui.activitylist +package io.realm.examples.objectserver.activitytracker.ui.activitylist import androidx.lifecycle.LiveData -import io.realm.examples.objectserver.advanced.model.App -import io.realm.examples.objectserver.advanced.model.entities.* -import io.realm.examples.objectserver.advanced.ui.BaseViewModel +import io.realm.examples.objectserver.activitytracker.model.App +import io.realm.examples.objectserver.activitytracker.model.entities.* +import io.realm.examples.objectserver.activitytracker.ui.BaseViewModel import io.realm.Realm import io.realm.RealmList import io.realm.RealmResults @@ -33,7 +33,7 @@ class SelectActivityViewModel: BaseViewModel() { ExcursionDetails, Orders } - private val navigationTarget = io.realm.examples.objectserver.advanced.ui.SingleLiveEvent>() + private val navigationTarget = io.realm.examples.objectserver.activitytracker.ui.SingleLiveEvent>() /** * Returns the list of all available activities for the day diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/bookingslist/BookingsListActivity.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/bookingslist/BookingsListActivity.kt index 94f71fa7bd..82186deb69 100644 --- a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/bookingslist/BookingsListActivity.kt +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/bookingslist/BookingsListActivity.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.examples.objectserver.advanced.ui.bookingslist +package io.realm.examples.objectserver.activitytracker.ui.bookingslist import android.os.Bundle import android.text.Editable @@ -24,10 +24,10 @@ import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import io.realm.examples.objectserver.advanced.R -import io.realm.examples.objectserver.advanced.databinding.ActivityBookingsListBinding -import io.realm.examples.objectserver.advanced.model.entities.TimeSlotId -import io.realm.examples.objectserver.advanced.ui.BaseActivity +import io.realm.examples.objectserver.activitytracker.R +import io.realm.examples.objectserver.activitytracker.databinding.ActivityBookingsListBinding +import io.realm.examples.objectserver.activitytracker.model.entities.TimeSlotId +import io.realm.examples.objectserver.activitytracker.ui.BaseActivity import java.util.* diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/bookingslist/BookingsListViewModel.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/bookingslist/BookingsListViewModel.kt index 54e24058c2..40e4db3632 100644 --- a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/bookingslist/BookingsListViewModel.kt +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/bookingslist/BookingsListViewModel.kt @@ -14,15 +14,15 @@ * limitations under the License. */ -package io.realm.examples.objectserver.advanced.ui.bookingslist +package io.realm.examples.objectserver.activitytracker.ui.bookingslist import androidx.lifecycle.LiveData import androidx.lifecycle.LiveDataReactiveStreams import androidx.lifecycle.MutableLiveData -import io.realm.examples.objectserver.advanced.model.entities.Booking -import io.realm.examples.objectserver.advanced.model.entities.TimeSlot -import io.realm.examples.objectserver.advanced.model.entities.TimeSlotId -import io.realm.examples.objectserver.advanced.ui.BaseViewModel +import io.realm.examples.objectserver.activitytracker.model.entities.Booking +import io.realm.examples.objectserver.activitytracker.model.entities.TimeSlot +import io.realm.examples.objectserver.activitytracker.model.entities.TimeSlotId +import io.realm.examples.objectserver.activitytracker.ui.BaseViewModel import io.realm.Case import io.realm.Realm import io.realm.RealmResults diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/bookingslist/BookingsRecyclerAdapter.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/bookingslist/BookingsRecyclerAdapter.kt index 38a72e28a6..da44a135fb 100644 --- a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/bookingslist/BookingsRecyclerAdapter.kt +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/bookingslist/BookingsRecyclerAdapter.kt @@ -14,19 +14,19 @@ * limitations under the License. */ -package io.realm.examples.objectserver.advanced.ui.bookingslist +package io.realm.examples.objectserver.activitytracker.ui.bookingslist import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView -import io.realm.examples.objectserver.advanced.databinding.ItemBookingsListBinding -import io.realm.examples.objectserver.advanced.model.entities.Booking -import io.realm.examples.objectserver.advanced.ui.RealmRecyclerViewAdapter +import io.realm.examples.objectserver.activitytracker.databinding.ItemBookingsListBinding +import io.realm.examples.objectserver.activitytracker.model.entities.Booking +import io.realm.examples.objectserver.activitytracker.ui.RealmRecyclerViewAdapter import io.realm.OrderedRealmCollection class BookingsRecyclerAdapter(private val viewModel: BookingsListViewModel, data: OrderedRealmCollection) - : io.realm.examples.objectserver.advanced.ui.RealmRecyclerViewAdapter(data, true) { + : io.realm.examples.objectserver.activitytracker.ui.RealmRecyclerViewAdapter(data, true) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { val layoutInflater = LayoutInflater.from(parent.context) @@ -40,7 +40,7 @@ class BookingsRecyclerAdapter(private val viewModel: BookingsListViewModel, data } // See https://medium.com/androiddevelopers/android-data-binding-recyclerview-db7c40d9f0e4 - inner class MyViewHolder(private val binding: io.realm.examples.objectserver.advanced.databinding.ItemBookingsListBinding) : RecyclerView.ViewHolder(binding.root) { + inner class MyViewHolder(private val binding: io.realm.examples.objectserver.activitytracker.databinding.ItemBookingsListBinding) : RecyclerView.ViewHolder(binding.root) { fun bind(item: Booking) { binding.item = item binding.vm = viewModel diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/checkin/CheckinActivity.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/checkin/CheckinActivity.kt index 7de0fc956b..7af1b154d8 100644 --- a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/checkin/CheckinActivity.kt +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/checkin/CheckinActivity.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.examples.objectserver.advanced.ui.checkin +package io.realm.examples.objectserver.activitytracker.ui.checkin import android.content.Intent import android.os.Bundle @@ -24,11 +24,11 @@ import android.widget.Toast import androidx.databinding.DataBindingUtil import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders -import io.realm.examples.objectserver.advanced.R -import io.realm.examples.objectserver.advanced.databinding.ActivityCheckinBinding -import io.realm.examples.objectserver.advanced.model.entities.ActivityId -import io.realm.examples.objectserver.advanced.ui.BaseActivity -import io.realm.examples.objectserver.advanced.ui.bookingslist.BookingsListActivity +import io.realm.examples.objectserver.activitytracker.R +import io.realm.examples.objectserver.activitytracker.databinding.ActivityCheckinBinding +import io.realm.examples.objectserver.activitytracker.model.entities.ActivityId +import io.realm.examples.objectserver.activitytracker.ui.BaseActivity +import io.realm.examples.objectserver.activitytracker.ui.bookingslist.BookingsListActivity class CheckinActivity : BaseActivity() { diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/checkin/CheckinViewModel.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/checkin/CheckinViewModel.kt index f0e763a398..0393e69f1f 100644 --- a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/checkin/CheckinViewModel.kt +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/checkin/CheckinViewModel.kt @@ -14,13 +14,13 @@ * limitations under the License. */ -package io.realm.examples.objectserver.advanced.ui.checkin +package io.realm.examples.objectserver.activitytracker.ui.checkin import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Transformations -import io.realm.examples.objectserver.advanced.model.entities.* -import io.realm.examples.objectserver.advanced.ui.BaseViewModel +import io.realm.examples.objectserver.activitytracker.model.entities.* +import io.realm.examples.objectserver.activitytracker.ui.BaseViewModel import io.realm.RealmResults import io.realm.Sort import io.realm.kotlin.where diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/checkin/TimeslotAdapter.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/checkin/TimeslotAdapter.kt index c0bb24a334..9a004d40fa 100644 --- a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/checkin/TimeslotAdapter.kt +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/checkin/TimeslotAdapter.kt @@ -14,16 +14,16 @@ * limitations under the License. */ -package io.realm.examples.objectserver.advanced.ui.checkin +package io.realm.examples.objectserver.activitytracker.ui.checkin import android.content.Context import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView -import io.realm.examples.objectserver.advanced.R -import io.realm.examples.objectserver.advanced.RealmBaseAdapter -import io.realm.examples.objectserver.advanced.model.entities.TimeSlot +import io.realm.examples.objectserver.activitytracker.R +import io.realm.examples.objectserver.activitytracker.RealmBaseAdapter +import io.realm.examples.objectserver.activitytracker.model.entities.TimeSlot import io.realm.RealmResults diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/login/LoginActivity.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/login/LoginActivity.kt index 37669ba81e..bee5d7f8b1 100644 --- a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/login/LoginActivity.kt +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/login/LoginActivity.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.examples.objectserver.advanced.ui.login +package io.realm.examples.objectserver.activitytracker.ui.login import android.app.ProgressDialog import android.content.Intent @@ -24,11 +24,11 @@ import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.AppCompatButton import androidx.appcompat.widget.AppCompatEditText import androidx.databinding.DataBindingUtil -import io.realm.examples.objectserver.advanced.Constants -import io.realm.examples.objectserver.advanced.R -import io.realm.examples.objectserver.advanced.databinding.ActivityLoginBinding -import io.realm.examples.objectserver.advanced.model.App -import io.realm.examples.objectserver.advanced.ui.activitylist.SelectActivityActivity +import io.realm.examples.objectserver.activitytracker.Constants +import io.realm.examples.objectserver.activitytracker.R +import io.realm.examples.objectserver.activitytracker.databinding.ActivityLoginBinding +import io.realm.examples.objectserver.activitytracker.model.App +import io.realm.examples.objectserver.activitytracker.ui.activitylist.SelectActivityActivity import io.realm.ErrorCode import io.realm.ObjectServerError import io.realm.SyncCredentials diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/orderlist/OrdersActivity.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/orderlist/OrdersActivity.kt index ebd63628ad..08651d7262 100644 --- a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/orderlist/OrdersActivity.kt +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/orderlist/OrdersActivity.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.examples.objectserver.advanced.ui.orderlist +package io.realm.examples.objectserver.activitytracker.ui.orderlist import android.os.Bundle import android.view.Menu @@ -24,14 +24,14 @@ import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import io.realm.examples.objectserver.advanced.ui.BaseActivity -import io.realm.examples.objectserver.advanced.R +import io.realm.examples.objectserver.activitytracker.ui.BaseActivity +import io.realm.examples.objectserver.activitytracker.R class OrdersActivity : BaseActivity() { private lateinit var viewModel: OrdersViewModel - private lateinit var binding: io.realm.examples.objectserver.advanced.databinding.ActivityOrderListBinding + private lateinit var binding: io.realm.examples.objectserver.activitytracker.databinding.ActivityOrderListBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/orderlist/OrdersRecyclerAdapter.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/orderlist/OrdersRecyclerAdapter.kt index e1bd4eb1c7..3a36778453 100644 --- a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/orderlist/OrdersRecyclerAdapter.kt +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/orderlist/OrdersRecyclerAdapter.kt @@ -14,13 +14,13 @@ * limitations under the License. */ -package io.realm.examples.objectserver.advanced.ui.orderlist +package io.realm.examples.objectserver.activitytracker.ui.orderlist import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView -import io.realm.examples.objectserver.advanced.databinding.ItemOrderListBinding -import io.realm.examples.objectserver.advanced.model.entities.Order +import io.realm.examples.objectserver.activitytracker.databinding.ItemOrderListBinding +import io.realm.examples.objectserver.activitytracker.model.entities.Order class OrdersRecyclerAdapter(private val viewModel: OrdersViewModel): RecyclerView.Adapter() { diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/orderlist/OrdersViewModel.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/orderlist/OrdersViewModel.kt index 952eee6afe..9e982adc94 100644 --- a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/orderlist/OrdersViewModel.kt +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/orderlist/OrdersViewModel.kt @@ -14,16 +14,16 @@ * limitations under the License. */ -package io.realm.examples.objectserver.advanced.ui.orderlist +package io.realm.examples.objectserver.activitytracker.ui.orderlist import android.os.HandlerThread import androidx.lifecycle.LiveData import androidx.lifecycle.LiveDataReactiveStreams import androidx.recyclerview.widget.DiffUtil -import io.realm.examples.objectserver.advanced.model.App -import io.realm.examples.objectserver.advanced.model.entities.Order -import io.realm.examples.objectserver.advanced.ui.BaseViewModel -import io.realm.examples.objectserver.advanced.ui.shared.createObservableForRealm +import io.realm.examples.objectserver.activitytracker.model.App +import io.realm.examples.objectserver.activitytracker.model.entities.Order +import io.realm.examples.objectserver.activitytracker.ui.BaseViewModel +import io.realm.examples.objectserver.activitytracker.ui.shared.createObservableForRealm import io.reactivex.BackpressureStrategy import io.reactivex.Flowable import io.reactivex.android.schedulers.AndroidSchedulers diff --git a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/shared/ObservableExt.kt b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/shared/ObservableExt.kt index 58e50a6557..7021906554 100644 --- a/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/shared/ObservableExt.kt +++ b/examples/objectServerActivityTrackerExample/src/main/java/io/realm/examples/objectserver/activitytracker/ui/shared/ObservableExt.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.examples.objectserver.advanced.ui.shared +package io.realm.examples.objectserver.activitytracker.ui.shared import io.reactivex.BackpressureStrategy import io.reactivex.Flowable diff --git a/examples/objectServerActivityTrackerExample/src/main/res/layout/activity_bookings_list.xml b/examples/objectServerActivityTrackerExample/src/main/res/layout/activity_bookings_list.xml index 5ba8966454..704d0951fc 100644 --- a/examples/objectServerActivityTrackerExample/src/main/res/layout/activity_bookings_list.xml +++ b/examples/objectServerActivityTrackerExample/src/main/res/layout/activity_bookings_list.xml @@ -7,7 +7,7 @@ + type="io.realm.examples.objectserver.activitytracker.ui.bookingslist.BookingsListViewModel" /> + tools:context="io.realm.examples.objectserver.activitytracker.io.realm.examples.objectserver.activitytracker.ui.orderlist.OrdersActivity"> + type="io.realm.examples.objectserver.activitytracker.ui.checkin.CheckinViewModel" /> + tools:context="io.realm.examples.objectserver.activitytracker.io.realm.examples.objectserver.activitytracker.ui.checkin.CheckinActivity"> - + + - + @@ -107,15 +110,15 @@ android:onClick="@{() -> viewModel.adhocCheckinSelected()}" android:orientation="vertical"> - + - - + type="io.realm.examples.objectserver.activitytracker.ui.activitylist.SelectActivityViewModel" /> + tools:context="io.realm.examples.objectserver.activitytracker.io.realm.examples.objectserver.activitytracker.ui.orderlist.OrdersActivity"> + type="io.realm.examples.objectserver.activitytracker.ui.orderlist.OrdersViewModel" /> + tools:context="io.realm.examples.objectserver.activitytracker.io.realm.examples.objectserver.activitytracker.ui.orderlist.OrdersActivity"> + type="io.realm.examples.objectserver.activitytracker.model.entities.Booking" /> + type="io.realm.examples.objectserver.activitytracker.ui.bookingslist.BookingsListViewModel" /> + type="io.realm.examples.objectserver.activitytracker.model.entities.Activity" /> + type="io.realm.examples.objectserver.activitytracker.ui.activitylist.SelectActivityViewModel" /> + type="io.realm.examples.objectserver.activitytracker.model.entities.Order" /> + type="io.realm.examples.objectserver.activitytracker.ui.orderlist.OrdersViewModel" /> - Activity Checkin Demo + Activity Tracker Demo Logout Checkin Activities