Repository: realm/realm-java Branch: main Commit: e5f4caac29d2 Files: 1417 Total size: 10.1 MB Directory structure: gitextract_frl3iah0/ ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ └── config.yml │ ├── advanced-issue-labeler.yml │ ├── auto_assign.yml │ ├── no-response.yml │ └── workflows/ │ ├── Issue-Needs-Attention.yml │ ├── auto-assign.yml │ ├── check-changelog.yml │ ├── check-pr-title.yml │ ├── gradle-wrapper-validation.yml │ ├── issue-labeler.yml │ ├── lock-threads.yml │ └── no-response.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dockerfile ├── Jenkinsfile ├── LICENSE ├── README.md ├── SUPPORT.md ├── build.gradle ├── dependencies.list ├── docs/ │ ├── README.md │ └── guides/ │ ├── adapters.md │ ├── async-api.md │ ├── crud/ │ │ ├── create.md │ │ ├── delete.md │ │ ├── filter-data.md │ │ ├── read.md │ │ ├── threading.md │ │ └── update.md │ ├── crud.md │ ├── install.md │ ├── model-data/ │ │ ├── data-types/ │ │ │ ├── collections.md │ │ │ ├── counters.md │ │ │ ├── embedded-objects.md │ │ │ ├── enums.md │ │ │ ├── field-types.md │ │ │ ├── realmany.md │ │ │ ├── realmdictionary.md │ │ │ └── realmset.md │ │ ├── data-types.md │ │ ├── define-a-realm-object-model.md │ │ ├── modify-an-object-schema.md │ │ └── relationships.md │ ├── model-data.md │ ├── quick-start-local.md │ ├── react-to-changes.md │ ├── realm-files/ │ │ ├── bundle-a-realm.md │ │ ├── encryption.md │ │ └── open-and-close-a-realm.md │ ├── realm-files.md │ ├── realm-query-language.md │ ├── test-and-debug/ │ │ ├── debugging.md │ │ ├── log-realm-events.md │ │ ├── testing.md │ │ └── troubleshooting.md │ └── troubleshooting.md ├── examples/ │ ├── architectureComponentsExample/ │ │ ├── build.gradle │ │ ├── lint.xml │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── io/ │ │ │ └── realm/ │ │ │ └── examples/ │ │ │ └── arch/ │ │ │ ├── ArchExampleActivity.java │ │ │ ├── BackgroundTask.java │ │ │ ├── CustomApplication.java │ │ │ ├── PersonFragment.java │ │ │ ├── PersonListFragment.java │ │ │ ├── PersonListViewModel.java │ │ │ ├── PersonViewModel.java │ │ │ ├── livemodel/ │ │ │ │ ├── LiveRealmObject.java │ │ │ │ └── LiveRealmResults.java │ │ │ ├── model/ │ │ │ │ └── Person.java │ │ │ └── utils/ │ │ │ └── ContextUtils.java │ │ └── res/ │ │ ├── drawable/ │ │ │ ├── ic_play_arrow_black_24dp.xml │ │ │ └── ic_stop_black_24dp.xml │ │ ├── layout/ │ │ │ ├── activity_arch_example.xml │ │ │ ├── fragment_person.xml │ │ │ ├── fragment_person_list.xml │ │ │ └── item_person.xml │ │ ├── values/ │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── values-w820dp/ │ │ └── dimens.xml │ ├── build.gradle │ ├── compatibilityExample/ │ │ ├── README.md │ │ ├── build.gradle │ │ ├── gradle.properties │ │ ├── lint.xml │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── io/ │ │ │ └── realm/ │ │ │ └── examples/ │ │ │ └── compatibility/ │ │ │ ├── MyActivity.kt │ │ │ └── MyApplication.kt │ │ └── res/ │ │ ├── layout/ │ │ │ └── activity_my_activty.xml │ │ └── values/ │ │ ├── dimens.xml │ │ ├── realm_colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ ├── coroutinesExample/ │ │ ├── build.gradle │ │ ├── proguard-rules.pro │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── io/ │ │ │ └── realm/ │ │ │ └── examples/ │ │ │ └── coroutinesexample/ │ │ │ ├── MainActivity.kt │ │ │ ├── MainApplication.kt │ │ │ ├── data/ │ │ │ │ └── newsreader/ │ │ │ │ ├── local/ │ │ │ │ │ ├── RealmNYTDao.kt │ │ │ │ │ ├── RealmNYTimes.kt │ │ │ │ │ └── repository/ │ │ │ │ │ └── NewsReaderRepository.kt │ │ │ │ └── network/ │ │ │ │ ├── NYTimesApiClient.kt │ │ │ │ ├── NYTimesService.kt │ │ │ │ └── model/ │ │ │ │ └── NYTimes.kt │ │ │ ├── di/ │ │ │ │ └── DependencyGraph.kt │ │ │ ├── ui/ │ │ │ │ ├── details/ │ │ │ │ │ ├── DetailsFragment.kt │ │ │ │ │ └── DetailsViewModel.kt │ │ │ │ └── main/ │ │ │ │ ├── MainAdapter.kt │ │ │ │ ├── MainFragment.kt │ │ │ │ └── MainViewModel.kt │ │ │ └── util/ │ │ │ └── NewsReaderFlowFactory.kt │ │ └── res/ │ │ ├── drawable/ │ │ │ └── ic_launcher_background.xml │ │ ├── drawable-v24/ │ │ │ └── ic_launcher_foreground.xml │ │ ├── layout/ │ │ │ ├── activity_main.xml │ │ │ ├── fragment_details.xml │ │ │ ├── fragment_main.xml │ │ │ └── item_article.xml │ │ ├── mipmap-anydpi-v26/ │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ └── values/ │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ ├── styles.xml │ │ └── themes.xml │ ├── encryptionExample/ │ │ ├── build.gradle │ │ ├── lint.xml │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── io/ │ │ │ └── realm/ │ │ │ └── examples/ │ │ │ └── encryption/ │ │ │ ├── EncryptionExampleActivity.java │ │ │ ├── MyApplication.java │ │ │ ├── Person.java │ │ │ └── Util.java │ │ └── res/ │ │ └── values/ │ │ ├── strings.xml │ │ └── styles.xml │ ├── gradle/ │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradle.properties │ ├── gradlew │ ├── gradlew.bat │ ├── gridViewExample/ │ │ ├── build.gradle │ │ ├── lint.xml │ │ ├── proguard-rules.pro │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── assets/ │ │ │ └── cities.json │ │ ├── java/ │ │ │ └── io/ │ │ │ └── realm/ │ │ │ └── examples/ │ │ │ └── realmgridview/ │ │ │ ├── City.java │ │ │ ├── CityAdapter.java │ │ │ ├── GridViewExampleActivity.java │ │ │ └── MyApplication.java │ │ └── res/ │ │ ├── layout/ │ │ │ ├── activity_realm_example.xml │ │ │ └── city_listitem.xml │ │ └── values/ │ │ ├── strings.xml │ │ └── styles.xml │ ├── introExample/ │ │ ├── build.gradle │ │ ├── lint.xml │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── io/ │ │ │ └── realm/ │ │ │ └── examples/ │ │ │ └── intro/ │ │ │ ├── IntroExampleActivity.java │ │ │ ├── MyApplication.java │ │ │ └── model/ │ │ │ ├── Cat.java │ │ │ ├── Dog.java │ │ │ └── Person.java │ │ └── res/ │ │ ├── layout/ │ │ │ └── activity_realm_basic_example.xml │ │ ├── values/ │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── values-w820dp/ │ │ └── dimens.xml │ ├── jsonExample/ │ │ ├── build.gradle │ │ ├── lint.xml │ │ ├── lombok.config │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── assets/ │ │ │ └── cities.json │ │ ├── java/ │ │ │ └── io/ │ │ │ └── realm/ │ │ │ └── examples/ │ │ │ └── json/ │ │ │ ├── City.java │ │ │ ├── CityAdapter.java │ │ │ ├── JsonExampleActivity.java │ │ │ └── MyApplication.java │ │ └── res/ │ │ ├── layout/ │ │ │ ├── activity_realm_example.xml │ │ │ └── city_listitem.xml │ │ ├── values/ │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── values-w820dp/ │ │ └── dimens.xml │ ├── kotlinExample/ │ │ ├── build.gradle │ │ ├── lint.xml │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── kotlin/ │ │ │ └── io/ │ │ │ └── realm/ │ │ │ └── examples/ │ │ │ └── kotlin/ │ │ │ ├── KotlinExampleActivity.kt │ │ │ ├── MyApplication.kt │ │ │ └── model/ │ │ │ ├── Cat.kt │ │ │ ├── Dog.kt │ │ │ └── Person.kt │ │ └── res/ │ │ ├── layout/ │ │ │ └── activity_realm_basic_example.xml │ │ ├── values/ │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── values-w820dp/ │ │ └── dimens.xml │ ├── migrationExample/ │ │ ├── build.gradle │ │ ├── lint.xml │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── io/ │ │ │ └── realm/ │ │ │ └── examples/ │ │ │ └── realmmigrationexample/ │ │ │ ├── MigrationExampleActivity.java │ │ │ ├── MyApplication.java │ │ │ └── model/ │ │ │ ├── Migration.java │ │ │ ├── Person.java │ │ │ └── Pet.java │ │ └── res/ │ │ ├── layout/ │ │ │ └── activity_realm_migration_example.xml │ │ ├── raw/ │ │ │ ├── default0.realm │ │ │ ├── default1.realm │ │ │ └── default2.realm │ │ ├── values/ │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── values-w820dp/ │ │ └── dimens.xml │ ├── moduleExample/ │ │ ├── app/ │ │ │ ├── build.gradle │ │ │ ├── keystore/ │ │ │ │ └── release.keystore │ │ │ ├── lint.xml │ │ │ ├── proguard-rules.pro │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── AndroidManifest.xml │ │ │ ├── java/ │ │ │ │ └── io/ │ │ │ │ └── realm/ │ │ │ │ └── examples/ │ │ │ │ └── appmodules/ │ │ │ │ ├── ModulesExampleActivity.java │ │ │ │ ├── MyApplication.java │ │ │ │ ├── model/ │ │ │ │ │ ├── Cow.java │ │ │ │ │ ├── CrossModuleLinks.java │ │ │ │ │ ├── Pig.java │ │ │ │ │ ├── Snake.java │ │ │ │ │ └── Spider.java │ │ │ │ └── modules/ │ │ │ │ └── CreepyAnimalsModule.java │ │ │ └── res/ │ │ │ ├── layout/ │ │ │ │ └── activity_modules_example.xml │ │ │ ├── values/ │ │ │ │ ├── dimens.xml │ │ │ │ ├── strings.xml │ │ │ │ └── styles.xml │ │ │ └── values-w820dp/ │ │ │ └── dimens.xml │ │ └── library/ │ │ ├── build.gradle │ │ ├── lint.xml │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── io/ │ │ │ └── realm/ │ │ │ └── examples/ │ │ │ └── librarymodules/ │ │ │ ├── Zoo.java │ │ │ ├── model/ │ │ │ │ ├── Cat.java │ │ │ │ ├── Dog.java │ │ │ │ ├── Elephant.java │ │ │ │ ├── EmbeddedAnimal.java │ │ │ │ ├── Lion.java │ │ │ │ └── Zebra.java │ │ │ └── modules/ │ │ │ ├── AllAnimalsModule.java │ │ │ ├── DomesticAnimalsModule.java │ │ │ └── ZooAnimalsModule.java │ │ └── res/ │ │ └── values/ │ │ └── strings.xml │ ├── mongoDbRealmExample/ │ │ ├── README.md │ │ ├── build.gradle │ │ ├── gradle.properties │ │ ├── lint.xml │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── mongodb/ │ │ │ └── realm/ │ │ │ └── example/ │ │ │ ├── CounterActivity.kt │ │ │ ├── LoginActivity.kt │ │ │ ├── MyApplication.kt │ │ │ └── model/ │ │ │ └── CRDTCounter.kt │ │ └── res/ │ │ ├── drawable/ │ │ │ └── button_counter.xml │ │ ├── layout/ │ │ │ ├── activity_counter.xml │ │ │ └── activity_login.xml │ │ ├── menu/ │ │ │ └── menu_counter.xml │ │ ├── values/ │ │ │ ├── dimens.xml │ │ │ ├── realm_colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── values-w820dp/ │ │ └── dimens.xml │ ├── multiprocessExample/ │ │ ├── .gitignore │ │ ├── build.gradle │ │ ├── proguard-rules.pro │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── io/ │ │ │ └── realm/ │ │ │ └── examples/ │ │ │ └── realmmultiprocessexample/ │ │ │ ├── AnotherProcessService.java │ │ │ ├── MainActivity.java │ │ │ ├── MyApplication.java │ │ │ ├── Utils.java │ │ │ └── models/ │ │ │ └── ProcessInfo.java │ │ └── res/ │ │ ├── layout/ │ │ │ └── activity_main.xml │ │ ├── values/ │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── values-w820dp/ │ │ └── dimens.xml │ ├── rxJavaExample/ │ │ ├── README.md │ │ ├── build.gradle │ │ ├── lint.xml │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── io/ │ │ │ └── realm/ │ │ │ └── examples/ │ │ │ └── rxjava/ │ │ │ ├── MainActivity.java │ │ │ ├── MyApplication.java │ │ │ ├── animation/ │ │ │ │ └── AnimationActivity.java │ │ │ ├── gotchas/ │ │ │ │ └── GotchasActivity.java │ │ │ ├── model/ │ │ │ │ └── Person.java │ │ │ ├── retrofit/ │ │ │ │ ├── GitHubApi.java │ │ │ │ ├── GitHubUser.java │ │ │ │ ├── RetrofitExample.java │ │ │ │ └── UserViewModel.java │ │ │ └── throttle/ │ │ │ └── ThrottleSearchActivity.java │ │ └── res/ │ │ ├── layout/ │ │ │ ├── activity_animations.xml │ │ │ ├── activity_gotchas.xml │ │ │ ├── activity_main.xml │ │ │ ├── activity_network.xml │ │ │ └── activity_throttlesearch.xml │ │ ├── values/ │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── values-w820dp/ │ │ └── dimens.xml │ ├── settings.gradle │ ├── threadExample/ │ │ ├── build.gradle │ │ ├── lint.xml │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── io/ │ │ │ └── realm/ │ │ │ └── examples/ │ │ │ └── threads/ │ │ │ ├── AsyncQueryFragment.java │ │ │ ├── AsyncTaskFragment.java │ │ │ ├── MyApplication.java │ │ │ ├── PassingObjectsFragment.java │ │ │ ├── ReceivingActivity.java │ │ │ ├── ReceivingService.java │ │ │ ├── ThreadExampleActivity.java │ │ │ ├── ThreadFragment.java │ │ │ ├── WakefulReceivingBroadcastReceiver.java │ │ │ ├── WakefulReceivingService.java │ │ │ ├── model/ │ │ │ │ ├── Dot.java │ │ │ │ ├── Person.java │ │ │ │ └── Score.java │ │ │ └── widget/ │ │ │ └── DotsView.java │ │ └── res/ │ │ ├── layout/ │ │ │ ├── activity_main.xml │ │ │ ├── activity_receiving.xml │ │ │ ├── fragment_async_query.xml │ │ │ ├── fragment_asynctask.xml │ │ │ ├── fragment_passing_objects.xml │ │ │ └── fragment_thread.xml │ │ ├── menu/ │ │ │ └── menu_backgroundthread.xml │ │ └── values/ │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── unitTestExample/ │ ├── README.md │ ├── build.gradle │ ├── gradle.properties │ ├── lint.xml │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── io/ │ │ └── realm/ │ │ └── examples/ │ │ └── unittesting/ │ │ └── jUnit4ExampleTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── io/ │ │ │ └── realm/ │ │ │ └── examples/ │ │ │ └── unittesting/ │ │ │ ├── ExampleActivity.java │ │ │ ├── model/ │ │ │ │ ├── Cat.java │ │ │ │ ├── Dog.java │ │ │ │ └── Person.java │ │ │ └── repository/ │ │ │ ├── DogRepository.java │ │ │ └── DogRepositoryImpl.java │ │ └── res/ │ │ ├── layout/ │ │ │ └── activity_example.xml │ │ ├── values/ │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── values-w820dp/ │ │ └── dimens.xml │ └── test/ │ └── java/ │ └── io/ │ └── realm/ │ └── examples/ │ └── unittesting/ │ ├── ExampleActivityTest.java │ └── ExampleRealmTest.java ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle-plugin/ │ ├── build.gradle │ ├── gradle/ │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradle.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle │ └── src/ │ ├── main/ │ │ ├── kotlin/ │ │ │ └── io/ │ │ │ └── realm/ │ │ │ └── gradle/ │ │ │ ├── Realm.kt │ │ │ └── SimpleAGPVersion.kt │ │ ├── resources/ │ │ │ └── META-INF/ │ │ │ └── gradle-plugins/ │ │ │ └── realm-android.properties │ │ └── templates/ │ │ └── Version.kt │ └── test/ │ └── groovy/ │ └── io/ │ └── realm/ │ └── gradle/ │ └── PluginTest.groovy ├── gradle.properties ├── gradlew ├── gradlew.bat ├── latest ├── library-benchmarks/ │ ├── build.gradle │ ├── gradle/ │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradle.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle │ └── src/ │ ├── androidTest/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── io/ │ │ └── realm/ │ │ └── benchmarks/ │ │ ├── CopyToRealmBenchmarks.kt │ │ ├── CopyToRealmOrUpdateBenchmarks.kt │ │ ├── FrozenObjectsBenchmarks.kt │ │ ├── RealmAllocBenchmarks.kt │ │ ├── RealmBenchmarks.kt │ │ ├── RealmInsertBenchmark.kt │ │ ├── RealmObjectReadBenchmarks.kt │ │ ├── RealmObjectWriteBenchmarks.kt │ │ ├── RealmQueryBenchmarks.kt │ │ ├── RealmResultsBenchmarks.kt │ │ └── entities/ │ │ ├── AllTypes.java │ │ └── AllTypesPrimaryKey.java │ └── main/ │ └── AndroidManifest.xml ├── library-build-transformer/ │ ├── .gitignore │ ├── README.md │ ├── build.gradle │ ├── gradle/ │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradle.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle │ └── src/ │ ├── main/ │ │ └── kotlin/ │ │ └── io/ │ │ └── realm/ │ │ └── buildtransformer/ │ │ ├── RealmBuildTransformer.kt │ │ ├── asm/ │ │ │ ├── ClassPoolTransformer.kt │ │ │ └── visitors/ │ │ │ ├── AnnotatedCodeStripVisitor.kt │ │ │ └── AnnotationVisitor.kt │ │ ├── ext/ │ │ │ └── FileExt.kt │ │ └── util/ │ │ └── Stopwatch.kt │ └── test/ │ ├── java/ │ │ └── io/ │ │ └── realm/ │ │ ├── buildtransformer/ │ │ │ └── testclasses/ │ │ │ ├── NestedTestClass.java │ │ │ ├── SimpleTestClass.java │ │ │ ├── SimpleTestFields.java │ │ │ ├── SimpleTestMethods.java │ │ │ ├── SubClass.java │ │ │ └── SuperClass.java │ │ └── internal/ │ │ └── annotations/ │ │ ├── CustomAnnotation.java │ │ └── ObjectServer.java │ └── kotlin/ │ └── io/ │ └── realm/ │ └── buildtransformer/ │ ├── DynamicClassLoader.kt │ └── VisitorTests.kt ├── mavencentral-properties.gradle ├── mavencentral-publications.gradle ├── mavencentral-publish.gradle ├── realm/ │ ├── build.gradle │ ├── config/ │ │ ├── checkstyle/ │ │ │ ├── checkstyle-suppressions.xml │ │ │ └── checkstyle.xml │ │ ├── findbugs/ │ │ │ └── findbugs-filter.xml │ │ ├── pmd/ │ │ │ └── ruleset.xml │ │ └── studio/ │ │ ├── Realm-style.xml │ │ └── Realm_lint.xml │ ├── gradle/ │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradle.properties │ ├── gradlew │ ├── gradlew.bat │ ├── kotlin-extensions/ │ │ ├── .gitignore │ │ ├── build.gradle │ │ ├── lib/ │ │ │ └── package-list.txt │ │ ├── proguard-rules.pro │ │ └── src/ │ │ ├── androidTest/ │ │ │ └── kotlin/ │ │ │ └── io/ │ │ │ └── realm/ │ │ │ ├── CoroutinesTests.kt │ │ │ ├── KotlinRealmModelTests.kt │ │ │ ├── KotlinRealmQueryTests.kt │ │ │ ├── KotlinRealmTests.kt │ │ │ └── entities/ │ │ │ ├── AllPropTypesClass.kt │ │ │ ├── PrimaryKeyClass.kt │ │ │ └── SimpleClass.kt │ │ ├── androidTestObjectServer/ │ │ │ └── kotlin/ │ │ │ └── io/ │ │ │ └── realm/ │ │ │ └── kotlin/ │ │ │ └── KotlinSyncedRealmTests.kt │ │ ├── main/ │ │ │ ├── AndroidManifest.xml │ │ │ └── kotlin/ │ │ │ └── io/ │ │ │ └── realm/ │ │ │ └── kotlin/ │ │ │ ├── DynamicRealmExtensions.kt │ │ │ ├── RealmExtensions.kt │ │ │ ├── RealmListExtensions.kt │ │ │ ├── RealmModelExtensions.kt │ │ │ ├── RealmObjectExtensions.kt │ │ │ ├── RealmQueryExtensions.kt │ │ │ └── RealmResultsExtensions.kt │ │ └── objectServer/ │ │ └── kotlin/ │ │ └── io/ │ │ └── realm/ │ │ └── kotlin/ │ │ └── SyncedRealmExtensions.kt │ ├── realm-annotations-processor/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── io/ │ │ │ │ └── realm/ │ │ │ │ └── processor/ │ │ │ │ ├── Backlink.kt │ │ │ │ ├── ClassCollection.kt │ │ │ │ ├── ClassMetaData.kt │ │ │ │ ├── Constants.kt │ │ │ │ ├── DefaultModuleGenerator.kt │ │ │ │ ├── ModuleMetaData.kt │ │ │ │ ├── OsObjectBuilderTypeHelper.kt │ │ │ │ ├── RealmFieldElement.kt │ │ │ │ ├── RealmJsonTypeHelper.kt │ │ │ │ ├── RealmProcessor.kt │ │ │ │ ├── RealmProxyClassGenerator.kt │ │ │ │ ├── RealmProxyInterfaceGenerator.kt │ │ │ │ ├── RealmProxyMediatorGenerator.kt │ │ │ │ ├── RealmVersionChecker.kt │ │ │ │ ├── TypeMirrors.kt │ │ │ │ ├── Utils.kt │ │ │ │ ├── ext/ │ │ │ │ │ └── JavaWriterExt.kt │ │ │ │ └── nameconverter/ │ │ │ │ ├── CamelCaseConverter.kt │ │ │ │ ├── IdentityConverter.kt │ │ │ │ ├── LowerCaseWithSeparatorConverter.kt │ │ │ │ ├── NameConverter.kt │ │ │ │ ├── PascalCaseConverter.kt │ │ │ │ └── WordTokenizer.kt │ │ │ ├── resources/ │ │ │ │ └── META-INF/ │ │ │ │ ├── gradle/ │ │ │ │ │ └── incremental.annotation.processors │ │ │ │ └── services/ │ │ │ │ └── javax.annotation.processing.Processor │ │ │ └── templates/ │ │ │ └── Version.java │ │ └── test/ │ │ ├── java/ │ │ │ └── io/ │ │ │ └── realm/ │ │ │ └── processor/ │ │ │ ├── NameConverterTests.java │ │ │ ├── RealmBacklinkProcessorTest.java │ │ │ ├── RealmCounterProcessorTest.java │ │ │ ├── RealmEmbeddedObjectsTest.java │ │ │ ├── RealmNameTest.java │ │ │ ├── RealmProcessorTest.java │ │ │ ├── RealmSyntheticTestClass.java │ │ │ └── ValueListProcessorTest.java │ │ └── resources/ │ │ ├── io/ │ │ │ └── realm/ │ │ │ ├── DefaultRealmModule.java │ │ │ ├── DefaultRealmModuleMediator.java │ │ │ ├── some_test_AllTypesRealmProxy-pre-dictionary.java │ │ │ ├── some_test_AllTypesRealmProxy.java │ │ │ ├── some_test_BooleansRealmProxy.java │ │ │ ├── some_test_EmbeddedClassRealmProxy.java │ │ │ ├── some_test_EmbeddedClassSimpleParentRealmProxy.java │ │ │ ├── some_test_NamePolicyMixedClassSettingsRealmProxy.java │ │ │ ├── some_test_NamePolicyModuleDefaultsRealmProxy.java │ │ │ ├── some_test_NullTypesRealmProxy.java │ │ │ └── some_test_SimpleRealmProxy.java │ │ └── some/ │ │ └── test/ │ │ ├── AllTypes.java │ │ ├── AppModuleAllClasses.java │ │ ├── AppModuleCustomClasses.java │ │ ├── BacklinkSelfReference.java │ │ ├── BacklinkSource.java │ │ ├── BacklinkTarget.java │ │ ├── Booleans.java │ │ ├── ConflictingFieldName.java │ │ ├── CustomAccessor.java │ │ ├── CustomInterface.java │ │ ├── EmbeddedClass.java │ │ ├── EmbeddedClassMissingFieldDescription.java │ │ ├── EmbeddedClassMissingFinalOnLinkingObjects.java │ │ ├── EmbeddedClassMultipleRequiredParents.java │ │ ├── EmbeddedClassOptionalParents.java │ │ ├── EmbeddedClassParent.java │ │ ├── EmbeddedClassPrimaryKey.java │ │ ├── EmbeddedClassRequiredParent.java │ │ ├── EmbeddedClassSimpleParent.java │ │ ├── EmbeddedObject.java │ │ ├── Empty.java │ │ ├── ExtendRealmList.java │ │ ├── FieldNames.java │ │ ├── FieldRealmResults.java │ │ ├── Final.java │ │ ├── InterfaceList.java │ │ ├── InterfaceObjectReference.java │ │ ├── InvalidAllTypesModuleMixedParameters.java │ │ ├── InvalidAllTypesModuleWrongType.java │ │ ├── InvalidLibraryModuleMixedParameters.java │ │ ├── InvalidLibraryModuleWrongType.java │ │ ├── InvalidListElementType.java │ │ ├── InvalidModelRealmModel_1.java │ │ ├── InvalidModelRealmModel_2.java │ │ ├── InvalidModelRealmModel_3.java │ │ ├── InvalidResultsElementType.java │ │ ├── LibraryModuleAllClasses.java │ │ ├── LibraryModuleCustomClasses.java │ │ ├── MissingGenericType.java │ │ ├── NamePolicyClassOnly.java │ │ ├── NamePolicyConflictingModuleDefinitionsForAllClasses.java │ │ ├── NamePolicyConflictingModuleDefinitionsForMixedDefinitions.java │ │ ├── NamePolicyConflictingModuleDefinitionsForNamedClasses.java │ │ ├── NamePolicyFieldNameOnly.java │ │ ├── NamePolicyMixedClassSettings.java │ │ ├── NamePolicyModule.java │ │ ├── NamePolicyModuleDefaults.java │ │ ├── NoAccessors.java │ │ ├── NullTypes.java │ │ ├── RealmDictionaryMissingGenerics.java │ │ ├── RealmDictionaryModel.java │ │ ├── RealmDictionaryModelRealmAnyRequired.java │ │ ├── RealmDictionaryModelRealmModelRequired.java │ │ ├── RealmDictionaryModelWrongType.java │ │ ├── RealmMapModel.java │ │ ├── RealmSetMissingGenerics.java │ │ ├── RealmSetModel.java │ │ ├── RealmSetModelWrongType.java │ │ ├── Simple.java │ │ ├── SimpleRealmModel.java │ │ ├── Transient.java │ │ ├── UseExtendRealmList.java │ │ ├── ValidModelRealmModel_ExtendingRealmObject.java │ │ ├── ValueList.java │ │ ├── Volatile.java │ │ ├── conflict/ │ │ │ └── BacklinkSelfReference.java │ │ └── ÁrvíztűrőTükörfúrógép.java │ ├── realm-library/ │ │ ├── build.gradle │ │ ├── gradle.properties │ │ ├── proguard-rules-build-common.pro │ │ ├── proguard-rules-build-objectServer.pro │ │ ├── proguard-rules-consumer-base.pro │ │ ├── proguard-rules-consumer-common.pro │ │ ├── proguard-rules-consumer-objectServer.pro │ │ └── src/ │ │ ├── androidTest/ │ │ │ ├── AndroidManifest.xml │ │ │ ├── assets/ │ │ │ │ ├── 080_annotationtypes.realm │ │ │ │ ├── 0841_annotationtypes.realm │ │ │ │ ├── 0841_pk_migration.realm │ │ │ │ ├── all_simple_types.json │ │ │ │ ├── all_types_invalid.json │ │ │ │ ├── all_types_null.json │ │ │ │ ├── all_types_primary_key_field_only.json │ │ │ │ ├── all_types_primary_key_null.json │ │ │ │ ├── array.json │ │ │ │ ├── asset_file.realm │ │ │ │ ├── backlinks-fieldInUse.realm │ │ │ │ ├── core6_string_pk_indexed.realm │ │ │ │ ├── date_as_iso8601_string.json │ │ │ │ ├── date_as_long.json │ │ │ │ ├── date_as_string.json │ │ │ │ ├── decimal128_as_double.json │ │ │ │ ├── decimal128_as_int.json │ │ │ │ ├── decimal128_as_long.json │ │ │ │ ├── decimal128_as_string.json │ │ │ │ ├── default-notnullable-primarykey.realm │ │ │ │ ├── default-nullable-primarykey.realm │ │ │ │ ├── default0.realm │ │ │ │ ├── empty.json │ │ │ │ ├── ios/ │ │ │ │ │ ├── 0.98.0-alltypes-mix.realm │ │ │ │ │ ├── 6.0.0-beta.2-alltypes-default-encrypted.realm │ │ │ │ │ ├── 6.0.0-beta.2-alltypes-default.realm │ │ │ │ │ ├── 6.0.0-beta.2-alltypes-max.realm │ │ │ │ │ ├── 6.0.0-beta.2-alltypes-min.realm │ │ │ │ │ ├── 6.0.0-beta.2-alltypes-null-value.realm │ │ │ │ │ ├── 6.0.0-beta.2-alltypes.realm │ │ │ │ │ └── README.md │ │ │ │ ├── list_alltypes_primarykey.json │ │ │ │ ├── mutablerealminteger-int.json │ │ │ │ ├── mutablerealminteger-long.json │ │ │ │ ├── mutablerealminteger-null.json │ │ │ │ ├── mutablerealminteger-required-null.json │ │ │ │ ├── nulltypes.json │ │ │ │ ├── nulltypes_invalid.json │ │ │ │ ├── objectid_as_string.json │ │ │ │ ├── other_json_object.json │ │ │ │ ├── readonly.realm │ │ │ │ ├── realmlist.json │ │ │ │ ├── realmlist_empty.json │ │ │ │ ├── rename-and-add-indexed.realm │ │ │ │ ├── rename-and-add.realm │ │ │ │ ├── single_child_object.json │ │ │ │ ├── string-only-pre-null-0.82.2.realm │ │ │ │ ├── string-only-required-pre-null-0.82.2.realm │ │ │ │ ├── unicode_codepoints.csv │ │ │ │ └── uuid_as_string.json │ │ │ ├── java/ │ │ │ │ ├── ThreadStressTests.java │ │ │ │ └── io/ │ │ │ │ └── realm/ │ │ │ │ ├── BulkInsertTests.java │ │ │ │ ├── CollectionTests.java │ │ │ │ ├── ColumnInfoTests.java │ │ │ │ ├── CustomRealmNameTests.java │ │ │ │ ├── DynamicRealmAsyncQueryTests.kt │ │ │ │ ├── DynamicRealmObjectTests.java │ │ │ │ ├── DynamicRealmTests.java │ │ │ │ ├── FrozenObjectsTests.java │ │ │ │ ├── GCTests.java │ │ │ │ ├── IOSRealmTests.java │ │ │ │ ├── LinkingObjectsDynamicTests.java │ │ │ │ ├── LinkingObjectsManagedTests.java │ │ │ │ ├── LinkingObjectsQueryTests.java │ │ │ │ ├── LinkingObjectsUnmanagedTests.java │ │ │ │ ├── ManagedOrderedRealmCollectionTests.java │ │ │ │ ├── ManagedRealmCollectionTests.java │ │ │ │ ├── ManagedRealmListForValueTests.java │ │ │ │ ├── ManagedRealmListForValue_toArrayTests.java │ │ │ │ ├── MediatorTest.java │ │ │ │ ├── MutableRealmIntegerTests.java │ │ │ │ ├── NotificationsTest.java │ │ │ │ ├── ObjectChangeSetTests.java │ │ │ │ ├── OrderedCollectionChangeSetTests.java │ │ │ │ ├── OrderedRealmCollectionIteratorTests.java │ │ │ │ ├── OrderedRealmCollectionSnapshotTests.java │ │ │ │ ├── OrderedRealmCollectionTests.java │ │ │ │ ├── QueryTests.java │ │ │ │ ├── RealmAnnotationTests.java │ │ │ │ ├── RealmAsyncQueryTests.java │ │ │ │ ├── RealmCacheTests.java │ │ │ │ ├── RealmChangeListenerTests.java │ │ │ │ ├── RealmCollectionTests.java │ │ │ │ ├── RealmConfigurationTests.java │ │ │ │ ├── RealmInMemoryTest.java │ │ │ │ ├── RealmInterprocessTest.java │ │ │ │ ├── RealmJsonAbsentPrimaryKeyTests.java │ │ │ │ ├── RealmJsonNullPrimaryKeyTests.java │ │ │ │ ├── RealmJsonTests.java │ │ │ │ ├── RealmLinkTests.java │ │ │ │ ├── RealmListTests.java │ │ │ │ ├── RealmMigrationTests.java │ │ │ │ ├── RealmModelTests.java │ │ │ │ ├── RealmNullPrimaryKeyTests.java │ │ │ │ ├── RealmObjectSchemaTests.java │ │ │ │ ├── RealmObjectTests.java │ │ │ │ ├── RealmPrimaryKeyTests.java │ │ │ │ ├── RealmProxyMediatorTests.java │ │ │ │ ├── RealmQueryTests.java │ │ │ │ ├── RealmResultsTests.java │ │ │ │ ├── RealmSchemaTests.java │ │ │ │ ├── RealmTests.java │ │ │ │ ├── RunTestInLooperThreadLifeCycleTest.java │ │ │ │ ├── RxJavaTests.java │ │ │ │ ├── SortTest.java │ │ │ │ ├── TypeBasedNotificationsTests.java │ │ │ │ ├── UTFStringsTests.java │ │ │ │ ├── UnManagedOrderedRealmCollectionTests.java │ │ │ │ ├── UnManagedRealmCollectionTests.java │ │ │ │ ├── entities/ │ │ │ │ │ ├── AllJavaTypes.java │ │ │ │ │ ├── AllJavaTypesUnsupportedTypes.java │ │ │ │ │ ├── AnimalModule.java │ │ │ │ │ ├── AnnotationNameConventions.java │ │ │ │ │ ├── AnnotationTypes.java │ │ │ │ │ ├── AssetFileModule.java │ │ │ │ │ ├── CatOwner.java │ │ │ │ │ ├── ConflictingFieldName.java │ │ │ │ │ ├── CustomMethods.java │ │ │ │ │ ├── CyclicType.java │ │ │ │ │ ├── CyclicTypePrimaryKey.java │ │ │ │ │ ├── DefaultValueConstructor.java │ │ │ │ │ ├── DefaultValueFromOtherConstructor.java │ │ │ │ │ ├── DefaultValueOfField.java │ │ │ │ │ ├── DefaultValueOverwriteNullLink.java │ │ │ │ │ ├── DefaultValueSetter.java │ │ │ │ │ ├── FieldOrder.java │ │ │ │ │ ├── HumanModule.java │ │ │ │ │ ├── IOSAllTypes.java │ │ │ │ │ ├── IOSChild.java │ │ │ │ │ ├── IndexedFields.java │ │ │ │ │ ├── MappedAllJavaTypes.java │ │ │ │ │ ├── MutableRealmIntegerTypes.java │ │ │ │ │ ├── NoPrimaryKeyNullTypes.java │ │ │ │ │ ├── NoPrimaryKeyWithPrimaryKeyObjectRelation.java │ │ │ │ │ ├── NonLatinFieldNames.java │ │ │ │ │ ├── NullablePrimitiveFields.java │ │ │ │ │ ├── Object4957.java │ │ │ │ │ ├── ObjectIdPrimaryKey.java │ │ │ │ │ ├── OwnerPrimaryKey.java │ │ │ │ │ ├── PrimaryKeyAsObjectId.java │ │ │ │ │ ├── PrimaryKeyMix.java │ │ │ │ │ ├── PrimaryKeyRequiredAsString.java │ │ │ │ │ ├── PrimaryKeyWithNoPrimaryKeyObjectRelation.java │ │ │ │ │ ├── PrimitiveListTypes.java │ │ │ │ │ ├── RandomPrimaryKey.java │ │ │ │ │ ├── StringAndInt.java │ │ │ │ │ ├── StringOnly.java │ │ │ │ │ ├── StringOnlyModule.java │ │ │ │ │ ├── StringOnlyReadOnly.java │ │ │ │ │ ├── StringOnlyRequired.java │ │ │ │ │ ├── Thread.java │ │ │ │ │ ├── conflict/ │ │ │ │ │ │ ├── AllJavaTypes.java │ │ │ │ │ │ ├── List.java │ │ │ │ │ │ ├── Map.java │ │ │ │ │ │ ├── String.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── migration/ │ │ │ │ │ │ ├── HandleBackLinksChild1.java │ │ │ │ │ │ ├── HandleBackLinksChild2.java │ │ │ │ │ │ ├── HandleBackLinksParent1.java │ │ │ │ │ │ ├── HandleBackLinksParent2.java │ │ │ │ │ │ ├── MigrationClassRenamed.java │ │ │ │ │ │ ├── MigrationCore6PKStringIndexedByDefault.java │ │ │ │ │ │ ├── MigrationFieldRenameAndAdd.java │ │ │ │ │ │ ├── MigrationFieldRenamed.java │ │ │ │ │ │ ├── MigrationFieldTypeToInt.java │ │ │ │ │ │ ├── MigrationFieldTypeToInteger.java │ │ │ │ │ │ ├── MigrationIndexedFieldRenamed.java │ │ │ │ │ │ ├── MigrationPosteriorIndexOnly.java │ │ │ │ │ │ └── MigrationPriorIndexOnly.java │ │ │ │ │ ├── pojo/ │ │ │ │ │ │ ├── AllTypesRealmModel.java │ │ │ │ │ │ ├── InvalidRealmModel.java │ │ │ │ │ │ ├── PojoWithRealmListOfRealmObject.java │ │ │ │ │ │ ├── RealmModelWithRealmListOfRealmModel.java │ │ │ │ │ │ ├── RealmModelWithRealmModelField.java │ │ │ │ │ │ ├── RealmObjectWithRealmListOfRealmModel.java │ │ │ │ │ │ └── RealmObjectWithRealmModelField.java │ │ │ │ │ └── realmname/ │ │ │ │ │ ├── ClassNameOverrideModulePolicy.java │ │ │ │ │ ├── ClassWithPolicy.java │ │ │ │ │ ├── ClassWithValueDefinedNames.java │ │ │ │ │ ├── CustomRealmNamesModule.java │ │ │ │ │ ├── DefaultPolicyFromModule.java │ │ │ │ │ └── FieldNameOverrideClassPolicy.java │ │ │ │ ├── instrumentation/ │ │ │ │ │ ├── ActivityLifecycle.java │ │ │ │ │ ├── Lifecycle.java │ │ │ │ │ ├── LifecycleComponentFactory.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── internal/ │ │ │ │ │ ├── AndroidCapabilitiesTest.java │ │ │ │ │ ├── JNIColumnInfoTest.java │ │ │ │ │ ├── JNINativeTest.java │ │ │ │ │ ├── JNIQueryTest.java │ │ │ │ │ ├── JNIRowTest.java │ │ │ │ │ ├── JNITableInsertTest.java │ │ │ │ │ ├── JNITableTest.java │ │ │ │ │ ├── ObserverPairListTests.java │ │ │ │ │ ├── OsListTests.java │ │ │ │ │ ├── OsObjectStoreTests.java │ │ │ │ │ ├── OsResultsTests.java │ │ │ │ │ ├── OsSharedRealmTests.java │ │ │ │ │ ├── PrimaryKeyTests.java │ │ │ │ │ ├── RealmNotifierTests.java │ │ │ │ │ ├── TableIndexAndDistinctTest.java │ │ │ │ │ ├── android/ │ │ │ │ │ │ ├── ISO8601UtilsTest.java │ │ │ │ │ │ └── JsonUtilsTest.java │ │ │ │ │ └── test/ │ │ │ │ │ └── ExtraTests.java │ │ │ │ ├── log/ │ │ │ │ │ └── RealmLogTests.java │ │ │ │ ├── migration/ │ │ │ │ │ └── MigrationPrimaryKey.java │ │ │ │ └── util/ │ │ │ │ ├── ExceptionHolder.java │ │ │ │ ├── LooperSpy.java │ │ │ │ ├── RealmBackgroundTask.java │ │ │ │ └── RealmThread.java │ │ │ ├── kotlin/ │ │ │ │ └── io/ │ │ │ │ └── realm/ │ │ │ │ ├── Decimal128Tests.kt │ │ │ │ ├── DictionaryMiscTests.kt │ │ │ │ ├── DictionaryTester.kt │ │ │ │ ├── EmbeddedObjectsTest.kt │ │ │ │ ├── GenericTester.kt │ │ │ │ ├── KotlinSchemaTests.kt │ │ │ │ ├── ManagedDictionaryTesters.kt │ │ │ │ ├── ManagedSetTester.kt │ │ │ │ ├── ModelDefaultValuesTests.kt │ │ │ │ ├── NoPKRealmModelSetTester.kt │ │ │ │ ├── NullMixedSetTester.kt │ │ │ │ ├── ObjectIdTests.kt │ │ │ │ ├── ParameterizedDictionaryTests.kt │ │ │ │ ├── ParameterizedSetTests.kt │ │ │ │ ├── RealmAnyCollectionTests.kt │ │ │ │ ├── RealmModelManagedSetTester.kt │ │ │ │ ├── SetMiscTests.kt │ │ │ │ ├── UUIDTests.kt │ │ │ │ ├── UnmanagedDictionaryTesters.kt │ │ │ │ ├── UnmanagedSetTester.kt │ │ │ │ ├── entities/ │ │ │ │ │ ├── DictionaryAllTypes.java │ │ │ │ │ ├── DictionaryContainerClass.kt │ │ │ │ │ ├── PopulatedDictionaryClass.kt │ │ │ │ │ ├── RealmAnyDefaultNonPK.kt │ │ │ │ │ ├── RealmAnyDefaultPK.kt │ │ │ │ │ ├── RealmAnyIndexed.kt │ │ │ │ │ ├── RealmAnyNotIndexed.kt │ │ │ │ │ ├── RealmAnyNotIndexedWithPK.kt │ │ │ │ │ ├── RealmAnyRealmListWithPK.kt │ │ │ │ │ ├── SetAllTypes.java │ │ │ │ │ ├── SetAllTypesPrimaryKey.java │ │ │ │ │ ├── SetContainerClass.kt │ │ │ │ │ └── embedded/ │ │ │ │ │ ├── EmbeddedSimpleChild.kt │ │ │ │ │ ├── EmbeddedSimpleListParent.kt │ │ │ │ │ ├── EmbeddedSimpleListParentWithoutPrimaryKey.kt │ │ │ │ │ ├── EmbeddedSimpleParent.kt │ │ │ │ │ ├── EmbeddedTreeLeaf.kt │ │ │ │ │ ├── EmbeddedTreeNode.kt │ │ │ │ │ ├── EmbeddedTreeParent.kt │ │ │ │ │ ├── EmbeddedWithConstructorArgs.kt │ │ │ │ │ └── SimpleEmbeddedObject.kt │ │ │ │ └── realmany/ │ │ │ │ ├── DynamicRealmAnyTests.kt │ │ │ │ ├── RealmAnyBulkInsertsTests.kt │ │ │ │ ├── RealmAnyChangeListenerTests.kt │ │ │ │ ├── RealmAnyHelper.kt │ │ │ │ ├── RealmAnyParameterizedQueryTests.kt │ │ │ │ ├── RealmAnyPrimitiveBulkInserts.kt │ │ │ │ ├── RealmAnyQueryTests.kt │ │ │ │ └── RealmAnyTests.kt │ │ │ └── res/ │ │ │ └── xml/ │ │ │ └── network_security_config.xml │ │ ├── androidTestObjectServer/ │ │ │ ├── assets/ │ │ │ │ ├── optionalsubscriptionfields.realm │ │ │ │ ├── synced_realm_e873fb25-11ef-498f-9782-3c8e1cd2a12c_no_client_id.realm │ │ │ │ └── versionTest.realm │ │ │ └── kotlin/ │ │ │ └── io/ │ │ │ └── realm/ │ │ │ ├── ApiKeyAuthTests.kt │ │ │ ├── AppConfigurationTests.kt │ │ │ ├── AppTests.kt │ │ │ ├── AppUserTests.java │ │ │ ├── CredentialsTests.kt │ │ │ ├── EmailPasswordAuthTests.kt │ │ │ ├── FunctionsTests.kt │ │ │ ├── ProgressListenerTests.kt │ │ │ ├── SchemaTests.kt │ │ │ ├── SyncedRealmMigrationTests.kt │ │ │ ├── UserProfileInfoTests.kt │ │ │ ├── admin/ │ │ │ │ └── ServerAdmin.kt │ │ │ ├── entities/ │ │ │ │ ├── DefaultSyncSchema.kt │ │ │ │ ├── DummySyncObject.kt │ │ │ │ ├── FlexSyncColor.kt │ │ │ │ ├── SyncAllTypes.kt │ │ │ │ ├── SyncAllTypesSchema.kt │ │ │ │ ├── SyncAllTypesWithFloat.kt │ │ │ │ ├── SyncColor.kt │ │ │ │ ├── SyncDog.kt │ │ │ │ ├── SyncPerson.kt │ │ │ │ ├── SyncSchemeMigration.kt │ │ │ │ ├── SyncStringOnly.kt │ │ │ │ └── SyncStringOnlyModule.kt │ │ │ ├── internal/ │ │ │ │ ├── async/ │ │ │ │ │ ├── RealmResultTaskImplTest.kt │ │ │ │ │ └── RealmStreamTaskImplTest.kt │ │ │ │ └── network/ │ │ │ │ ├── LoggingInterceptorTest.kt │ │ │ │ └── NetworkRequestTests.kt │ │ │ ├── mongodb/ │ │ │ │ ├── AppExt.kt │ │ │ │ ├── MongoClientTest.kt │ │ │ │ ├── UserTests.kt │ │ │ │ ├── push/ │ │ │ │ │ └── PushTest.kt │ │ │ │ └── sync/ │ │ │ │ ├── FlexibleSyncConfigurationTests.kt │ │ │ │ ├── FlexibleSyncIntegrationTests.kt │ │ │ │ ├── MutableSubscriptionSetTests.kt │ │ │ │ ├── ProgressTests.kt │ │ │ │ ├── SSLConfigurationTests.kt │ │ │ │ ├── SessionTests.kt │ │ │ │ ├── SubscriptionSetTests.kt │ │ │ │ ├── SubscriptionTests.kt │ │ │ │ ├── SyncConfigurationTests.kt │ │ │ │ ├── SyncExt.kt │ │ │ │ ├── SyncSessionExt.kt │ │ │ │ ├── SyncedRealmTests.kt │ │ │ │ └── TrustManagerCertificateValidationTests.java │ │ │ ├── transport/ │ │ │ │ ├── HttpNetworkTransportInterceptor.kt │ │ │ │ ├── OkHttpNetworkTransportTests.kt │ │ │ │ └── OsJavaNetworkTransportTests.kt │ │ │ └── util/ │ │ │ ├── KotlinTestUtils.kt │ │ │ ├── KotlinTestUtilsTests.kt │ │ │ └── mongodb/ │ │ │ └── CustomType.java │ │ ├── main/ │ │ │ ├── AndroidManifest.xml │ │ │ ├── cpp/ │ │ │ │ ├── .clang-format │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── io_realm_RealmQuery.cpp │ │ │ │ ├── io_realm_internal_CheckedRow.cpp │ │ │ │ ├── io_realm_internal_NativeObjectReference.cpp │ │ │ │ ├── io_realm_internal_OsCollectionChangeSet.cpp │ │ │ │ ├── io_realm_internal_OsList.cpp │ │ │ │ ├── io_realm_internal_OsMap.cpp │ │ │ │ ├── io_realm_internal_OsMapChangeSet.cpp │ │ │ │ ├── io_realm_internal_OsObject.cpp │ │ │ │ ├── io_realm_internal_OsObjectSchemaInfo.cpp │ │ │ │ ├── io_realm_internal_OsObjectStore.cpp │ │ │ │ ├── io_realm_internal_OsRealmConfig.cpp │ │ │ │ ├── io_realm_internal_OsResults.cpp │ │ │ │ ├── io_realm_internal_OsSchemaInfo.cpp │ │ │ │ ├── io_realm_internal_OsSet.cpp │ │ │ │ ├── io_realm_internal_OsSharedRealm.cpp │ │ │ │ ├── io_realm_internal_Property.cpp │ │ │ │ ├── io_realm_internal_Table.cpp │ │ │ │ ├── io_realm_internal_TableQuery.cpp │ │ │ │ ├── io_realm_internal_TestUtil.cpp │ │ │ │ ├── io_realm_internal_UncheckedRow.cpp │ │ │ │ ├── io_realm_internal_Util.cpp │ │ │ │ ├── io_realm_internal_core_NativeRealmAny.cpp │ │ │ │ ├── io_realm_internal_core_NativeRealmAnyCollection.cpp │ │ │ │ ├── io_realm_internal_objectstore_OsApp.cpp │ │ │ │ ├── io_realm_internal_objectstore_OsAppCredentials.cpp │ │ │ │ ├── io_realm_internal_objectstore_OsAsyncOpenTask.cpp │ │ │ │ ├── io_realm_internal_objectstore_OsJavaNetworkTransport.cpp │ │ │ │ ├── io_realm_internal_objectstore_OsKeyPathMapping.cpp │ │ │ │ ├── io_realm_internal_objectstore_OsMongoClient.cpp │ │ │ │ ├── io_realm_internal_objectstore_OsMongoCollection.cpp │ │ │ │ ├── io_realm_internal_objectstore_OsMongoDatabase.cpp │ │ │ │ ├── io_realm_internal_objectstore_OsMutableSubscriptionSet.cpp │ │ │ │ ├── io_realm_internal_objectstore_OsObjectBuilder.cpp │ │ │ │ ├── io_realm_internal_objectstore_OsPush.cpp │ │ │ │ ├── io_realm_internal_objectstore_OsSubscription.cpp │ │ │ │ ├── io_realm_internal_objectstore_OsSubscriptionSet.cpp │ │ │ │ ├── io_realm_internal_objectstore_OsSyncUser.cpp │ │ │ │ ├── io_realm_internal_objectstore_OsWatchStream.cpp │ │ │ │ ├── io_realm_log_RealmLog.cpp │ │ │ │ ├── io_realm_mongodb_ApiKeyAuthImpl.cpp │ │ │ │ ├── io_realm_mongodb_EmailPasswordAuthImpl.cpp │ │ │ │ ├── io_realm_mongodb_FunctionsImpl.cpp │ │ │ │ ├── io_realm_mongodb_User.cpp │ │ │ │ ├── io_realm_mongodb_mongo_iterable_AggregateIterable.cpp │ │ │ │ ├── io_realm_mongodb_mongo_iterable_FindIterable.cpp │ │ │ │ ├── io_realm_mongodb_sync_ClientResetRequiredError.cpp │ │ │ │ ├── io_realm_mongodb_sync_Sync.cpp │ │ │ │ ├── io_realm_mongodb_sync_SyncSession.cpp │ │ │ │ ├── java_accessor.hpp │ │ │ │ ├── java_binding_context.cpp │ │ │ │ ├── java_binding_context.hpp │ │ │ │ ├── java_class_global_def.cpp │ │ │ │ ├── java_class_global_def.hpp │ │ │ │ ├── java_exception_def.cpp │ │ │ │ ├── java_exception_def.hpp │ │ │ │ ├── java_network_transport.hpp │ │ │ │ ├── java_object_accessor.hpp │ │ │ │ ├── java_query_descriptor.cpp │ │ │ │ ├── java_query_descriptor.hpp │ │ │ │ ├── jni_impl/ │ │ │ │ │ ├── android_logger.cpp │ │ │ │ │ └── android_logger.hpp │ │ │ │ ├── jni_util/ │ │ │ │ │ ├── bson_util.cpp │ │ │ │ │ ├── bson_util.hpp │ │ │ │ │ ├── hack.cpp │ │ │ │ │ ├── hack.hpp │ │ │ │ │ ├── java_class.cpp │ │ │ │ │ ├── java_class.hpp │ │ │ │ │ ├── java_exception_thrower.cpp │ │ │ │ │ ├── java_exception_thrower.hpp │ │ │ │ │ ├── java_global_ref_by_copy.cpp │ │ │ │ │ ├── java_global_ref_by_copy.hpp │ │ │ │ │ ├── java_global_ref_by_move.cpp │ │ │ │ │ ├── java_global_ref_by_move.hpp │ │ │ │ │ ├── java_global_weak_ref.cpp │ │ │ │ │ ├── java_global_weak_ref.hpp │ │ │ │ │ ├── java_local_ref.hpp │ │ │ │ │ ├── java_method.cpp │ │ │ │ │ ├── java_method.hpp │ │ │ │ │ ├── jni_utils.cpp │ │ │ │ │ ├── jni_utils.hpp │ │ │ │ │ ├── log.cpp │ │ │ │ │ └── log.hpp │ │ │ │ ├── observable_collection_wrapper.hpp │ │ │ │ ├── observable_dictionary_wrapper.hpp │ │ │ │ ├── subscription_wrapper.hpp │ │ │ │ ├── utf8.hpp │ │ │ │ ├── util.cpp │ │ │ │ └── util.hpp │ │ │ └── java/ │ │ │ └── io/ │ │ │ └── realm/ │ │ │ ├── BaseRealm.java │ │ │ ├── Case.java │ │ │ ├── CollectionUtils.java │ │ │ ├── CompactOnLaunchCallback.java │ │ │ ├── DefaultCompactOnLaunchCallback.java │ │ │ ├── DynamicRealm.java │ │ │ ├── DynamicRealmObject.java │ │ │ ├── FieldAttribute.java │ │ │ ├── FrozenPendingRow.java │ │ │ ├── ImmutableRealmObjectSchema.java │ │ │ ├── ImmutableRealmSchema.java │ │ │ ├── ImportFlag.java │ │ │ ├── ManagedListOperator.java │ │ │ ├── ManagedMapManager.java │ │ │ ├── MapChangeListener.java │ │ │ ├── MapChangeSet.java │ │ │ ├── MutableRealmInteger.java │ │ │ ├── MutableRealmObjectSchema.java │ │ │ ├── MutableRealmSchema.java │ │ │ ├── ObjectChangeSet.java │ │ │ ├── OrderedCollectionChangeSet.java │ │ │ ├── OrderedRealmCollection.java │ │ │ ├── OrderedRealmCollectionChangeListener.java │ │ │ ├── OrderedRealmCollectionImpl.java │ │ │ ├── OrderedRealmCollectionSnapshot.java │ │ │ ├── ProxyState.java │ │ │ ├── ProxyUtils.java │ │ │ ├── Realm.java │ │ │ ├── RealmAny.java │ │ │ ├── RealmAnyNativeFunctionsImpl.java │ │ │ ├── RealmAnyOperator.java │ │ │ ├── RealmAsyncTask.java │ │ │ ├── RealmCache.java │ │ │ ├── RealmChangeListener.java │ │ │ ├── RealmCollection.java │ │ │ ├── RealmConfiguration.java │ │ │ ├── RealmDictionary.java │ │ │ ├── RealmFieldType.java │ │ │ ├── RealmList.java │ │ │ ├── RealmMap.java │ │ │ ├── RealmMapEntrySet.java │ │ │ ├── RealmMigration.java │ │ │ ├── RealmModel.java │ │ │ ├── RealmObject.java │ │ │ ├── RealmObjectChangeListener.java │ │ │ ├── RealmObjectSchema.java │ │ │ ├── RealmQuery.java │ │ │ ├── RealmResults.java │ │ │ ├── RealmSchema.java │ │ │ ├── RealmSet.java │ │ │ ├── SetChangeListener.java │ │ │ ├── SetChangeSet.java │ │ │ ├── SetValueOperator.java │ │ │ ├── Sort.java │ │ │ ├── TypeSelectorForMap.java │ │ │ ├── coroutines/ │ │ │ │ ├── FlowFactory.java │ │ │ │ └── RealmFlowFactory.java │ │ │ ├── exceptions/ │ │ │ │ ├── RealmError.java │ │ │ │ ├── RealmException.java │ │ │ │ ├── RealmFileException.java │ │ │ │ ├── RealmMigrationNeededException.java │ │ │ │ ├── RealmPrimaryKeyConstraintException.java │ │ │ │ └── package-info.java │ │ │ ├── internal/ │ │ │ │ ├── Capabilities.java │ │ │ │ ├── CheckedRow.java │ │ │ │ ├── ColumnIndices.java │ │ │ │ ├── ColumnInfo.java │ │ │ │ ├── EmptyLoadChangeSet.java │ │ │ │ ├── FinalizerRunnable.java │ │ │ │ ├── Freezable.java │ │ │ │ ├── IOException.java │ │ │ │ ├── IdentitySet.java │ │ │ │ ├── InvalidRow.java │ │ │ │ ├── Keep.java │ │ │ │ ├── KeepMember.java │ │ │ │ ├── ManageableObject.java │ │ │ │ ├── NativeContext.java │ │ │ │ ├── NativeObject.java │ │ │ │ ├── NativeObjectReference.java │ │ │ │ ├── ObjectServerFacade.java │ │ │ │ ├── ObservableCollection.java │ │ │ │ ├── ObservableMap.java │ │ │ │ ├── ObservableSet.java │ │ │ │ ├── ObserverPairList.java │ │ │ │ ├── OsCollection.java │ │ │ │ ├── OsCollectionChangeSet.java │ │ │ │ ├── OsList.java │ │ │ │ ├── OsMap.java │ │ │ │ ├── OsMapChangeSet.java │ │ │ │ ├── OsObject.java │ │ │ │ ├── OsObjectSchemaInfo.java │ │ │ │ ├── OsObjectStore.java │ │ │ │ ├── OsRealmConfig.java │ │ │ │ ├── OsResults.java │ │ │ │ ├── OsSchemaInfo.java │ │ │ │ ├── OsSet.java │ │ │ │ ├── OsSharedRealm.java │ │ │ │ ├── PendingRow.java │ │ │ │ ├── Property.java │ │ │ │ ├── RealmAnyNativeFunctions.java │ │ │ │ ├── RealmCore.java │ │ │ │ ├── RealmNotifier.java │ │ │ │ ├── RealmObjectProxy.java │ │ │ │ ├── RealmProxyMediator.java │ │ │ │ ├── Row.java │ │ │ │ ├── StatefulCollectionChangeSet.java │ │ │ │ ├── Table.java │ │ │ │ ├── TableQuery.java │ │ │ │ ├── TestUtil.java │ │ │ │ ├── UncheckedRow.java │ │ │ │ ├── UnmanagedSubscription.java │ │ │ │ ├── Util.java │ │ │ │ ├── android/ │ │ │ │ │ ├── AndroidCapabilities.java │ │ │ │ │ ├── AndroidRealmNotifier.java │ │ │ │ │ ├── ISO8601Utils.java │ │ │ │ │ ├── JsonUtils.java │ │ │ │ │ ├── TypeUtils.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── annotations/ │ │ │ │ │ └── ObjectServer.java │ │ │ │ ├── async/ │ │ │ │ │ ├── BadVersionException.java │ │ │ │ │ ├── BgPriorityCallable.java │ │ │ │ │ ├── BgPriorityRunnable.java │ │ │ │ │ ├── RealmAsyncTaskImpl.java │ │ │ │ │ └── RealmThreadPoolExecutor.java │ │ │ │ ├── coroutines/ │ │ │ │ │ └── InternalFlowFactory.kt │ │ │ │ ├── modules/ │ │ │ │ │ ├── CompositeMediator.java │ │ │ │ │ ├── FilterableMediator.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── objectstore/ │ │ │ │ │ ├── OsKeyPathMapping.java │ │ │ │ │ ├── OsMutableSubscriptionSet.java │ │ │ │ │ ├── OsObjectBuilder.java │ │ │ │ │ ├── OsSubscription.java │ │ │ │ │ └── OsSubscriptionSet.java │ │ │ │ ├── package-info.java │ │ │ │ └── util/ │ │ │ │ └── Pair.java │ │ │ ├── log/ │ │ │ │ ├── LogLevel.java │ │ │ │ ├── RealmLog.java │ │ │ │ ├── RealmLogger.java │ │ │ │ └── package-info.java │ │ │ ├── mongodb/ │ │ │ │ └── sync/ │ │ │ │ ├── MutableSubscriptionSet.java │ │ │ │ ├── Subscription.java │ │ │ │ └── SubscriptionSet.java │ │ │ ├── package-info.java │ │ │ └── rx/ │ │ │ ├── CollectionChange.java │ │ │ ├── ObjectChange.java │ │ │ ├── RealmObservableFactory.java │ │ │ ├── RxObservableFactory.java │ │ │ └── package-info.java │ │ ├── objectServer/ │ │ │ ├── AndroidManifest.xml │ │ │ └── java/ │ │ │ └── io/ │ │ │ └── realm/ │ │ │ ├── exceptions/ │ │ │ │ └── DownloadingRealmInterruptedException.java │ │ │ ├── internal/ │ │ │ │ ├── ErrorCategory.java │ │ │ │ ├── SyncObjectServerFacade.java │ │ │ │ ├── async/ │ │ │ │ │ ├── RealmEventStreamAsyncTaskImpl.java │ │ │ │ │ ├── RealmEventStreamTaskImpl.java │ │ │ │ │ ├── RealmResultTaskImpl.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── events/ │ │ │ │ │ ├── ChangeEvent.java │ │ │ │ │ ├── NetworkEventStream.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── jni/ │ │ │ │ │ └── JniBsonProtocol.java │ │ │ │ ├── log/ │ │ │ │ │ └── obfuscator/ │ │ │ │ │ ├── ApiKeyObfuscator.java │ │ │ │ │ ├── CustomFunctionObfuscator.java │ │ │ │ │ ├── EmailPasswordObfuscator.java │ │ │ │ │ ├── RegexPatternObfuscator.java │ │ │ │ │ └── TokenObfuscator.java │ │ │ │ ├── mongodb/ │ │ │ │ │ └── Request.java │ │ │ │ ├── network/ │ │ │ │ │ ├── LoggingInterceptor.java │ │ │ │ │ ├── MockableNetworkTransport.java │ │ │ │ │ ├── NetworkRequest.java │ │ │ │ │ ├── NetworkStateReceiver.java │ │ │ │ │ ├── OkHttpNetworkTransport.java │ │ │ │ │ ├── StreamNetworkTransport.java │ │ │ │ │ └── VoidNetworkRequest.java │ │ │ │ ├── objectserver/ │ │ │ │ │ ├── EventStream.java │ │ │ │ │ ├── SyncWorker.java │ │ │ │ │ └── Token.java │ │ │ │ └── objectstore/ │ │ │ │ ├── OsApp.java │ │ │ │ ├── OsAppCredentials.java │ │ │ │ ├── OsAsyncOpenTask.java │ │ │ │ ├── OsJavaNetworkTransport.java │ │ │ │ ├── OsMongoClient.java │ │ │ │ ├── OsMongoCollection.java │ │ │ │ ├── OsMongoDatabase.java │ │ │ │ ├── OsPush.java │ │ │ │ ├── OsSyncUser.java │ │ │ │ ├── OsWatchStream.java │ │ │ │ └── package-info.java │ │ │ └── mongodb/ │ │ │ ├── ApiKeyAuthImpl.java │ │ │ ├── App.java │ │ │ ├── AppConfiguration.java │ │ │ ├── AppException.java │ │ │ ├── AuthenticationListener.java │ │ │ ├── Credentials.java │ │ │ ├── EmailPasswordAuthImpl.java │ │ │ ├── ErrorCode.java │ │ │ ├── FunctionsImpl.java │ │ │ ├── RealmEventStreamAsyncTask.java │ │ │ ├── RealmEventStreamTask.java │ │ │ ├── RealmResultTask.java │ │ │ ├── User.java │ │ │ ├── UserIdentity.java │ │ │ ├── UserProfile.java │ │ │ ├── auth/ │ │ │ │ ├── ApiKey.java │ │ │ │ ├── ApiKeyAuth.java │ │ │ │ ├── EmailPasswordAuth.java │ │ │ │ └── GoogleAuthType.java │ │ │ ├── functions/ │ │ │ │ └── Functions.java │ │ │ ├── log/ │ │ │ │ └── obfuscator/ │ │ │ │ └── HttpLogObfuscator.java │ │ │ ├── mongo/ │ │ │ │ ├── MongoClient.java │ │ │ │ ├── MongoCollection.java │ │ │ │ ├── MongoDatabase.java │ │ │ │ ├── MongoNamespace.java │ │ │ │ ├── events/ │ │ │ │ │ ├── BaseChangeEvent.java │ │ │ │ │ └── UpdateDescription.java │ │ │ │ ├── iterable/ │ │ │ │ │ ├── AggregateIterable.java │ │ │ │ │ ├── FindIterable.java │ │ │ │ │ ├── MongoCursor.java │ │ │ │ │ └── MongoIterable.java │ │ │ │ ├── options/ │ │ │ │ │ ├── CountOptions.java │ │ │ │ │ ├── FindOneAndModifyOptions.java │ │ │ │ │ ├── FindOptions.java │ │ │ │ │ ├── InsertManyResult.java │ │ │ │ │ └── UpdateOptions.java │ │ │ │ └── result/ │ │ │ │ ├── DeleteResult.java │ │ │ │ ├── InsertOneResult.java │ │ │ │ └── UpdateResult.java │ │ │ ├── push/ │ │ │ │ └── Push.java │ │ │ └── sync/ │ │ │ ├── AutomaticClientResetStrategy.java │ │ │ ├── ClientResetRequiredError.java │ │ │ ├── ConnectionListener.java │ │ │ ├── ConnectionState.java │ │ │ ├── DiscardUnsyncedChangesStrategy.java │ │ │ ├── ManuallyRecoverUnsyncedChangesStrategy.java │ │ │ ├── Progress.java │ │ │ ├── ProgressListener.java │ │ │ ├── ProgressMode.java │ │ │ ├── RecoverOrDiscardUnsyncedChangesStrategy.java │ │ │ ├── RecoverUnsyncedChangesStrategy.java │ │ │ ├── Sync.java │ │ │ ├── SyncClientResetStrategy.java │ │ │ ├── SyncConfiguration.java │ │ │ ├── SyncSession.java │ │ │ └── package-info.java │ │ ├── overview.html │ │ ├── syncIntegrationTest/ │ │ │ ├── assets/ │ │ │ │ ├── trusted_ca.pem │ │ │ │ └── untrusted_ca.pem │ │ │ ├── java/ │ │ │ │ └── io/ │ │ │ │ └── realm/ │ │ │ │ ├── BaseIntegrationTest.java │ │ │ │ ├── IsolatedIntegrationTests.java │ │ │ │ ├── StandardIntegrationTest.java │ │ │ │ └── objectserver/ │ │ │ │ ├── ProcessCommitTests.java │ │ │ │ ├── model/ │ │ │ │ │ ├── PartialSyncModule.java │ │ │ │ │ ├── PartialSyncObjectA.java │ │ │ │ │ ├── PartialSyncObjectB.java │ │ │ │ │ ├── ProcessInfo.java │ │ │ │ │ └── TestObject.java │ │ │ │ ├── package-info.java │ │ │ │ └── utils/ │ │ │ │ ├── HttpUtils.java │ │ │ │ └── RemoteIntegrationTestService.java │ │ │ └── kotlin/ │ │ │ └── io/ │ │ │ └── realm/ │ │ │ ├── EncryptedSynchronizedRealmTests.kt │ │ │ ├── SyncSessionTests.kt │ │ │ └── SyncedRealmIntegrationTests.kt │ │ ├── syncTestUtils/ │ │ │ ├── java/ │ │ │ │ └── io/ │ │ │ │ └── realm/ │ │ │ │ └── objectserver/ │ │ │ │ └── utils/ │ │ │ │ ├── Constants.java │ │ │ │ ├── UserFactory.java │ │ │ │ └── UserFactoryStore.java │ │ │ └── kotlin/ │ │ │ └── io/ │ │ │ └── realm/ │ │ │ ├── RealmExt.kt │ │ │ ├── TestApp.kt │ │ │ ├── TestSyncConfigurationFactory.kt │ │ │ └── mongodb/ │ │ │ ├── SyncTestUtils.kt │ │ │ └── sync/ │ │ │ └── SyncConfigurationExt.kt │ │ ├── testObjectServer/ │ │ │ └── kotlin/ │ │ │ └── io/ │ │ │ └── realm/ │ │ │ ├── ObfuscatorHelper.kt │ │ │ ├── internal/ │ │ │ │ └── log/ │ │ │ │ └── obfuscator/ │ │ │ │ ├── ApiKeyObfuscatorTest.kt │ │ │ │ ├── CustomFunctionObfuscatorTest.kt │ │ │ │ ├── EmailPasswordObfuscatorTest.kt │ │ │ │ └── TokenObfuscatorTest.kt │ │ │ └── mongodb/ │ │ │ └── log/ │ │ │ └── obfuscator/ │ │ │ └── HttpLogObfuscatorTest.kt │ │ └── testUtils/ │ │ ├── java/ │ │ │ └── io/ │ │ │ └── realm/ │ │ │ ├── TestHelper.java │ │ │ ├── TestRealmConfigurationFactory.java │ │ │ ├── entities/ │ │ │ │ ├── AllTypes.java │ │ │ │ ├── AllTypesModelModule.java │ │ │ │ ├── AllTypesPrimaryKey.java │ │ │ │ ├── AnnotationIndexTypes.java │ │ │ │ ├── BacklinkWithOverridenNames.java │ │ │ │ ├── BacklinksSource.java │ │ │ │ ├── BacklinksTarget.java │ │ │ │ ├── Cat.java │ │ │ │ ├── Dog.java │ │ │ │ ├── DogPrimaryKey.java │ │ │ │ ├── KeywordFieldNames.java │ │ │ │ ├── NullTypes.java │ │ │ │ ├── Owner.java │ │ │ │ ├── PrimaryKeyAsBoxedByte.java │ │ │ │ ├── PrimaryKeyAsBoxedInteger.java │ │ │ │ ├── PrimaryKeyAsBoxedLong.java │ │ │ │ ├── PrimaryKeyAsBoxedShort.java │ │ │ │ ├── PrimaryKeyAsByte.java │ │ │ │ ├── PrimaryKeyAsInteger.java │ │ │ │ ├── PrimaryKeyAsLong.java │ │ │ │ ├── PrimaryKeyAsShort.java │ │ │ │ ├── PrimaryKeyAsString.java │ │ │ │ ├── PrimaryKeyAsUUID.java │ │ │ │ ├── PrimaryKeyRequiredAsBoxedByte.java │ │ │ │ ├── PrimaryKeyRequiredAsBoxedInteger.java │ │ │ │ ├── PrimaryKeyRequiredAsBoxedLong.java │ │ │ │ ├── PrimaryKeyRequiredAsBoxedShort.java │ │ │ │ └── SyncByteArray.java │ │ │ ├── objectid/ │ │ │ │ └── NullPrimaryKey.java │ │ │ ├── rule/ │ │ │ │ ├── RunInLooperThread.java │ │ │ │ ├── RunTestInLooperThread.java │ │ │ │ ├── RunTestWithRemoteService.java │ │ │ │ └── RunWithRemoteService.java │ │ │ └── services/ │ │ │ ├── RemoteProcessService.java │ │ │ └── RemoteTestService.java │ │ └── kotlin/ │ │ └── io/ │ │ └── realm/ │ │ ├── entities/ │ │ │ └── AllKotlinTypes.kt │ │ └── rule/ │ │ └── BlockingLooperThread.kt │ ├── settings.gradle │ ├── templates/ │ │ ├── README.md │ │ └── redirect.html.template │ └── tools/ │ ├── backlink-ut-source/ │ │ ├── missingField/ │ │ │ ├── source/ │ │ │ │ └── io/ │ │ │ │ └── realm/ │ │ │ │ └── entities/ │ │ │ │ ├── BacklinksMissingFieldSource.java │ │ │ │ ├── BacklinksMissingFieldSourceModule.java │ │ │ │ ├── BacklinksMissingFieldTarget.java │ │ │ │ └── BacklinksMissingFieldTargetModule.java │ │ │ └── target/ │ │ │ └── io/ │ │ │ └── realm/ │ │ │ └── entities/ │ │ │ ├── BacklinksMissingFieldSource.java │ │ │ ├── BacklinksMissingFieldSourceModule.java │ │ │ ├── BacklinksMissingFieldTarget.java │ │ │ └── BacklinksMissingFieldTargetModule.java │ │ └── wrongType/ │ │ ├── source/ │ │ │ └── io/ │ │ │ └── realm/ │ │ │ └── entities/ │ │ │ ├── BacklinksWrongTypeSource.java │ │ │ ├── BacklinksWrongTypeSourceModule.java │ │ │ ├── BacklinksWrongTypeTarget.java │ │ │ └── BacklinksWrongTypeTargetModule.java │ │ └── target/ │ │ └── io/ │ │ └── realm/ │ │ └── entities/ │ │ ├── BacklinksWrongTypeSource.java │ │ ├── BacklinksWrongTypeSourceModule.java │ │ ├── BacklinksWrongTypeTarget.java │ │ └── BacklinksWrongTypeTargetModule.java │ └── bin/ │ └── cgen ├── realm-annotations/ │ ├── build.gradle │ ├── gradle/ │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradle.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle │ └── src/ │ └── main/ │ └── java/ │ └── io/ │ └── realm/ │ └── annotations/ │ ├── Beta.java │ ├── Ignore.java │ ├── Index.java │ ├── LinkingObjects.java │ ├── PrimaryKey.java │ ├── RealmClass.java │ ├── RealmField.java │ ├── RealmModule.java │ ├── RealmNamingPolicy.java │ └── Required.java ├── realm-transformer/ │ ├── build.gradle │ ├── gradle/ │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradle.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── io/ │ │ │ └── realm/ │ │ │ ├── gradle/ │ │ │ │ └── RealmPluginExtension.java │ │ │ └── transformer/ │ │ │ └── Utils.java │ │ ├── kotlin/ │ │ │ └── io/ │ │ │ └── realm/ │ │ │ ├── analytics/ │ │ │ │ ├── AnalyticsData.kt │ │ │ │ ├── ComputerIdentifierGenerator.kt │ │ │ │ ├── RealmAnalytics.kt │ │ │ │ └── UrlEncodedAnalytics.kt │ │ │ └── transformer/ │ │ │ ├── ByteCodeModifier.kt │ │ │ ├── ManagedClassPool.kt │ │ │ ├── RealmTransformer.kt │ │ │ ├── Stopwatch.kt │ │ │ ├── build/ │ │ │ │ ├── BuildTemplate.kt │ │ │ │ ├── FullBuild.kt │ │ │ │ └── IncrementalBuild.kt │ │ │ └── ext/ │ │ │ ├── CtClassExt.kt │ │ │ └── ProjectExt.kt │ │ └── templates/ │ │ └── Version.java │ └── test/ │ └── kotlin/ │ └── io/ │ └── realm/ │ └── transformer/ │ └── ByteCodeModifierTest.kt ├── settings.gradle ├── tools/ │ ├── analyze_realm_metrics.sh │ ├── build-id.py │ ├── buildids.txt │ ├── fix-h1s.sh │ ├── publish_release.sh │ ├── realm-cli.sh │ ├── release.sh │ ├── sync_test_server/ │ │ ├── Dockerfile │ │ ├── app_config_generator.sh │ │ ├── app_template/ │ │ │ ├── auth_providers/ │ │ │ │ ├── anon-user.json │ │ │ │ ├── api-key.json │ │ │ │ ├── custom-function.json │ │ │ │ └── local-userpass.json │ │ │ ├── config.json │ │ │ ├── functions/ │ │ │ │ ├── authFunc/ │ │ │ │ │ ├── config.json │ │ │ │ │ └── source.js │ │ │ │ ├── authorizedOnly/ │ │ │ │ │ ├── config.json │ │ │ │ │ └── source.js │ │ │ │ ├── canReadPartition/ │ │ │ │ │ ├── config.json │ │ │ │ │ └── source.js │ │ │ │ ├── canWritePartition/ │ │ │ │ │ ├── config.json │ │ │ │ │ └── source.js │ │ │ │ ├── confirmFunc/ │ │ │ │ │ ├── config.json │ │ │ │ │ └── source.js │ │ │ │ ├── error/ │ │ │ │ │ ├── config.json │ │ │ │ │ └── source.js │ │ │ │ ├── firstArg/ │ │ │ │ │ ├── config.json │ │ │ │ │ └── source.js │ │ │ │ ├── null/ │ │ │ │ │ ├── config.json │ │ │ │ │ └── source.js │ │ │ │ ├── resetFunc/ │ │ │ │ │ ├── config.json │ │ │ │ │ └── source.js │ │ │ │ ├── sum/ │ │ │ │ │ ├── config.json │ │ │ │ │ └── source.js │ │ │ │ ├── testAuthFunc/ │ │ │ │ │ ├── config.json │ │ │ │ │ └── source.js │ │ │ │ ├── triggerClientReset/ │ │ │ │ │ ├── config.json │ │ │ │ │ └── source.js │ │ │ │ └── void/ │ │ │ │ ├── config.json │ │ │ │ └── source.js │ │ │ ├── graphql/ │ │ │ │ └── config.json │ │ │ ├── secrets.json │ │ │ └── services/ │ │ │ ├── BackingDB/ │ │ │ │ ├── config.json │ │ │ │ └── rules/ │ │ │ │ ├── test_data.EmbeddedSimpleParent.json │ │ │ │ ├── test_data.FlexSyncColor.json │ │ │ │ ├── test_data.SyncColor.json │ │ │ │ ├── test_data.SyncDog.json │ │ │ │ ├── test_data.SyncPerson.json │ │ │ │ ├── test_data.custom_user_data.json │ │ │ │ ├── test_data.mongo_data.json │ │ │ │ └── test_data.mongo_data_alt.json │ │ │ └── gcm/ │ │ │ └── config.json │ │ ├── bind_android_ports.sh │ │ ├── keys/ │ │ │ ├── HowToGenerateKey.txt │ │ │ ├── android_test_certificate.crt │ │ │ ├── private.pem │ │ │ ├── public.pem │ │ │ └── test_token.json │ │ ├── mongodb-realm-command-server.js │ │ ├── start_local_server.sh │ │ ├── start_server.sh │ │ ├── stop_local_server.sh │ │ └── stop_server.sh │ ├── unroll_stacktrace.sh │ └── update_gradle_wrapper.sh └── version.txt ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: General Questions and Inquiries url: https://www.mongodb.com/community/forums/tags/c/realm-sdks/58/java about: Please ask general design/architecture questions in the community forums. - name: MongoDB Atlas Device Sync Production Issues url: https://support.mongodb.com/ about: Please report urgent production issues to the support portal directly. ================================================ FILE: .github/advanced-issue-labeler.yml ================================================ # NOTE: This is a common file that is overwritten by realm/ci-actions sync service # and should only be modified in that repository. # syntax - https://github.com/redhat-plumbers-in-action/advanced-issue-labeler#policy # Below keys map from the option used in issue form dropdowns to issue labels # Limitation: # Currently it's not possible to use strings containing ,␣ in the dropdown menus in the issue forms. --- policy: - template: [bug.yml, feature.yml] section: - id: [frequency] label: - name: 'Frequency:Once' keys: ['Once'] - name: 'Frequency:Sometimes' keys: ['Sometimes'] - name: 'Frequency:Always' keys: ['Always'] - id: [repro] label: - name: 'Repro:Always' keys: ['Always'] - name: 'Repro:Sometimes' keys: ['Sometimes'] - name: 'Repro:No' keys: ['No'] - id: [sync, flavour, services] block-list: [] label: - name: 'SDK-Use:Local' keys: ['Local Database only'] - name: 'SDK-Use:Sync' keys: ['Atlas Device Sync'] - name: 'SDK-Use:Services' keys: ['Atlas App Services: Function or GraphQL or DataAPI etc'] - name: ['SDK-Use:All'] keys: ['Both Atlas Device Sync and Atlas App Services'] - id: [encryption] block-list: [] label: - name: 'Encryption:On' keys: ['Yes'] - name: 'Encryption:Off' keys: ['No'] - id: [app-type] block-list: [] label: - name: 'App-type:Unity' keys: ['Unity'] - name: 'App-type:Xamarin' keys: ['Xamarin'] - name: 'App-type:WPF' keys: ['WPF'] - name: 'App-type:Console' keys: ['Console or Server'] - name: 'App-type:Other' keys: ['Other'] - id: [importance] block-list: [] label: - name: 'Importance:Dealbraker' keys: ['Dealbreaker'] - name: 'Importance:Major' keys: ['Would be a major improvement'] - name: 'Importance:Workaround' keys: ['I would like to have it but have a workaround'] - name: 'Importance:Nice' keys: ['Fairly niche but nice to have anyway'] ================================================ FILE: .github/auto_assign.yml ================================================ addAssignees: author addReviewers: false runOnDraft: true ================================================ FILE: .github/no-response.yml ================================================ # Configuration for probot-no-response - https://github.com/probot/no-response # Number of days of inactivity before an Issue is closed for lack of response daysUntilClose: 14 # Label requiring a response responseRequiredLabel: More-information-needed # Comment to post when closing an Issue for lack of response. Set to `false` to disable closeComment: > This issue has been automatically closed because there has been no response to our request for more information from the original author. With only the information that is currently in the issue, we don't have enough information to take action. Please reach out if you have or find the answers we need so that we can investigate further. ================================================ FILE: .github/workflows/Issue-Needs-Attention.yml ================================================ # NOTE: This is a common file that is overwritten by realm/ci-actions sync service # and should only be modified in that repository. name: Issue Needs Attention # This workflow is triggered on issue comments. on: issue_comment: types: created jobs: applyNeedsAttentionLabel: uses: realm/ci-actions/.github/workflows/issue-needs-attention.yml@main ================================================ FILE: .github/workflows/auto-assign.yml ================================================ # NOTE: This is a common file that is overwritten by realm/ci-actions sync service # and should only be modified in that repository. name: 'Auto Assign' on: pull_request: types: [opened] jobs: add-assignee: runs-on: ubuntu-latest steps: - uses: kentaro-m/auto-assign-action@248761c4feb3917c1b0444e33fad1a50093b9847 ================================================ FILE: .github/workflows/check-changelog.yml ================================================ # NOTE: This is a common file that is overwritten by realm/ci-actions sync service # and should only be modified in that repository. name: "Check Changelog" on: pull_request: types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] jobs: changelog: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@8230315d06ad95c617244d2f265d237a1682d445 with: submodules: false - name: Enforce Changelog uses: dangoslen/changelog-enforcer@c0b9fd225180a405c5f21f7a74b99e2eccc3e951 with: skipLabels: no-changelog missingUpdateErrorMessage: Please add an entry in CHANGELOG.md or apply the 'no-changelog' label to skip this check. ================================================ FILE: .github/workflows/check-pr-title.yml ================================================ # NOTE: This is a common file that is overwritten by realm/ci-actions sync service # and should only be modified in that repository. name: "Check PR Title" on: pull_request: types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled, converted_to_draft, edited] jobs: check-pr-title: name: Check PR Title runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: false - name: Enforce PR title uses: realm/ci-actions/title-checker@main with: regex: R[A-Z]{2,6}-[0-9]{1,6} error-hint: Invalid PR title. Make sure it's prefixed with the JIRA ticket the PR addresses or add the no-jira-ticket label. ignore-labels: 'no-jira-ticket' ================================================ FILE: .github/workflows/gradle-wrapper-validation.yml ================================================ name: "Validate Gradle Wrapper" on: [push, pull_request] permissions: contents: read # to fetch code (actions/checkout) jobs: validation: name: "Validation" runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: gradle/wrapper-validation-action@v1 ================================================ FILE: .github/workflows/issue-labeler.yml ================================================ # NOTE: This is a common file that is overwritten by realm/ci-actions sync service # and should only be modified in that repository. # See configuration in .github/advanced-issue-labeler.yml name: Issue labeler (policy) on: issues: types: [ opened ] jobs: label-issues-policy: runs-on: ubuntu-latest permissions: issues: write strategy: matrix: template: [ bug.yml, feature.yml ] steps: - uses: actions/checkout@v4 - name: Parse issue form uses: stefanbuck/github-issue-parser@c1a559d78bfb8dd05216dab9ffd2b91082ff5324 # v3.0.1 id: issue-parser with: template-path: .github/ISSUE_TEMPLATE/${{ matrix.template }} - name: Set labels based on policy uses: redhat-plumbers-in-action/advanced-issue-labeler@6ee6fddfd744ee26b977e6a0436916f256896971 # v2.0.3 with: issue-form: ${{ steps.issue-parser.outputs.jsonString }} template: ${{ matrix.template }} token: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/lock-threads.yml ================================================ name: 'Lock Threads' on: schedule: - cron: '0 * * * *' workflow_dispatch: permissions: issues: write pull-requests: write discussions: write concurrency: group: lock-threads jobs: action: runs-on: ubuntu-latest steps: - uses: dessant/lock-threads@v5 with: issue-inactive-days: 30 pr-inactive-days: 30 log-output: true ================================================ FILE: .github/workflows/no-response.yml ================================================ # NOTE: This is a common file that is overwritten by realm/ci-actions sync service # and should only be modified in that repository. name: No Response # Both `issue_comment` and `scheduled` event types are required for this Action # to work properly. on: issue_comment: types: [created] schedule: # Schedule at 00:00 every day - cron: '0 0 * * *' jobs: noResponse: runs-on: ubuntu-latest steps: - uses: lee-dohm/no-response@v0.5.0 with: token: ${{ github.token }} responseRequiredLabel: More-information-needed ================================================ FILE: .gitignore ================================================ # Gradle build artifacts build realm/build !realm-transformer/src/main/kotlin/io/realm/transformer/build # Gradle cache .gradle # Gradle local properties local.properties # Core core core-* realm-sync-android-* !realm/realm-library/src/main/java/io/realm/internal/core/ # Android Studio .idea *.iml # DS_Store .DS_Store */.DS_Store # JNI libs realm_version_check.timestamp # Build artifacts *.so *.d *.o # Backup files *.bak *~ # Navigation Editor .navigation # Distribution files realm*.jar realm*.aar distribution/*/realm distribution/javadoc distribution/version.txt distribution/RealmGridViewExample/app/src distribution/RealmIntroExample/app/src distribution/RealmMigrationExample/app/src # Generated JNI headers realm/realm-library/src/main/cpp/jni_include # Downloaded core realm/realm-library/distribution # Cmake output realm/realm-library/.externalNativeBuild realm/realm-library/.cxx ================================================ FILE: .gitmodules ================================================ [submodule "realm/realm-library/src/main/cpp/realm-core"] path = realm/realm-library/src/main/cpp/realm-core url = https://github.com/realm/realm-core.git ================================================ FILE: CHANGELOG.md ================================================ ## 11.0.0 (2024-09-DD) ### Breaking Changes * None. ### Enhancements * None. ### Fixed * None. ### Compatibility * File format: Generates Realms with format v23. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 13.0.0 or above is required to open Realms created by this version. * Gradle 7.5 and above. * Android Gradle Plugin 7.4.0 and above. ### Internal * Updated links to new documentation URL. ## 10.19.0 (2024-09-13) ### Breaking Changes * None. ### Enhancements * None. ### Fixed * Use 16 KB ELF packaging for native artifacts produced by `realm-library`, allowing them to be loaded on devices with 16 KB memory page sizes. (Issue [#7894](https://github.com/realm/realm-java/issues/7894)) ### Compatibility * File format: Generates Realms with format v23. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 13.0.0 or above is required to open Realms created by this version. * Gradle 7.5 and above. * Android Gradle Plugin 7.4.0 and above. ### Internal * Updated links to new documentation URL. ## 10.18.0 (2024-02-06) ### Breaking Changes * None. ### Enhancements * None. ### Fixed * None. ### Compatibility * File format: Generates Realms with format v23. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 13.0.0 or above is required to open Realms created by this version. * Gradle 7.5 and above. * Android Gradle Plugin 7.4.0 and above. ### Internal * Updated to Realm Core 13.26.0, commit 5533505d18fda93a7a971d58a191db5005583c92. * Updated to CMake 3.27.7. ## 10.17.0 (2023-10-13) This release upgrades the Sync metadata in a way that is not compatible with older versions. To downgrade a Sync app from this version, you'll need to manually delete the metadata folder located at $[SYNC-ROOT-DIRECTORY]/mongodb-realm/[APP-ID]/server-utility/metadata/. This will log out all users. ### Breaking Changes * None. ### Enhancements * [RealmApp] Simplified the number of error codes in `ErrorCode`. A number of enum entries have been deprecated. (Issue [#7837](https://github.com/realm/realm-java/pull/7837)). ### Fixed * Rare corruption causing 'Invalid streaming format cookie'-exception. Typically following compact, convert or copying to a new file. (Issue [#7775](https://github.com/realm/realm-java/issues/7775), since v10.13.0 (Core v12.12.0)) * [RealmApp] Crash when opening a Realm with a proxy enabled. (Issue [#7828](https://github.com/realm/realm-java/issues/7828)) ### Compatibility * File format: Generates Realms with format v23. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 13.0.0 or above is required to open Realms created by this version. * Gradle 7.5 and above. * Android Gradle Plugin 7.4.0 and above. ### Internal * Updated to Realm Core 13.23.0, commit 5abbf7f10fb3ef6bd622877cc840ada804bccb89. ## 10.16.2 (2023-10-12) ### Breaking Changes * None. ### Enhancements * None. ### Fixed * Realm objects accessors behave as an unmanaged object after an incremental build. (Issue [#7844](https://github.com/realm/realm-java/pull/7844)) * [RealmApp] Crash when opening a Realm with a proxy enabled. (Issue [#7828](https://github.com/realm/realm-java/issues/7828)) * [RealmApp] It was possible to create a `User` object with invalid state that would throw a `NullPointerException` when accessed. (Issue [#7847](https://github.com/realm/realm-java/issues/7847)) ### Compatibility * File format: Generates Realms with format v23. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 13.0.0 or above is required to open Realms created by this version. * Gradle 7.5 and above. * Android Gradle Plugin 7.4.0 and above. ### Internal * None. ## 10.16.1 (2023-06-26) ### Breaking Changes * None. ### Enhancements * None. ### Fixed * [RealmApp] Sync errors could return the error code UNKNOWN instead of the actual error code. (Issue [#7823](https://github.com/realm/realm-java/issues/7823)) ### Compatibility * File format: Generates Realms with format v23. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 13.0.0 or above is required to open Realms created by this version. * Gradle 7.5 and above. * Android Gradle Plugin 7.4.0 and above. ### Internal * None. ## 10.16.0 (2023-06-02) ### Breaking Changes * None. ### Enhancements * Add support for sorting query results on dictionary values through `RealmQuery.rawPredicate(" SORT([''] )")` (Issue [#7817](https://github.com/realm/realm-java/issues/7817)). * Improve performance of equality queries on a non-indexed mixed property by about 30%. (Core Issue [#6506](https://github.com/realm/realm-core/issues/6506)) * [RealmApp] Support for migrating from Partition-based to Flexible Sync automatically on the device if the server has migrated to Flexible Sync. (Core Issue [#6554](https://github.com/realm/realm-core/issues/6554)) ### Fixed * Add support for incremental builds on the bytecode transformation with the new AGP transform API. Incremental builds can be disabled by setting the gradle property `io.realm.disableIncrementalBuilds` to `true`. (Issue [#7802](https://github.com/realm/realm-java/issues/7802)) ### Compatibility * File format: Generates Realms with format v23. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 13.0.0 or above is required to open Realms created by this version. * Gradle 7.5 and above. * Android Gradle Plugin 7.4.0 and above. ### Internal * Updated to Realm Core 13.13.0, commit 79183be6417821431fff44a6d416a68664957c66. ## 10.15.1 (2023-05-20) ### Breaking Changes * None. ### Enhancements * None. ### Fixed * Building with Realm on Java 17 would fail with `java.lang.IllegalAccessError: class io.realm.processor.Utils (in unnamed module @0x5316ec7f) cannot access class com.sun.tools.javac.code.Symbol$ClassSymbol`. (Issue [#7799](https://github.com/realm/realm-java/issues/7799)) ### Compatibility * File format: Generates Realms with format v23. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 13.0.0 or above is required to open Realms created by this version. * Gradle 7.5 and above. * Android Gradle Plugin 7.4.0 and above. ### Internal * Updated to Google Compile Testing 0.21.0. * Updated Annotation Processor to use Java 11 reflection API's. ## 10.15.0 (2023-04-19) ### Breaking Changes * Minimum version of Android Gradle Plugin has been raised to 7.4. * Minimum version of Java has been raised to 11. * Minimum supported version of Gradle has been raised to 7.5. ### Enhancements * None. ### Fixed * Building with Realm on Java 17 would fail with `java.lang.IllegalAccessError: class io.realm.processor.Utils (in unnamed module @0x5316ec7f) cannot access class com.sun.tools.javac.code.Symbol$ClassSymbol`. (Issue [#7799](https://github.com/realm/realm-java/issues/7799)) ### Compatibility * File format: Generates Realms with format v23. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 13.0.0 or above is required to open Realms created by this version. * Gradle 7.5 and above. * Android Gradle Plugin 7.4.0 and above. ### Internal * None. ## 10.14.0-transformer-api (2023-04-14) This release will bump the Realm file format from version 22 to 23. Opening a file with an older format will automatically upgrade it. Downgrading to a previous file format is not possible. ### Breaking Changes * [RealmApp] As a result of a refactor on the some error codes and categories have been deleted and new ones have been added, see PR for more details. ([#7760](https://github.com/realm/realm-java/pull/7760)) ### Enhancements * Updated OpenSSL from 1.1.1n to 3.0.8. * Realm will now use a lot less memory and disk space when different versions of realm objects are used. (Core Issue [#5440](https://github.com/realm/realm-core/issues/5440)) * Realm will now use a lot less memory and disk space when different versions of realm objects are used. (Core Issue [#5440](https://github.com/realm/realm-core/issues/5440)) * Realm will now continuously track and reduce the size of the Realm file when it is in use rather that only when opening the file with Configuration.compactOnLaunch enabled. (Core Issue [#5754](https://github.com/realm/realm-core/issues/5754)) * Multiple processes can now access the same encrypted Realm instead of throwing `Encrypted interprocess sharing is currently unsupported`. (Core Issue [#1845](https://github.com/realm/realm-core/issues/1845)) ### Fixed * Set consider string and binary data equivalent. This could cause the client to be inconsistent with the server if a string and some binary data with equivalent content was inserted from Atlas. ([#4860](https://github.com/realm/realm-core/issues/4860), since v11.0.0) * Fixed wrong assertion on query error that could result in a crash. ([#6038](https://github.com/realm/realm-core/issues/6038), since v11.7.0) * Fixed a bug that would result in `RealmDictionary` not being able to find `double` values due not taking the precision of the input parameter into consideration when using `RealmDictionary.contains`. * Not possible to open an encrypted file on a device with a page size bigger than the one on which the file was produced. ([#8030](https://github.com/realm/realm-swift/issues/8030), since v12.11.0) * Fix no notification for write transaction that contains only change to backlink property. ([#4994](https://github.com/realm/realm-core/issues/4994), since v11.4.1) ### Compatibility * File format: Generates Realms with format v23. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 11.0.0-alpha.0 or above is required to open Realms created by this version. ### Internal * Updated to Realm Core 13.9.0, commit 063927de66f79a0afffbbe36c0bb14d27deba8f2. ## 10.14.0 (2023-04-13) This release will bump the Realm file format from version 22 to 23. Opening a file with an older format will automatically upgrade it. Downgrading to a previous file format is not possible. ### Breaking Changes * [RealmApp] As a result of a refactor on the some error codes and categories have been deleted and new ones have been added, see PR for more details. ([#7760](https://github.com/realm/realm-java/pull/7760)) ### Enhancements * Updated OpenSSL from 1.1.1n to 3.0.8. * Realm will now use a lot less memory and disk space when different versions of realm objects are used. (Core Issue [#5440](https://github.com/realm/realm-core/issues/5440)) * Realm will now continuously track and reduce the size of the Realm file when it is in use rather that only when opening the file with Configuration.compactOnLaunch enabled. (Core Issue [#5754](https://github.com/realm/realm-core/issues/5754)) * Multiple processes can now access the same encrypted Realm instead of throwing `Encrypted interprocess sharing is currently unsupported`. (Core Issue [#1845](https://github.com/realm/realm-core/issues/1845)) ### Fixed * Set consider string and binary data equivalent. This could cause the client to be inconsistent with the server if a string and some binary data with equivalent content was inserted from Atlas. ([#4860](https://github.com/realm/realm-core/issues/4860), since v11.0.0) * Fixed wrong assertion on query error that could result in a crash. ([#6038](https://github.com/realm/realm-core/issues/6038), since v11.7.0) * Fixed a bug that would result in `RealmDictionary` not being able to find `double` values due not taking the precision of the input parameter into consideration when using `RealmDictionary.contains`. * Not possible to open an encrypted file on a device with a page size bigger than the one on which the file was produced. ([#8030](https://github.com/realm/realm-swift/issues/8030), since v12.11.0) * Fix no notification for write transaction that contains only change to backlink property. ([#4994](https://github.com/realm/realm-core/issues/4994), since v11.4.1) ### Compatibility * File format: Generates Realms with format v23. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 11.0.0-alpha.0 or above is required to open Realms created by this version. ### Internal * Updated to Realm Core 13.9.0, commit 063927de66f79a0afffbbe36c0bb14d27deba8f2. ## 10.13.3-transformer-api (2023-03-16) ### Enhancements * None. ### Fixed * Added support for automatic handling of orphan embedded objects after migrating regular object properties to become embedded objects. (Issue [#7769](https://github.com/realm/realm-java/issues/7769)). * Unit tests not being executed. (Issue [#7771](https://github.com/realm/realm-java/issues/7771)) * Instrumented unit tests failed to execute because of the Realm dependencies being missing. (Issue [#7736](https://github.com/realm/realm-java/issues/7736)) ### Compatibility * File format: Generates Realms with format v22. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 11.0.0-alpha.0 or above is required to open Realms created by this version. ### Internal * None. ## 10.13.2-transformer-api (2023-01-27) ### Enhancements * None. ### Fixed * Fix zip path separator for transformer on Windows (Issue [#7757](https://github.com/realm/realm-java/issues/7757)) ### Compatibility * File format: Generates Realms with format v22. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 11.0.0-alpha.0 or above is required to open Realms created by this version. ### Internal * None. ## 10.13.1-transformer-api (2023-01-16) ### Enhancements * None. ### Fixed * Add support for Gradle configuration cache. (Issue [#7299](https://github.com/realm/realm-java/issues/7299)) ### Compatibility * File format: Generates Realms with format v22. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 11.0.0-alpha.0 or above is required to open Realms created by this version. ### Internal * None. ### Credits * Thanks to @pstavytskyi-turo for adding support for Gradle configuration cache. (Issue [#7299](https://github.com/realm/realm-java/issues/7299)) ## 10.13.1 (2023-03-16) ### Enhancements * None. ### Fixed * Added support for automatic handling of orphan embedded objects after migrating regular object properties to become embedded objects. (Issue [#7769](https://github.com/realm/realm-java/issues/7769)). ### Compatibility * File format: Generates Realms with format v22. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 11.0.0-alpha.0 or above is required to open Realms created by this version. ### Internal * None. ## 10.13.0-transformer-api (2012-12-12) ### Enhancements * [RealmApp] Added option for working with Device Sync from an internal network. `SyncConfiguration.trustedRootCA(assetPath)` can embed a custom certificate in the app that will be used by Sync. (Issue [#7739](https://github.com/realm/realm-java/pull/7739)). * [RealmApp] Added option for working with Device Sync from an internal network. `SyncConfiguration.disableSSLVerification()` makes it possible to turn off local SSL validation. (Issue [#7739](https://github.com/realm/realm-java/pull/7739)). ### Fixed * Fixed database corruption and encryption issues on apple platforms. (Issue [#5076](https://github.com/realm/realm-js/issues/5076)) * [Sync] Bootstraps will not be applied in a single write transaction - they will be applied 1MB of changesets at a time. (Issue [#5999](https://github.com/realm/realm-core/pull/5999)). * [Sync] Fixed a race condition which could result in operation cancelled errors being delivered to `Realm.open` rather than the actual sync error which caused things to fail. (Issue [#5968](https://github.com/realm/realm-core/pull/5968)). ### Compatibility * File format: Generates Realms with format v22. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 11.0.0-alpha.0 or above is required to open Realms created by this version. ### Internal * Updated to Realm Core 12.13.0, commit b77443ca7fa25407869ca537bf3ae912b1427bff. ## 10.13.0 (2022-12-05) ### Enhancements * [RealmApp] Added option for working with Device Sync from an internal network. `SyncConfiguration.trustedRootCA(assetPath)` can embed a custom certificate in the app that will be used by Sync. (Issue [#7739](https://github.com/realm/realm-java/pull/7739)). * [RealmApp] Added option for working with Device Sync from an internal network. `SyncConfiguration.disableSSLVerification()` makes it possible to turn off local SSL validation. (Issue [#7739](https://github.com/realm/realm-java/pull/7739)). ### Fixed * Fixed database corruption and encryption issues on apple platforms. (Issue [#5076](https://github.com/realm/realm-js/issues/5076)) * [Sync] Bootstraps will not be applied in a single write transaction - they will be applied 1MB of changesets at a time. (Issue [#5999](https://github.com/realm/realm-core/pull/5999)). * [Sync] Fixed a race condition which could result in operation cancelled errors being delivered to `Realm.open` rather than the actual sync error which caused things to fail. (Issue [#5968](https://github.com/realm/realm-core/pull/5968)). ### Compatibility * File format: Generates Realms with format v22. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 11.0.0-alpha.0 or above is required to open Realms created by this version. ### Internal * Updated to Realm Core 12.13.0, commit b77443ca7fa25407869ca537bf3ae912b1427bff. ## 10.12.0-transformer-api (2022-09-28) ### Breaking Changes * Only works with Android Gradle Plugin 7.4 or newer. (Issue [#7714](https://github.com/realm/realm-java/issues/7714)) ### Enhancements * [RealmApp] Introduced `SyncSession.RecoverOrDiscardUnsyncedChangesStrategy`, an alternative automatic client reset strategy that tries to automatically recover any unsynced data from the client, and discards any unsynced data if not possible. This is now the default client reset policy if not explicitly set in the `SyncConfiguration`. * [RealmApp] Introduced `SyncSession.RecoverUnsyncedChangesStrategy`, an alternative automatic client reset strategy that tries to automatically recover any unsynced data from the client and will revert to manual client reset if not possible. * [RealmApp] Flexible sync client reset is no longer limited to `ManuallyRecoverChangesStrategy`, it now supports all available strategies: `RecoverOrDiscardUnsyncedChangesStrategy`, `RecoverUnsyncedChangesStrategy`, `DiscardUnsyncedChangesStrategy` and `ManuallyRecoverChangesStrategy`. ### Fixed * Now queries can point to fields with query language-reserved words like 'desc', 'sort', 'distinct', etc. Issue [#7705](https://github.com/realm/realm-java/issues/7705) ### Compatibility * File format: Generates Realms with format v22. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 11.0.0-alpha.0 or above is required to open Realms created by this version. ### Internal * Update to Realm Core 12.6.0, commit: 5da7744b4056ad185c025bccf0924f17f73f7a91. ## 10.12.0 (2022-09-22) ### Enhancements * [RealmApp] Introduced `SyncSession.RecoverOrDiscardUnsyncedChangesStrategy`, an alternative automatic client reset strategy that tries to automatically recover any unsynced data from the client, and discards any unsynced data if not possible. This is now the default client reset policy if not explicitly set in the `SyncConfiguration`. * [RealmApp] Introduced `SyncSession.RecoverUnsyncedChangesStrategy`, an alternative automatic client reset strategy that tries to automatically recover any unsynced data from the client and will revert to manual client reset if not possible. * [RealmApp] Flexible sync client reset is no longer limited to `ManuallyRecoverChangesStrategy`, it now supports all available strategies: `RecoverOrDiscardUnsyncedChangesStrategy`, `RecoverUnsyncedChangesStrategy`, `DiscardUnsyncedChangesStrategy` and `ManuallyRecoverChangesStrategy`. ### Fixed * Now queries can point to fields with query language-reserved words like 'desc', 'sort', 'distinct', etc. Issue [#7705](https://github.com/realm/realm-java/issues/7705) ### Compatibility * File format: Generates Realms with format v22. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 11.0.0-alpha.0 or above is required to open Realms created by this version. ### Internal * Update to Realm Core 12.6.0, commit: 5da7744b4056ad185c025bccf0924f17f73f7a91. ## 10.11.1 (2022-07-14) ### Enhancements * None ### Fixed * Fixed deadlock while trying to close all Realm instances during a manual client reset. Issue [#7696](https://github.com/realm/realm-java/pull/7696)) * [RealmApp] Throw RuntimeException if subscription set is requested and flexible sync is not enabled. (Realm Core issue [#5079](https://github.com/realm/realm-core/issues/5079)) * Adding an object to a Set, deleting the parent object, and then deleting the previously mentioned object causes crash. (Realm Core issue [#5387](https://github.com/realm/realm-core/issues/5387), since 11.0.0) * [RealmApp] The sync client may have sent a corrupted upload cursor leading to a fatal error from the server due to an uninitialized variable. ([#5460](https://github.com/realm/realm-core/pull/5460, since v11.14.0) * [RealmApp] Flexible sync would not correctly resume syncing if a bootstrap was interrupted. ([#5466](https://github.com/realm/realm-core/pull/5466, since v11.8.0) * [RealmApp] Flexible sync subscription state changes will now correctly be reported after sync progress is reported. ([#5553](https://github.com/realm/realm-core/pull/5553, since v12.0.0) ### Compatibility * File format: Generates Realms with format v22. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 11.0.0-alpha.0 or above is required to open Realms created by this version. ### Internal * Update to Realm Core 12.3.0, commit: 55a48c287b5e3a8ca129c257ec7e3b92bcb2a05f. ## 10.11.0 (2022-05-20) ### Enhancements * Throw a more comprehensive error when initializing Realm on an Instant App. ### Fixed * Fixed various corruption bugs when encryption is used. (Realm Core issue [#5360](https://github.com/realm/realm-core/issues/5360), since 10.10.0) * Fixed imprecise conversion from double/float to Decimal128. (Realm Core issue [#5191](https://github.com/realm/realm-core/pull/5191)) * Fixed `RealmQuery.distinct` when it receives three or more arguments. (Issue [#7639](https://github.com/realm/realm-java/pull/7639)) * Fix issues resolving class information for `copyToRealmOrUpdate` for already managed objects in multi module projects. (Issue [#7650](https://github.com/realm/realm-java/issues/7650)) ### Compatibility * File format: Generates Realms with format v22. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 11.0.0-alpha.0 or above is required to open Realms created by this version. ### Internal * Update to Realm Core 11.14.0, commit: db7ca86cf7ff8c9c3da6c7e742ecd46315ddc280. ### Credits * Thanks to @Mr4Mike4 for fixing `RealmQuery.distinct` when it receives three or more arguments ([#7639](https://github.com/realm/realm-java/pull/7639)). * Thanks to @Waboodoo for fixing some typos ([#7646](https://github.com/realm/realm-java/pull/7646)). * Thanks to @ZherebtsovAlexandr for updating the use of the deprecated method `offer` to `trySend` ([#7648](https://github.com/realm/realm-java/pull/7648)). ## 10.10.1 (2022-01-26) ### Enhancements * [RealmApp] Add support for setting the filename on Flexible and Partition Sync configurations. ### Fixed * [RealmApp] Creating multiple anonymous subscriptions for a Realm would throw an exception. ### Compatibility * File format: Generates Realms with format v22. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 11.0.0-alpha.0 or above is required to open Realms created by this version. ### Internal * Updated to Android Gradle Plugin 7.1.0 * Updated to Gradle 7.3.3. ## 10.10.0 (2022-01-18) ### Enhancements * [RealmApp] Add support for a new mode for synchronized realms: Flexible Sync that only synchronizes selective parts of the backend data. The following classes have been added to support this: `Subscription`, `SubscriptionSet` and `MutableSubscriptionSet`. This mode and all APIs are marked as Beta. ### Fixed * [RealmApp] The sync client will now drain the receive queue when send fails with ECONNRESET - ensuring that any error message from the server gets received and processed. (Realm Core issue [#5078](https://github.com/realm/realm-core/pull/5078)) * [RealmApp] UserIdentity metadata table grows indefinitely. (Realm Core issue [#5152](https://github.com/realm/realm-core/issues/5152)) * Schema validation was missing for embedded objects in sets, resulting in an unhelpful error being thrown if the user attempted to define one. * Output from the annotation processor was not deterministic, which could result in cache misses. (Issue [#7615](https://github.com/realm/realm-java/issues/7615)) * Crashes when using `RealmAny` inside `RealmList` on ARM 32 devices. (Issue [#7626](https://github.com/realm/realm-java/issues/7626)) ### Compatibility * File format: Generates Realms with format v22. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 11.0.0-alpha.0 or above is required to open Realms created by this version. ### Internal * Update to Realm Core 11.9.0, commit: 733f12702d16ab0d0c7fea0831a2aee5ca5c26db. ### Credits * Thanks to @jprinet for making the annotation processor output deterministic. ## 10.9.0 (2021-12-06) ### Enhancements * [RealmApp] Add support for UUID's as partition values. (Issue [#7598](https://github.com/realm/realm-java/issues/7598)) * [RealmApp] Reduced native memory usage when working with synchronized Realms. * [RealmApp] Make it possible to bundle synchronized Realms in apps using `Realm.writeCopyTo()` and `SyncConfiguration.Builder.assetFile()`. * The Realm Transformer and Realm Gradle Plugin now supports the Gradle Configuration Cache. (Issue [#7299](https://github.com/realm/realm-java/issues/7299)) * [RealmApp] Introduced `SyncSession.DiscardUnsyncedChangesStrategy`, an alternative automatic client reset strategy that doesn't require the Realm to be closed, but discards any unsynced data from the client. This is now the default policy if not overridden. ### Deprecated * [RealmApp] `SyncSession.ClientResetHandler()`. Use `SyncSession.ManuallyRecoverUnsyncedChangesStrategy()` instead. * [RealmApp] `AppConfiguration.Builder.defaultClientResetHandler()`. Use `AppConfiguration.Builder.setDefaultSyncClientResetStrategy()` instead. * [RealmApp] `AppConfiguration.getDefaultClientResetHandler()`. Use `AppConfiguration.getDefaultSyncClientResetStrategy()` instead. * [RealmApp] `SyncConfiguration.Builder.clientResetHandler()`. Use `SyncConfiguration.Builder.setSyncClientResetStrategy()` instead. * [RealmApp] `SyncConfiguration.getClientResetHandler()`. Use `SyncConfiguration.getSyncClientResetStrategy()` instead. ### Fixed * [RealmApp] Setting `AppConfiguration.syncRootDirectory()` didn't have any effect beside creating the new folder. Realms were still placed in the default location. * [RealmApp] Bug where progress notifiers continue to be called after the download of a synced realm is complete. (Issue [Realm Core #4919](https://github.com/realm/realm-core/issues/4919)) * [RealmApp] User being left in the logged in state when the user's refresh token expires. (Issue [Realm Core #4882](https://github.com/realm/realm-core/issues/4882), since v10) * Using "sort", "distinct", or "limit" as field name in query expression would cause an "Invalid predicate" error. (Issue [#7545](), since v10.X.X) * Crash when quering with 'Not()' followed by empty group. (Issue [Realm Core #4168]() since v1.0.0) * Streaming download notifiers reported incorrect values for transferrable bytes. (Issue [Realm Core #5008]() since v11.5.2) * `@sum` and `@avg` queries on Dictionaries of floats or doubles used too much precision for intermediates, resulting in incorrect rounding. ### Compatibility * File format: Generates Realms with format v22. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 11.0.0-alpha.0 or above is required to open Realms created by this version. ### Internal * Updated to Realm Core 11.7.0, commit: 81eafa44879eb5f5829b345005abf99adb306133. * Building the SDK now requires JDK 11. * Updated to Gradle 7.2. * Updated to Android Gradle Plugin 7.1.0-beta03. * Updated to Kotlin 1.5.31. * Updated to Kotlin Coroutines 1.5.2. * Updated to CMake 3.21.4. * Updated to NDK 23.1.7779620. * Disable analytics for any value of the `REALM_DISABLE_ANALYTICS` environment variable, not just `true`. * Disable analytics whenever the `CI` environment variable is set. ## 10.8.1 (2021-10-28) ### Enhancements * None. ### Fixed * [RealmApp] Failing to refresh the access token due to a 401/403 error will now correctly emit an error with `ErrorCode.BAD_AUTHENTICATION` rather than `ErrorCode.PERMISSION_DENIED`. (Realm Core [#4881](https://github.com/realm/realm-core/issues/4881), since 10.6.1) * [RealmApp] If an object with a null primary key was deleted by another sync client, the exception `KeyNotFound: No such object` could be triggered. ([Realm Core #4885](https://github.com/realm/realm-core/issues/4885), since 10.0.0) * Exceptions inside change listeners running on background looper threads would crash the Looper with a native `JNI DETECTED ERROR IN APPLICATION: JNI NewLocalRef called with pending exception` instead of the original Java exception. This could also happen when canceling a corutine using a background looper as a Dispatcher. * [RealmApp] Reduced native memory use when synchronizing changes with the server in the background. ### Compatibility * File format: Generates Realms with format v22. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 11.0.0-alpha.0 or above is required to open Realms created by this version. ### Internal * Updated to Realm Core 11.4.1, commit: 23f60515a00f076a9e3f2dc672fe1ae07601ee90. ## 10.8.0 (2021-08-27) ### Enhancements * [RealmApp] `ErrorCode.INVALID_EMAIL_PASSWORD` has been added, and is now thrown instead of `ErrorCode.SERVICE_UNKNOWN` when loggin in with the wrong credentials. * `RealmQuery.rawPredicate()` now accepts a "BETWEEN" operator. Can be used like "age BETWEEN {20, 60}" which means "'Age' must be in the open interval ]20;60[". * [RealmApp] Added `User.remove()` and `User.removeAsync()` that makes it possible to delete a user's Realm(s) from the device. ### Fixed * [RealmApp] Crash when integrating a schema from the server with a `RealmAny` property to a Realm File that already had that property defined locally. ([Realm Core #4873](https://github.com/realm/realm-core/issues/4873), since 10.0.0) * [RealmApp] Refreshing the access token after 30 minutes would fail silently, causing infinite retries every 10 seconds. This would also block opening Realms when opening an app with an already logged in user. (Issue [#7501](https://github.com/realm/realm-java/issues/7501), since 10.0.0) * [RealmApp] Clarified Javadoc for `User.logOut()` and `User.logOutAsync()` as these methods do not delete a user's Realm(s). * Build error when having cross module model references (Issue [#7474](https://github.com/realm/realm-java/issues/7474), since v10.4.0) ### Compatibility * File format: Generates Realms with format v22. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 11.0.0-alpha.0 or above is required to open Realms created by this version. ### Internal * Updated to Realm Core 11.3.0, commit: 321c79a67119db8177af13eefd5378586648ba73. ## 10.7.1 (2021-08-03) ### Enhancements * None. ### Fixed * [RealmApp] Crash when an object which is linked to by a `RealmAny` is invalidated (Sync only). ([Realm Core #4828](https://github.com/realm/realm-core/issues/4828), since v10.6.0) * Object change listeners did not handle the object being deleted properly, which could result in assertion failures mentioning "m_table" in ObjectNotifier ([Realm Core #4824](https://github.com/realm/realm-core/issues/4824), since v10.6.0). * Crash when delivering notifications over a nested hierarchy of lists of `RealmAny` that contain object references. ([Realm Core #4803](https://github.com/realm/realm-core/issues/4803), since v10.6.0) ### Compatibility * File format: Generates Realms with format v22. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 11.0.0-alpha.0 or above is required to open Realms created by this version. ### Internal * Updated to Realm Core 11.2.0, commit: 583fc73040709383470797813096bee17802398e. ## 10.7.0 (2021-07-27) ### Breaking Changes * Removed automatic injection of repositories from Gradle plugin. From now on `mavenCentral()` repository needs to be added manually. (Issue [#7365](https://github.com/realm/realm-java/issues/7365)) ### Enhancements * None. ### Fixed * [RealmApp] Realm.getInstanceAsync does not wait for the initial remote data. (Issue [#7517](https://github.com/realm/realm-java/issues/7517)) * Build errors when doing incremental builds with Android Studio's _Apply Changes..._-actions. (Issue [#7473](https://github.com/realm/realm-java/issues/7473)) ### Compatibility * File format: Generates Realms with format v22. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 11.0.0-alpha.0 or above is required to open Realms created by this version. ### Internal * Updated to Realm Core 11.1.1, commit: 71db56caba8f8ef0398eedfffb82a908cb94ccec. ## 10.6.1 (2021-07-01) ### Enhancements * None. ### Fixed * [RealmApp] Configuring HTTP timeout through `AppConfiguration.Builder.requestTimeout()` did not work correctly. (Issue [#7455](https://github.com/realm/realm-java/issues/7455)) * [RealmApp] A recursive loop that would eventually crash trying to refresh a user app token when it had been revoked by an admin. Now this situation logs the user out and reports an error. (Issue [#7501](https://github.com/realm/realm-java/issues/7501)) * An endless recursive loop that could cause a stack overflow when computing changes on a set of objects which contained cycles. (Realm Core Issue [#4767](https://github.com/realm/realm-core/issues/4767)) * Opening cached Realms no longer trigger `android.os.strictmode.DiskReadViolation`. (Issue [#7500](https://github.com/realm/realm-java/issues/7500])) * `NullPointerException` was thrown instead of `IllegalStateException` when calling `Realm.executeTransaction()` on a closed Realm. (Issue [#7511](https://github.com/realm/realm-java/issues/7511), since 10.0.0) * `RealmDictionary` did not handle hash collisions correctly. (Realm Core issue [#4776](https://github.com/realm/realm-core/issues/4767)) * Crash after clearing a List or Set of `RealmAny` containing references to objects (Realm Core issue [#4774](https://github.com/realm/realm-core/issues/4774)) ### Compatibility * File format: Generates Realms with format v22. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 11.0.0-alpha.0 or above is required to open Realms created by this version. ### Internal * Updated to Realm Core 11.0.4, commit: 44304ce6104c4a9fc7e2359990c75be3b867b8fe. ## 10.6.0 (2021-06-15) This release combines all changes from 10.6.0-BETA.1 and 10.6.0-BETA.2. ### Breaking Changes * [RealmApp] Sync protocol version increased to 3. This version adds support for the new data types introduced in file format version 21. * Primary keys now have automatic indexes again. Indexes was removed in v10.0.0 because they were not needed, but it caused issues when upgrading from a pre v10 version of Realm, and in some cases resulted in large delays when upgrading the fileformat. (Issue [#7426](https://github.com/realm/realm-java/issues/7426), since 10.0.0). * Queries no longer do nullability checks on non-nullable fields, so using `null` as an argument will not throw an `IllegalArgumentException`. * String query filters `contains`, `beginsWith`, `endsWith`, and `like`, now throw a null pointer exception on null values. * The query builder no longer throw `IllegalStateException` but `IllegalArgumentException`. * The `distinct` query filter on unsupported fields no longer throws an exception when applied through when querying across relationships. * The `distinct` query filter no longer throws an exception when applied on non-existent fields. * `RealmFieldType` has been updated to account for the new types being added. ### Enhancements * Added support for `java.util.UUID` as supported field in model classes. * Added support for `java.util.UUID` as a primary key. * Added support for `RealmAny` as supported field in model classes. A `RealmAny` is used to represent a polymorphic Realm value or Realm Object, is indexable but cannot be used as a primary key. See [Javadoc for RealmAny](https://docs.mongodb.com/realm-sdks/java/latest/io/realm/RealmAny.html). * Added support for `RealmDictionary` as supported field in model classes. A `RealmDictionary` is a `Map` of strings to values - all types under the `RealmAny` umbrella can be used as values. See [Javadoc for RealmDictionary](https://docs.mongodb.com/realm-sdks/java/latest/io/realm/RealmDictionary.html) and [Javadoc for RealmMap](https://docs.mongodb.com/realm-sdks/java/latest/io/realm/RealmMap.html). `RealmDictionary` is not yet supported by any of the `Realm.insert` and `Realm.createFromJson` methods - This support will be added in a future release. * Added support for `RealmSet` as supported field in model classes. A `RealmSet` is a collection that implements the Java `Set` interface and contains no duplicate values - all types under the `RealmAny` umbrella can be used as values. See [Javadoc for RealmSet](https://docs.mongodb.com/realm-sdks/java/latest/io/realm/RealmSet.html). `RealmSet` is not yet supported by any of the `Realm.insert` and `Realm.createFromJson` methods - This support will be added in a future release. * Allow UTF8 encoded characters in property names in string-based queries ([#4467](https://github.com/realm/realm-core/issues/4467)) * The error message when the initial steps of opening a Realm file fails is now more descriptive. * Make conversion of Decimal128 to/from string work for numbers with more than 19 significant digits. ([#4548](https://github.com/realm/realm-core/issues/4548)) * Remove type coercion on bool and ObjectId when doing queries. * Allow passing arguments into string-based query predicates. * Queries across relationships now support the `between` operator. * Queries on numerical fields (byte, short, int, long, float, double, decimal128) now accept any numerical value as an argument. * `isEmpty` query filter can now be applied on `RealmList` and `RealmObject` fields. ### Fixed * Fix assertion failures such as "!m_notifier_skip_version.version" or "m_notifier_sg->get_version() + 1 == new_version.version" when performing writes inside change notification callbacks. Previously refreshing the Realm by beginning a write transaction would skip delivering notifications, leaving things in an inconsistent state. Notifications are now delivered recursively when needed instead. ([Cocoa #7165](https://github.com/realm/realm-cocoa/issues/7165)). * Fixed name aliasing not working in sort/distinct clauses when doing string-based queries. ([#4550](https://github.com/realm/realm-core/issues/4550), never before working). * Potential/unconfirmed fix for crashes associated with failure to memory map (low on memory, low on virtual address space). For example ([#4514](https://github.com/realm/realm-core/issues/4514)). * Syncing large Decimal128 values will cause "Assertion failed: cx.w[1] == 0" ([#4519](https://github.com/realm/realm-core/issues/4519), since v10.0.0) * Classes names "class_class_..." were not handled correctly when doing queries ([#4480](https://github.com/realm/realm-core/issues/4480)) * Fix collection notification reporting for modifications. This could be observed by receiving the wrong indices of modifications on sorted or distinct results, or notification blocks sometimes not being called when only modifications have occurred. ([#4573](https://github.com/realm/realm-core/pull/4573) since v6). ### Compatibility * File format: Generates Realms with format v22. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 11.0.0-alpha.0 or above is required to open Realms created by this version. ### Internal * Updated to Realm Core 11.0.3, commit de25ad9db783f931e7652d5c1431d5610b2ad67b. ## 10.6.0-BETA.2 (2021-06-14) ### Breaking Changes * `MapChangeSet.getDeletionsCount()` has been replaced with `MapChangeSet.getDeletions()` that return the keys for entries that has been deleted instead of just the number of deleted entries. * Primary keys now have automatic indexes again. Indexes was removed in v10.0.0 because they were not needed, but it caused issues when upgrading from a pre v10 version of Realm, and in some cases resulted in large delays when upgrading the fileformat. (Issue [#7426](https://github.com/realm/realm-java/issues/7426), since 10.0.0). ### Enhancements * Allow `insert` and `insertOrUpdate` operations on `RealmObject` or `RealmObject` collections containing `RealmDictionary` or `RealmSet` fields. * Added support for `RealmDictionary` in `DynamicRealmObject` with `setDictionary(String fieldName, RealmDictionary dictionary)`, `getDictionary(String fieldName, Class primitiveType)`, and `getDictionary(String fieldName)`. * Added support for `RealmSet` in `DynamicRealmObject` with `setRealmSet(String fieldName, RealmSet realmSet)`, `getRealmSet(String fieldName, Class primitiveType)`, and `getRealmSet(String fieldName)`. ### Fixed * Removed wrong `@Nullable` annotation on `RealmQuery.maxRealmAny()`. * Fixed `RealmAny.getValueClass()` returning the `RealmObject` proxy class instead of the model class on a `RealmAny` referencing a managed `RealmObject`. ### Compatibility * File format: Generates Realms with format v22. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 11.0.0-alpha.0 or above is required to open Realms created by this version. ### Internal * Updated to Realm Core 11.0.2, commit a30382469eb72c0cf1824b44e7062071c2f3f3a9. * Updated to Gradle 6.8.3. ## 10.6.0-BETA.1 (2021-05-17) ### Breaking Changes * [RealmApp] Sync protocol version increased to 3. This version adds support for the new data types introduced in file format version 21. * File format version bumped to 21. In this version we support new basic datatypes `UUID` and `RealmAny`, as well as `RealmSet` and `RealmMap` collections with string-based keys (i.e. `RealmDictionary`). * Queries no longer do nullability checks on non-nullable fields, so using `null` as an argument will not throw an `IllegalArgumentException`. * String query filters `contains`, `beginsWith`, `endsWith`, and `like`, now throw a null pointer exception on null values. * The query builder no longer throw `IllegalStateException` but `IllegalArgumentException`. * The `distinct` query filter on unsupported fields no longer throws an exception when applied through when querying across relationships. * The `distinct` query filter no longer throws an exception when applied on non-existent fields. ### Enhancements * Added support for `java.util.UUID` as supported field in model classes. * Added support for `java.util.UUID` as a primary key. * Added support for `RealmAny` as supported field in model classes. A `RealmAny` is used to represent a polymorphic Realm value or Realm Object, is indexable but cannot be used as a primary key. See [Javadoc for RealmAny](https://docs.mongodb.com/realm-sdks/java/latest/io/realm/RealmAny.html). * Added support for `RealmDictionary` as supported field in model classes. A `RealmDictionary` is a `Map` of strings to values - all types under the `RealmAny` umbrella can be used as values. See [Javadoc for RealmDictionary](https://docs.mongodb.com/realm-sdks/java/latest/io/realm/RealmDictionary.html) and [Javadoc for RealmMap](https://docs.mongodb.com/realm-sdks/java/latest/io/realm/RealmMap.html). `RealmDictionary` is not yet supported by any of the `Realm.insert` and `Realm.createFromJson` methods - This support will be added in a future release. * Added support for `RealmSet` as supported field in model classes. A `RealmSet` is a collection that implements the Java `Set` interface and contains no duplicate values - all types under the `RealmAny` umbrella can be used as values. See [Javadoc for RealmSet](https://docs.mongodb.com/realm-sdks/java/latest/io/realm/RealmSet.html). `RealmSet` is not yet supported by any of the `Realm.insert` and `Realm.createFromJson` methods - This support will be added in a future release. * Allow UTF8 encoded characters in property names in string-based queries ([#4467](https://github.com/realm/realm-core/issues/4467)) * The error message when the initial steps of opening a Realm file fails is now more descriptive. * Make conversion of Decimal128 to/from string work for numbers with more than 19 significant digits. ([#4548](https://github.com/realm/realm-core/issues/4548)) * Remove type coercion on bool and ObjectId when doing queries. * Allow passing arguments into string-based query predicates. * Queries across relationships now support the `between` operator. * Queries on numerical fields (byte, short, int, long, float, double, decimal128) now accept any numerical value as an argument. * `isEmpty` query filter can now be applied on `RealmList` and `RealmObject` fields. ### Fixed * Fix assertion failures such as "!m_notifier_skip_version.version" or "m_notifier_sg->get_version() + 1 == new_version.version" when performing writes inside change notification callbacks. Previously refreshing the Realm by beginning a write transaction would skip delivering notifications, leaving things in an inconsistent state. Notifications are now delivered recursively when needed instead. ([Cocoa #7165](https://github.com/realm/realm-cocoa/issues/7165)). * Fixed name aliasing not working in sort/distinct clauses when doing string-based queries. ([#4550](https://github.com/realm/realm-core/issues/4550), never before working). * Potential/unconfirmed fix for crashes associated with failure to memory map (low on memory, low on virtual address space). For example ([#4514](https://github.com/realm/realm-core/issues/4514)). * Syncing large Decimal128 values will cause "Assertion failed: cx.w[1] == 0" ([#4519](https://github.com/realm/realm-core/issues/4519), since v10.0.0) * Classes names "class_class_..." were not handled correctly when doing queries ([#4480](https://github.com/realm/realm-core/issues/4480)) * Fix collection notification reporting for modifications. This could be observed by receiving the wrong indices of modifications on sorted or distinct results, or notification blocks sometimes not being called when only modifications have occurred. ([#4573](https://github.com/realm/realm-core/pull/4573) since v6). ### Compatibility * File format: Generates Realms with format v21. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 11.0.0-alpha.0 or above is required to open Realms created by this version. ### Internal * Updated to Realm Core 11.0.0-beta.4, commit: d50aef63a8aaf435e3afed82b589b47d8e1ab1ab. ## 10.5.1 (2021-06-14) ### Enhancements * None. ### Fixes * [RealmApp] Errors related to "uncaught exception in notifier thread: N5realm11KeyNotFoundE: No such object". This could happen in a sync'd app when a linked object was deleted by another client. * [RealmApp] Replacing a referenced embedded object could result in a "ERROR: ArrayInsert: Invalid" error. (Issue [#7480](https://github.com/realm/realm-java/issues/7480)) * Notifications now trigger correctly on Linux kernel 5.5 and above. So far this only impacted the preview emulator image for Android 12. (Issue[#7321](https://github.com/realm/realm-java/issues/7321)) * Raw query predicates not supporting integer constants above 32 bits on a 32 bit platform. ### Compatibility * File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. * Realm Studio 10.0.0 or above is required to open Realms created by this version. ### Internal * Updated to Realm Core 10.8.1, commit 2a67b996faf9e0b5d47ac402c4d3926713f99418. ## 10.5.0 (2021-05-07) ### Breaking Changes * [RealmApp] `SyncSession.State.WaitingForAccessToken` has been added. It represents the local access token not longer being valid, but is automatically being refreshed. ### Enhancements * We now make a backup of a Realm file prior to any file format upgrade. The backup is retained for 3 months. Backups from before a file format upgrade allows for better analysis of any upgrade failure. We also restore a backup, if a) an attempt is made to open a realm file whith a "future" file format and b) a backup file exist that fits the current file format. The backup file is placed next to the real Realm file and is named `.v.backup.realm`. * The error message when the intial steps of opening a Realm file fails is now more descriptive. ### Fixes * [RealmApp] Client Reset errors now correctly forward the server error message. (Issue [#7363](https://github.com/realm/realm-java/issues/7363), since 10.0.0) * [RealmApp] All `AppException`s now correctly report the error message through `RuntimeException.getMessage()` instead of only through `AppException.getErrorMessage()`. * [RealmApp] Proactively check the expiry time on the access token and refresh it before attempting to initiate a sync session. This prevents some error logs from appearing on the client such as: "ERROR: Connection[1]: Websocket: Expected HTTP response 101 Switching Protocols, but received: HTTP/1.1 401 Unauthorized" (RCORE-473, since v10.0.0). * Fix name aliasing not working in sort/distinct clauses of raw string predicates. * Fix collection notification reporting for modifications. This could be observed by receiving the wrong indices of modifications on sorted or distinct results, or notification sometimes not being called when only modifications have occured. (since v7.0.0). * Make conversion of Decimal128 to/from string work for numbers with more than 19 significant digits. (#4548) ### Compatibility * File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. * Realm Studio 10.0.0 or above is required to open Realms created by this version. ### Internal * Updated to Realm Core 10.7.1, commit 5043c25e1d8f5971002e0fec85dea5ea3d7eb3d7. ## 10.4.0 (2021-03-26) All releases from 10.4.0 and forward are now found on `mavenCentral()` instead of `jcenter()`. A minimal supported setup will therefore now look like this: ``` allprojects { buildscript { repositories { mavenCentral() } dependencies { classpath "io.realm:realm-gradle-plugin:10.4.0" } } repositories { mavenCentral() } } ``` `SNAPSHOT` releases have also been moved from `http://oss.jfrog.org/artifactory/oss-snapshot-local` to `https://oss.sonatype.org/content/repositories/snapshots/`. See [here](https://github.com/realm/realm-java/blob/master/README.md#using-snapshots) for more information. ### Enhancements * Added support for the string-based Realm Query Language through `RealmQuery.rawPredicate(...)`. This allows many new type of queries not previously supported by the typed query API. See the Javadoc on this method for further details. (Issue [#6116](https://github.com/realm/realm-java/pull/6116)) * Performance of sorting on more than one property has been improved. Especially important if many elements match on the first property. ### Fixes * Calling max/min/sum/avg on a List may give wrong results (Realm Core [#4252](https://github.com/realm/realm-core/issues/4252), since v10.0.0) * Fix an issue when using `RealmResults.freeze()` across threads with different transaction versions. Previously, copying the `RealmsResults`' native resource could result in a stale state or objects from a future version. (Realm Core [#4254](https://github.com/realm/realm-core/pull/4254)). * On 32-bit devices you may get exception with "No such object" when upgrading to v10.* ([#7314](https://github.com/realm/realm-java/issues/7314), since v10.0.0) ### Compatibility * File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. * Realm Studio 10.0.0 or above is required to open Realms created by this version. ### Internal * Updated to Realm Core 10.5.6, commit 92129110dece2cee59839e20be3a7067084a1196. * Updated to NDK 22.0.7026061. * Updated to ReLinker 1.4.3. ## 10.3.1 (2021-01-28) ### Enhancements * None. ### Fixes * RxJava Flowables/Observables and Coroutine Flows would crash if they were created from a `RealmList` and the parent object holding the list was deleted. Now, the stream is disposed/closed instead. (Issue [#7242](https://github.com/realm/realm-java/issues/7242)) * Fixes Realm models default values containing objects with a PK might crash with a `RealmPrimaryKeyConstraintException`. (Issue [#7269] (https://github.com/realm/realm-java/issues/7269)) ### Compatibility * File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. * Realm Studio 10.0.0 or above is required to open Realms created by this version. ### Internal * None. ## 10.3.0 (2021-01-08) ### Enhancements * [RealmApp] Upgraded to OpenSSL 1.1.1g. ### Fixes * [RealmApp] Integrating a remote Sync changeset into the local Realm could result in an `Index out of range error`. * Change notifications not firing when removing and adding an object with the same primary key within a transaction (Issue [#7098](https://github.com/realm/realm-java/issues/7098)). * Race condition which would lead to "uncaught exception in notifier thread: N5realm15InvalidTableRefE: transaction_ended" and a crash when the source Realm was closed or invalidated at a very specific time during the first run of a collection notifier (Core issue [#3761](https://github.com/realm/realm-core/issues/3761), since v7.0.0). * Deleting and recreating objects with embedded objects could fail (Core issue [#4240](https://github.com/realm/realm-core/pull/4240), since v10.0.0) * Added `@Nullable` annotation to input parameter in `RealmObject.isValid(item)` to avoid mismatch warnings from Kotlin code (Issue [#7216](https://github.com/realm/realm-java/issues/7216)). ### Compatibility * File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. * Realm Studio 10.0.0 or above is required to open Realms created by this version. ### Internal * Updated to Realm Core: 10.3.3 (Monorepo). * Updated to Realm Core commit: 8af0f8d609491986b49f2c986e771d9dc445664d. ## 10.2.0 (2020-12-02) ### Deprecated * [RealmApp] `Credentials.google(authenticationCode: String)`. Use `Credentials.google(token: String, authType: GoogleAuthType)` instead. ### Breaking Changes * None. ### Enhancements * [RealmApp] Added `Credentials.google(token: String, authType: GoogleAuthType)`, as MongoDB Realm now supports multiple ways of logging into Google Accounts. ### Fixes * [RealmApp] Bug that would prevent eventual consistency during conflict resolution. Affected clients would experience data divergence and potentially consistency errors as a result if they experienced conflict resolution between cycles of Create-Erase-Create for objects with primary keys. * Clean up JNI references to prevent crash from JNI reference table overflow (Issue [#7217](https://github.com/realm/realm-java/issues/7217)) ### Compatibility * File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. * Realm Studio 10.0.0 or above is required to open Realms created by this version. ### Internal * Updated to Realm Sync: 10.1.4. * Updated to Object Store commit: f838a27402c5b5243280102014defd844420abba66eb93c10334507d9c0fd513. ## 10.1.2 (2020-12-02) ### Breaking Changes * None. ### Enhancements * None. ### Fixes * Complementary fix for missed edge case in https://github.com/realm/realm-java/pull/7220 where KAPT crash if we process a RealmObject referencing a type in RealmList defined in another module. (Issue [#7213](https://github.com/realm/realm-java/issues/7213), since v10.0.0). ### Compatibility * File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. * Realm Studio 10.0.0 or above is required to open Realms created by this version. ## 10.1.1 (2020-11-27) ### Breaking Changes * None. ### Enhancements * None. ### Fixes * KAPT crash when processing a RealmObject referenced from another module (changed revealed after we started checking for embedded types). (Issue [#7213](https://github.com/realm/realm-java/issues/7213), since v10.0.0). ### Compatibility * File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. * Realm Studio 10.0.0 or above is required to open Realms created by this version. ### Internal * Updated to Realm Sync: 10.1.3. * Updated to Realm Core: 10.1.3. * Updated to Object Store commit: fc6daca61133aa9601e4cb34fbeb9ec7569e162e. ## 10.1.0 (2020-11-23) ### Breaking Changes * None. ### Enhancements * Added `FlowFactory` interface that allows customization of `Flow` emissions, just as we do with `RxObservableFactory`. A default implementation, `RealmFlowFactory`, is provided when building `RealmConfiguration`s. * Added `toChangeSetFlow` methods (similar to the Rx `asChangesetFlowable` methods) for `RealmObject`, `RealmResults` and `RealmList`. ### Fixes * Fixed crash when adding classes containing an `ObjectId` as primary key to the schema. (Issue [#7189](https://github.com/realm/realm-java/issues/7189), since v10.0.0) * Fixed crash when creating proxy classes containing an `ObjectId` as primary key. (Issue [#7197](https://github.com/realm/realm-java/issues/7197), since v10.0.0) * Fixed crash where calls to `toFlow` could crash if the Flow job is canceled and object updates are emitted after that happens. (Issue [7211](https://github.com/realm/realm-java/issues/7211), since v10.0.1) ### Compatibility * File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. * Realm Studio 10.0.0 or above is required to open Realms created by this version. ### Internal * Updated to Realm Sync: 10.1.3. * Updated to Realm Core: 10.1.3. * Updated to Object Store commit: fc6daca61133aa9601e4cb34fbeb9ec7569e162e. ## 10.0.1 (2020-11-06) ### Breaking Changes * None. ### Enhancements * Improved the error message for `NoSuchTable` errors. In some cases an outdated native reference was used,but the table was still there. In those cases an `InvalidTableRef` error is now used. ### Fixes * [RealmApp] The `SyncConfiguration.Builder.allowQueriesOnUiThread` flag was wrongly initialized to `false` keeping users from running queries from the UI thread when using synced Realms. It now defaults to `true`, allowing queries to be run from the UI. (Issue [#7177](https://github.com/realm/realm-java/issues/7177), since 10.0.0) * Crash with `Assertion failed: m_method_id != nullptr with (method_name, signature) = ["", "(Ljava/lang/String;)V"]` when `Minify` is enabled. (Issue [#7159](https://github.com/realm/realm-java/pull/7159), since 10.0.0) * Fix crash in case insensitive query on indexed string columns when nothing matches (Cocoa issue [#6836](https://github.com/realm/realm-cocoa/issues/6836), since v10.0.0) * Fix list of primitives with nullable values where `Lst::is_null(ndx)` always false even on null values, (Core issue [#3987](https://github.com/realm/realm-core/pull/3987), since v10.0.0). * Fix queries for the size of a list of primitive nullable ints returning size + 1. (Core issue [#4016](https://github.com/realm/realm-core/pull/4016), since v10.0.0). ### Compatibility * File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. * Realm Studio 10.0.0 or above is required to open Realms created by this version. ### Internal * Updated to Realm Sync: 10.1.0. * Updated to Realm Core: 10.1.0. * Updated to Object Store commit: fd246c54de7d1fee6bcbeb3609de75a4eccd5b70. ## 10.0.0 (2020-10-15) NOTE: This is a unified release note covering all v10.0.0-BETA.X v10.0.0-RC.X releases. NOTE: Support for syncing with realm.cloud.io and/or Realm Object Server has been replaced with support for syncing with MongoDB Realm Cloud. NOTE: This version upgrades the Realm file format to version 20. It is not possible to downgrade to earlier versions than v10.0.0-BETA.7. Non-sync Realms will be upgraded automatically. Synced Realms can only be automatically upgraded if created with Realm Java v10.0.0-BETA.1 and above. ### Breaking Changes * [RealmApp] Most APIs for interacting with Realm Cloud have changed significantly. All new APIs can be found in the `io.realm.mongodb` package. The entry point is through the `App` class from which you can create and login users and otherwise interact with MongoDB Realm. See [the docs](https://docs.mongodb.com/realm/android/) for further details. Synced Realms still use a `SyncConfiguration` that are largely created the same way. * [RealmApp] Client Resets are now handled through a custom `SyncConfiguration.Builder.clientResetHandler()` instead of through the default session error handler `SyncConfiguration.Builder.errorHandler()` * [RealmApp] Realm files have changed location on disk. They are now located in `getFiles()/mongodb-realm`. * [RealmApp] All synced model classes not marked as embedded are required to have a primary key named `_id`. It is possible to use `@RealmField(name = "_id")` to map from any Java or Kotlin property. * From now on it is by default not allowed to run transactions with either `Realm.executeTransaction()` or `DynamicRealm.executeTransaction()` from the UI thread. Doing so will yield a `RealmException`. Users can override this behavior by using `RealmConfiguration.Builder.allowWritesOnUiThread(true)` when building a `RealmConfiguration` to obtain a Realm or DynamicRealm instance, however, we do not recommend doing so. Instead, we recommend using `executeTransactionAsync()` or, alternatively, using non-UI threads when calling `executeTransaction()` for both `Realm`s and `DynamicRealm`s. ### Enhancements * Users can now opt out from allowing queries to be launched from the UI thread by using `RealmConfiguration.Builder.allowQueriesOnUiThread(false)`. A `RealmException` will be thrown when calling `RealmQuery.findAll()`, `RealmQuery.findFirst()`, `RealmQuery.minimumDate()`, `RealmQuery.maximumDate()`, `RealmQuery.count()`, `RealmQuery.sum()`, `RealmQuery.max()`, `RealmQuery.min()`, `RealmQuery.average()` and `RealmQuery.averageDecimal128()` from the UI thread after having used `allowQueriesOnUiThread(false)`. Queries will be allowed from the thread from which the Realm instance was obtained as it always has been by default, although we recommend using `RealmQuery.findAllAsync()` or `RealmQuery.findFirstAsync()`, or, alternatively, using a non-UI thread to launch them. * `BaseRealm.refresh()` will throw a `RealmException` if it is being called from the UI thread if `allowQueriesOnUiThread` is set to `false`, though it will be allowed by default. * Added `DynamicRealm.executeTransactionAsync()`. * Added Kotlin extension suspend function `Realm.executeTransactionAwait()` which runs transactions inside coroutines. * Added Kotlin extension function `RealmResults.toFlow()` which returns a Kotlin flow, similar to our RxJava convenience method `asFlowable()`. * Added Kotlin extension function `RealmList.toFlow()` which returns a Kotlin flow, similar to our RxJava convenience method `asFlowable()`. * Added Kotlin extension function `RealmModel.toFlow()` which returns a Kotlin flow, similar to our RxJava convenience method `asFlowable()`. * RealmLists can now be marked final. (Issue [#6892](https://github.com/realm/realm-java/issues/6892)) * Added support for `distinct` queries on non-index and linked fields. (Issue [#1906](https://github.com/realm/realm-java/issues/1906)) * Added support for `org.bson.types.Decimal128` and `org.bson.types.ObjectId` as supported fields in model classes. * Added support for `org.bson.types.ObjectId` as a primary key. * Added support for "Embedded Objects". They are enabled using `@RealmClass(embedded = true)`. An embedded object must have exactly one parent object linking to it and it will be deleted when the parent is. Embedded objects can also be the parent of other embedded classes. Read more [here](https://docs.mongodb.com/realm/android/embedded-objects/). (Issue [#6713](https://github.com/realm/realm-java/issues/6713)) ### Fixes * None. ### Compatibility * File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. * Realm Studio 10.0.0 or above is required to open Realms created by this version. ### Internal * Updated to Realm Sync: 10.0.0. * Updated to Realm Core: 10.0.0. ## 10.0.0-RC.2 (2020-10-12) ### Enhancements * [RealmApp] Illegal schemas where embedded object classes referenced each other is now correctly detected and throws and exception when opening a Realm with such a schema. ### Fixed * [RealmApp] It is now possible to use types different than `ObjectId` for the `_id` field in documents inserted with `MongoCollection.insertOne` and `MongoCollection.insertMany`. * [RealmApp] Lossy round trip of Double and Timestamps through functions when using Bson. (ObjectStore issue (#1106)[https://github.com/realm/realm-object-store/issues/1106]) ### Compatibility * File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java 10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. * Realm Studio 10.0.0 and above is required to open Realms created by this version. ### Internal * Updated to Object Store commit: 6b44209e6fcac0137e193c96444f93c50d184d06. ## 10.0.0-RC.1 (2020-10-02) We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. The old Realm Cloud legacy APIs have undergone significant refactoring. The new APIs are all located in the `io.realm.mongodb` package with `io.realm.mongodb.App` as the entry point. ### Breaking Changes * From now on it is not allowed by default to run transactions with either `Realm.executeTransaction()` or `DynamicRealm.executeTransaction()` from the UI thread. Doing so will yield a `RealmException`. Users can override this behavior by using `RealmConfiguration.Builder.allowWritesOnUiThread(true)` when building a `RealmConfiguration` to obtain a Realm or DynamicRealm instance, though we do not recommend doing so. Instead, we recommend using `executeTransactionAsync()` or, alternatively, using non-UI threads when calling `executeTransaction()` for both `Realm`s and `DynamicRealm`s. ### Enhancements * Users can now opt out from allowing queries to be launched from the UI thread by using `RealmConfiguration.Builder.allowQueriesOnUiThread(false)`. A `RealmException` will be thrown when calling `RealmQuery.findAll()`, `RealmQuery.findFirst()`, `RealmQuery.minimumDate()`, `RealmQuery.maximumDate()`, `RealmQuery.count()`, `RealmQuery.sum()`, `RealmQuery.max()`, `RealmQuery.min()`, `RealmQuery.average()` and `RealmQuery.averageDecimal128()` from the UI thread after having used `allowQueriesOnUiThread(false)`. Queries will be allowed from the thread from which the Realm instance was obtained as it always has been by default, although we recommend using `RealmQuery.findAllAsync()` or `RealmQuery.findFirstAsync()`, or, alternatively, using a non-UI thread to launch them. * `BaseRealm.refresh()` will throw a `RealmException` if it is being called from the UI thread if `allowQueriesOnUiThread` is set to `false`, though it will be allowed by default. * Added `DynamicRealm.executeTransactionAsync()`. * Added Kotlin extension suspend function `Realm.executeTransactionAwait()` which runs transactions inside coroutines. * Added Kotlin extension function `RealmResults.toFlow()` which returns a Kotlin flow, similar to our RxJava convenience method `asFlowable()`. * Added Kotlin extension function `RealmList.toFlow()` which returns a Kotlin flow, similar to our RxJava convenience method `asFlowable()`. * Added Kotlin extension function `RealmModel.toFlow()` which returns a Kotlin flow, similar to our RxJava convenience method `asFlowable()`. ### Fixed * Using `Realm.copyToRealmOrUpdate()` and `Realm.insertOrUpdate()` did not correctly update objects if they contained lists of embedded objets. Instead of replacing the original list, list items was appended to the original list. Note, some corner cases are still not supported. See [#7138](https://github.com/realm/realm-java/issues/7138) for more information. (Issue [#7131](https://github.com/realm/realm-java/issues/7131), since 10.0.0-BETA.1). ### Compatibility * File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java 10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. * Realm Studio 10.0.0 and above is required to open Realms created by this version. ### Internal * Updated to Object Store commit: ef6736cc07a8b94d1242c522969114bb8047deef * Updated to Realm Sync 10.0.0-beta.14. * Updated to Realm Core 10.0.0-beta.9. ## 10.0.0-BETA.8 (2020-09-23) We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. The old Realm Cloud legacy APIs have undergone significant refactoring. The new APIs are all located in the `io.realm.mongodb` package with `io.realm.mongodb.App` as the entry point. ### Fixed * [RealmApp] Logging in caused an `token contains an invalid number of segments` error. (Issue [#7117](https://github.com/realm/realm-java/issues/7117), since 10.0.0-BETA.7) * [RealmApp] The order of arguments to `EmailPassword.resetPassword()` was not handled correctly, resulting in resetting the password failing. (Issue [#7116](https://github.com/realm/realm-java/issues/7116), since 10.0.0-BETA.1) ### Compatibility * File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java 10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. * Realm Studio 10.0.0 and above is required to open Realms created by this version. ### Internal * Updated to Object Store commit: 035eb07f3ef313bfb78c046be9cf6b4f065d6772. ## 10.0.0-BETA.7 (2020-09-16) We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. The old Realm Cloud legacy APIs have undergone significant refactoring. The new APIs are all located in the `io.realm.mongodb` package with `io.realm.mongodb.App` as the entry point. WARNING: This release upgrades the fileformat to 20. Non-sync Realms will be upgraded automatically. Synced Realms can only be automatically upgraded if created with Realm Java 10.0.0-BETA.1 and above. ### Breaking Changes * [RealmApp] Moved `User.remove()` to `App.removeUser()`. * [RealmApp] Renamed `ApiKeyAuth.createApiKey()` to `ApiKeyAuth.create()` and `ApiKeyAuth.createApiKeyAsync()` to `ApiKeyAuth.createAsync()`. * [RealmApp] Renamed `ApiKeyAuth.fetchApiKey()` to `ApiKeyAuth.fetch()` and `ApiKeyAuth.fetchApiKeyAsync()` to `ApiKeyAuth.fetchAsync()`. * [RealmApp] Renamed `ApiKeyAuth.fetchAllApiKeys()` to `ApiKeyAuth.fetchAll()` and `ApiKeyAuth.fetchAllApiKeysAsync()` to `ApiKeyAuth.fetchAllAsync()`. * [RealmApp] Renamed `ApiKeyAuth.deleteApiKey()` to `ApiKeyAuth.delete()` and `ApiKeyAuth.deleteApiKeyAsync()` to `ApiKeyAuth.deleteAsync()`. * [RealmApp] Renamed `ApiKeyAuth.enableApiKey()` to `ApiKeyAuth.enable()` and `ApiKeyAuth.enableApiKeyAsync()` to `ApiKeyAuth.enableAsync()`. * [RealmApp] Renamed `ApiKeyAuth.disableApiKey()` to `ApiKeyAuth.disable()` and `ApiKeyAuth.disableApiKeyAsync()` to `ApiKeyAuth.disableAsync()`. * [RealmApp] Renamed `User.getApiKeysAuth()` to `User.getApiKeys()`. * [RealmApp] Renamed `UserApiKey` class to `ApiKey`. * [RealmApp] Removed support for `Credentials.serverApiKey()`. * [RealmApp] Renamed `App.getEmailPasswordAuth()` to `App.getEmailPassword()`. * [RealmApp] User profile methods `getName()`, `getEmail()`, `getPictureUrl()`, `getFirstName()`, `getLastName()`, `getGender()`, `getBirthday()`, `getMinAge()` and `getMaxAge()` are now available under a new class `UserProfile`. It can be accessed using `User.getProfile()`. * [RealmApp] Renamed `Sync.refreshConnections()` to `Sync.reconnect()`. * [RealmApp] Renamed `Credentials.IdentityProvider` to `Credentials.Provider`. * [RealmApp] Removed support for `User.getLocalId()`. * [RealmApp] Client Resets are now handled through a custom `SyncConfiguration.Builder.clientResetHandler()` instead of through the default session error handler `SyncConfiguration.Builder.errorHandler()` ### Enhancements * [RealmApp] It is now possible to create App instances with different app id's. * [RealmApp] Support for using `null` as a partition value. * [RealmApp] Improve errors exception messages from `SyncSession.downloadAllServerChanges()` and `SyncSession.uploadAllLocalChanges()`. * [RealmApp] Support for watching MongoCollection change streams (Issue [#6912](https://github.com/realm/realm-java/issues/6912)) * [RealmApp] Support for retrying a custom confirmation function on an User for a given email (Issue [#7079](https://github.com/realm/realm-java/pull/7079)) * [RealmApp] Support for getting all app sessions via `Sync.getAllSessions()`. * [RealmApp] Support to retrieve the MongoClient service name using `MongoClient.getServiceName()` * [RealmApp] Support to retrieve the MongoDatabase name using `MongoDatabase.getName()` * [RealmApp] Support to retrieve the MongoCollection name using `MongoCollection.getName()` ### Fixed * If you have a realm file growing towards 2Gb and have a table with more than 16 columns, then you may get a "Key not found" exception when updating an object. If asserts are enabled at the sdk level, you may get an "assert(m_has_refs)" instead. ([#3194](https://github.com/realm/realm-js/issues/3194), since v7.0.0) * In cases where you have more than 32 columns in a table, you may get a currrupted file resulting in various crashes ([#7057](https://github.com/realm/realm-java/issues/7057), since v7.0.0) ### Compatibility * File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java 10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. * Realm Studio 10.0.0 and above is required to open Realms created by this version. ### Internal * Updated to Object Store commit: 6ab48d3b4b1e0865f68b84d5993bb2aad910320b. * Updated to Realm Sync 10.0.0-beta.11. * Updated to Realm Core 10.0.0-beta.7. ## 10.0.0-BETA.6 (2020-08-17) We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. The old Realm Cloud legacy APIs have undergone significant refactoring. The new APIs are all located in the `io.realm.mongodb` package with `io.realm.mongodb.App` as the entry point. ### Breaking Changes * [RealmApp] Realm files have changed location on disk, so Realms should upload all their data to the server before upgrading. * [RealmApp] Removed GMS Task framework and added RealmResultTask to provide with a mechanism to operate with asynchronous operations. MongoCollection has been updated to reflect this change. ### Enhancements * [RealmApp] Credentials information (e.g. username, password) displayed in Logcat is now obfuscated by default, even if [LogLevel] is set to DEBUG, TRACE or ALL. * RealmLists can now be marked final. (Issue [#6892](https://github.com/realm/realm-java/issues/6892)) * It is now possible to create embedded objects using [DynamicRealm]s. (Issue [#6982](https://github.com/realm/realm-java/pull/6982)) * Added extra validation and more meaningful error messages when creating embedded objects pointing to the wrong parent property. (See issue above) ### Fixed * [RealmApp] The same user opening different Realms with different partion key values would crash with an IllegalArgumentException. (Issue [#6882](https://github.com/realm/realm-java/issues/6882), since 10.0.0-BETA.1) * [RealmApp] Sync would not refresh the access token if started with an expired one. (Since 10.0.0-BETA.1) * [RealmApp] Leaking objects when registering session listeners. (Issue [#6916](https://github.com/realm/realm-java/issues/6916)) * Added support for Json-import of objects containing embedded objects. (Issue [#6896](https://github.com/realm/realm-java/issues/6896)) * Upgrading the file format result did in some cases not work correctly. This could result in a number of crashes, e.g. `FORMAT_UPGRADE_REQUIRED`. (Issue [#6889](https://github.com/realm/realm-java/issues/6889), since 7.0.0) * Bug in memory mapping management. This bug could result in multiple different asserts as well as segfaults. In many cases stack backtraces would include members of the EncyptedFileMapping near the top - even if encryption was not used at all. In other cases asserts or crashes would be in methods reading an array header or array element. In all cases the application would terminate immediately. (Realm Core PR [#3838](https://github.com/realm/realm-core/pull/3838), since 7.0.0) * It was possible to use `RealmObjectSchema` to mark a Class as embedded even if some of the objects broke the constraints for being embedded. ### Compatibility * File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later). * APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. * Realm Studio 10.0.0 and above is required to open Realms created by this version. ### Internal * Upgraded to Object Store commit: 5b5fb8a90192cb4ee6799e7465745cd2067f939b. * Upgraded to Realm Sync 10.0.0-beta.6. * Upgraded to Realm Core 10.0.0-beta.4. ## 10.0.0-BETA.5 (2020-06-19) We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. The old Realm Cloud legacy API's have undergone significant refactoring. The new API's are all located in the `io.realm.mongodb` package with `io.realm.mongodb.App` as the entry point. ### Enhancements * [RealmApp] Added support for Api Keys, Server Api Keys and Custom Functions as Credential types when logging in. * Added support for `distinct` queries on non-index and linked fields. (Issue [#1906](https://github.com/realm/realm-java/issues/1906)) ### Fixed * None. ### Compatibility * File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later). * APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. * Realm Studio 10.0.0 and above is required to open Realms created by this version. ### Internal * Upgraded to Object Store commit: e1570f8d3d7cf4d77f049933e6a241a501301383. ## 10.0.0-BETA.4 (2020-06-11) We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. The old Realm Cloud legacy API's have undergone significant refactoring. The new API's are all located in the `io.realm.mongodb` package with `io.realm.mongodb.App` as the entry point. ### Breaking Changes * None. ### Enhancements * [RealmApp] Added support for Custom Data using `User.customData()` and `User.refreshCustomData()`. * [RealmApp] Added support for managing push notifications using `App.getPush()`. ### Fixed * [RealmApp] Opening a synced Realm for a cached user with expired access token would crash the app with `Assertion failed: cls with (class_name) = ["io/realm/internal/objectstore/OsJavaNetworkTransport$Response"]`. (Issue [#6937](https://github.com/realm/realm-java/issues/6937), since 10.0.0-BETA.1) ### Compatibility * File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later). * APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. * Realm Studio 10.0.0 and above is required to open Realms created by this version. ### Internal * Updated to Object Store commit: 017d58fbec8a18ab003976b4c346308df88349a6. ## 10.0.0-BETA.3 (2020-06-09) We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. The old Realm Cloud legacy API's have undergone significant refactoring. The new API's are all located in the `io.realm.mongodb` package with `io.realm.mongodb.App` as the entry point. ### Breaking Changes * None. ### Enhancements * None. ### Fixed * [RealmApp] When restarting an app, the base URL used would in some cases be incorrect. (Since 10.0.0-BETA.2) ### Compatibility * File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later). * APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. * Realm Studio 10.0.0 and above is required to open Realms created by this version. ### Internal * Updated to Object Store commit: c02707bc28e1886970c5da29ef481dc0cb6c3dd8. ## 10.0.0-BETA.2 (2020-06-08) We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. The old Realm Cloud legacy API's have undergone significant refactoring. The new API's are all located in the `io.realm.mongodb` package with `io.realm.mongodb.App` as the entry point. ### Breaking Changes * None. ### Enhancements * None. ### Fixed * [RealmApp] `AppConfiguration` did not fallback to the correct default baseUrl if none was provided. (Since 10.0.0-BETA.1) * [RealmApp] When restarting an app, re-using the already logged in user would result in Sync not resuming. (Since 10.0.0-BETA.1) ### Compatibility * File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later). * APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. * Realm Studio 10.0.0 and above is required to open Realms created by this version. ### Internal * Updated to Object Store commit: c50be4dd178ef7e11d453f61a5ac2afa8c1c10bf. * Updated to Realm Sync 10.0.0-beta.2. ## 10.0.0-BETA.1 (2020-06-05) We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. The old Realm Cloud legacy API's have undergone significant refactoring. The new API's are all located in the `io.realm.mongodb` package with `io.realm.mongodb.App` as the entry point. ### Breaking Changes * [RealmApp] Removed all references and API's releated to permissions. These are now managed through MongoDB Realm. * [RealmApp] Query Based Sync API's and Subscriptions. These API's are not initially supported by MongoDB Realm. They will be re-introduced in a future release. `SyncConfiguration.partitionKey()` has been added as a replacement. * [RealmApp] Removed support for Client Resync. These API's are not initially supported by MongoDB Realm. They will be re-introduced in a future release. * [RealmApp] Removed suppport for custom SSL certificates. These API's are not initially supported by MongoDB Realm. They will be re-introduced in a future release. * [RealmApp] Destructive updates of a schema of a synced Realm will now consistently throw an `UnsupportedOperationException` instead of some methods throwing `IllegalArgumentException`. The affected methods are `RealmSchema.remove(String)`, `RealmSchema.rename(String, String)`, `RealmObjectSchema.setClassName(String)`, `RealmObjectSchema.removeField(String)`, `RealmObjectSchema.renameField(String, String)`, `RealmObjectSchema.removeIndex(String)`, `RealmObjectSchema.removePrimaryKey()`, `RealmObjectSchema.addPrimaryKey(String)` and `RealmObjectSchema.addField(String, Class, FieldAttribute)` ### Enhancements * Added support for `org.bson.types.Decimal128` and `org.bson.types.ObjectId` as supported fields in model classes. * Added support for `org.bson.types.ObjectId` as a primary key. * Added support for "Embedded Objects". They are enabled using `@RealmClass(embedded = true)`. An embedded object must have exactly one parent object linking to it and it will be deleted when the the parent is. Embedded objects can also be the parent of other embedded classes. Read more [here](https://realm.io/docs/java/latest/#embedded-objects). (Issue [#6713](https://github.com/realm/realm-java/issues/6713)) ### Fixed * After upgrading a Realm file, you may at some point receive a 'NoSuchTable' exception. (Issue [Core#3701](https://github.com/realm/realm-core/issues/3701), since 7.0.0) * If the Realm file upgrade process was interrupted/killed for various reasons, the following run would some assertions failing. (Issue [#6866](https://github.com/realm/realm-java/issues/6866), since 7.0.0). ### Compatibility * File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later). * APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. * Realm Studio 10.0.0 and above is required to open Realms created by this version. ### Internal * Updated to Object Store commit: 6d081a53377514f9b77736cb03051a03d829da922. * Updated to Realm Sync 10.0.0-beta.1. * Updated to Realm Core 10.0.0-beta.1. * OKHttp was upgraded to 3.12.0 from 3.10.0. * Updated Android Gradle Plugin to 3.6.1. * Updated Gradle to 5.6.4 * Updated Dokka to 0.10.1 * Updated Android Build Tools to 29.0.2. * Updated compileSdkVersion to 29. ## 7.0.8 (2020-10-01) ### Enhancements * Slightly improve performance of most operations which read data from the Realm file. ### Fixes * Making a query in an indexed property may give a "Key not found" exception. (.NET issue [#2025](https://github.com/realm/realm-dotnet/issues/2025), since 7.0.0) * Queries for null on non-nullable indexed integer properties could return wrong results if 0 entries should be found. (Since 7.0.0) * Rerunning an equals query on an indexed string column which previously had more than one match and now has one match would sometimes throw a "key not found" exception. (Cocoa issue [#6536](https://github.com/realm/realm-cocoa/issues/6536), Since 7.0.0) ### Compatibility * Realm Object Server: 3.23.1 or later. * File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later). * APIs are backwards compatible with all previous release of realm-java in the 7.x.y series. ### Internal * Upgraded to Object Store commit: 8a68df3e9fa7743c13d927eb7fc330ed9bb06693. * Upgraded to Realm Sync: 5.0.28. * Upgraded to Realm Core: 6.1.3. ## 7.0.7 (2020-09-25) ### Enhancements * None. ### Fixes * When querying a class where object references are part of the condition, the application may crash if objects have recently been added to the target table. (Issue [#7118](https://github.com/realm/realm-java/issues/7118), since v7.0.0) ### Compatibility * Realm Object Server: 3.23.1 or later. * File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later). * APIs are backwards compatible with all previous release of realm-java in the 7.x.y series. ### Internal * Upgraded to Object Store commit: 37e86c2905bfd424c16fc5d7860a1298bfc0ffa2. * Upgraded to Realm Sync: 5.0.25. * Upgraded to Realm Core: 6.1.1. ## 7.0.6 (2020-09-18) ### Enhancements * Better exception messaging for UTF encoding errors. ([Issue #7093](https://github.com/realm/realm-java/pull/7093)) ### Fixes * Fixes concurrent modification exceptions in the schema when refreshing a Realm (Issue [#6876](https://github.com/realm/realm-java/issues/6876)) * If you use encryption your application cound crash with a message like "Opening Realm files of format version 0 is not supported by this version of Realm". ([#6889](https://github.com/realm/realm-java/issues/6889) among others, since v7.0.0) ### Compatibility * Realm Object Server: 3.23.1 or later. * Realm Studio: 5.0.0 or later. * File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later). * APIs are backwards compatible with all previous release of realm-java in the 7.x.y series. ### Internal * Upgraded to Object Store commit: e29b5515df8b8adfe2454424b78878bb63879307. * Upgraded to Realm Sync: 5.0.23. * Upgraded to Realm Core: 6.0.26. ## 7.0.5 (2020-09-09) ### Enhancements * None. ### Fixes * If you have a Realm file growing towards 2Gb and have a model class with more than 16 properties, then you may get a "Key not found" exception when updating an object. (Realm JS issue [#3194](https://github.com/realm/realm-js/issues/3194), since v7.0.0) * In cases where you have more than 32 properties in a model class, you may get a currrupted file resulting in various crashes (Issue [#7057](https://github.com/realm/realm-java/issues/7057), since v7.0.0) ### Compatibility * Realm Object Server: 3.23.1 or later. * Realm Studio: 5.0.0 or later. * File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later). * APIs are backwards compatible with all previous release of realm-java in the 7.x.y series. ### Internal * Upgraded to Realm Sync: 5.0.22. * Upgraded to Realm Core: 6.0.25. ## 7.0.4 (2020-09-08) Note: Fileformat has been bumped from 10 to 11. This means that downgrading to an earlier version of Realm is not possible and Realm Studio 5.0.0 must be used to view Realm files. ### Enhancements * None. ### Fixes * In some cases a frozen Realm of the wrong version could be returned. ([ObjectStore issue #1078](https://github.com/realm/realm-object-store/pull/1078)) * Upgrading files with string primary keys would result in a file where it was not possible to find the objects by primary key. ([Core issue #3893](https://github.com/realm/realm-core/pull/3893), since 7.0.0) * NullPointerException when calling `toString` on RealmObjects with a binary field containing `null`. (Issue [#7084](https://github.com/realm/realm-java/issues/7084), since 7.0.0) ### Compatibility * Realm Object Server: 3.23.1 or later. * Realm Studio: 5.0.0 or later. * File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later). * APIs are backwards compatible with all previous release of realm-java in the 7.x.y series. ### Internal * Upgraded to Object Store commit: 286d7cb2f10c41f89a2efb43b22938610ccad4cf. * Upgraded to Realm Sync: 5.0.21. * Upgraded to Realm Core: 6.0.24. ## 7.0.3 (2020-09-01) ### Enhancements * Added `Realm.getNumberOfActiveVersions()`, which returns the current number of active versions maintained by the Realm file. ### Fixes * Creating a query inside a change listener could in some cases result in the version being pinned, which would either drastically increase filesize or cause `RealmConfiguration.maxNumberOfActiveVersions()` to trigger. (Issue [#6977](https://github.com/realm/realm-java/issues/6977), since 7.0.0) * If you upgrade a Realm file where you have "" elements in a list of non-nullable strings, the upgrade would crash. * If an attempt to upgrade a Realm file has ended with a crash with "migrate_links" in the call stack, the Realm ended in a corrupt state where further upgrade was not possible. A remedy for this situation is now provided. ### Compatibility * Realm Object Server: 3.23.1 or later. * Realm Studio: 4.0.0 or later. * File format: Generates Realms with format v10 (Reads and upgrades all previous formats from Realm Java 2.0 and later). * APIs are backwards compatible with all previous release of realm-java in the 7.x.y series. ### Internal * Upgraded to Object Store commit: eef80f42e6ede2294eb60f048228012d9b7bc627. * Upgraded to Realm Sync: 5.0.19. * Upgraded to Realm Core: 6.0.22. * The upgrade logic for upgrading fileformats has changed so that progress is now recorded explicitly in a table. This makes the logic simpler and reduces the chance of errors. It will also make it easier to detect if a file has only been partially upgraded. ## 7.0.2 (2020-08-14) ### Enhancements * None. ### Fixes * [ObjectServer] Calling `SyncManager.refreshConnections()` did not correctly refresh connections in all cases, which could delay reconnects up to 5 minutes. (Issue [#7003](https://github.com/realm/realm-java/issues/7003)) * Upgrading the file format result did in some cases not work correctly. This could result in a number of crashes, e.g. `FORMAT_UPGRADE_REQUIRED`. (Issue [#6889](https://github.com/realm/realm-java/issues/6889), since 7.0.0) * Bug in memory mapping management. This bug could result in multiple different asserts as well as segfaults. In many cases stack backtraces would include members of the EncyptedFileMapping near the top - even if encryption was not used at all. In other cases asserts or crashes would be in methods reading an array header or array element. In all cases the application would terminate immediately. (Issue [#3838](https://github.com/realm/realm-core/pull/3838), since 7.0.0) * Crash when retrieving `null` valued primitive fields from dynamic realm. (Issue [#7025](https://github.com/realm/realm-java/issues/7025)) ### Compatibility * Realm Object Server: 3.23.1 or later. * Realm Studio: 4.0.0 or later. * File format: Generates Realms with format v10 (Reads and upgrades all previous formats from Realm Java 2.0 and later). * APIs are backwards compatible with all previous release of realm-java in the 7.x.y series. ### Internal * Upgraded to Realm Sync 5.0.15. * Upgraded to Realm Core 6.0.17. ## 7.0.1 (2020-07-01) ### Enhancements * None. ### Fixes * Upgrading older Realm files with String indexes was very slow. (Issue [#6875](https://github.com/realm/realm-java/issues/6875), since 7.0.0) * Aborting upgrading a Realm file could result in the file getting corrupted. (Isse [#6866](https://github.com/realm/realm-java/issues/6866), since 7.0.0) * Automatic indexes on primary keys are now correctly stripped when upgrading the file as they are no longer needed. (Since 7.0.0) * `NoSuchTable` was thrown after comitting a transaction. (Issue [#6947](https://github.com/realm/realm-java/issues/6947)) ### Compatibility * Realm Object Server: 3.23.1 or later. * Realm Studio: 4.0.0 or later. * File format: Generates Realms with format v10 (Reads and upgrades all previous formats from Realm Java 2.0 and later). * APIs are backwards compatible with all previous release of realm-java in the 7.x.y series. ### Internal * Upgraded to Realm Sync 5.0.7. * Upgraded to Realm Core 6.0.8. ## 7.0.0 (2020-05-16) NOTE: This version bumps the Realm file format to version 10. Files created with previous versions of Realm will be automatically upgraded. It is not possible to downgrade to version 9 or earlier. Only [Studio 3.11](https://github.com/realm/realm-studio/releases/tag/v3.11.0) or later will be able to open the new file format. NOTE: This version bumps the Realm file format to version 10. Files created with previous versions of Realm will be automatically upgraded. It is not possible to downgrade to version 9 or earlier. Only [Realm Studio 4](https://github.com/realm/realm-studio/releases/tag/v4.0.0) or later will be able to open the new file format. ### Breaking Changes * [ObjectServer] Removed deprecated method `SyncConfiguration.Builder.partialRealm()`. Use `SyncConfiguration.Builder.fullSynchronization()` instead. * [ObjectServer] Removed deprecated methods `SyncConfiguration.automatic()` and `SyncConfiguration.automatic(User, Uri)`. Use `SyncUser.getDefaultConfiguration()` and `SyncUser.createConfiguration(Url)`. * [ObjectServer] Removed deprecated method `ErrorCode.fromInt(int)`. * [ObjectServer] Removed deprecated method `SyncCredentials.nickname(name)` and `SyncCredentials.nickname(name, isAdmin)`. Use `SyncCredentials.usernamePassword(username, password)` instead. * [ObjectServer] Deprecated state `SyncSession.State.ERROR` has been removed. Use `SyncConfiguration.Builder.errorHandler(ErrorHandler)` instead. * [ObjectServer] `IncompatibleSyncedFileException` is removed as it is no longer used. * [ObjectServer] New error codes thrown by the underlying sync layers now have proper enum mappings in `ErrorCode.java`. A few other errors have been renamed in order to have consistent naming. (Issue [#6387](https://github.com/realm/realm-java/issues/6387)) * RxJava Flowables and Observables are now subscribed to and unsubscribed to asynchronously on the thread holding the live Realm, instead of previously where this was done synchronously. * All RxJava Flowables and Observables now return frozen objects instead of live objects. This can be configured using `RealmConfiguration.Builder.rxFactory(new RealmObservableFactory(true|false))`. By using frozen objects, it is possible to send RealmObjects across threads, which means that all RxJava operators should now be supported without the need to copy Realm data into unmanaged objects. * MIPS is not supported anymore. * Realm now requires `minSdkVersion` 16. Up from 9. * [ObjectServer] `IncompatibleSyncedFileException` is removed and no longer thrown. ### Enhancements * Added `Realm.freeze()`, `RealmObject.freeze()`, `RealmResults.freeze()` and `RealmList.freeze()`. These methods will return a frozen version of the current Realm data. This data can be read from any thread without throwing an `IllegalStateException`, but will never change. All frozen Realms and data can be closed by calling `Realm.close()` on the frozen Realm, but fully closing all live Realms will also close the frozen ones. Frozen data can be queried as normal, but trying to mutate it in any way will throw an `IllegalStateException`. This includes all methods that attempt to refresh or add change listeners. (Issue [#6590](https://github.com/realm/realm-java/pull/6590)) * Added `Realm.isFrozen()`, `RealmObject.isFrozen()`, `RealmObject.isFrozen(RealmModel)`, `RealmResults.isFrozen()` and `RealmList.isFrozen()`, which returns whether or not the data is frozen. * Added `RealmConfiguration.Builder.maxNumberOfActiveVersions(long number)`. Setting this will cause Realm to throw an `IllegalStateException` if too many versions of the Realm data are live at the same time. Having too many versions can dramatically increase the filesize of the Realm. * Storing large binary blobs in Realm files no longer forces the file to be at least 8x the size of the largest blob. * Reduce the size of transaction logs stored inside the Realm file, reducing file size growth from large transactions. * `RealmResults.asJSON()` is no longer `@Beta` * The default `toString()` for proxy objects now print the length of binary fields. (Issue [#6767](https://github.com/realm/realm-java/pull/6767)) ### Fixes * If a DynamicRealm and Realm was opened for the same file they would share transaction state by accident. The implication was that writes to a `Realm` would immediately show up in the `DynamicRealm`. This has been fixed, so now it is required to call `refresh()` on the other Realm or wait for normal change listeners to detect the change. ### Compatibility * Realm Object Server: 3.23.1 or later. * Realm Studio: 4.0.0 or later. * File format: Generates Realms with format v10 (Reads and upgrades all previous formats from Realm Java 2.0 and later). * APIs are backwards compatible with all previous release of realm-java in the 7.x.y series. ### Internal * `OsSharedRealm.VersionID.hashCode()` was not implemented correctly and included the memory location in the hashcode. * OKHttp was upgraded to 3.10.0 from 3.9.0. * The NDK has been upgraded from r10e to r21. * The compiler used for C++ code has changed from GCC to Clang. * OpenSSL used by Realms encryption layer has been upgraded from 1.0.2k to 1.1.1b. * Updated to Object Store commit: 820b74e2378f111991877d43068a95d2b7a2e404. * Updated to Realm Sync 5.0.3. * Updated to Realm Core 6.0.4. ### Credits * Thanks to @joxon for better support for binary fields in proxy objects. ## 6.1.0(2020-01-17) ### Fixed * None. ### Compatibility * Realm Object Server: 3.23.1 or later. * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * APIs are backwards compatible with all previous release of realm-java in the 6.x.y series. ### Internal * None. ## 6.1.0(2020-01-17) ### Enhancements * The Realm Gradle plugin now applies `kapt` when used in Kotlin Multiplatform projects. Note, Realm Java still only works for the Android part of a Kotlin Multiplatform project. (Issue [#6653](https://github.com/realm/realm-java/issues/6653)) * The error message shown when no native code could be found for the device is now much more descriptive. This is particular helpful if an app is using App Bundle or APK Split and the resulting APK was side-loaded outside the Google Play Store. (Issue [#6673](https://github.com/realm/realm-java/issues/6673)) * `RealmResults.asJson()` now encode binary data as Base64 and null object links are reported as `null` instead of `[]`. ### Fixed * Fixed using `RealmList` with a primitive type sometimes crashing with `Destruction of mutex in use`. (Issue [#6689](https://github.com/realm/realm-java/issues/6689)) * `RealmObjectSchema.transform()` would crash if one of the `DynamicRealmObject` provided are deleted from the Realm. (Issue [#6657](https://github.com/realm/realm-java/issues/6657), since 0.86.0) * The Realm Transformer will no longer attempt to send anonymous metrics when Gradle is invoked with `--offline`. (Issue [#6691](https://github.com/realm/realm-java/issues/6691)) ### Compatibility * Realm Object Server: 3.23.1 or later. * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * APIs are backwards compatible with all previous release of realm-java in the 6.x.y series. ### Internal * Updated to ReLinker 1.4.0. * Updated to Object Store commit: 2a204063e1e1a366efbdd909fbea9effceb7d3c4. * Updated to Realm Sync 4.9.4. * Updated to Realm Core 5.23.8. ### Credits * Thanks to @sellmair (Sebastian Sellmair) for improving Kotlin Multiplatform support. ## 6.0.2(2019-11-21) ### Enhancements * None. ### Fixed * [ObjectServer] `SyncSession` progress listeners now work correctly in combination with `SyncConfiguration.waitForInitialRemoteData()`. * The `@RealmModule` annotation would be stripped on an empty class when using R8 resulting in apps crashing on startup with `io.realm.DefaultRealmModule is not a RealmModule. Add @RealmModule to the class definition.`. ([#6449](https://github.com/realm/realm-java/issues/6449)) ### Compatibility * Realm Object Server: 3.23.1 or later. * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * APIs are backwards compatible with all previous release of realm-java in the 6.x.y series. ### Internal * Updated to Object Store commit: ad96a4c334b475dd67d50c1ca419e257d7a21e18. * Updated to Realm Sync v4.8.3. ## 6.0.1(2019-11-11) NOTE: Anyone using encrypted Realms are strongly advised to upgrade to this version. ### Enhancements * None ### Fixed * When using encrypted Realms a race condition could lead to the Realm ending up corrupted when the file increased in size. This could manifest as a wide array of different error messages. Most commonly seen has been "Fatal signal 11 (SIGSEGV) from Java_io_realm_internal_UncheckedRow_nativeGetString", "RealmFileException: Top ref outside file" and "Unable to open a realm at path. ACCESS_ERROR: Invalid mnemonic". ([#6152](https://github.com/realm/realm-java/issues/6152), since 5.0.0) * `RealmResults.asJSON()` now prints lists with primitive values directly instead of wrapping each value in an object with an `!ARRAY_VALUE` property. ### Compatibility * Realm Object Server: 3.23.1 or later. * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * APIs are backwards compatible with all previous release of realm-java in the 6.x.y series. ### Internal * Updated to Realm Sync 4.7.12. * Updated to Realm Core 5.23.6. ### Credits * Thanks to Vladimir Konkov (@vladimirfx) for help with isolating ([#6152](https://github.com/realm/realm-java/issues/6152)). ## 6.0.0(2019-10-01) ### Breaking Changes * [ObjectServer] The `PermissionManager` is no longer backed by Realms but instead a REST API. This means that the `PermissionManager` class has been removed and all methods have been moved to `SyncUser`. Some method names have been renamed slightly and return values for methods have changed from `RealmResults` to `List`. This should only have an impact if change listeners were used to listen for changes. In these cases, you must now manually retry the request. ### Enhancements None. ### Fixed None. ### Compatibility * Realm Object Server: 3.23.1 or later. * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * APIs are backwards compatible with all previous release of realm-java in the 6.x.y series. ### Internal * [ObjectServer] The OKHttp client will now follow redirects from the Realm Object Server. ## 5.15.2(2019-09-30) ### Enhancements * None. ### Fixed * `null` values were not printed correctly when using `RealmResults.asJSON()` (Realm Core Issue [#3399](https://github.com/realm/realm-core/pull/3399)) * [ObjectServer] Queries with nullable `Date`'s did not serialize correctly. Only relevant if using Query-based Synchronization. (Realm Core issue [#3388](https://github.com/realm/realm-core/pull/3388)) * [ObjectServer] Fixed crash with `java.lang.IllegalStateException: The following changes cannot be made in additive-only schema mode` when opening an old Realm created between Realm Java 5.10.0 and Realm Java 5.13.0. (Issue [#6619](https://github.com/realm/realm-java/issues/6619), since 5.13.0). ### Compatibility * Realm Object Server: 3.21.0 or later. * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. ### Internal * Updated to Object Store commit: 8416010e4be5e32ba552ff3fb29e500f3102d3db. * Updated to Realm Sync 4.7.8. * Updated to Realm Core 5.23.5. * Updated Docker image used on CI to Node 10. ## 5.15.1(2019-09-09) ### Enhancements * None. ### Fixed * Projects with `flatDirs` repositories defined crashed the build with `MissingPropertyException`. (Issue [#6610](https://github.com/realm/realm-java/issues/6610), since 5.15.0). ### Compatibility * Realm Object Server: 3.21.0 or later. * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. ### Internal * None. ## 5.15.0(2019-09-05) ### Enhancements * [ObjectServer] Added support for Client Resync for fully synchronized Realms which automatically will recover the local Realm in case the server is rolled back. This largely replaces the Client Reset mechanism. Can be configured using `SyncConfiguration.Builder.clientResyncMode()`. (Issue [#6487](https://github.com/realm/realm-java/issues/6487)) ### Fixed * Huawei devices reporting `Permission denied` when opening a Realm file after an app upgrade or factory reset. This does not automatically fix already existing Realm files. See [this FAQ entry](https://realm.io/docs/java/latest/#huawei-permission-denied) for more details. (Issue [#5715](https://github.com/realm/realm-java/issues/5715)) * `Realm.copyToRealm()` and `Realm.insertOrUpdate()` crashed on model classes if `@LinkingObjects` was used to target a field with a re-defined internal name in the parent class (e.g. by using `@RealmField`). (Issue [#6581](https://github.com/realm/realm-java/issues/6581)) ### Compatibility * Realm Object Server: 3.21.0 or later. * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. ### Internal * Implemented direct access to sync workers on Cloud, bypassing the Sync Proxy: the binding will override the sync session's url prefix if the token refresh response for a realm contains a sync worker path field. * Updated to Object Store commit: 9f19d79fde248ba37cef0bd52fe64984f9d71be0. * Updated to Realm Sync 4.7.4. * Updated to Realm Core 5.23.2. ## 5.14.0(2019-08-12) ### Deprecated * [ObjectServer] `SyncCredentials.nickname()` has been deprecated in favour of `SyncCredentials.usernamePassword()`. * [ObjectServer] `SyncCredentials.IdentityProvider.NICKNAME` has been deprecated in favour of `SyncCredentials.IdentityProvider.USERNAME_PASSWORD`. ### Enhancements * None. ### Fixed * None. ### Compatibility * Realm Object Server: 3.21.0 or later. * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. ### Internal * None. ## 5.13.1(2019-08-05) ### Enhancements * None. ### Fixed * [ObjectServer] The C++ networking layer now correctly uses any system defined proxy the same way the Java networking layer does. (Issue [#6574](https://github.com/realm/realm-java/pull/6574)). * The Realm bytecode transformer now works correctly with Android Gradle Plugin 3.6.0-alpha01 and beyond. (Issue [#6531](https://github.com/realm/realm-java/issues/6531)). * Queries on RealmLists with objects containing indexed integers could return the wrong result. (Issue [#6522](https://github.com/realm/realm-java/issues/6522), since 5.11.0) ### Compatibility * Realm Object Server: 3.21.0 or later. * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. ### Internal * Updated JavaAssist in the Realm Transformer to 3.25.0-GA. * Updated to Realm Core 5.23.1. * Updated to Realm Sync 4.7.1. * Updated to Object Store commit: bcc6a7524e52071bfcd35cf740f506e0cc6a595e ## 5.13.0(2019-07-23) ### Enhancements * [ObjectServer] Added support for faster initial synchronization for fully synchronized Realms. (Issue [#6469](https://github.com/realm/realm-java/issues/6469)) * [ObjectServer] Improved session lifecycle debug output. (Issue [#6552](https://github.com/realm/realm-java/pull/6552)). ### Fixed * None. ### Compatibility * Realm Object Server: 3.21.0 or later. * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. ### Internal * Updated to Realm Core 5.22.0. * Updated to Realm Sync 4.6.1. * Updated to Object Store commit f0d75261fc8d332c20dc82f643dd795c0f4c7aec ## 5.12.0(2019-06-20) ### Enhancements * [ObjectServer] Added `SyncManager.refreshConnections()` that can be used to manually trigger a reconnect for all sessions. This is useful if the device has been offline for a long time or fail to detect that it regained connectivity. (Issue [#259](https://github.com/realm/realm-java-private/issues/259)) * Added `RealmResults.asJson()` in `@Beta` that returns the result of the query as a JSON payload (#6540). ### Fixed * [ObjectServer] `PermissionManager` stopped working if an intermittent network error was reported. (Issue [#6492](https://github.com/realm/realm-java/issues/6492), since 3.7.0) * The Kotlin extensions library no longer defines a `app_name`, which in some cases conflicted with the `app_name` defined by applications. (Issue [#6536](https://github.com/realm/realm-java/issues/6536), since 4.3.0) ### Compatibility * Realm Object Server: 3.21.0 or later. * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. ### Internal * Updated to Realm Core 5.22.0. * Updated to Realm Sync 4.6.1. * Updated to Object Store commit 7c3ff8235579550a3e3c6060c47140b2005174f5 ## 5.11.0(2019-05-01) NOTE: This version is only compatible with Realm Object Server 3.21.0 or later. ### Enhancements * [ObjectServer] Added `RealmQuery.includeLinkingObjects()`. This is only relevant for Query-based Realms and tells subscriptions to include objects linked through `@LinkingObjects` fields as part of the subscription as well. Objects referenced through objects and lists are always included as a default. (Issue [#6426](https://github.com/realm/realm-java/issues/6426)) * Encryption now uses hardware optimized functions, which significantly improves the performance of encrypted Realms. ([Realm Core PR #3241](https://github.com/realm/realm-core/pull/3241)) * Improved query performance when using `RealmQuery.in()` queries. ([Realm Core PR #3250](https://github.com/realm/realm-core/pull/3250)). * Improved query performance when querying Integer fields with indexes, e.g. primary key fields. ([Realm Core PR #3272](https://github.com/realm/realm-core/pull/3272)). * Improved write performance when writing changes to disk ([Realm Core PR #2927](https://github.com/realm/realm-sync/issues/2927)) * Added support for incremental annotation processing added in Gradle 4.7. (Issue [#5906](https://github.com/realm/realm-java/issues/5906)). ### Fixed * [ObjectServer] Fix an error in the calculation of the `downloadableBytes` value sent by `ProgressListeners`. * [ObjectServer] HTTP requests made by the Sync client now always include a Host: header, as required by HTTP/1.1, although its value will be empty if no value is specified by the application. * [ObjectServer] The server no longer rejects subscriptions based on queries with distinct and/or limit clauses. * [ObjectServer] If a user had `canCreate` but not `canUpdate` privileges on a class, the user would be able to create the object, but not actually set any meaningful values on that object, despite the rule that objects created within the same transaction can always be modified. * Native crash happening if bulk updating a field in a `RealmResult` would cause the object to no longer be part of the query result. (Issue [#6478](https://github.com/realm/realm-java/issues/6478), since 5.8.0). ### Compatibility * Realm Object Server: 3.21.0 or later. * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. ### Internal * Updated to Realm Core 5.19.1. * Updated to Relm Sync 4.4.2. * Updated to Object Store commit e4b1314d21b521fd604af7f1aacf3ca94272c19a ## 5.10.0(2019-03-22) ### Enhancements * [ObjectServer] Added 4 new fields to query-based Subscriptions: `createdAt`, `updatedAt`, `expiresAt` and `timeToLive`. These make it possible to better reason about and control current subscriptions. (Issue [#6453](https://github.com/realm/realm-java/issues/6453)) * [ObjectServer] Added the option of updating the query controlled by a Subscription using either `RealmQuery.findAllAsync(String name, boolean update)`, `RealmQuery.subscribe(String name, boolean update)` or `Subscription.setQuery(RealmQuery query)`. (Issue [#6453](https://github.com/realm/realm-java/issues/6453)) * [ObjectServer] Added the option of setting a time-to-live for subscriptions. Setting this will automatically delete the subscription after the provided TTL has expired and the subscription hasn't been used. (Issue [#6453](https://github.com/realm/realm-java/issues/6453)) ### Fixed * Dates returned from the Realm file no longer overflow or underflow if they exceed `Long.MAX_VALUE` or `Long.MIN_VALUE` but instead clamp to their respective value. (Issue [#2722](https://github.com/realm/realm-java/issues/2722)) ### Compatibility * Realm Object Server: 3.11.0 or later. * File format: Generates Realms with format v9 (Reads and upgrades all previous formats). * APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. ### Internal * Updated to Object Store commit: e9819ed9c77ed87b5d7bed416a76cd5bcf255802 ## 5.9.1(2019-02-21) ### Enhancements * None ### Fixed * [ObjectServer] Reporting too many errors from the native layer resulted in a native crash with `local reference table overflow`. (Issue [#249](https://github.com/realm/realm-java-private/issues/249), since 5.9.0) ### Compatibility * Realm Object Server: 3.11.0 or later. * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. ### Internal * None ## 5.9.0(2019-01-15) ### Enhancements * [ObjectServer] Added `ObjectServerError.getErrorType()` and `ObjectServerError.getErrorType()` which returns the underlying native error information. This is especially relevant if `ObjectServerError.getErrorCode()` returns `UNKNOWN`. [#6364](https://github.com/realm/realm-java/issues/6364) * 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. (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). ### Compatibility * Realm Object Server: 3.11.0 or later. * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. ### Internal * Updated to Object Store commit: f964c2640f635e76839559cb703732e9e906ba4c * Updated Realm Sync to 3.14.13 * Updated Realm Core to 5.12.7 ## 5.8.0 (2018-11-06) This release also contains all changes from 5.8.0-BETA1 and 5.8.0-BETA2. ### Enhancements * [ObjectServer] Added Subscription class available to Query-based Realms. This exposes a Subscription more directly. This class is in beta. [#6231](https://github.com/realm/realm-java/pull/6231). * [ObjectServer] Added `Realm.getSubscriptions()`, `Realm.getSubscriptions(String pattern)` and `Realm.getSubscription` to make it easier to find existing subscriptions. These API's are in beta. [#6231](https://github.com/realm/realm-java/pull/6231) * [ObjectServer] Added `RealmQuery.subscribe()` and `RealmQuery.subscribe(String name)` to subscribe immediately inside a transaction. These API's are in beta. [#6231](https://github.com/realm/realm-java/pull/6231) * [ObjectServer] Added support for subscribing directly inside `SyncConfiguration.initialData()`. This can be coupled with `SyncConfiguration.waitForInitialRemoteData()` in order to block a Realm from opening until the initial subscriptions are ready and have downloaded data. This API are in beta. [#6231](https://github.com/realm/realm-java/pull/6231) * [ObjectServer] Improved performance when merging changes from the server. * [ObjectServer] Added support for timeouts when uploading or downloading data manually using `SyncSession.downloadAllServerChanges(long timeout, TimeUnit unit)` and `SyncSession.uploadAllLocalChanges(long timeout, TimeUnit unit)`. [#6073](https://github.com/realm/realm-java/pull/6073) * [ObjectServer] Added support for timing out when downloading initial data for synchronized Realms using `SyncConfiguration.waitForInitialRemoteData(long timeout, TimeUnit unit)`. [#6247](https://github.com/realm/realm-java/issues/6247) * [ObjectServer] Added `Realm.init(Context, String)` which defines a custom User-Agent String sent to the Realm Object Server when a session is created. Using this requires Realm Object Server 3.12.4 or later. [#6267](https://github.com/realm/realm-java/issues/6267) * Added support for `ImportFlag`s to `Realm.copyToRealm()` and `Realm.copyToRealmOrUpdate()`. This makes it possible to choose a mode so only fields that actually changed are written to disk. This improves notifications and Object Server performance. [#6224](https://github.com/realm/realm-java/pull/6224) * Added support for bulk updating the same property in all objects that are part of a query result using `RealmResults.setValue(String fieldName, Object value)` or one of the specialized overrides that have been added for all supported types, e.g. `RealmResults.setString(String fieldName, String value)`. [#762](https://github.com/realm/realm-java/issues/762) ### Fixed * All known bugs introduced in 5.8.0-BETA1 and 5.8.0-BETA2. See the release notes for these releases. ### Compatibility * Realm Object Server: 3.11.0 or later. * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. ### Internal * Updated to Object Store commit: f0dfe6c03be49194bc40777901059eaf55e7bff6 * Updated Realm Sync to 3.13.1 * Updated Realm Core to 5.12.0 ## 5.8.0-BETA2 (2018-10-19) ### Enhancements * None ### Fixed * `RealmResults` listeners not triggering the initial callback for Query-based Realm when the device is offline [#6235](https://github.com/realm/realm-java/issues/6235). ### Known Bugs * `Realm.copyToRealm()` and `Realm.copyToRealmOrUpdate` has been rewritten to support import flags. It is currently ~30% slower than in 5.7.0. * IllegalStateException thrown when trying to create an object with a primary key that already exists when using `Realm.copyToRealm`, will always report "null" instead of the correct primary key value. * When using `ImportFlag.DO_NOT_SET_SAME_VALUES`, lists will still be written and reported as changed, even if they didn't change. ### Compatibility * Realm Object Server: 3.11.0 or later. * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. ### Internal * None ## 5.8.0-BETA1 (2018-10-11) ### Enhancements * Added new `ImportFlag` class that is used to specify additional behaviour when importing data into Realm [#6224](https://github.com/realm/realm-java/pull/6224). * Added support for `ImportFlag` to `Realm.copyToRealm()` and `Realm.copyToRealmOrUpdate()` [#6224](https://github.com/realm/realm-java/pull/6224). ### Fixed * None ### Known Bugs * `Realm.copyToRealm()` and `Realm.copyToRealmOrUpdate` has been rewritten to support import flags. It is currently ~30% slower than in 5.7.0. * IllegalStateException thrown when trying to create an object with a primary key that already exists when using `Realm.copyToRealm`, will always report "null" instead of the correct primary key value. * When using `ImportFlag.DO_NOT_SET_SAME_VALUES`, lists will still be written and reported as changed, even if they didn't change. ### Compatibility * Realm Object Server: 3.11.0 or later. * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. ## 5.7.1 (2018-10-22) ### Enhancements * None ### Fixed * [ObjectServer] `RealmResults` listeners not triggering the initial callback for Query-based Realm when the device is offline. (Issue [#6235](https://github.com/realm/realm-java/issues/6235), since 5.0.0). ### Compatibility * Realm Object Server: 3.11.0 or later. * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. ### Internal * Updated to Object Store commit: 362b886628b3aefc5b7a0bc32293d794dc1d4ad5 ## 5.7.0 (2018-09-24) ### Enhancements * [ObjectServer] Devices will now report download progress for read-only Realms which will allow the server to compact files sooner, saving server space. This does not affect the client. You will need to upgrade your Realm Object Server to at least version 3.11.0 or use [Realm Cloud](https://cloud.realm.io). If you try to connect to a ROS v3.10.x or previous, you will see an error like `Wrong protocol version in Sync HTTP request, client protocol version = 25, server protocol version = 24`. ### Fixed * None ### Compatibility * Realm Object Server: 3.11.0 or later. * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. ### Internal * Sync Protocol version increased to 25. * Updated Realm Sync to 3.10.1 * Updated Realm Core to 5.10.2 ## 5.6.0 (2018-09-24) ### Enhancements * [ObjectServer] Added `RealmPermissions.findOrCreate(String roleName)` and `ClassPermissions.findOrCreate(String roleName)` ([#6168](https://github.com/realm/realm-java/issues/6168)). * `@RealmClass("name")` and `@RealmField("name")` can now be used as a shorthand for defining custom name mappings ([#6145](https://github.com/realm/realm-java/issues/6145)). * Added support for `RealmQuery.limit(long limit)` ([#544](https://github.com/realm/realm-java/issues/544)). When building a `RealmQuery`, `sort()`, `distinct()` and `limit()` will now be applied in the order they are called. Before this release, `sort()` and `distinct()` could be called any order, but `sort()` would always be applied before `distinct()`. * Building with Android App Bundle is now supported ([#5977](https://github.com/realm/realm-java/issues/5977)). ### Fixed * None ### Compatibility * Realm Object Server: 3.11.0 or later. * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. ### Internal * Updated ReLinker to 1.3.0. * Updated to Object Store commit: 7e19c51af72c3343b453b8a13c82dfda148e4bbc ## 5.5.0 (2018-08-31) ### Enhancements * [ObjectServer] Added `ConnectionState` enum describing the states a connection can be in. * [ObjectServer] Added `SyncSession.isConnected()` and `SyncSession.getConnectionState()`. * [ObjectServer] Added support for observing connection changes for a session using `SyncSession.addConnectionChangeListener()` and `SyncSession.removeConnectionChangeListener()`. * [ObjectServer] Added Kotlin extension property `Realm.syncSession` for synchronized Realms. * [ObjectServer] Added Kotlin extension method `Realm.classPermissions()`. * [ObjectServer] Added support for starting and stopping synchronization using `SyncSession.start()` and `SyncSession.stop()` (#6135). * [ObjectServer] Added API's for making it easier to work with network proxies (#6163): * `SyncManager.setAuthorizationHeaderName(String headerName)` * `SyncManager.setAuthorizationHeaderName(String headerName, String host)` * `SyncManager.addCustomRequestHeader(String headerName, String headerValue)` * `SyncManager.addCustomRequestHeader(String headerName, String headerValue, String host)` * `SyncManager.addCustomRequestHeaders(Map headers)` * `SyncManager.addCustomRequestHeaders(Map headers, String host)` * `SyncConfiguration.Builder.urlPrefix(String prefix)` ### Fixed * Methods and classes requiring synchronized Realms have been removed from the standard AAR package. They are now only visible when enabling synchronized Realms in Gradle. The methods and classes will still be visible in the source files and docs, but annotated with `@ObjectServer` (#5799). ### Internal * Updated to Realm Sync 3.9.4 * Updated to Realm Core 5.8.0 * Updated to Object Store commit: b0fc2814d9e6061ce5ba1da887aab6cfba4755ca ### Credits * Thanks to @lucasdornelasv for improving the performance of `Realm.copyToRealm()`, `Realm.copyToRealmOrUpdate()` and `Realm.copyFromRealm()` #(6124). ## 5.4.3 (YYYY-MM-DD) ### Bug Fixes * [ObjectServer] ProGuard was not configured correctly when working with Subscriptions for Query-based Realms. ## 5.4.2 (2018-08-09) ### Bug Fixes * [ObjectServer] Fixed bugs in the Sync Client that could lead to memory corruption and crashes. ### Internal * Upgraded to Realm Sync 3.8.8 ## 5.4.1 (2018-08-03) ### Bug Fixes * Compile time crash if no `targetSdk` was defined in Gradle. This was introduced in 5.4.0 (#6082). * Fix Realm Gradle Plugin adding dependencies in a way incompatible with Kotlin Android Extensions. This was introduced in Realm Java 5.4.0 (#6080). ## 5.4.0 (2018-07-22) ### Enhancements * Removing a ChangeListener on invalid objects or `RealmResults` should warn instead of throwing (fixes #5855). ### Bug Fixes * [ObjectServer] Using Android Network Security Configuration is necessary to install the custom root CA for tests (API >= 24) (#5970). * Fixes issue with the incremental build causing direct access to model without accessor to fail (#6056). * `RealmQuery.distinct()` is now correctly applied when calling `RealmQuery.count()` (#5958). ### Internal * Upgraded to Realm Core 5.7.2 * Upgraded to Realm Sync 3.8.1 * [ObjectServer] Improved performance when integrating changes from the server. * Added extra information about the state of the Realm file if an exception is thrown due to Realm not being able to open it. * Removed internal dependency on Groovy in the Realm Transformer (#3971). ### Credits * Thanks to @kageiit for removing Groovy from the Realm Transformer (#3971). ## 5.3.1 (2018-06-19) ### Bug Fixes * [ObjectServer] Fixed a bug which could potentially flood Realm Object Server with PING messages. * Calling `Realm.deleteAll()` on a Realm file that contains more classes than in the schema throws exception (#5745). * `Realm.isEmpty()` returning false in some cases, even if all tables part of the schema are empty (#5745). * Fixed rare native crash materializing as `Assertion failed: ref + size <= after_ref with (ref, size, after_ref, ndx, m_free_positions.size())` (#5300). ### Internal * Upgraded to Realm Core 5.6.2 * Upgraded to Realm Sync 3.5.6 * Upgraded to Object Store commit `0bcb9643b8fb14323df697999b79c4a5341a8a21` ## 5.3.0 (2018-06-12) ### Enhancements * [ObjectServer] `Realm.compactRealm(config)` now works on synchronized Realms (#5937). * [ObjectServer] `SyncConfiguration.compactOnLaunch()` and `SyncConfiguration.compactOnLaunch(callback)` has been added (#5937). * Added `RealmQuery.getRealm()`, `RealmResults.getRealm()`, `RealmList.getRealm()` and `OrderedRealmCollectionSnapshot.getRealm()` (#5997). * Removing a ChangeListener on invalid objects or `RealmResults` should warn instead of throwing (fixes #5855). ### Internal * Upgraded to Realm Core 5.6.0 * Upgraded to Realm Sync 3.5.2 ## 5.2.0 (2018-06-06) The feature previously named Partial Sync is now called Query-Based Sync and is now the default mode when synchronizing Realms. This has impacted a number of API's. See below for the details. ### Deprecated * [ObjectServer] `SyncConfiguration.automatic()` has been deprecated in favour of `SyncUser.getDefaultConfiguration()`. * [ObjectServer] `new SyncConfiguration.Builder(user, url)` has been deprecated in favour of `SyncUser.createConfiguration(url)`. NOTE: Creating configurations using `SyncUser` will default to using query-based Realms, while creating them using `new SyncConfiguration.Builder(user, url)` will default to fully synchronized Realms. * [ObjectServer] With query-based sync being the default `SyncConfiguration.Builder.partialRealm()` has been deprecated. Use `SyncConfiguration.Builder.fullSynchronization()` if you want full synchronisation instead. ### Enhancements * [ObjectServer] Added `SyncUser.createConfiguration(url)`. Realms created this way are query-based Realms by default. * [ObjectServer] Added `SyncUser.getDefaultConfiguration()`. * The Realm bytecode transformer now supports incremental builds (#3034). * Improved speed and allocations when parsing field descriptions in queries (#5547). ### Bug Fixes * Having files that ends with `RealmProxy` will no longer break the Realm Transformer (#3709). ### Internal * Module mediator classes being generated now produces a stable output enabling better support for incremental builds (#3034). ## 5.1.0 (2018-04-25) ### Enhancements * [ObjectServer] Added support for `SyncUser.requestPasswordReset()`, `SyncUser.completePasswordReset()` and their async variants. This makes it possible to reset the password for users created using `Credentials.usernamePassword()` where they used their email as username (#5821). * [ObjectServer] Added support for `SyncUser.requestEmailConfirmation()`, `SyncUser.confirmEmail()` and their async variants. This makes it possible to ask users to confirm their email. This is only supported for users created using `Credentials.usernamePassword()` who have used an email as their username (#5821). * `RealmQuery.in()` now support `null` which will always return no matches (#4011). * Added support for `RealmQuery.alwaysTrue()` and `RealmQuery.alwaysFalse()`. ### Bug Fixes * Changing a primary key from being nullable to being required could result in objects being deleted (##5899). ## 5.0.1 (2018-04-09) ### Enhancements * [ObjectServer] `SyncConfiguration.automatic()` will make use of the host port to work out the default Realm URL. * [ObjectServer] A role is now automatically created for each user with that user as its only member. This simplifies the common use case of restricting access to specific objects to a single user. This role can be accessed at `PermissionUser.getRole()`. * [ObjectServer] Expose `Role.getMembers()` to access the list of associated `UserPermission`. ### Bug Fixes * `RealmList.move()` did not move items correctly for unmanaged lists (#5860). * `RealmObject.isValid()` not correctly returns `false` if `null` is provided as an argument (#5865). * `RealmQuery.findFirst()` and `RealmQuery.findFirstAsync()` not working correctly with sorting (#5714). * Permission `noPrivileges` and `allPrivileges` were returning opposite privileges. * Fixes an issue caused by JNI local table reference overflow (#5880). ### Internal * Upgraded to Realm Sync 3.0.1 * Upgraded to Realm Core 5.4.2 ## 5.0.0 (2018-03-15) This release is compatible with the Realm Object Server 3.0.0-beta.3 or later. ### Known Bugs * API's marked @ObjectServer are shipped as part of the base binary, they should only be available when enabling synchronized Realms. ### Breaking Changes * [ObjectServer] Renamed `SyncUser.currentUser()` to `SyncUser.current()`. * [ObjectServer] Renamed `SyncUser.login(...)` and `SyncUser.loginAsync(...)` to `SyncUser.logIn(...)` and `SyncUser.logInAsync(...)`. * [ObjectServer] Renamed `SyncUser.logout()` to `SyncUser.logOut()`. * The `OrderedCollectionChangeSet` parameter in `OrderedRealmCollectionChangeListener.onChange()` is no longer nullable. Use `changeSet.getState()` instead (#5619). * `realm.subscribeForObjects()` have been removed. Use `RealmQuery.findAllAsync(String subscriptionName)` and `RealmQuery.findAllAsync()` instead. * Removed previously deprecated `RealmQuery.findAllSorted()`, `RealmQuery.findAllSortedAsync()` `RealmQuery.distinct()` and `RealmQuery.distinctAsync()`. * Renamed `RealmQuery.distinctValues()` to `RealmQuery.distinct()` ### Enhancements * [ObjectServer] Added support for partial Realms. Read [here](https://realm.io/docs/java/latest/#partial-realms) for more information. * [ObjectServer] Added support for Object Level Permissions (requires partial synchronized Realms). Read [here](https://realm.io/docs/java/latest/#partial-realms) for more information. * [ObjectServer] Added `SyncConfiguration.automatic()` and `SyncConfiguration.automatic(SyncUser user)` (#5806). * Added two new methods to `OrderedCollectionChangeSet`: `getState()` and `getError()` (#5619). ## Bug Fixes * Better exception message if a non model class is provided to methods only accepting those (#5779). ### Internal * Upgraded to Realm Sync 3.0.0 * Upgraded to Realm Core 5.3.0 ## 4.4.0 (2018-03-13) ### Enhancements * Added support for mapping between a Java name and the underlying name in the Realm file using `@RealmModule`, `@RealmClass` and `@RealmField` annotations (#5280). ## Bug Fixes * [ObjectServer] Fixed an issue where login after a logout will not resume Syncing (https://github.com/realm/my-first-realm-app/issues/22). ## 4.3.4 (2018-02-06) ## Bug Fixes * Added missing `RealmQuery.oneOf()` for Kotlin that accepts non-nullable types (#5717). * [ObjectServer] Fixed an issue preventing sync to resume when the network is back (#5677). ## 4.3.3 (2018-01-19) ### Internal * Downgrade JavaAssist to 3.21.0-GA to fix an issue with a `ClassNotFoundException` at runtime (#5641). ## 4.3.2 (2018-01-17) ### Bug Fixes * Throws a better exception message when calling `RealmObjectSchema.addField()` with a `RealmModel` class (#3388). * Use https for Realm version checker (#4043). * Prevent Realms Gradle plugin from transitively forcing specific versions of Google Build Tools onto downstream projects (#5640). * [ObjectServer] logging a warning message instead of throwing an exception, when sync report an unknown error code (#5403). ### Enhancements * [ObjectServer] added support for both Anonymous and Nickname authentication. ### Internal * Upgraded to Realm Sync 2.2.9 * Upgraded to Realm Core 5.1.2 ## 4.3.1 (2017-12-06) ### Bug Fixes * Fixed kotlin standard library being added to both Java and Kotlin projects (#5587). ## 4.3.0 (2017-12-05) ### Deprecated * Support for mips devices are deprecated. * `RealmQuery.findAllSorted()` and `RealmQuery.findAllSortedAsync()` variants in favor of predicate `RealmQuery.sort().findAll()`. * `RealmQuery.distinct()` and `RealmQuery.distinctAsync()` variants in favor of predicate `RealmQuery.distinctValues().findAll()` ### Enhancements * [ObjectServer] Added explicit support for JSON Web Tokens (JWT) using `SyncCredentials.jwt(String token)`. It requires Object Server 2.0.23+ (#5580). * Projects using Kotlin now include additional extension functions that make working with Kotlin easier. See [docs](https://realm.io/docs/java/latest/#kotlin) for more info (#4684). * New query predicate: `sort()`. * New query predicate: `distinctValues()`. Will be renamed to `distinct` in next major version. * The Realm annotation processor now has a stable output when there are no changes to model classes, improving support for incremental compilers (#5567). ### Bug Fixes * Added missing `toString()` for the implementation of `OrderedCollectionChangeSet`. * Sync queries are evaluated immediately to solve the performance issue when the query results are huge, `RealmResults.size()` takes too long time (#5387). * Correctly close the Realm instance if an exception was thrown while opening it. This avoids `IllegalStateException` when deleting the Realm in the catch block (#5570). * Fixed the listener on `RealmList` not being called when removing the listener then adding it again (#5507). Please notice that a similar issue still exists for `RealmResults`. ### Internal * Use `OsList` instead of `OsResults` to add notification token on for `RealmList`. * Updated Gradle and plugins to support Android Studio `3.0.0` (#5472). * Upgraded to Realm Sync 2.1.8. * Upgraded to Realm Core 4.0.4. ### Credits * Thanks to @tbsandee for fixing a typo (#5548). * Thanks to @vivekkiran for updating Gradle and plugins to support Android Studio `3.0.0` (#5472). * Thanks to @madisp for adding better support for incremental compilers (#5567). ## 4.2.0 (2017-11-17) ### Enhancements * Added support for using non-encrypted Realms in multiple processes. Some caveats apply. Read [doc](https://realm.io/docs/java/latest/#multiprocess) for more info (#1091). * Added support for importing primitive lists from JSON (#5362). * [ObjectServer] Support SSL validation using Android TrustManager (no need to specify `trustedRootCA` in `SynConfiguration` if the certificate is installed on the device), fixes (#4759). * Added the and() function to `RealmQuery` in order to improve readability. ### Bug Fixes * Leaked file handler in the Realm Transformer (#5521). * Potential fix for "RealmError: Incompatible lock file" crash (#2459). ### Internal * Updated JavaAssist to 3.22.0-GA. * Upgraded to Realm Sync 2.1.4. * Upgraded to Realm Core 4.0.3. ### Credits * Thanks to @rakshithravi1997 for adding `RealmQuery.and()` (#5520). ## 4.1.1 (2017-10-27) ### Bug Fixes * Fixed the compile warnings of using deprecated method `RealmProxyMediator.getTableName()` in generated mediator classes (#5455). * [ObjectServer] now retrying network query when encountering any `IOException` (#5453). * Fixed a `NoClassDefFoundError` due to using `@SafeVarargs` below API 19 (#5463). ### Internal * Updated Realm Sync to 2.1.0. ## 4.1.0 (2017-10-20) ### Enhancements * `Realm.deleteRealm()` and `RealmConfiguration.assetFile()` are multi-processes safe now. ### Bug Fixes * Fix some potential database corruption caused by deleting the Realm file while a Realm instance are still opened in another process or the sync client thread. * Added `realm.ignoreKotlinNullability` as a kapt argument to disable treating kotlin non-null types as `@Required` (#5412) (introduced in `v3.6.0`). * Increased http connect/write timeout for low bandwidth network. ## 4.0.0 (2017-10-16) ### Breaking Changes The internal file format has been upgraded. Opening an older Realm will upgrade the file automatically, but older versions of Realm will no longer be able to read the file. * [ObjectServer] Updated protocol version to 22 which is only compatible with Realm Object Server >= 2.0.0. * [ObjectServer] Removed deprecated APIs `SyncUser.retrieveUser()` and `SyncUser.retrieveUserAsync()`. Use `SyncUser.retrieveInfoForUser()` and `retrieveInfoForUserAsync()` instead. * [ObjectServer] `SyncUser.Callback` now accepts a generic parameter indicating type of object returned when `onSuccess` is called. * [ObjectServer] Renamed `SyncUser.getAccessToken` to `SyncUser.getRefreshToken`. * [ObjectServer] Removed deprecated API `SyncUser.getManagementRealm()`. * Calling `distinct()` on a sorted `RealmResults` no longer clears any sorting defined (#3503). * Relaxed upper bound of type parameter of `RealmList`, `RealmQuery`, `RealmResults`, `RealmCollection`, `OrderedRealmCollection` and `OrderedRealmCollectionSnapshot`. * Realm has upgraded its RxJava1 support to RxJava2 (#3497) * `Realm.asObservable()` has been renamed to `Realm.asFlowable()`. * `RealmList.asObservable()` has been renamed to `RealmList.asFlowable()`. * `RealmResults.asObservable()` has been renamed to `RealmResults.asFlowable()`. * `RealmObject.asObservable()` has been renamed to `RealmObject.asFlowable()`. * `RxObservableFactory` now return RxJava2 types instead of RxJava1 types. * Removed deprecated APIs `RealmSchema.close()` and `RealmObjectSchema.close()`. Those don't have to be called anymore. * Removed deprecated API `RealmResults.removeChangeListeners()`. Use `RealmResults.removeAllChangeListeners()` instead. * Removed deprecated API `RealmObject.removeChangeListeners()`. Use `RealmObject.removeAllChangeListeners()` instead. * Removed `UNSUPPORTED_TABLE`, `UNSUPPORTED_MIXED` and `UNSUPPORTED_DATE` from `RealmFieldType`. * Removed deprecated API `RealmResults.distinct()`/`RealmResults.distinctAsync()`. Use `RealmQuery.distinct()`/`RealmQuery.distinctAsync()` instead. * `RealmQuery.createQuery(Realm, Class)`, `RealmQuery.createDynamicQuery(DynamicRealm, String)`, `RealmQuery.createQueryFromResult(RealmResults)` and `RealmQuery.createQueryFromList(RealmList)` have been removed. Use `Realm.where(Class)`, `DynamicRealm.where(String)`, `RealmResults.where()` and `RealmList.where()` instead. ### Enhancements * [ObjectServer] `SyncUserInfo` now also exposes a users metadata using `SyncUserInfo.getMetadata()` * `RealmList` can now contain `String`, `byte[]`, `Boolean`, `Long`, `Integer`, `Short`, `Byte`, `Double`, `Float` and `Date` values. [Queries](https://github.com/realm/realm-java/issues/5361) and [Importing primitive lists from JSON](https://github.com/realm/realm-java/issues/5362) are not supported yet. * Added support for lists of primitives in `RealmObjectSchema` with `addRealmListField(String fieldName, Class primitiveType)` * Added support for lists of primitives in `DynamicRealmObject` with `setList(String fieldName, RealmList list)` and `getList(String fieldName, Class primitiveType)`. * Minor performance improvement when copy/insert objects into Realm. * Added `static RealmObject.getRealm(RealmModel)`, `RealmObject.getRealm()` and `DynamicRealmObject.getDynamicRealm()` (#4720). * Added `RealmResults.asChangesetObservable()` that emits the pair `(results, changeset)` (#4277). * Added `RealmList.asChangesetObservable()` that emits the pair `(list, changeset)` (#4277). * Added `RealmObject.asChangesetObservable()` that emits the pair `(object, changeset)` (#4277). * All Realm annotations are now kept at runtime, allowing runtime tools access to them (#5344). * Speedup schema initialization when a Realm file is first accessed (#5391). ### Bug Fixes * [ObjectServer] Exposing a `RealmConfiguration` that allows a user to open the backup Realm after the client reset (#4759/#5223). * [ObjectServer] Realm no longer throws a native “unsupported instruction” exception in some cases when opening a synced Realm asynchronously (https://github.com/realm/realm-object-store/issues/502). * [ObjectServer] Fixed "Cannot open the read only Realm" issue when get`PermissionManager` (#5414). * Throw `IllegalArgumentException` instead of `IllegalStateException` when calling string/binary data setters if the data length exceeds the limit. * Added support for ISO8601 2-digit time zone designators (#5309). * "Bad File Header" caused by the device running out of space while compacting the Realm (#5011). * `RealmQuery.equalTo()` failed to find null values on an indexed field if using Case.INSENSITIVE (#5299). * Assigning a managed object's own list to itself would accidentally clear it (#5395). * Don't try to acquire `ApplicationContext` if not available in `Realm.init(Context)` (#5389). * Removing and re-adding a changelistener from inside a changelistener sometimes caused notifications to be missed (#5411). ### Internal * Upgraded to Realm Sync 2.0.2. * Upgraded to Realm Core 4.0.2. * Upgraded to OkHttp 3.9.0. * Upgraded to RxJava 2.1.4. * Use Object Store to create the primary key table. ### Credits * Thanks to @JussiPekonen for adding support for 2-digit time zone designators when importing JSON (#5309). ## 3.7.2 (2017-09-12) ### Bug Fixes * Fixed a JNI memory issue when doing queries which might potentially cause various native crashes. * Fixed a bug that `RealmList.deleteFromRealm(int)`, `RealmList.deleteFirstFromRealm()` and `RealmList.deleteLastFromRealm()` did not remove target objects from Realm. This bug was introduced in `3.7.1` (#5233). * Crash with "'xxx' doesn't exist in current schema." when ProGuard is enabled (#5211). ## 3.7.1 (2017-09-07) ### Bug Fixes * Fixed potential memory leaks of `LinkView` when calling bulk insertions APIs. * Fixed possible assertion when using `PermissionManager` at the beginning (#5195). * Crash caused by JNI couldn't find `SharedRealm`'s inner classes when ProGuard is enabled (#5211). ### Internal * Replaced LinkView with Object Store's List. * Renaming `io.realm.internal.CollectionChangeSet` to `io.realm.internal.OsCollectionChangeSet`. ## 3.7.0 (2017-09-01) ### Deprecated * [ObjectServer] `SyncUser.getManagementRealm()`. Use `SyncUser.getPermissionManager()` instead. ### Enhancements * [ObjectServer] `SyncUser.getPermissionManager` added as a helper API for working with permissions and permission offers. ### Internal * [ObjectServer] Upgraded OkHttp to 3.7.0. ## 3.6.0 (2017-09-01) ### Breaking Changes * [ObjectServer] `SyncUser.logout()` no longer throws an exception when associated Realms instances are not closed (#4962). ### Deprecated * [ObjectServer] `SyncUser#retrieveUser` and `SyncUser#retrieveUserAsync` replaced by `SyncUser#retrieveInfoForUser` and `SyncUser#retrieveInfoForUserAsync` which returns a `SyncUserInfo` with mode information (#5008). * [ObjectServer] `SyncUser#Callback` replaced by the generic version `SyncUser#RequestCallback`. ### Enhancements * [ObjectServer] Added `SyncSession.uploadAllLocalChanges()`. * [ObjectServer] APIs of `UserStore` have been changed to support same user identity but different authentication server scenario. * [ObjectServer] Added `SyncUser.allSessions` to retrieve the all valid sessions belonging to the user (#4783). * Added `Nullable` annotation to methods that may return `null` in order to improve Kotlin usability. This also introduced a dependency to `com.google.code.findbugs:jsr305`. * `org.jetbrains.annotations.NotNull` is now an alias for `@Required`. This means that the Realm Schema now fully understand Kotlin non-null types. * Added support for new data type `MutableRealmIntegers`. The new type behaves almost exactly as a reference to a Long (mutable nullable, etc) but supports `increment` and `decrement` methods, which implement a Conflict Free Replicated Data Type, whose value will converge even when changed across distributed devices with poor connections (#4266). * Added more detailed exception message for `RealmMigrationNeeded`. * Bumping schema version only without any actual schema changes will just succeed even when the migration block is not supplied. It threw an `RealmMigrationNeededException` before in the same case. * Throw `IllegalStateException` when schema validation fails because of wrong declaration of `@LinkingObjects`. ### Bug Fixes * Potential crash after using `Realm.getSchema()` to change the schema of a typed Realm. `Realm.getSchema()` now returns an immutable `RealmSchema` instance. * `Realm.copyToRealmOrUpdate()` could cause a `RealmList` field to contain duplicated elements (#4957). * `RealmSchema.create(String)` and `RealmObjectSchema.setClassName(String)` did not accept class name whose length was 51 to 57. * Workaround for an Android JVM crash when using `compactOnLaunch()` (#4964). * Class name in exception message from link query is wrong (#5096). * The `compactOnLaunch` callback is no longer invoked if the Realm at that path is already open on other threads. ### Internal * [ObjectServer] removed `ObjectServerUser` and its inner classes, in a step to reduce `SyncUser` complexity (#3741). * [ObjectServer] changed the `SyncSessionStopPolicy` to `AfterChangesUploaded` to align with other binding and to prevent use cases where the Realm might be deleted before the last changes get synchronized (#5028). * Upgraded Realm Sync to 1.10.8 * Let Object Store handle migration. ## 3.5.0 (2017-07-11) ### Enhancements * Added `RealmConfiguration.Builder.compactOnLaunch()` to compact the file on launch (#3739). * [ObjectServer] Adding user lookup API for administrators (#4828). * An `IllegalStateException` will be thrown if the given `RealmModule` doesn't include all required model classes (#3398). ### Bug Fixes * Bug in `isNull()`, `isNotNull()`, `isEmpty()`, and `isNotEmpty()` when queries involve nullable fields in link queries (#4856). * Bug in how to resolve field names when querying `@LinkingObjects` as the last field (#4864). * Rare crash in `RealmLog` when log level was set to `LogLevel.DEBUG`. * Broken case insensitive query with indexed field (#4788). * [ObjectServer] Bug related to the behaviour of `SyncUser#logout` and the use of invalid `SyncUser` with `SyncConfiguration` (#4822). * [ObjectServer] Not all error codes from the server were recognized correctly, resulting in UNKNOWN being reported instead. * [ObjectServer] Prevent the use of a `SyncUser` that explicitly logged out, to open a Realm (#4975). ### Internal * Use Object Store to do table initialization. * Removed `Table#Table()`, `Table#addEmptyRow()`, `Table#addEmptyRows()`, `Table#add(Object...)`, `Table#pivot(long,long,PivotType)` and `Table#createnative()`. * Upgraded Realm Core to 2.8.6 * Upgraded Realm Sync to 1.10.5 * Removed `io.realm.internal.OutOfMemoryError`. `java.lang.OutOfMemoryError` will be thrown instead. ## 3.4.0 (2017-06-22) ### Breaking Changes * [ObjectServer] Updated protocol version to 18 which is only compatible with ROS > 1.6.0. ### Deprecated * `RealmSchema.close()` and `RealmObjectSchema.close()`. They don't need to be closed manually. They were added to the public API by mistake. ### Enhancements * [ObjectServer] Added support for Sync Progress Notifications through `SyncSession.addDownloadProgressListener(ProgressMode, ProgressListener)` and `SyncSession.addUploadProgressListener(ProgressMode, ProgressListener)` (#4104). * [ObjectServer] Added `SyncSession.getState()` (#4784). * Added support for querying inverse relationships (#2904). * Moved inverse relationships out of beta stage. * Added `Realm.getDefaultConfiguration()` (#4725). ### Bug Fixes * [ObjectServer] Bug which may crash when the JNI local reference limitation was reached on sync client thread. * [ObjectServer] Retrying connections with exponential backoff, when encountering `ConnectException` (#4310). * When converting nullable BLOB field to required, `null` values should be converted to `byte[0]` instead of `byte[1]`. * Bug which may cause duplicated primary key values when migrating a nullable primary key field to not nullable. `RealmObjectSchema.setRequired()` and `RealmObjectSchema.setNullable()` will throw when converting a nullable primary key field with null values stored to a required primary key field. ### Internal * Upgraded to Realm Sync 1.10.1 * Upgraded to Realm Core 2.8.4 ### Credits * Thanks to Anis Ben Nsir (@abennsir) for upgrading Roboelectric in the unitTestExample (#4698). ## 3.3.2 (2017-06-09) ### Bug Fixes * [ObjectServer] Crash when an authentication error happens (#4726). * [ObjectServer] Enabled encryption with Sync (#4561). * [ObjectServer] Admin users did not connect correctly to the server (#4750). ### Internal * Factor out internal interface ManagedObject. ## 3.3.1 (2017-05-26) ### Bug Fixes * [ObjectServer] Accepted extra columns against synced Realm (#4706). ## 3.3.0 (2017-05-24) ### Enhancements * [ObjectServer] Added two options to `SyncConfiguration` to provide a trusted root CA `trustedRootCA` and to disable SSL validation `disableSSLVerification` (#4371). * [ObjectServer] Added support for changing passwords through `SyncUser.changePassword()` using an admin user (#4588). ### Bug Fixes * Queries on proguarded Realm model classes, failed with "Table not found" (#4673). ## 3.2.1 (2017-05-19) ### Enhancements * Not in transaction illegal state exception message changed to "Cannot modify managed objects outside of a write transaction.". ### Bug Fixes * [ObjectServer] `schemaVersion` was mistakenly required in order to trigger migrations (#4658). * [ObjectServer] Fields removed from model classes will now correctly be hidden instead of throwing an exception when opening the Realm (#4658). * Random crashes which were caused by a race condition in encrypted Realm (#4343). ### Internal * Upgraded to Realm Sync 1.8.5. * Upgraded to Realm Core 2.8.0. ## 3.2.0 (2017-05-16) ### Enhancements * [ObjectServer] Added support for `SyncUser.isAdmin()` (#4353). * [ObjectServer] New set of Permission API's have been added to `SyncUser` through `SyncUser.getPermissionManager()` (#4296). * [ObjectServer] Added support for changing passwords through `SyncUser.changePassword()` (#4423). * [ObjectServer] Added support for `SyncConfiguration.Builder.waitForInitialRemoteData()` (#4270). * Transient fields are now allowed in model classes, but are implicitly treated as having the `@Ignore` annotation (#4279). * Added `Realm.refresh()` and `DynamicRealm.refresh()` (#3476). * Added `Realm.getInstanceAsync()` and `DynamicRealm.getInstanceAsync()` (#2299). * Added `DynamicRealmObject#linkingObjects(String,String)` to support linking objects on `DynamicRealm` (#4492). * Added support for read only Realms using `RealmConfiguration.Builder.readOnly()` and `SyncConfiguration.Builder.readOnly()`(#1147). * Change listeners will now auto-expand variable names to be more descriptive when using Android Studio. * The `toString()` methods for the standard and dynamic proxies now print "proxy", or "dynamic" before the left bracket enclosing the data. ### Bug Fixes * `@LinkingObjects` annotation now also works with Kotlin (#4611). ### Internal * Use separated locks for different `RealmCache`s (#4551). ## 3.1.4 (2017-05-04) ## Bug fixes * Added missing row validation check in certain cases on invalidated/deleted objects (#4540). * Initializing Realm is now more resilient if `Context.getFilesDir()` isn't working correctly (#4493). * `OrderedRealmCollectionSnapshot.get()` returned a wrong object (#4554). * `onSuccess` callback got triggered infinitely if a synced transaction was committed in the async transaction's `onSuccess` callback (#4594). ## 3.1.3 (2017-04-20) ### Enhancements * [ObjectServer] Resume synchronization as soon as the connectivity is back (#4141). ### Bug Fixes * `equals()` and `hashCode()` of managed `RealmObject`s that come from linking objects don't work correctly (#4487). * Field name was missing in exception message when `null` was set to required field (#4484). * Now throws `IllegalStateException` when a getter of linking objects is called against deleted or not yet loaded `RealmObject`s (#4499). * `NullPointerException` caused by local transaction inside the listener of `findFirstAsync()`'s results (#4495). * Native crash when adding listeners to `RealmObject` after removing listeners from the same `RealmObject` before (#4502). * Native crash with "Invalid argument" error happened on some Android 7.1.1 devices when opening Realm on external storage (#4461). * `OrderedRealmCollectionChangeListener` didn't report change ranges correctly when circular link's field changed (#4474). ### Internal * Upgraded to Realm Sync 1.6.0. * Upgraded to Realm Core 2.6.1. ## 3.1.2 (2017-04-12) ### Bug Fixes * Crash caused by JNI couldn't find `OsObject.notifyChangeListeners` when ProGuard is enabled (#4461). * Incompatible return type of `RealmSchema.getAll()` and `BaseRealm.getSchema()` (#4443). * Memory leaked when synced Realm was initialized (#4465). * An `IllegalStateException` will be thrown when starting iterating `OrderedRealmCollection` if the Realm is closed (#4471). ## 3.1.1 (2017-04-07) ### Bug Fixes * Crash caused by Listeners on `RealmObject` getting triggered the 2nd time with different changed field (#4437). * Unintentionally exposing `StandardRealmSchema` (#4443). * Workaround for crashes on specific Samsung devices which are caused by a buggy `memmove` call (#3651). ## 3.1.0 (2017-04-05) ### Breaking Changes * Updated file format of Realm files. Existing Realm files will automatically be migrated to the new format when they are opened, but older versions of Realm cannot open these files. * [ObjectServer] Due to file format changes, Realm Object Server 1.3.0 or later is required. ### Enhancements * Added support for reverse relationships through the `@LinkingObjects` annotation. See `io.realm.annotations.LinkingObjects` for documentation. * This feature is in `@Beta`. * Queries on linking objects do not work. Queries like `where(...).equalTo("field.linkingObjects.id", 7).findAll()` are not yet supported. * Backlink verification is incomplete. Evil code can cause native crashes. * The listener on `RealmObject` will only be triggered if the object changes (#3894). * Added `RealmObjectChangeListener` interface that provide detailed information about `RealmObject` field changes. * Listeners on `RealmList` and `RealmResults` will be triggered immediately when the transaction is committed on the same thread (#4245). * The real `RealmMigrationNeededException` is now thrown instead of `IllegalArgumentException` if no migration is provided for a Realm that requires it. * `RealmQuery.distinct()` can be performed on unindexed fields (#2285). * `targetSdkVersion` is now 25. * [ObjectServer] In case of a Client Reset, information about the location of the backed up Realm file is now reported through the `ErrorHandler` interface (#4080). * [ObjectServer] Authentication URLs now automatically append `/auth` if no other path segment is set (#4370). ### Bug Fixes * Crash with `LogicError` with `Bad version number` on notifier thread (#4369). * `Realm.migrateRealm(RealmConfiguration)` now fails correctly with an `IllegalArgumentException` if a `SyncConfiguration` is provided (#4075). * Potential cause for Realm file corruptions (never reported). * Add `@Override` annotation to proxy class accessors and stop using raw type in proxy classes in order to remove warnings from javac (#4329). * `findFirstAsync()` now returns an invalid object if there is no object matches the query condition instead of running the query repeatedly until it can find one (#4352). * [ObjectServer] Changing the log level after starting a session now works correctly (#4337). ### Internal * Using the Object Store's Session and SyncManager. * Upgraded to Realm Sync 1.5.0. * Upgraded to Realm Core 2.5.1. * Upgraded Gradle to 3.4.1 ## 3.0.0 (2017-02-28) ### Breaking Changes * `RealmResults.distinct()` returns a new `RealmResults` object instead of filtering on the original object (#2947). * `RealmResults` is auto-updated continuously. Any transaction on the current thread which may have an impact on the order or elements of the `RealmResults` will change the `RealmResults` immediately instead of change it in the next event loop. The standard `RealmResults.iterator()` will continue to work as normal, which means that you can still delete or modify elements without impacting the iterator. The same is not true for simple for-loops. In some cases a simple for-loop will not work (https://realm.io/docs/java/3.0.0/api/io/realm/OrderedRealmCollection.html#loops), and you must use the new createSnapshot() method. * `RealmChangeListener` on `RealmObject` will now also be triggered when the object is deleted. Use `RealmObject.isValid()` to check this state(#3138). * `RealmObject.asObservable()` will now emit the object when it is deleted. Use `RealmObject.isValid()` to check this state (#3138). * Removed deprecated classes `Logger` and `AndroidLogger` (#4050). ### Deprecated * `RealmResults.removeChangeListeners()`. Use `RealmResults.removeAllChangeListeners()` instead. * `RealmObject.removeChangeListeners()`. Use `RealmObject.removeAllChangeListeners()` instead. * `RealmResults.distinct()` and `RealmResults.distinctAsync()`. Use `RealmQuery.distinct()` and `RealmQuery.distinctAsync()` instead. ### Enhancements * Added support for sorting by link's field (#672). * Added `OrderedRealmCollectionSnapshot` class and `OrderedRealmCollection.createSnapshot()` method. `OrderedRealmCollectionSnapshot` is useful when changing `RealmResults` or `RealmList` in simple loops. * Added `OrderedRealmCollectionChangeListener` interface for supporting fine-grained collection notifications. * Added support for ChangeListeners on `RealmList`. * Added `RealmList.asObservable()`. ### Bug Fixes * Element type checking in `DynamicRealmObject#setList()` (#4252). * Now throws `IllegalStateException` instead of process crash when any of thread confined methods in `RealmQuery` is called from wrong thread (#4228). * Now throws `IllegalStateException` when any of thread confined methods in `DynamicRealmObject` is called from wrong thread (#4258). ### Internal * Use Object Store's `Results` as the backend for `RealmResults` (#3372). - Use Object Store's notification mechanism to trigger listeners. - Local commits triggers Realm global listener and `RealmObject` listener on current thread immediately instead of in the next event loop. ## 2.3.2 (2017-02-27) ### Bug fixes * Log levels in JNI layer were all reported as "Error" (#4204). * Encrypted realms can end up corrupted if many threads are reading and writing at the same time (#4128). * "Read-only file system" exception when compacting Realm file on external storage (#4140). ### Internal * Updated to Realm Sync v1.2.1. * Updated to Realm Core v2.3.2. ### Enhancements * Improved performance of getters and setters in proxy classes. ## 2.3.1 (2017-02-07) ### Enhancements * [ObjectServer] The `serverUrl` given to `SyncConfiguration.Builder()` is now more lenient and will also accept only paths as argument (#4144). * [ObjectServer] Add a timer to refresh periodically the access_token. ### Bug fixes * NPE problem in SharedRealm.finalize() (#3730). * `RealmList.contains()` and `RealmResults.contains()` now correctly use custom `equals()` method on Realm model classes. * Build error when the project is using Kotlin (#4087). * Bug causing classes to be replaced by classes already in Gradle's classpath (#3568). * NullPointerException when notifying a single object that it changed (#4086). ## 2.3.0 (2017-01-19) ### Object Server API Changes * Realm Sync v1.0.0 has been released, and Realm Mobile Platform is no longer considered in beta. * Breaking change: Location of Realm files are now placed in `getFilesDir()/` instead of `getFilesDir()/`. This is done in order to support shared Realms among users, while each user retaining their own local copy. * Breaking change: `SyncUser.all()` now returns Map instead of List. * Breaking change: Added a default `UserStore` saving users in a Realm file (`RealmFileUserStore`). * Breaking change: Added multi-user support to `UserStore`. Added `get(String)` and `remove(String)`, removed `remove()` and renamed `get()` to `getCurrent()`. * Breaking change: Changed the order of arguments to `SyncCredentials.custom()` to match iOS: token, provider, userInfo. * Added support for `PermissionOffer` and `PermissionOfferResponse` to `SyncUser.getManagementRealm()`. * Exceptions thrown in error handlers are ignored but logged (#3559). * Removed unused public constants in `SyncConfiguration` (#4047). * Fixed bug, preventing Sync client to renew the access token (#4038) (#4039). * Now `SyncUser.logout()` properly revokes tokens (#3639). ### Bug fixes * Fixed native memory leak setting the value of a primary key (#3993). * Activated Realm's annotation processor on connectedTest when the project is using kapt (#4008). * Fixed "too many open files" issue (#4002). * Added temporary work-around for bug crashing Samsung Tab 3 devices on startup (#3651). ### Enhancements * Added `like` predicate for String fields (#3752). ### Internal * Updated to Realm Sync v1.0.0. * Added a Realm backup when receiving a Sync client reset message from the server. ## 2.2.2 (2017-01-16) ### Object Server API Changes (In Beta) * Disabled `Realm.compactRealm()` when sync is enabled as it might corrupt the Realm (https://github.com/realm/realm-core/issues/2345). ### Bug fixes * "operation not permitted" issue when creating Realm file on some devices' external storage (#3629). * Crash on API 10 devices (#3726). * `UnsatisfiedLinkError` caused by `pipe2` (#3945). * Unrecoverable error with message "Try again" when the notification fifo is full (#3964). * Realm migration wasn't triggered when the primary key definition was altered (#3966). * Use phantom reference to solve the finalize time out issue (#2496). ### Enhancements * All major public classes are now non-final. This is mostly a compromise to support Mockito. All protected fields/methods are still not considered part of the public API and can change without notice (#3869). * All Realm instances share a single notification daemon thread. * Fixed Java lint warnings with generated proxy classes (#2929). ### Internal * Upgraded Realm Core to 2.3.0. * Upgraded Realm Sync to 1.0.0-BETA-6.5. ## 2.2.1 (2016-11-12) ### Object Server API Changes (In Beta) * Fixed `SyncConfiguration.toString()` so it now outputs a correct description instead of an empty string (#3787). ### Bug fixes * Added version number to the native library, preventing ReLinker from accidentally loading old code (#3775). * `Realm.getLocalInstanceCount(config)` throwing NullPointerException if called after all Realms have been closed (#3791). ## 2.2.0 (2016-11-12) ### Object Server API Changes (In Beta) * Added support for `SyncUser.getManagementRealm()` and permission changes. ### Bug fixes * Kotlin projects no longer create the `RealmDefaultModule` if no Realm model classes are present (#3746). * Remove `includedescriptorclasses` option from ProGuard rule file in order to support built-in shrinker of Android Gradle Plugin (#3714). * Unexpected `RealmMigrationNeededException` was thrown when a field was added to synced Realm. ### Enhancements * Added support for the `annotationProcessor` configuration provided by Android Gradle Plugin 2.2.0 or later. Realm plugin adds its annotation processor to the `annotationProcessor` configuration instead of `apt` configuration if it is available and the `com.neenbedankt.android-apt` plugin is not used. In Kotlin projects, `kapt` is used instead of the `annotationProcessor` configuration (#3026). ## 2.1.1 (2016-10-27) ### Bug fixes * Fixed a bug in `Realm.insert` and `Realm.insertOrUpdate` methods causing a `StackOverFlow` when you try to insert a cyclic graph of objects between Realms (#3732). ### Object Server API Changes (In Beta) * Set default RxFactory to `SyncConfiguration`. ### Bug fixes * ProGuard configuration introduced in 2.1.0 unexpectedly kept classes that did not have the @KeepMember annotation (#3689). ## 2.1.0 (2016-10-25) ### Breaking changes * * `SecureUserStore` has been moved to its own GitHub repository: https://github.com/realm/realm-android-user-store See https://github.com/realm/realm-android-user-store/blob/master/README.md for further info on how to include it. ### Object Server API Changes (In Beta) * Renamed `User` to `SyncUser`, `Credentials` to `SyncCredentials` and `Session` to `SyncSession` to align names with Cocoa. * Removed `SyncManager.setLogLevel()`. Use `RealmLog.setLevel()` instead. * `SyncUser.logout()` now correctly clears `SyncUser.currentUser()` (#3638). * Missing ProGuard configuration for libraries used by Sync extension (#3596). * Error handler was not called when sync session failed (#3597). * Added `User.all()` that returns all known Realm Object Server users. * Upgraded Realm Sync to 1.0.0-BETA-3.2 ### Deprecated * `Logger`. Use `RealmLogger` instead. * `AndroidLogger`. The logger for Android is implemented in native code instead. ### Bug fixes * The following were not kept by ProGuard: names of native methods not in the `io.realm.internal` package, names of classes used in method signature (#3596). * Permission error when a database file was located on external storage (#3140). * Memory leak when unsubscribing from a RealmResults/RealmObject RxJava Observable (#3552). ### Enhancements * `Realm.compactRealm()` now works for encrypted Realms. * Added `first(E defaultValue)` and `last(E defaultValue)` methods to `RealmList` and `RealmResult`. These methods will return the provided object instead of throwing an `IndexOutOfBoundsException` if the list is empty. * Reduce transformer logger verbosity (#3608). * `RealmLog.setLevel(int)` for setting the log level across all loggers. ### Internal * Upgraded Realm Core to 2.1.3 ### Credits * Thanks to Max Furman (@maxfurman) for adding support for `first()` and `last()` default values. ## 2.0.2 (2016-10-06) This release is not protocol-compatible with previous versions of the Realm Mobile Platform. The base library is still fully compatible. ### Bug fixes * Build error when using Java 7 (#3563). ### Internal * Upgraded Realm Core to 2.1.0 * Upgraded Realm Sync to 1.0.0-BETA-2.0. ## 2.0.1 (2016-10-05) ### Bug fixes * `android.net.conn.CONNECTIVITY_CHANGE` broadcast caused `RuntimeException` if sync extension was disabled (#3505). * `android.net.conn.CONNECTIVITY_CHANGE` was not delivered on Android 7 devices. * `distinctAsync` did not respect other query parameters (#3537). * `ConcurrentModificationException` from Gradle when building an application (#3501). ### Internal * Upgraded to Realm Core 2.0.1 / Realm Sync 1.3-BETA ## 2.0.0 (2016-09-27) This release introduces support for the Realm Mobile Platform! See for an overview of these great new features. ### Breaking Changes * Files written by Realm 2.0 cannot be read by 1.x or earlier versions. Old files can still be opened. * It is now required to call `Realm.init(Context)` before calling any other Realm API. * Removed `RealmConfiguration.Builder(Context)`, `RealmConfiguration.Builder(Context, File)` and `RealmConfiguration.Builder(File)` constructors. * `isValid()` now always returns `true` instead of `false` for unmanaged `RealmObject` and `RealmList`. This puts it in line with the behaviour of the Cocoa and .NET API's (#3101). * armeabi is not supported anymore. * Added new `RealmFileException`. - `IncompatibleLockFileException` has been removed and replaced by `RealmFileException` with kind `INCOMPATIBLE_LOCK_FILE`. - `RealmIOExcpetion` has been removed and replaced by `RealmFileException`. * `RealmConfiguration.Builder.assetFile(Context, String)` has been renamed to `RealmConfiguration.Builder.assetFile(String)`. * Object with primary key is now required to define it when the object is created. This means that `Realm.createObject(Class)` and `DynamicRealm.createObject(String)` now throws `RealmException` if they are used to create an object with a primary key field. Use `Realm.createObject(Class, Object)` or `DynamicRealm.createObject(String, Object)` instead. * Importing from JSON without the primary key field defined in the JSON object now throws `IllegalArgumentException`. * Now `Realm.beginTransaction()`, `Realm.executeTransaction()` and `Realm.waitForChange()` throw `RealmMigrationNeededException` if a remote process introduces incompatible schema changes (#3409). * The primary key value of an object can no longer be changed after the object was created. Instead a new object must be created and all fields copied over. * Now `Realm.createObject(Class)` and `Realm.createObject(Class,Object)` take the values from the model's fields and default constructor. Creating objects through the `DynamicRealm` does not use these values (#777). * When `Realm.create*FromJson()`s create a new `RealmObject`, now they take the default values defined by the field itself and its default constructor for those fields that are not defined in the JSON object. ### Enhancements * Added `realmObject.isManaged()`, `RealmObject.isManaged(obj)` and `RealmCollection.isManaged()` (#3101). * Added `RealmConfiguration.Builder.directory(File)`. * `RealmLog` has been moved to the public API. It is now possible to control which events Realm emit to Logcat. See the `RealmLog` class for more details. * Typed `RealmObject`s can now continue to access their fields properly even though the schema was changed while the Realm was open (#3409). * A `RealmMigrationNeededException` will be thrown with a cause to show the detailed message when a migration is needed and the migration block is not in the `RealmConfiguration`. ### Bug fixes * Fixed a lint error in proxy classes when the 'minSdkVersion' of user's project is smaller than 11 (#3356). * Fixed a potential crash when there were lots of async queries waiting in the queue. * Fixed a bug causing the Realm Transformer to not transform field access in the model's constructors (#3361). * Fixed a bug causing a build failure when the Realm Transformer adds accessors to a model class that was already transformed in other project (#3469). * Fixed a bug causing the `NullPointerException` when calling getters/setters in the model's constructors (#2536). ### Internal * Moved JNI build to CMake. * Updated Realm Core to 2.0.0. * Updated ReLinker to 1.2.2. ## 1.2.0 (2016-08-19) ### Bug fixes * Throw a proper exception when operating on a non-existing field with the dynamic API (#3292). * `DynamicRealmObject.setList` should only accept `RealmList` (#3280). * `DynamicRealmObject.getX(fieldName)` now throws a proper exception instead of a native crash when called with a field name of the wrong type (#3294). * Fixed a concurrency crash which might happen when `Realm.executeTransactionAsync()` tried to call `onSucess` after the Realm was closed. ### Enhancements * Added `RealmQuery.in()` for a comparison against multiple values. * Added byte array (`byte[]`) support to `RealmQuery`'s `equalTo` and `notEqualTo` methods. * Optimized internal caching of schema classes (#3315). ### Internal * Updated Realm Core to 1.5.1. * Improved sorting speed. * Completely removed the `OptionalAPITransformer`. ### Credits * Thanks to Brenden Kromhout (@bkromhout) for adding binary array support to `equalTo` and `notEqualTo`. ## 1.1.1 (2016-07-01) ### Bug fixes * Fixed a wrong JNI method declaration which might cause "method not found" crash on some devices. * Fixed a bug that `Error` in the background async thread is not forwarded to the caller thread. * Fixed a crash when an empty `Collection` is passed to `insert()`/`insertOrUpdate()` (#3103). * Fixed a bug that does not transfer the primary key when `RealmSchemaObject.setClassName()` is called to rename a class (#3118). * Fixed bug in `Realm.insert` and `Realm.insertOrUpdate` methods causing a `RealmList` to be cleared when inserting a managed `RealmModel` (#3105). * Fixed a concurrency allocation bug in storage engine which might lead to some random crashes. * Bulk insertion now throws if it is not called in a transaction (#3173). * The IllegalStateException thrown when accessing an empty RealmObject is now more meaningful (#3200). * `insert()` now correctly throws an exception if two different objects have the same primary key (#3212). * Blackberry Z10 throwing "Function not implemented" (#3178). * Reduced the number of file descriptors used by Realm Core (#3197). * Throw a proper `IllegalStateException` if a `RealmChangeListener` is used inside an IntentService (#2875). ### Enhancements * The Realm Annotation processor no longer consumes the Realm annotations. Allowing other annotation processors to run. ### Internal * Updated Realm Core to 1.4.2. * Improved sorting speed. ## 1.1.0 (2016-06-30) ### Bug fixes * A number of bug fixes in the storage engine related to memory management in rare cases when a Realm has been compacted. * Disabled the optional API transformer since it has problems with DexGuard (#3022). * `OnSuccess.OnSuccess()` might not be called with the correct Realm version for async transaction (#1893). * Fixed a bug in `copyToRealm()` causing a cyclic dependency objects being duplicated. * Fixed a build failure when model class has a conflicting name such as `Map`, `List`, `String`, ... (#3077). ### Enhancements * Added `insert(RealmModel obj)`, `insertOrUpdate(RealmModel obj)`, `insert(Collection collection)` and `insertOrUpdate(Collection collection)` to perform batch inserts (#1684). * Enhanced `Table.toString()` to show a PrimaryKey field details (#2903). * Enabled ReLinker when loading a Realm from a custom path by adding a `RealmConfiguration.Builder(Context, File)` constructor (#2900). * Changed `targetSdkVersion` of `realm-library` to 24. * Logs warning if `DynamicRealm` is not closed when GC happens as it does for `Realm`. ### Deprecated * `RealmConfiguration.Builder(File)`. Use `RealmConfiguration.Builder(Context, File)` instead. ### Internal * Updated Realm Core to 1.2.0. ## 1.0.1 (2016-05-25) ### Bug fixes * Fixed a crash when calling `Table.toString()` in debugger (#2429). * Fixed a race condition which would cause some `RealmResults` to not be properly updated inside a `RealmChangeListener`. This could result in crashes when accessing items from those results (#2926/#2951). * Revised `RealmResults.isLoaded()` description (#2895). * Fixed a bug that could cause Realm to lose track of primary key when using `RealmObjectSchema.removeField()` and `RealmObjectSchema.renameField()` (#2829/#2926). * Fixed a bug that prevented some devices from finding async related JNI methods correctly. * Updated ProGuard configuration in order not to depend on Android's default configuration (#2972). * Fixed a race condition between Realms notifications and other UI events. This could e.g. cause ListView to crash (#2990). * Fixed a bug that allowed both `RealmConfiguration.Builder.assetFile()`/`deleteRealmIfMigrationNeeded()` to be configured at the same time, which leads to the asset file accidentally being deleted in migrations (#2933). * Realm crashed outright when the same Realm file was opened in two processes. Realm will now optimistically retry opening for 1 second before throwing an Error (#2459). ### Enhancements * Removes RxJava related APIs during bytecode transforming to make RealmObject plays well with reflection when rx.Observable doesn't exist. ## 1.0.0 (2016-05-25) No changes since 0.91.1. ## 0.91.1 (2016-05-25) * Updated Realm Core to 1.0.1. ### Bug fixes * Fixed a bug when opening a Realm causes a staled memory mapping. Symptoms are error messages like "Bad or incompatible history type", "File format version doesn't match", and "Encrypted interprocess sharing is currently unsupported". ## 0.91.0 (2016-05-20) * Updated Realm Core to 1.0.0. ### Breaking changes * Removed all `@Deprecated` methods. * Calling `Realm.setAutoRefresh()` or `DynamicRealm.setAutoRefresh()` from non-Looper thread throws `IllegalStateException` even if the `autoRefresh` is false (#2820). ### Bug fixes * Calling RealmResults.deleteAllFromRealm() might lead to native crash (#2759). * The annotation processor now correctly reports an error if trying to reference interfaces in model classes (#2808). * Added null check to `addChangeListener` and `removeChangeListener` in `Realm` and `DynamicRealm` (#2772). * Calling `RealmObjectSchema.addPrimaryKey()` adds an index to the primary key field, and calling `RealmObjectSchema.removePrimaryKey()` removes the index from the field (#2832). * Log files are not deleted when calling `Realm.deleteRealm()` (#2834). ### Enhancements * Upgrading to OpenSSL 1.0.1t. From July 11, 2016, Google Play only accept apps using OpenSSL 1.0.1r or later (https://support.google.com/faqs/answer/6376725, #2749). * Added support for automatically copying an initial database from assets using `RealmConfiguration.Builder.assetFile()`. * Better error messages when certain file operations fail. ### Credits * Paweł Surówka (@thesurix) for adding the `RealmConfiguration.Builder.assetFile()`. ## 0.90.1 * Updated Realm Core to 0.100.2. ### Bug fixes * Opening a Realm while closing a Realm in another thread could lead to a race condition. * Automatic migration to the new file format could in rare circumstances lead to a crash. * Fixing a race condition that may occur when using Async API (#2724). * Fixed CannotCompileException when related class definition in android.jar cannot be found (#2703). ### Enhancements * Prints path when file related exceptions are thrown. ## 0.90.0 * Updated Realm Core to 0.100.0. ### Breaking changes * RealmChangeListener provides the changed object/Realm/collection as well (#1594). * All JSON methods on Realm now only wraps JSONException in RealmException. All other Exceptions are thrown as they are. * Marked all methods on `RealmObject` and all public classes final (#1594). * Removed `BaseRealm` from the public API. * Removed `HandlerController` from the public API. * Removed constructor of `RealmAsyncTask` from the public API (#1594). * `RealmBaseAdapter` has been moved to its own GitHub repository: https://github.com/realm/realm-android-adapters See https://github.com/realm/realm-android-adapters/blob/master/README.md for further info on how to include it. * File format of Realm files is changed. Files will be automatically upgraded but opening a Realm file with older versions of Realm is not possible. ### Deprecated * `Realm.allObjects*()`. Use `Realm.where(clazz).findAll*()` instead. * `Realm.distinct*()`. Use `Realm.where(clazz).distinct*()` instead. * `DynamicRealm.allObjects*()`. Use `DynamicRealm.where(className).findAll*()` instead. * `DynamicRealm.distinct*()`. Use `DynamicRealm.where(className).distinct*()` instead. * `Realm.allObjectsSorted(field, sort, field, sort, field, sort)`. Use `RealmQuery.findAllSorted(field[], sort[])` instead. * `RealmQuery.findAllSorted(field, sort, field, sort, field, sort)`. Use `RealmQuery.findAllSorted(field[], sort[])` instead. * `RealmQuery.findAllSortedAsync(field, sort, field, sort, field, sort)`. Use `RealmQuery.findAllSortedAsync(field[], sort[])` instead. * `RealmConfiguration.setModules()`. Use `RealmConfiguration.modules()` instead. * `Realm.refresh()` and `DynamicRealm.refresh()`. Use `Realm.waitForChange()`/`stopWaitForChange()` or `DynamicRealm.waitForChange()`/`stopWaitForChange()` instead. ### Enhancements * `RealmObjectSchema.getPrimaryKey()` (#2636). * `Realm.createObject(Class, Object)` for creating objects with a primary key directly. * Unit tests in Android library projects now detect Realm model classes. * Better error message if `equals()` and `hashCode()` are not properly overridden in custom Migration classes. * Expanding the precision of `Date` fields to cover full range (#833). * `Realm.waitForChange()`/`stopWaitForChange()` and `DynamicRealm.waitForChange()`/`stopWaitForChange()` (#2386). ### Bug fixes * `RealmChangeListener` on `RealmObject` is not triggered when adding listener on returned `RealmObject` of `copyToRealmOrUpdate()` (#2569). ### Credits * Thanks to Brenden Kromhout (@bkromhout) for adding `RealmObjectSchema.getPrimaryKey()`. ## 0.89.1 ### Bug fixes * @PrimaryKey + @Required on String type primary key no longer throws when using copyToRealm or copyToRealmOrUpdate (#2653). * Primary key is cleared/changed when calling RealmSchema.remove()/RealmSchema.rename() (#2555). * Objects implementing RealmModel can be used as a field of RealmModel/RealmObject (#2654). ## 0.89.0 ### Breaking changes * @PrimaryKey field value can now be null for String, Byte, Short, Integer, and Long types. Older Realms should be migrated, using RealmObjectSchema.setNullable(), or by adding the @Required annotation (#2515). * `RealmResults.clear()` now throws UnsupportedOperationException. Use `RealmResults.deleteAllFromRealm()` instead. * `RealmResults.remove(int)` now throws UnsupportedOperationException. Use `RealmResults.deleteFromRealm(int)` instead. * `RealmResults.sort()` and `RealmList.sort()` now return the sorted result instead of sorting in-place. * `RealmList.first()` and `RealmList.last()` now throw `ArrayIndexOutOfBoundsException` if `RealmList` is empty. * Removed deprecated method `Realm.getTable()` from public API. * `Realm.refresh()` and `DynamicRealm.refresh()` on a Looper no longer have any effect. `RealmObject` and `RealmResults` are always updated on the next event loop. ### Deprecated * `RealmObject.removeFromRealm()` in place of `RealmObject.deleteFromRealm()` * `Realm.clear(Class)` in favour of `Realm.delete(Class)`. * `DynamicRealm.clear(Class)` in place of `DynamicRealm.delete(Class)`. ### Enhancements * Added a `RealmModel` interface that can be used instead of extending `RealmObject`. * `RealmCollection` and `OrderedRealmCollection` interfaces have been added. `RealmList` and `RealmResults` both implement these. * `RealmBaseAdapter` now accept an `OrderedRealmCollection` instead of only `RealmResults`. * `RealmObjectSchema.isPrimaryKey(String)` (#2440) * `RealmConfiguration.initialData(Realm.Transaction)` can now be used to populate a Realm file before it is used for the first time. ### Bug fixes * `RealmObjectSchema.isRequired(String)` and `RealmObjectSchema.isNullable(String)` don't throw when the given field name doesn't exist. ### Credits * Thanks to @thesurix for adding `RealmConfiguration.initialData()`. ## 0.88.3 * Updated Realm Core to 0.97.3. ### Enhancements * Throws an IllegalArgumentException when calling Realm.copyToRealm()/Realm.copyToRealmOrUpdate() with a RealmObject which belongs to another Realm instance in a different thread. * Improved speed of cleaning up native resources (#2496). ### Bug fixes * Field annotated with @Ignored should not have accessors generated by the bytecode transformer (#2478). * RealmResults and RealmObjects can no longer accidentially be GC'ed if using `asObservable()`. Previously this caused the observable to stop emitting (#2485). * Fixed an build issue when using Realm in library projects on Windows (#2484). * Custom equals(), toString() and hashCode() are no longer incorrectly overwritten by the proxy class (#2545). ## 0.88.2 * Updated Realm Core to 0.97.2. ### Enhancements * Outputs additional information when incompatible lock file error occurs. ### Bug fixes * Race condition causing BadVersionException when running multiple async writes and queries at the same time (#2021/#2391/#2417). ## 0.88.1 ### Bug fixes * Prevent throwing NullPointerException in RealmConfiguration.equals(RealmConfiguration) when RxJava is not in the classpath (#2416). * RealmTransformer fails because of missing annotation classes in user's project (#2413). * Added SONAME header to shared libraries (#2432). * now DynamicRealmObject.toString() correctly shows null value as "null" and the format is aligned to the String from typed RealmObject (#2439). * Fixed an issue occurring while resolving ReLinker in apps using a library based on Realm (#2415). ## 0.88.0 (2016-03-10) * Updated Realm Core to 0.97.0. ### Breaking changes * Realm has now to be installed as a Gradle plugin. * DynamicRealm.executeTransaction() now directly throws any RuntimeException instead of wrapping it in a RealmException (#1682). * DynamicRealm.executeTransaction() now throws IllegalArgumentException instead of silently accepting a null Transaction object. * String setters now throw IllegalArgumentException instead of RealmError for invalid surrogates. * DynamicRealm.distinct()/distinctAsync() and Realm.distinct()/distinctAsync() now throw IllegalArgumentException instead of UnsupportedOperationException for invalid type or unindexed field. * All thread local change listeners are now delayed until the next Looper event instead of being triggered when committing. * Removed RealmConfiguration.getSchemaMediator() from public API which was deprecated in 0.86.0. Please use RealmConfiguration.getRealmObjectClasses() to obtain the set of model classes (#1797). * Realm.migrateRealm() throws a FileNotFoundException if the Realm file doesn't exist. * It is now required to unsubscribe from all Realm RxJava observables in order to fully close the Realm (#2357). ### Deprecated * Realm.getInstance(Context). Use Realm.getInstance(RealmConfiguration) or Realm.getDefaultInstance() instead. * Realm.getTable(Class) which was public because of the old migration API. Use Realm.getSchema() or DynamicRealm.getSchema() instead. * Realm.executeTransaction(Transaction, Callback) and replaced it with Realm.executeTransactionAsync(Transaction), Realm.executeTransactionAsync(Transaction, OnSuccess), Realm.executeTransactionAsync(Transaction, OnError) and Realm.executeTransactionAsync(Transaction, OnSuccess, OnError). ### Enhancements * Support for custom methods, custom logic in accessors, custom accessor names, interface implementation and public fields in Realm objects (#909). * Support to project Lombok (#502). * RealmQuery.isNotEmpty() (#2025). * Realm.deleteAll() and RealmList.deleteAllFromRealm() (#1560). * RealmQuery.distinct() and RealmResults.distinct() (#1568). * RealmQuery.distinctAsync() and RealmResults.distinctAsync() (#2118). * Improved .so loading by using [ReLinker](https://github.com/KeepSafe/ReLinker). * Improved performance of RealmList#contains() (#897). * distinct(...) for Realm, DynamicRealm, RealmQuery, and RealmResults can take multiple parameters (#2284). * "realm" and "row" can be used as field name in model classes (#2255). * RealmResults.size() now returns Integer.MAX_VALUE when actual size is greater than Integer.MAX_VALUE (#2129). * Removed allowBackup from AndroidManifest (#2307). ### Bug fixes * Error occurring during test and (#2025). * Error occurring during test and connectedCheck of unit test example (#1934). * Bug in jsonExample (#2092). * Multiple calls of RealmResults.distinct() causes to return wrong results (#2198). * Calling DynamicRealmObject.setList() with RealmList (#2368). * RealmChangeListeners did not triggering correctly if findFirstAsync() didn't find any object. findFirstAsync() Observables now also correctly call onNext when the query completes in that case (#2200). * Setting a null value to trigger RealmChangeListener (#2366). * Preventing throwing BadVersionException (#2391). ### Credits * Thanks to Bill Best (@wmbest2) for snapshot testing. * Thanks to Graham Smith (@grahamsmith) for a detailed bug report (#2200). ## 0.87.5 (2016-01-29) * Updated Realm Core to 0.96.2. - IllegalStateException won't be thrown anymore in RealmResults.where() if the RealmList which the RealmResults is created on has been deleted. Instead, the RealmResults will be treated as empty forever. - Fixed a bug causing a bad version exception, when using findFirstAsync (#2115). ## 0.87.4 (2016-01-28) * Updated Realm Core to 0.96.0. - Fixed bug causing BadVersionException or crashing core when running async queries. ## 0.87.3 (2016-01-25) * IllegalArgumentException is now properly thrown when calling Realm.copyFromRealm() with a DynamicRealmObject (#2058). * Fixed a message in IllegalArgumentException thrown by the accessors of DynamicRealmObject (#2141). * Fixed RealmList not returning DynamicRealmObjects of the correct underlying type (#2143). * Fixed potential crash when rolling back removal of classes that reference each other (#1829). * Updated Realm Core to 0.95.8. - Fixed a bug where undetected deleted object might lead to seg. fault (#1945). - Better performance when deleting objects (#2015). ## 0.87.2 (2016-01-08) * Removed explicit GC call when committing a transaction (#1925). * Fixed a bug when RealmObjectSchema.addField() was called with the PRIMARY_KEY modifier, the field was not set as a required field (#2001). * Fixed a bug which could throw a ConcurrentModificationException in RealmObject's or RealmResults' change listener (#1970). * Fixed RealmList.set() so it now correctly returns the old element instead of the new (#2044). * Fixed the deployment of source and javadoc jars (#1971). ## 0.87.1 (2015-12-23) * Upgraded to NDK R10e. Using gcc 4.9 for all architectures. * Updated Realm Core to 0.95.6 - Fixed a bug where an async query can be copied incomplete in rare cases (#1717). * Fixed potential memory leak when using async query. * Added a check to prevent removing a RealmChangeListener from a non-Looper thread (#1962). (Thank you @hohnamkung.) ## 0.87.0 (2015-12-17) * Added Realm.asObservable(), RealmResults.asObservable(), RealmObject.asObservable(), DynamicRealm.asObservable() and DynamicRealmObject.asObservable(). * Added RealmConfiguration.Builder.rxFactory() and RxObservableFactory for custom RxJava observable factory classes. * Added Realm.copyFromRealm() for creating detached copies of Realm objects (#931). * Added RealmObjectSchema.getFieldType() (#1883). * Added unitTestExample to showcase unit and instrumentation tests. Examples include jUnit3, jUnit4, Espresso, Robolectric, and MPowermock usage with Realm (#1440). * Added support for ISO8601 based dates for JSON import. If JSON dates are invalid a RealmException will be thrown (#1213). * Added APK splits to gridViewExample (#1834). ## 0.86.1 (2015-12-11) * Improved the performance of removing objects (RealmResults.clear() and RealmResults.remove()). * Updated Realm Core to 0.95.5. * Updated ProGuard configuration (#1904). * Fixed a bug where RealmQuery.findFirst() returned a wrong result if the RealmQuery had been created from a RealmResults.where() (#1905). * Fixed a bug causing DynamicRealmObject.getObject()/setObject() to use the wrong class (#1912). * Fixed a bug which could cause a crash when closing Realm instances in change listeners (#1900). * Fixed a crash occurring during update of multiple async queries (#1895). * Fixed listeners not triggered for RealmObject & RealmResults created using copy or create methods (#1884). * Fixed RealmChangeListener never called inside RealmResults (#1894). * Fixed crash when calling clear on a RealmList (#1886). ## 0.86.0 (2015-12-03) * BREAKING CHANGE: The Migration API has been replaced with a new API. * BREAKING CHANGE: RealmResults.SORT_ORDER_ASCENDING and RealmResults.SORT_ORDER_DESCENDING constants have been replaced by Sort.ASCENDING and Sort.DESCENDING enums. * BREAKING CHANGE: RealmQuery.CASE_SENSITIVE and RealmQuery.CASE_INSENSITIVE constants have been replaced by Case.SENSITIVE and Case.INSENSITIVE enums. * BREAKING CHANGE: Realm.addChangeListener, RealmObject.addChangeListener and RealmResults.addChangeListener hold a strong reference to the listener, you should unregister the listener to avoid memory leaks. * BREAKING CHANGE: Removed deprecated methods RealmQuery.minimum{Int,Float,Double}, RealmQuery.maximum{Int,Float,Double}, RealmQuery.sum{Int,Float,Double} and RealmQuery.average{Int,Float,Double}. Use RealmQuery.min(), RealmQuery.max(), RealmQuery.sum() and RealmQuery.average() instead. * BREAKING CHANGE: Removed RealmConfiguration.getSchemaMediator() which is public by mistake. And RealmConfiguration.getRealmObjectClasses() is added as an alternative in order to obtain the set of model classes (#1797). * BREAKING CHANGE: Realm.addChangeListener, RealmObject.addChangeListener and RealmResults.addChangeListener will throw an IllegalStateException when invoked on a non-Looper thread. This is to prevent registering listeners that will not be invoked. * BREAKING CHANGE: trying to access a property on an unloaded RealmObject obtained asynchronously will throw an IllegalStateException * Added new Dynamic API using DynamicRealm and DynamicRealmObject. * Added Realm.getSchema() and DynamicRealm.getSchema(). * Realm.createOrUpdateObjectFromJson() now works correctly if the RealmObject class contains a primary key (#1777). * Realm.compactRealm() doesn't throw an exception if the Realm file is opened. It just returns false instead. * Updated Realm Core to 0.95.3. - Fixed a bug where RealmQuery.average(String) returned a wrong value for a nullable Long/Integer/Short/Byte field (#1803). - Fixed a bug where RealmQuery.average(String) wrongly counted the null value for average calculation (#1854). ## 0.85.1 (2015-11-23) * Fixed a bug which could corrupt primary key information when updating from a Realm version <= 0.84.1 (#1775). ## 0.85.0 (2016-11-19) * BREAKING CHANGE: Removed RealmEncryptionNotSupportedException since the encryption implementation changed in Realm's underlying storage engine. Encryption is now supported on all devices. * BREAKING CHANGE: Realm.executeTransaction() now directly throws any RuntimeException instead of wrapping it in a RealmException (#1682). * BREAKING CHANGE: RealmQuery.isNull() and RealmQuery.isNotNull() now throw IllegalArgumentException instead of RealmError if the fieldname is a linked field and the last element is a link (#1693). * Added Realm.isEmpty(). * Setters in managed object for RealmObject and RealmList now throw IllegalArgumentException if the value contains an invalid (unmanaged, removed, closed, from different Realm) object (#1749). * Attempting to refresh a Realm while a transaction is in process will now throw an IllegalStateException (#1712). * The Realm AAR now also contains the ProGuard configuration (#1767). (Thank you @skyisle.) * Updated Realm Core to 0.95. - Removed reliance on POSIX signals when using encryption. ## 0.84.2 * Fixed a bug making it impossible to convert a field to become required during a migration (#1695). * Fixed a bug making it impossible to read Realms created using primary keys and created by iOS (#1703). * Fixed some memory leaks when an Exception is thrown (#1730). * Fixed a memory leak when using relationships (#1285). * Fixed a bug causing cached column indices to be cleared too soon (#1732). ## 0.84.1 (2015-10-28) * Updated Realm Core to 0.94.4. - Fixed a bug that could cause a crash when running the same query multiple times. * Updated ProGuard configuration. See [documentation](https://realm.io/docs/java/latest/#proguard) for more details. * Updated Kotlin example to use 1.0.0-beta. * Fixed warnings reported by "lint -Xlint:all" (#1644). * Fixed a bug where simultaneous opening and closing a Realm from different threads might result in a NullPointerException (#1646). * Fixed a bug which made it possible to externally modify the encryption key in a RealmConfiguration (#1678). ## 0.84.0 (2015-10-22) * Added support for async queries and transactions. * Added support for parsing JSON Dates with timezone information. (Thank you @LateralKevin.) * Added RealmQuery.isEmpty(). * Added Realm.isClosed() method. * Added Realm.distinct() method. * Added RealmQuery.isValid(), RealmResults.isValid() and RealmList.isValid(). Each method checks whether the instance is still valid to use or not(for example, the Realm has been closed or any parent object has been removed). * Added Realm.isInTransaction() method. * Updated Realm Core to version 0.94.3. - Fallback for mremap() now work correctly on BlackBerry devices. * Following methods in managed RealmList now throw IllegalStateException instead of native crash when RealmList.isValid() returns false: add(int,RealmObject), add(RealmObject) * Following methods in managed RealmList now throw IllegalStateException instead of ArrayIndexOutOfBoundsException when RealmList.isValid() returns false: set(int,RealmObject), move(int,int), remove(int), get(int) * Following methods in managed RealmList now throw IllegalStateException instead of returning 0/null when RealmList.isValid() returns false: clear(), removeAll(Collection), remove(RealmObject), first(), last(), size(), where() * RealmPrimaryKeyConstraintException is now thrown instead of RealmException if two objects with same primary key are inserted. * IllegalStateException is now thrown when calling Realm's clear(), RealmResults's remove(), removeLast(), clear() or RealmObject's removeFromRealm() from an incorrect thread. * Fixed a bug affecting RealmConfiguration.equals(). * Fixed a bug in RealmQuery.isNotNull() which produced wrong results for binary data. * Fixed a bug in RealmQuery.isNull() and RealmQuery.isNotNull() which validated the query prematurely. * Fixed a bug where closed Realms were trying to refresh themselves resulting in a NullPointerException. * Fixed a bug that made it possible to migrate open Realms, which could cause undefined behavior when querying, reading or writing data. * Fixed a bug causing column indices to be wrong for some edge cases. See #1611 for details. ## 0.83.1 (2015-10-15) * Updated Realm Core to version 0.94.1. - Fixed a bug when using Realm.compactRealm() which could make it impossible to open the Realm file again. - Fixed a bug, so isNull link queries now always return true if any part is null. ## 0.83 (2015-10-08) * BREAKING CHANGE: Database file format update. The Realm file created by this version cannot be used by previous versions of Realm. * BREAKING CHANGE: Removed deprecated methods and constructors from the Realm class. * BREAKING CHANGE: Introduced boxed types Boolean, Byte, Short, Integer, Long, Float and Double. Added null support. Introduced annotation @Required to indicate a field is not nullable. String, Date and byte[] became nullable by default which means a RealmMigrationNeededException will be thrown if an previous version of a Realm file is opened. * Deprecated methods: RealmQuery.minimum{Int,Float,Double}, RealmQuery.maximum{Int,Float,Double}. Use RealmQuery.min() and RealmQuery.max() instead. * Added support for x86_64. * Fixed an issue where opening the same Realm file on two Looper threads could potentially lead to an IllegalStateException being thrown. * Fixed an issue preventing the call of listeners on refresh(). * Opening a Realm file from one thread will no longer be blocked by a transaction from another thread. * Range restrictions of Date fields have been removed. Date fields now accepts any value. Milliseconds are still removed. ## 0.82.2 (2015-09-04) * Fixed a bug which might cause failure when loading the native library. * Fixed a bug which might trigger a timeout in Context.finalize(). * Fixed a bug which might cause RealmObject.isValid() to throw an exception if the object is deleted. * Updated Realm core to version 0.89.9 - Fixed a potential stack overflow issue which might cause a crash when encryption was used. - Embedded crypto functions into Realm dynamic lib to avoid random issues on some devices. - Throw RealmEncryptionNotSupportedException if the device doesn't support Realm encryption. At least one device type (HTC One X) contains system bugs that prevents Realm's encryption from functioning properly. This is now detected, and an exception is thrown when trying to open/create an encrypted Realm file. It's up to the application to catch this and decide if it's OK to proceed without encryption instead. ## 0.82.1 (2015-08-06) * Fixed a bug where using the wrong encryption key first caused the right key to be seen as invalid. * Fixed a bug where String fields were ignored when updating objects from JSON with null values. * Fixed a bug when calling System.exit(0), the process might hang. ## 0.82 (2015-07-28) * BREAKING CHANGE: Fields with annotation @PrimaryKey are indexed automatically now. Older schemas require a migration. * RealmConfiguration.setModules() now accept ignore null values which Realm.getDefaultModule() might return. * Trying to access a deleted Realm object throw throws a proper IllegalStateException. * Added in-memory Realm support. * Closing realm on another thread different from where it was created now throws an exception. * Realm will now throw a RealmError when Realm's underlying storage engine encounters an unrecoverable error. * @Index annotation can also be applied to byte/short/int/long/boolean/Date now. * Fixed a bug where RealmQuery objects are prematurely garbage collected. * Removed RealmQuery.between() for link queries. ## 0.81.1 (2015-06-22) * Fixed memory leak causing Realm to never release Realm objects. ## 0.81 (2015-06-19) * Introduced RealmModules for working with custom schemas in libraries and apps. * Introduced Realm.getDefaultInstance(), Realm.setDefaultInstance(RealmConfiguration) and Realm.getInstance(RealmConfiguration). * Deprecated most constructors. They have been been replaced by Realm.getInstance(RealmConfiguration) and Realm.getDefaultInstance(). * Deprecated Realm.migrateRealmAtPath(). It has been replaced by Realm.migrateRealm(RealmConfiguration). * Deprecated Realm.deleteFile(). It has been replaced by Realm.deleteRealm(RealmConfiguration). * Deprecated Realm.compactFile(). It has been replaced by Realm.compactRealm(RealmConfiguration). * RealmList.add(), RealmList.addAt() and RealmList.set() now copy unmanaged objects transparently into Realm. * Realm now works with Kotlin (M12+). (Thank you @cypressious.) * Fixed a performance regression introduced in 0.80.3 occurring during the validation of the Realm schema. * Added a check to give a better error message when null is used as value for a primary key. * Fixed unchecked cast warnings when building with Realm. * Cleaned up examples (remove old test project). * Added checking for missing generic type in RealmList fields in annotation processor. ## 0.80.3 (2015-05-22) * Calling Realm.copyToRealmOrUpdate() with an object with a null primary key now throws a proper exception. * Fixed a bug making it impossible to open Realms created by Realm-Cocoa if a model had a primary key defined. * Trying to using Realm.copyToRealmOrUpdate() with an object with a null primary key now throws a proper exception. * RealmChangedListener now also gets called on the same thread that did the commit. * Fixed bug where Realm.createOrUpdateWithJson() reset Date and Binary data to default values if not found in the JSON output. * Fixed a memory leak when using RealmBaseAdapter. * RealmBaseAdapter now allow RealmResults to be null. (Thanks @zaki50.) * Fixed a bug where a change to a model class (`RealmList` to `RealmList`) would not throw a RealmMigrationNeededException. * Fixed a bug where setting multiple RealmLists didn't remove the previously added objects. * Solved ConcurrentModificationException thrown when addChangeListener/removeChangeListener got called in the onChange. (Thanks @beeender) * Fixed duplicated listeners in the same realm instance. Trying to add duplicated listeners is ignored now. (Thanks @beeender) ## 0.80.2 (2015-05-04) * Trying to use Realm.copyToRealmOrUpdate() with an object with a null primary key now throws a proper exception. * RealmMigrationNeedException can now return the path to the Realm that needs to be migrated. * Fixed bug where creating a Realm instance with a hashcode collision no longer returned the wrong Realm instance. * Updated Realm Core to version 0.89.2 - fixed bug causing a crash when opening an encrypted Realm file on ARM64 devices. ## 0.80.1 (2015-04-16) * Realm.createOrUpdateWithJson() no longer resets fields to their default value if they are not found in the JSON input. * Realm.compactRealmFile() now uses Realm Core's compact() method which is more failure resilient. * Realm.copyToRealm() now correctly handles referenced child objects that are already in the Realm. * The ARM64 binary is now properly a part of the Eclipse distribution package. * A RealmMigrationExceptionNeeded is now properly thrown if @Index and @PrimaryKey are not set correctly during a migration. * Fixed bug causing Realms to be cached even though they failed to open correctly. * Added Realm.deleteRealmFile(File) method. * Fixed bug causing queries to fail if multiple Realms has different field ordering. * Fixed bug when using Realm.copyToRealm() with a primary key could crash if default value was already used in the Realm. * Updated Realm Core to version 0.89.0 - Improved performance for sorting RealmResults. - Improved performance for refreshing a Realm after inserting or modifying strings or binary data. - Fixed bug causing incorrect result when querying indexed fields. - Fixed bug causing corruption of string index when deleting an object where there are duplicate values for the indexed field. - Fixed bug causing a crash after compacting the Realm file. * Added RealmQuery.isNull() and RealmQuery.isNotNull() for querying relationships. * Fixed a potential NPE in the RealmList constructor. ## 0.80 (2015-03-11) * Queries on relationships can be case sensitive. * Fixed bug when importing JSONObjects containing NULL values. * Fixed crash when trying to remove last element of a RealmList. * Fixed bug crashing annotation processor when using "name" in model classes for RealmObject references * Fixed problem occurring when opening an encrypted Realm with two different instances of the same key. * Version checker no longer reports that updates are available when latest version is used. * Added support for static fields in RealmObjects. * Realm.writeEncryptedCopyTo() has been reenabled. ## 0.79.1 (2015-02-20) * copyToRealm() no longer crashes on cyclic data structures. * Fixed potential crash when using copyToRealmOrUpdate with an object graph containing a mix of elements with and without primary keys. ## 0.79 (2015-02-16) * Added support for ARM64. * Added RealmQuery.not() to negate a query condition. * Added copyToRealmOrUpdate() and createOrUpdateFromJson() methods, that works for models with primary keys. * Made the native libraries much smaller. Arm went from 1.8MB to 800KB. * Better error reporting when trying to create or open a Realm file fails. * Improved error reporting in case of missing accessors in model classes. * Re-enabled RealmResults.remove(index) and RealmResults.removeLast(). * Primary keys are now supported through the @PrimaryKey annotation. * Fixed error when instantiating a Realm with the wrong key. * Throw an exception if deleteRealmFile() is called when there is an open instance of the Realm. * Made migrations and compression methods synchronised. * Removed methods deprecated in 0.76. Now Realm.allObjectsSorted() and RealmQuery.findAllSorted() need to be used instead. * Reimplemented Realm.allObjectSorted() for better performance. ## 0.78 (2015-01-22) * Added proper support for encryption. Encryption support is now included by default. Keys are now 64 bytes long. * Added support to write an encrypted copy of a Realm. * Realm no longer incorrectly warns that an instance has been closed too many times. * Realm now shows a log warning if an instance is being finalized without being closed. * Fixed bug causing Realms to be cached during a RealmMigration resulting in invalid realms being returned from Realm.getInstance(). * Updated core to 0.88. ## 0.77 (2015-01-16) * Added Realm.allObjectsSorted() and RealmQuery.findAllSorted() and extending RealmResults.sort() for multi-field sorting. * Added more logging capabilities at the JNI level. * Added proper encryption support. NOTE: The key has been increased from 32 bytes to 64 bytes (see example). * Added support for unmanaged objects and custom constructors. * Added more precise imports in proxy classes to avoid ambiguous references. * Added support for executing a transaction with a closure using Realm.executeTransaction(). * Added RealmObject.isValid() to test if an object is still accessible. * RealmResults.sort() now has better error reporting. * Fixed bug when doing queries on the elements of a RealmList, ie. like Realm.where(Foo.class).getBars().where().equalTo("name"). * Fixed bug causing refresh() to be called on background threads with closed Realms. * Fixed bug where calling Realm.close() too many times could result in Realm not getting closed at all. This now triggers a log warning. * Throw NoSuchMethodError when RealmResults.indexOf() is called, since it's not implemented yet. * Improved handling of empty model classes in the annotation processor * Removed deprecated static constructors. * Introduced new static constructors based on File instead of Context, allowing to save Realm files in custom locations. * RealmList.remove() now properly returns the removed object. * Calling realm.close() no longer prevent updates to other open realm instances on the same thread. ## 0.76.0 (2014-12-19) * RealmObjects can now be imported using JSON. * Gradle wrapper updated to support Android Studio 1.0. * Fixed bug in RealmObject.equals() so it now correctly compares two objects from the same Realm. * Fixed bug in Realm crashing for receiving notifications after close(). * Realm class is now marked as final. * Replaced concurrency example with a better thread example. * Allowed to add/remove RealmChangeListeners in RealmChangeListeners. * Upgraded to core 0.87.0 (encryption support, API changes). * Close the Realm instance after migrations. * Added a check to deny the writing of objects outside of a transaction. ## 0.75.1 (2014-12-03) * Changed sort to be an in-place method. * Renamed SORT_ORDER_DECENDING to SORT_ORDER_DESCENDING. * Added sorting functionality to allObjects() and findAll(). * Fixed bug when querying a date column with equalTo(), it would act as lessThan() ## 0.75.0 (2014-11-28) * Realm now implements Closeable, allowing better cleanup of native resources. * Added writeCopyTo() and compactRealmFile() to write and compact a Realm to a new file. * RealmObject.toString(), equals() and hashCode() now support models with cyclic references. * RealmResults.iterator() and listIterator() now correctly iterates the results when using remove(). * Bug fixed in Exception text when field names was not matching the database. * Bug fixed so Realm no longer throws an Exception when removing the last object. * Bug fixed in RealmResults which prevented sub-querying. * The Date type does not support millisecond resolution, and dates before 1901-12-13 and dates after 2038-01-19 are not supported on 32 bit systems. * Fixed bug so Realm no longer throws an Exception when removing the last object. * Fixed bug in RealmResults which prevented sub-querying. ## 0.74.0 (2014-11-19) * Added support for more field/accessors naming conventions. * Added case sensitive versions of string comparison operators equalTo and notEqualTo. * Added where() to RealmList to initiate queries. * Added verification of fields names in queries with links. * Added exception for queries with invalid field name. * Allow static methods in model classes. * An exception will now be thrown if you try to move Realm, RealmResults or RealmObject between threads. * Fixed a bug in the calculation of the maximum of date field in a RealmResults. * Updated core to 0.86.0, fixing a bug in cancelling an empty transaction, and major query speedups with floats/doubles. * Consistent handling of UTF-8 strings. * removeFromRealm() now calls moveLastOver() which is faster and more reliable when deleting multiple objects. ## 0.73.1 (2014-11-05) * Fixed a bug that would send infinite notifications in some instances. ## 0.73.0 (2014-11-04) * Fixed a bug not allowing queries with more than 1024 conditions. * Rewritten the notification system. The API did not change but it's now much more reliable. * Added support for switching auto-refresh on and off (Realm.setAutoRefresh). * Added RealmBaseAdapter and an example using it. * Added deleteFromRealm() method to RealmObject. ## 0.72.0 (2014-10-27) * Extended sorting support to more types: boolean, byte, short, int, long, float, double, Date, and String fields are now supported. * Better support for Java 7 and 8 in the annotations processor. * Better support for the Eclipse annotations processor. * Added Eclipse support to the distribution folder. * Added Realm.cancelTransaction() to cancel/abort/rollback a transaction. * Added support for link queries in the form realm.where(Owner.class).equalTo("cat.age", 12).findAll(). * Faster implementation of RealmQuery.findFirst(). * Upgraded core to 0.85.1 (deep copying of strings in queries; preparation for link queries). ## 0.71.0 (2014-10-07) * Simplified the release artifact to a single Jar file. * Added support for Eclipse. * Added support for deploying to Maven. * Throw exception if nested transactions are used (it's not allowed). * Javadoc updated. * Fixed [bug in RealmResults](https://github.com/realm/realm-java/issues/453). * New annotation @Index to add search index to a field (currently only supporting String fields). * Made the annotations processor more verbose and strict. * Added RealmQuery.count() method. * Added a new example about concurrency. * Upgraded to core 0.84.0. ## 0.70.1 (2014-09-30) * Enabled unit testing for the realm project. * Fixed handling of camel-cased field names. ## 0.70.0 (2014-09-29) * This is the first public beta release. ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing ## Filing Issues Whether you find a bug, typo or an API call that could be clarified, please [file an issue](https://github.com/realm/realm-java/issues) on our GitHub repository. When filing an issue, please provide as much of the following information as possible in order to help us fix it: 1. **Goals** 2. **Expected results** 3. **Actual results** 4. **Steps to reproduce** 5. **Code sample that highlights the issue** (link to full Android Studio projects that we can compile ourselves are ideal) 6. **Version of Realm/Android Studio/OS** If you'd like to send us sensitive sample code to help troubleshoot your issue, you can email directly. ## Contributing Enhancements We love contributions to Realm! If you'd like to contribute code, documentation, or any other improvements, please [file a Pull Request](https://github.com/realm/realm-java/pulls) on our GitHub repository. Make sure to accept our [CLA](#CLA)! ### CLA Realm welcomes all contributions! The only requirement we have is that, like many other projects, we need to have a [Contributor License Agreement](https://en.wikipedia.org/wiki/Contributor_License_Agreement) (CLA) in place before we can accept any external code. Our own CLA is a modified version of the Apache Software Foundation’s CLA. [Please submit your CLA electronically using our Google form](https://docs.google.com/forms/d/e/1FAIpQLSeQ9ROFaTu9pyrmPhXc-dEnLD84DbLuT_-tPNZDOL9J10tOKQ/viewform) so we can accept your submissions. The GitHub username you file there will need to match that of your Pull Requests. If you have any questions or cannot file the CLA electronically, you can email . ## Repository Guidelines ### Code Style While we havn't described our code style yet, please just follow the existing style you see in the files you change. For source code written in C++, we format it using `clang-format`. You can use the [plugin](https://plugins.jetbrains.com/plugin/8396-clangformatij): mark the entire file and right-click to execute `clang-format` before committing any changes. Of course, if you don't use Android Studio to edit C++ code, run `clang-format` on the command-line. ### Nullability by Annotataion To improve code quality and usability in Kotlin, nullability of parameters and return types must be annotated with JSR305 annotations. If a parameter is nullable, you must add `@Nullable` annotation to the parameter. On the other hand, if a parameter is non-null, you don't need to add `@Nonnull` annotation since all parameters are treated as `@Nonnull` by default. For return types, there is no default nullability. If a method can return `null` as a return value, you must add `@Nullable` annotation to the return type. Currently, `Nonnull` annotation is not mandatory if the method never return `null`. When you add a new package, you must add `package-info.java` and add `@javax.annotation.ParametersAreNonnullByDefault` to the package. Please note that you can't add multiple `package-info.java` in the same package but different location (for example, main and androidTest). When you add a package to both main and androidTest, you only need to add `package-info.java` to main. ### Unit Tests All PR's must be accompanied by related unit tests. All bug fixes must have a unit test proving that the bug is fixed. You can use `./realm/gradlew connectedCheck createDebugCoverageReport` to generate a coverage report to check for missing unit test coverage. The aim is 100% code coverage. When writing unit tests, use the following guide lines: 1) Unit tests must be written using JUnit4. 2) All tests for a class should be grouped in a class called `Tests`, unless the functionality is cross- cutting like [`RxJavaTests`](https://github.com/realm/realm-java/blob/master/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java) or [`RealmAsyncQueryTests`](https://github.com/realm/realm-java/blob/master/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java). 3) Test methods should use camelCase and underscore `_` between logical sections to increase method name readability. Methods should ideally start with the name of the method being tested. Patterns like: `_`, `__` or `` are encouraged. 4) All unit tests creating Realms must do so using the [`TestRealmConfigurationFactory`](https://github.com/realm/realm-java/blob/master/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java) or [`RunInLooperThread`](https://github.com/realm/realm-java/blob/master/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java) test rules. This ensures that all Realms are properly closed and deleted between each test. 5) Use the `@RunInLooperThread` rule for any test that depends on Realms notification system. 6) Input-parameters should be boundary tested. Especially `Null/NotNull`, but also the state of Realm objects like unmanaged objects, deleted objects, objects from other threads. 7) Unit tests are not required to only have 1 test. It is acceptable to combine multiple tests into one unit test, but if it fails, it should be clear why it failed. E.g. you can group related tests with the same setup like negative tests. If you do so, make sure to separate each "subtest" with a comment stating what you test. 8) Use only `@Test(expected = xxx.class)` if the test case contains one line. If the test contains multiple lines and it is the last line that is tested, use the `ExceptedException` rule instead. In all other cases, use the following pattern: try { somethingThatThrowsIllegalArgument(); } catch (IllegalArgumentException ignored) { } 9) Use comments to make the intent of the unit test easily understandable at a glance. A simple one line comment is often easier to read `thanALongCamelCasedSentenceThatAttemptsToDescribeWhatHappens`. Describe the test steps inside the method, if it's not glaringly obvious. This is an example of how a unit test class could look like: @RunWith(AndroidJUnit4.class) public class RealmTests { @Rule public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); @Rule public final RunInLooperThread looperThread = new RunInLooperThread(); private Realm realm; @Before public void setUp() { RealmConfiguration config = configFactory.createConfiguration(); realm = Realm.getInstance(config); } @After public void tearDown() { if (realm != null) { realm.close(); } } @Test(expected = IllegalStateException.class) public void createObject_outsideTransaction() { realm.createObject(Foo.class); } @Test public void createObject_illegalInput { // Class not part of the schema try { realm.createObject(Foo.class); } catch (IllegalArgumentException ignored) { } // Null class try { realm.createObject(null); } catch (IllegalArgumentException ignored) { } } @Test @RunTestInLooperThread public void addChangeListener_notifiedOnLocalCommit() { realm.addChangeListener(new RealmChangeListener() { @Override public void onChange() { assert(1, realm.allObjects(Foo.class).size()); looperThread.testComplete(); } }); realm.beginTransaction(); realm.createObject(Foo.class); realm.commitTransaction(); } } ### Javadoc All public classes and methods must have Javadoc describing their purpose. ```java /** * Checks if given field is equal to the provided value. * *
 * {@code
 *   // A multi-line code sample should be formatted like this.
 *   // Please wrap the code element in a 
 tag.
 * }
 * 
* * @param fieldName the field to compare. * @param fieldValue the value to compare with. * @param caseSensitive if {@code true}, substring matching is case sensitive. Setting this to {@code false} works for English locale characters only. * @param caseSensitive if true, substring matching is case sensitive. Setting this to false only works for English * locale characters. * @return the query object. * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. * @throws IllegalArgumentException if field name doesn't exists, it doesn't contain a list of links or the type * of the object represented by the DynamicRealmObject doesn't match. * @deprecated Please use {@link #average(String)} instead. * @see #endGroup() */ public RealmQuery equalTo(String fieldName, String fieldValue, boolean caseSensitive) { // ... } ``` * Method descriptions begin with a verb phrase, e.g. "Checks" instead of "Check". * Capitalize the first letter of the method and @deprecated descriptions. Everything else starts with lower case. * Empty line between method description and the rest. * End all descriptions with a period `.` (except @see). * Reference other Realm classes using `{@link ...}`. * Wrap Java values in `{@code ...}`. * @throws description must start with "if". * Never list generic exceptions like `RuntimeException`, `Exception` or `Error`. Always reference the specific error. * Line-length maximum is 120 chars. Parameter descriptions that go above this, should be split into multiple lines and indented. Otherwise do not use indentation (contrary to Oracle guidelines). Above is based on the official guidelines from Oracle regarding Javadoc: http://www.oracle.com/technetwork/articles/java/index-137868.html ### Branch Strategy We have two branches for shared development: `master` and `releases`. We make releases from each. `master`: * The `master` branch is where major/minor versions are released from. * It is for new features and/or breaking changes. `releases`: * The releases branch is where patch versions are released from. * It is mainly for bug fixes. * Every commit is automatically merged to `master`. * Minor changes (e.g. to documentation, tests, and the build system) may not affect end users but should still be merged to `releases` to avoid diverging too far from `master` and to reduce the likelihood of merge conflicts. ================================================ FILE: Dockerfile ================================================ FROM ubuntu:22.04 # Locales RUN apt-get clean && apt-get -y update && apt-get install -y locales && locale-gen en_US.UTF-8 ENV LANG "en_US.UTF-8" ENV LANGUAGE "en_US.UTF-8" ENV LC_ALL "en_US.UTF-8" ENV TZ=Europe/Copenhagen RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone # Set the environment variables ENV JAVA_HOME /usr/lib/jvm/java-11-openjdk-amd64 ENV JAVA8_HOME /usr/lib/jvm/java-8-openjdk-amd64 ENV ANDROID_HOME /opt/android-sdk-linux # Need by cmake ENV ANDROID_NDK_HOME /opt/android-ndk ENV ANDROID_NDK /opt/android-ndk ENV PATH ${PATH}:${ANDROID_HOME}/emulator:${ANDROID_HOME}/cmdline-tools/latest:${ANDROID_HOME}/cmdline-tools/latest/bin:${ANDROID_HOME}/platform-tools ENV PATH ${PATH}:${NDK_HOME} ENV NDK_CCACHE /usr/bin/ccache ENV CCACHE_CPP2 yes ENV REALM_DISABLE_ANALYTICS true # Keep the packages in alphabetical order to make it easy to avoid duplication # tzdata needs to be installed first. See https://askubuntu.com/questions/909277/avoiding-user-interaction-with-tzdata-when-installing-certbot-in-a-docker-contai # `file` is need by the Android Emulator RUN DEBIAN_FRONTEND=noninteractive \ && apt-get update -qq \ && apt-get install -y tzdata \ && apt-get install -y \ bsdmainutils \ bridge-utils \ build-essential \ ccache \ curl \ file \ git \ jq \ libc6 \ libgcc1 \ libglu1 \ libncurses5 \ libstdc++6 \ libz1 \ libvirt-clients \ libvirt-daemon-system \ openjdk-11-jdk-headless \ openjdk-8-jdk-headless \ qemu-kvm \ s3cmd \ unzip \ virt-manager \ wget \ zip \ ninja-build \ && apt-get clean # Install the Android SDK # See https://developer.android.com/studio/index.html#downloads for latest version RUN cd /opt && \ wget -q https://dl.google.com/android/repository/commandlinetools-linux-8512546_latest.zip -O android-tools-linux.zip && \ mkdir --parents ${ANDROID_HOME}/cmdline-tools/latest && \ unzip android-tools-linux.zip -d ${ANDROID_HOME}/cmdline-tools/latest && \ mv ${ANDROID_HOME}/cmdline-tools/latest/cmdline-tools/* ${ANDROID_HOME}/cmdline-tools/latest/ && \ rm -f android-tools-linux.zip # Grab what's needed in the SDK RUN sdkmanager --update # 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 # The `yes` is for accepting all non-standard tool licenses. # Please keep all sections in descending order! RUN yes | sdkmanager \ 'build-tools;30.0.3' \ 'emulator' \ 'extras;android;m2repository' \ 'platforms;android-30' \ 'platform-tools' \ 'ndk;23.1.7779620' \ 'system-images;android-31;default;x86_64' # Make the SDK universally writable RUN chmod -R a+rwX ${ANDROID_HOME} # Ensure a new enough version of CMake is available. RUN cd /opt \ && wget -nv https://cmake.org/files/v3.22/cmake-3.27.7-linux-x86_64.tar.gz \ && tar zxf cmake-3.27.7-linux-x86_64.tar.gz # Workaround for https://issuetracker.google.com/issues/206099937 RUN ln -s /usr/bin/ninja /opt/cmake-3.22.1-linux-x86_64/bin/ninja ENV PATH "/opt/cmake-3.27.7-linux-x86_64/bin:$PATH" ================================================ FILE: Jenkinsfile ================================================ #!groovy @Library('realm-ci') _ import groovy.json.JsonOutput // CONSTANTS // Branches from which we release SNAPSHOT's. Only release branches need to run on actual hardware. releaseBranches = ['main', 'next-major', 'support-new-datatypes', 'releases', 'release/transformer-api' ] // Branches that are "important", so if they do not compile they will generate a Slack notification slackNotificationBranches = [ 'main', 'releases', 'next-major', 'support-new-datatypes', 'release/transformer-api' ] // WARNING: Only set to `false` as an absolute last resort. Doing this will disable all integration // tests. enableIntegrationTests = true // RUNTIME PROPERTIES // Will store whether or not this build was successful. buildSuccess = false // Will be set to `true` if this build is a full release that should be available on Maven Central. // This is determined by comparing the current git tag to the version number of the build. publishBuild = false mongoDbRealmContainer = null mongoDbRealmCommandServerContainer = null emulatorContainer = null dockerNetworkId = UUID.randomUUID().toString() currentBranch = (env.CHANGE_BRANCH == null) ? env.BRANCH_NAME : env.CHANGE_BRANCH isReleaseBranch = releaseBranches.contains(currentBranch) // FIXME: Always used the emulator until we can enable more reliable devices // 'android' nodes have android devices attached and 'brix' are physical machines in Copenhagen. // nodeSelector = (releaseBranches.contains(currentBranch)) ? 'android' : 'docker-cph-03' // Switch to `brix` when all CPH nodes work: https://jira.mongodb.org/browse/RCI-14 nodeSelector = 'docker-cph-01' try { node(nodeSelector) { timeout(time: 150, unit: 'MINUTES') { // Allocate a custom workspace to avoid having % in the path (it breaks ld) ws('/tmp/realm-java') { stage('SCM') { checkout([ $class : 'GitSCM', branches : scm.branches, gitTool : 'native git', extensions : scm.extensions + [ [$class: 'CleanCheckout'], [$class: 'SubmoduleOption', recursiveSubmodules: true] ], userRemoteConfigs: scm.userRemoteConfigs ]) } // Check type of Build. We are treating this as a release build if we are building // the exact Git SHA that was tagged. echo "Building from branch: $currentBranch" gitTag = readGitTag() echo "Git tag: ${gitTag ?: 'none'}" if (!gitTag) { gitSha = sh(returnStdout: true, script: 'git rev-parse HEAD').trim().take(8) echo "Building non-release: ${gitSha}" setBuildName(gitSha) publishBuild = false } else { def version = readFile('version.txt').trim() if (gitTag != "v${version}") { error "Git tag '${gitTag}' does not match v${version}" } else { echo "Building release: '${gitTag}'" setBuildName("Tag ${gitTag}") sh """ set +x sh tools/publish_release.sh verify """ publishBuild = true } } // Toggles for PR vs. Master builds. // - For PR's, we favor speed > absolute correctness. So we just build for x86, use an // emulator and run unit tests for the ObjectServer variant. // - For branches from which we make releases, we build all architectures and run tests // on an actual device. def useEmulator = false def emulatorImage = "" def buildFlags = "" def instrumentationTestTarget = "connectedAndroidTest" def deviceSerial = "" if (!isReleaseBranch) { // Build development branch useEmulator = true emulatorImage = "system-images;android-31;default;x86_64" // Build core from source instead of doing it from binary buildFlags = "-PbuildTargetABIs=x86_64 -PenableLTO=false -PbuildCore=true" instrumentationTestTarget = "connectedObjectServerDebugAndroidTest" deviceSerial = "emulator-5554" } else { // Build main/release branch // FIXME: Use emulator until we can get reliable devices on CI. // But still build all ABI's and run all types of tests. useEmulator = true emulatorImage = "system-images;android-31;default;x86_64" buildFlags = "-PenableLTO=true -PbuildCore=true" instrumentationTestTarget = "connectedAndroidTest" deviceSerial = "emulator-5554" } try { def buildEnv = null stage('Prepare Docker Images') { // TODO Caching is currently disabled (with -do-not-cache suffix) due to the upload speed // in Copenhagen being too slow. So the upload times out. buildEnv = buildDockerEnv("ci/realm-java:main", push: currentBranch == 'main-do-not-cache') def props = readProperties file: 'dependencies.list' echo "Version in dependencies.list: ${props.MONGODB_REALM_SERVER}" def mdbRealmImage = docker.image("docker.pkg.github.com/realm/ci/mongodb-realm-test-server:${props.MONGODB_REALM_SERVER}") docker.withRegistry('https://docker.pkg.github.com', 'github-packages-token') { mdbRealmImage.pull() } def commandServerEnv = docker.build 'mongodb-realm-command-server', "tools/sync_test_server" // Prepare Docker containers used by Instrumentation tests // TODO: How much of this logic can be moved to start_server.sh for shared logic with local testing. withCredentials([ [$class: 'AmazonWebServicesCredentialsBinding', credentialsId: 'realm-kotlin-baas-aws-credentials', accessKeyVariable: 'BAAS_AWS_ACCESS_KEY_ID', secretKeyVariable: 'BAAS_AWS_SECRET_ACCESS_KEY'] ]) { def tempDir = runCommand('mktemp -d -t app_config.XXXXXXXXXX') sh "tools/sync_test_server/app_config_generator.sh ${tempDir} tools/sync_test_server/app_template partition auto testapp1" sh "tools/sync_test_server/app_config_generator.sh ${tempDir} tools/sync_test_server/app_template partition email testapp2" sh "tools/sync_test_server/app_config_generator.sh ${tempDir} tools/sync_test_server/app_template flex function testapp3" sh "docker network create ${dockerNetworkId}" mongoDbRealmContainer = mdbRealmImage.run("--network ${dockerNetworkId} -v$tempDir:/apps -e AWS_ACCESS_KEY_ID='$BAAS_AWS_ACCESS_KEY_ID' -e AWS_SECRET_ACCESS_KEY='$BAAS_AWS_SECRET_ACCESS_KEY'") mongoDbRealmCommandServerContainer = commandServerEnv.run("--network container:${mongoDbRealmContainer.id} -v$tempDir:/apps") sh "timeout 60 sh -c \"while [[ ! -f $tempDir/testapp1/app_id || ! -f $tempDir/testapp2/app_id ]]; do echo 'Waiting for server to start'; sleep 1; done\"" } } // There is a chance that real devices are attached to the host, so if the emulator is // running we need to make sure that ADB and tests targets the correct device. String restrictDevice = "" if (deviceSerial != null) { restrictDevice = "-e ANDROID_SERIAL=${deviceSerial} " } buildEnv.inside("-e HOME=/tmp " + "-e _JAVA_OPTIONS=-Duser.home=/tmp " + "--privileged " + "-v /dev/kvm:/dev/kvm " + "-v /dev/bus/usb:/dev/bus/usb " + "-v ${env.HOME}/gradle-cache:/tmp/.gradle " + "-v ${env.HOME}/.android:/tmp/.android " + "-v ${env.HOME}/ccache:/tmp/.ccache " + restrictDevice + "-e REALM_CORE_DOWNLOAD_DIR=/tmp/.gradle " + "--network container:${mongoDbRealmContainer.id} ") { // Lock required around all usages of Gradle as it isn't // able to share its cache between builds. lock("${env.NODE_NAME}-android") { if (useEmulator) { // TODO: We should wait until the emulator is online. For now assume it starts fast enough // before the tests will run, since the library needs to build first. sh """yes '\n' | avdmanager create avd -n CIEmulator -k '${emulatorImage}' --force""" sh "adb start-server" // https://stackoverflow.com/questions/56198290/problems-with-adb-exe // Need to go to ANDROID_HOME due to https://askubuntu.com/questions/1005944/emulator-avd-does-not-launch-the-virtual-device sh "cd \$ANDROID_HOME/tools && emulator -avd CIEmulator -no-boot-anim -no-window -wipe-data -noaudio -partition-size 4096 -memory 1536 &" try { runBuild(buildFlags, instrumentationTestTarget) } finally { sh "adb emu kill" } } else { runBuild(buildFlags, instrumentationTestTarget) } // Release the library if needed if (publishBuild) { runPublish() } } } } finally { // We assume that creating these containers and the docker network can be considered an atomic operation. if (mongoDbRealmContainer != null && mongoDbRealmCommandServerContainer != null) { archiveServerLogs(mongoDbRealmContainer.id, mongoDbRealmCommandServerContainer.id) mongoDbRealmContainer.stop() mongoDbRealmCommandServerContainer.stop() sh "docker network rm ${dockerNetworkId}" } if (emulatorContainer != null) { emulatorContainer.stop() } } } } currentBuild.rawBuild.setResult(Result.SUCCESS) buildSuccess = true } } catch(Exception e) { currentBuild.rawBuild.setResult(Result.FAILURE) buildSuccess = false throw e } finally { if (slackNotificationBranches.contains(currentBranch)) { node { withCredentials([[$class: 'StringBinding', credentialsId: 'slack-webhook-java-ci-channel', variable: 'SLACK_URL']]) { def payload = null if (!buildSuccess) { payload = JsonOutput.toJson([ username: "Realm CI", icon_emoji: ":realm_new:", text: "*The ${currentBranch} branch is broken!*\n<${env.BUILD_URL}|Click here> to check the build." ]) } else if (currentBuild.getPreviousBuild() && currentBuild.getPreviousBuild().getResult().toString() != "SUCCESS" && buildSuccess) { payload = JsonOutput.toJson([ username: "Realm CI", icon_emoji: ":realm_new:", text: "*${currentBranch} is back to normal!*\n<${env.BUILD_URL}|Click here> to check the build." ]) } if (payload != null) { sh "curl -X POST --data-urlencode \'payload=${payload}\' ${env.SLACK_URL}" } } } } } // Runs all build steps def runBuild(buildFlags, instrumentationTestTarget) { stage('Build') { withCredentials([ [$class: 'StringBinding', credentialsId: 'maven-central-java-ring-file', variable: 'SIGN_KEY'], [$class: 'StringBinding', credentialsId: 'maven-central-java-ring-file-password', variable: 'SIGN_KEY_PASSWORD'], ]) { sh "chmod +x gradlew" def signingFlags = "" if (isReleaseBranch) { signingFlags = "-PsignBuild=true -PsignSecretRingFile=\"${SIGN_KEY}\" -PsignPassword=${SIGN_KEY_PASSWORD}" } sh "./gradlew assemble ${buildFlags} ${signingFlags} --stacktrace" } } stage('Tests') { parallel 'JVM' : { try { sh "chmod +x gradlew && ./gradlew check ${buildFlags} --stacktrace" } finally { storeJunitResults 'realm/realm-annotations-processor/build/test-results/test/TEST-*.xml' storeJunitResults 'examples/unitTestExample/build/test-results/**/TEST-*.xml' storeJunitResults 'realm/realm-library/build/test-results/**/TEST-*.xml' step([$class: 'LintPublisher']) } }, // FIXME https://github.com/realm/realm-java/issues/7593 // 'JVM8 introExample check' : { // // Force build with JVM8, by disabling the cache, and check introExample. // sh """ // cd examples/moduleExample // JAVA_HOME=\$JAVA8_HOME ../gradlew check ${buildFlags} --stacktrace // """ // }, 'Realm Transformer' : { try { gradle('realm-transformer', 'check') } finally { storeJunitResults 'realm-transformer/build/test-results/test/TEST-*.xml' } }, // 'Static code analysis' : { // try { // gradle('realm', "spotbugsMain pmd checkstyle ${buildFlags}") // } finally { // publishHTML(target: [ // allowMissing: false, // alwaysLinkToLastBuild: false, // keepAll: true, // reportDir: 'realm/realm-library/build/reports/spotbugs', // reportFiles: 'main.html', // reportName: 'Spotbugs report' // ]) // publishHTML(target: [ // allowMissing: false, // alwaysLinkToLastBuild: false, // keepAll: true, // reportDir: 'realm/realm-library/build/reports/pmd', // reportFiles: 'pmd.html', // reportName: 'PMD report' // ]) // publishHTML(target: [ // allowMissing: false, // alwaysLinkToLastBuild: false, // keepAll: true, // reportDir: 'realm/realm-library/build/reports/checkstyle', // reportFiles: 'checkstyle.html', // reportName: 'Checkstyle report' // ]) // } // }, 'Gradle Plugin' : { try { gradle('gradle-plugin', 'check') } finally { storeJunitResults 'gradle-plugin/build/test-results/test/TEST-*.xml' } }, 'JavaDoc': { sh "./gradlew javadoc ${buildFlags} --stacktrace" } } stage('Device Tests') { if (enableIntegrationTests) { String backgroundPid try { backgroundPid = startLogCatCollector() forwardAdbPorts() gradle('realm', "${instrumentationTestTarget} ${buildFlags}") gradle('examples', ":unitTestExample:connectedDebugAndroidTest") } finally { stopLogCatCollector(backgroundPid) storeJunitResults 'realm/realm-library/build/outputs/androidTest-results/connected/**/TEST-*.xml' storeJunitResults 'realm/kotlin-extensions/build/outputs/androidTest-results/connected/**/TEST-*.xml' } } else { echo "Instrumentation tests were disabled." } } // TODO: add support for running monkey on the example apps def collectMetrics = ['main'].contains(currentBranch) echo "Collecting metrics: $collectMetrics" if (collectMetrics) { stage('Collect metrics') { collectAarMetrics() } } echo "Releasing SNAPSHOT: ($isReleaseBranch, $publishBuild)" if (isReleaseBranch && !publishBuild) { stage('Publish SNAPSHOT') { withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'maven-central-credentials', passwordVariable: 'MAVEN_CENTRAL_PASSWORD', usernameVariable: 'MAVEN_CENTRAL_USER']]) { sh "chmod +x gradlew && ./gradlew mavenCentralUpload ${buildFlags} -PossrhUsername='$MAVEN_CENTRAL_USER' -PossrhPassword='$MAVEN_CENTRAL_PASSWORD' --stacktrace" } } } } def runPublish() { stage('Publish Release') { withCredentials([ [$class: 'StringBinding', credentialsId: 'maven-central-java-ring-file', variable: 'SIGN_KEY'], [$class: 'StringBinding', credentialsId: 'maven-central-java-ring-file-password', variable: 'SIGN_KEY_PASSWORD'], [$class: 'StringBinding', credentialsId: 'slack-webhook-java-ci-channel', variable: 'SLACK_URL_CI'], [$class: 'StringBinding', credentialsId: 'slack-webhook-releases-channel', variable: 'SLACK_URL_RELEASE'], [$class: 'UsernamePasswordMultiBinding', credentialsId: 'maven-central-credentials', passwordVariable: 'MAVEN_CENTRAL_PASSWORD', usernameVariable: 'MAVEN_CENTRAL_USER'], [$class: 'AmazonWebServicesCredentialsBinding', accessKeyVariable: 'DOCS_S3_ACCESS_KEY', credentialsId: 'mongodb-realm-docs-s3', secretKeyVariable: 'DOCS_S3_SECRET_KEY'], [$class: 'AmazonWebServicesCredentialsBinding', accessKeyVariable: 'REALM_S3_ACCESS_KEY', credentialsId: 'tightdb-s3-ci', secretKeyVariable: 'REALM_S3_SECRET_KEY'] ]) { // TODO Make sure that buildFlags and signingFlags are unified across builds sh """ set +x sh tools/publish_release.sh '$MAVEN_CENTRAL_USER' '$MAVEN_CENTRAL_PASSWORD' \ '$REALM_S3_ACCESS_KEY' '$REALM_S3_SECRET_KEY' \ '$DOCS_S3_ACCESS_KEY' '$DOCS_S3_SECRET_KEY' \ '$SLACK_URL_RELEASE' '$SLACK_URL_CI' \ '-PsignBuild=true -PsignSecretRingFile="${SIGN_KEY}" -PsignPassword=${SIGN_KEY_PASSWORD} -PenableLTO=true -PbuildCore=true' """ } } } def forwardAdbPorts() { sh """ adb reverse tcp:9080 tcp:9080 && adb reverse tcp:9443 tcp:9443 && adb reverse tcp:8888 tcp:8888 && adb reverse tcp:9090 tcp:9090 """ } String startLogCatCollector() { // Cancel build quickly if no device is available. The lock acquired already should // ensure we have access to a device. If not, it is most likely a more severe problem. timeout(time: 1, unit: 'MINUTES') { // Need ADB as root to clear all buffers: https://stackoverflow.com/a/47686978/1389357 sh 'adb devices' sh """adb root adb logcat -b all -c adb logcat -v time > 'logcat.txt' & echo \$! > pid """ return readFile("pid").trim() } } def stopLogCatCollector(String backgroundPid) { // The pid might not be available if the build was terminated early or stopped due to // a build error. if (backgroundPid != null) { sh "kill ${backgroundPid}" zip([ 'zipFile': 'logcat.zip', 'archive': true, 'glob' : 'logcat.txt' ]) sh 'rm logcat.txt' } } def archiveServerLogs(String mongoDbRealmContainerId, String commandServerContainerId) { sh "docker logs ${commandServerContainerId} > ./command-server.log" zip([ 'zipFile': 'command-server-log.zip', 'archive': true, 'glob' : 'command-server.log' ]) sh 'rm command-server.log' sh "docker cp ${mongoDbRealmContainerId}:/var/log/stitch.log ./stitch.log" zip([ 'zipFile': 'stitchlog.zip', 'archive': true, 'glob' : 'stitch.log' ]) sh 'rm stitch.log' sh "docker cp ${mongoDbRealmContainerId}:/var/log/mongodb.log ./mongodb.log" zip([ 'zipFile': 'mongodb.zip', 'archive': true, 'glob' : 'mongodb.log' ]) sh 'rm mongodb.log' } def sendMetrics(String metricName, String metricValue, Map tags) { def tagsString = getTagsString(tags) withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: '5b8ad2d9-61a4-43b5-b4df-b8ff6b1f16fa', passwordVariable: 'influx_pass', usernameVariable: 'influx_user']]) { sh "curl -i -XPOST 'https://influxdb.realmlab.net/write?db=realm' --data-binary '${metricName},${tagsString} value=${metricValue}i' --user '${env.influx_user}:${env.influx_pass}'" } } @NonCPS def getTagsString(Map tags) { return tags.collect { k,v -> "$k=$v" }.join(',') } def storeJunitResults(String path) { step([ $class: 'JUnitResultArchiver', allowEmptyResults: true, testResults: path ]) } def collectAarMetrics() { def flavors = ['base', 'objectServer'] for (def i = 0; i < flavors.size(); i++) { def flavor = flavors[i] sh """set -xe cd realm/realm-library/build/outputs/aar unzip realm-android-library-${flavor}-release.aar -d unzipped${flavor} find \$ANDROID_HOME -name d8 | sort -r | head -n 1 > d8 \$(cat d8) --release --output ./unzipped${flavor} unzipped${flavor}/classes.jar cat ./unzipped${flavor}/temp${flavor}.dex | head -c 92 | tail -c 4 | hexdump -e '1/4 \"%d\"' > methods${flavor} """ def methods = readFile("realm/realm-library/build/outputs/aar/methods${flavor}") sendMetrics('methods', methods, ['flavor':flavor]) def aarFile = findFiles(glob: "realm/realm-library/build/outputs/aar/realm-android-library-${flavor}-release.aar")[0] sendMetrics('aar_size', aarFile.length as String, ['flavor':flavor]) def soFiles = findFiles(glob: "realm/realm-library/build/outputs/aar/unzipped${flavor}/jni/*/librealm-jni.so") for (def j = 0; j < soFiles.size(); j++) { def soFile = soFiles[j] def abiName = soFile.path.tokenize('/')[-2] def libSize = soFile.length as String sendMetrics('abi_size', libSize, ['flavor':flavor, 'type':abiName]) } } } def gradle(String commands) { sh "chmod +x gradlew && ./gradlew ${commands} --stacktrace" } def gradle(String relativePath, String commands) { sh "cd ${relativePath} && chmod +x gradlew && ./gradlew ${commands} --stacktrace" } def readGitTag() { def command = 'git describe --exact-match --tags HEAD' def returnStatus = sh(returnStatus: true, script: command) if (returnStatus != 0) { return null } return sh(returnStdout: true, script: command).trim() } def runCommand(String command){ return sh(script: command, returnStdout: true).trim() } ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS ================================================ FILE: README.md ================================================ > [!WARNING] > We announced the deprecation of Atlas Device Sync + Realm SDKs in September 2024. For more information please see: > - [SDK Deprecation](https://www.mongodb.com/docs/atlas/device-sdks/deprecation/) > - [Device Sync Deprecation](https://www.mongodb.com/docs/atlas/app-services/sync/device-sync-deprecation/) > realm [![Maven Central](https://img.shields.io/maven-central/v/io.realm/realm-gradle-plugin?colorB=4dc427&label=Maven%20Central)](https://search.maven.org/artifact/io.realm/realm-gradle-plugin) [![License](https://img.shields.io/badge/License-Apache-blue.svg)](https://github.com/realm/realm-java/blob/master/LICENSE) Realm is a mobile database that runs directly inside phones, tablets or wearables. This repository holds the source code for the Java version of Realm, which currently runs only on Android. ## Realm Kotlin The [Realm Kotlin SDK](https://github.com/realm/realm-kotlin) is now GA and can be used for both Android and Kotlin Multiplatform. While we are still adding features, please consider using Realm Kotlin for any new project, and let us know if you miss anything there! ## Features * **Mobile-first:** Realm is the first database built from the ground up to run directly inside phones, tablets, and wearables. * **Simple:** Data is directly exposed as objects and queryable by code, removing the need for ORM's riddled with performance & maintenance issues. Plus, we've worked hard to [keep our API down to very few classes](https://www.mongodb.com/docs/atlas/device-sdks/sdk/java/): most of our users pick it up intuitively, getting simple apps up & running in minutes. * **Modern:** Realm supports easy thread-safety, relationships & encryption. * **Fast:** Realm is faster than even raw SQLite on common operations while maintaining an extremely rich feature set. * **[Device Sync](https://www.mongodb.com/atlas/app-services/device-sync)**: Makes it simple to keep data in sync across users, devices, and your backend in real-time. Get started for free with [a template application](https://github.com/mongodb/template-app-react-native-todo) and [create the cloud backend](http://mongodb.com/realm/register?utm_medium=github_atlas_CTA&utm_source=realm_js_github). ## Getting Started Please see the [Quick Start](docs/guides/quick-start-local.md) to add Realm to your project. ## Documentation Documentation for Realm can be found in the [docs/](docs/README.md) directory. The Javadoc and Kotlin Extensions API Reference docs can be generated from source. ## Getting Help - **Got a question?**: Look for previous questions on the [#realm tag](https://stackoverflow.com/questions/tagged/realm?sort=newest) — or [ask a new question](http://stackoverflow.com/questions/ask?tags=realm). We actively monitor & answer questions on StackOverflow! You can also check out our [Community Forum](https://developer.mongodb.com/community/forums/tags/c/realm/9/realm-sdk) where general questions about how to do something can be discussed. - **Think you found a bug?** [Open an issue](https://github.com/realm/realm-java/issues/new?template=bug_report.md). If possible, include the version of Realm, a full log, the Realm file, and a project that shows the issue. - **Have a feature request?** [Open an issue](https://github.com/realm/realm-java/issues/new?template=feature_request.md). Tell us what the feature should do, and why you want the feature. ## Using Snapshots If you want to test recent bugfixes or features that have not been packaged in an official release yet, you can use a **-SNAPSHOT** release of the current development version of Realm via Gradle, available on [Sonatype OSS](https://oss.sonatype.org/#nexus-search;quick~realm-gradle-plugin) ``` buildscript { repositories { mavenCentral() google() maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } jcenter() } dependencies { classpath "io.realm:realm-gradle-plugin:-SNAPSHOT" } } allprojects { repositories { mavenCentral() google() maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } jcenter() } } ``` See [version.txt](version.txt) for the latest version number. ## Building Realm In case you don't want to use the precompiled version, you can build Realm yourself from source. ### Prerequisites * Download the [**JDK 8**](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) from Oracle and install it. * The latest stable version of Android Studio. Currently [4.1.1](https://developer.android.com/studio/). * Download & install the Android SDK **Build-Tools 29.0.3**, **Android Pie (API 29)** (for example through Android Studio’s **Android SDK Manager**). * Install CMake version 3.18.4 and build Ninja. * Install the NDK (Side-by-side) **21.0.6113669** from the SDK Manager in Android Studio. Remember to check `☑ Show package details` in the manager to display all available versions. * Add the Android home environment variable to your profile: ``` export ANDROID_HOME=~/Library/Android/sdk ``` * If you are launching Android Studio from the macOS Finder, you should also run the following command: ``` launchctl setenv ANDROID_HOME "$ANDROID_HOME" ``` * If you'd like to specify the location in which to store the archives of Realm Core, define the `REALM_CORE_DOWNLOAD_DIR` environment variable. It enables caching core release artifacts. ``` export REALM_CORE_DOWNLOAD_DIR=~/.realmCore ``` macOS users must also run the following command for Android Studio to see this environment variable. ``` launchctl setenv REALM_CORE_DOWNLOAD_DIR "$REALM_CORE_DOWNLOAD_DIR" ``` It would be a good idea to add all of the symbol definitions (and their accompanying `launchctl` commands, if you are using macOS) to your `~/.profile` (or `~/.zprofile` if the login shell is `zsh`) * If you develop Realm Java with Android Studio, we recommend you to exclude some directories from indexing target by executing following steps on Android Studio. It really speeds up indexing phase after the build. - Under `/realm/realm-library/`, select `build`, `.cxx` and `distribution` folders in `Project` view. - Press `Command + Shift + A` to open `Find action` dialog. If you are not using default keymap nor using macOS, you can find your shortcut key in `Keymap` preference by searching `Find action`. - Search `Excluded` (not `Exclude`) action and select it. Selected folder icons should become orange (in default theme). - Restart Android Studio. ### Download sources You can download the source code of Realm Java by using git. Since realm-java has git submodules, use `--recursive` when cloning the repository. ``` git clone git@github.com:realm/realm-java.git --recursive ``` or ``` git clone https://github.com/realm/realm-java.git --recursive ``` ### Build Once you have completed all the pre-requisites building Realm is done with a simple command. ``` ./gradlew assemble ``` That command will generate: * a jar file for the Realm Gradle plugin * an aar file for the Realm library * a jar file for the annotations * a jar file for the annotations processor The full build may take an hour or more, to complete. ### Building from source It is possible to build Realm Java with the submodule version of Realm Core. This is done by providing the following parameter when building: `-PbuildCore=true`. ``` ./gradlew assembleBase -PbuildCore=true ``` You can turn off interprocedural optimizations with the following parameter: `-PenableLTO=false`. ``` ./gradlew assembleBase -PenableLTO=false` ``` Note: Building the `Base` variant would always build realm-core. Note: Interprocedural optimizations are enabled by default. Note: If you want to build from source inside Android Studio, you need to update the Gradle parameters by going into the Realm projects settings `Settings > Build, Execution, Deployment > Compiler > Command-line options` and add `-PbuildCore=true` or `-PenableLTO=false` to it. Alternatively you can add it into your `gradle.properties`: ``` buildCore=true enableLTO=false ``` Note: If building on OSX you might like to prevent Gatekeeper to block all NDK executables by disabling it: `sudo spctl --master-disable`. Remember to enable it afterwards: `sudo spctl --master-enable` ### Other Commands * `./gradlew tasks` will show all the available tasks * `./gradlew javadoc` will generate the Javadocs * `./gradlew monkeyExamples` will run the monkey tests on all the examples * `./gradlew installRealmJava` will install the Realm library and plugin to mavenLocal() * `./gradlew clean -PdontCleanJniFiles` will remove all generated files except for JNI related files. This reduces recompilation time a lot. * `./gradlew connectedUnitTests -PbuildTargetABIs=$(adb shell getprop ro.product.cpu.abi)` will build JNI files only for the ABI which corresponds to the connected device. These tests require a running Object Server (see below) Generating the Javadoc using the command above may generate warnings. The Javadoc is generated despite the warnings. ### Upgrading Gradle Wrappers All gradle projects in this repository have `wrapper` task to generate Gradle Wrappers. Those tasks refer to `gradle` property defined in `/dependencies.list` to determine Gradle Version of generating wrappers. We have a script `./tools/update_gradle_wrapper.sh` to automate these steps. When you update Gradle Wrappers, please obey the following steps. 1. Edit `gradle` property in defined in `/dependencies.list` to new Gradle Wrapper version. 2. Execute `/tools/update_gradle_wrapper.sh`. ### Gotchas The repository is organized into six Gradle projects: * `realm`: it contains the actual library (including the JNI layer) and the annotations processor. * `realm-annotations`: it contains the annotations defined by Realm. * `realm-transformer`: it contains the bytecode transformer. * `gradle-plugin`: it contains the Gradle plugin. * `examples`: it contains the example projects. This project directly depends on `gradle-plugin` which adds a dependency to the artifacts produced by `realm`. * The root folder is another Gradle project. All it does is orchestrate the other jobs. This means that `./gradlew clean` and `./gradlew cleanExamples` will fail if `assembleExamples` has not been executed first. Note that IntelliJ [does not support multiple projects in the same window](https://youtrack.jetbrains.com/issue/IDEABKL-6118#) so each of the six Gradle projects must be imported as a separate IntelliJ project. Since the repository contains several completely independent Gradle projects, several independent builds are run to assemble it. Seeing a line like: `:realm:realm-library:compileBaseDebugAndroidTestSources UP-TO-DATE` in the build log does *not* imply that you can run `./gradlew :realm:realm-library:compileBaseDebugAndroidTestSources`. ## Examples The `./examples` folder contains many example projects showing how Realm can be used. If this is the first time you checkout or pull a new version of this repository to try the examples, you must call `./gradlew installRealmJava` from the top-level directory first. Otherwise, the examples will not compile as they depend on all Realm artifacts being installed in `mavenLocal()`. ## Running Tests on a Device To run these tests, you must have a device connected to the build computer, and the `adb` command must be in your `PATH` 1. Connect an Android device and verify that the command `adb devices` shows a connected device: ```sh adb devices List of devices attached 004c03eb5615429f device ``` 2. Run instrumentation tests: ```sh cd realm ./gradlew connectedBaseDebugAndroidTest ``` These tests may take as much as half an hour to complete. ## Running Tests Using The Realm Object Server Tests in `realm/realm-library/src/syncIntegrationTest` require a running testing server to work. A docker image can be built from `tools/sync_test_server/Dockerfile` to run the test server. `tools/sync_test_server/start_server.sh` will build the docker image automatically. To run a testing server locally: 1. Install [docker](https://www.docker.com/products/overview) and run it. 2. Run `tools/sync_test_server/start_server.sh`: ```sh cd tools/sync_test_server ./start_server.sh ``` This command will not complete until the server has stopped. 3. Run instrumentation tests In a new terminal window, run: ```sh cd realm ./gradlew connectedObjectServerDebugAndroidTest ``` Note that if using VirtualBox (Genymotion), the network needs to be bridged for the tests to work. This is done in `VirtualBox > Network`. Set "Adapter 2" to "Bridged Adapter". These tests may take as much as half an hour to complete. ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md) for more details! This project adheres to the [MongoDB Code of Conduct](https://www.mongodb.com/community-code-of-conduct). By participating, you are expected to uphold this code. Please report unacceptable behavior to [community-conduct@mongodb.com](mailto:community-conduct@mongodb.com). The directory `realm/config/studio` contains lint and style files recommended for project code. Import them from Android Studio with Android Studio > Preferences... > Code Style > Manage... > Import, or Android Studio > Preferences... > Inspections > Manage... > Import. Once imported select the style/lint in the drop-down to the left of the Manage... button. ## License Realm Java is published under the Apache 2.0 license. Realm Core is also published under the Apache 2.0 license and is available [here](https://github.com/realm/realm-core). ## Feedback **_If you use Realm and are happy with it, all we ask is that you, please consider sending out a tweet mentioning [@realm](http://twitter.com/realm) to share your thoughts!_** **_And if you don't like it, please let us know what you would like improved, so we can fix it!_** ================================================ FILE: SUPPORT.md ================================================ # Support The Realm team is here to help you with your Realm-related issues! ## Documentation Before asking questions, please familiarize yourself with our [Java](https://www.mongodb.com/docs/atlas/device-sdks/sdk/java/) documentation. We also have a number of [Troubleshooting Notes](https://www.mongodb.com/docs/atlas/device-sdks/sdk/java/troubleshooting/) which cover various topics that may be of interest. ## Stack Overflow If you have questions about configuring or using Realm you can ask them on Stack Overflow. We continually monitor the [`realm` tag](https://stackoverflow.com/tags/realm). Please also tag your question with `java`, `android`, or other tags as appropriate. When asking questions on Stack Overflow, please keep in mind Stack Overflow's [question guidelines](https://stackoverflow.com/help/how-to-ask), and please use their search functionality to see if your question has been asked before. ## GitHub Issues If you are running into issues with Realm, including potential bugs or feature requests, we encourage you to file an issue on our [GitHub issue tracker](https://github.com/realm/realm-java/issues). Please check out our [Contribution Guidelines](CONTRIBUTING.md) for information on how to properly file an issue. We greatly appreciate demonstration projects that we can run for ourselves in order to see issues or potential bugs; we prioritize clearly-written tickets that include reproduction cases. You may attach these to the ticket; let us know if you need to share them confidentially, and we’ll provide instructions on how to do so. ================================================ FILE: build.gradle ================================================ buildscript { def properties = new Properties() properties.load(new FileInputStream("${projectDir}/dependencies.list")) repositories { jcenter() maven { url "https://plugins.gradle.org/m2/" } } } def currentVersion = file("${projectDir}/version.txt").text.trim() // Find property in either System environment or Gradle properties. // If set in both places, Gradle properties win. def getPropertyValueOrThrow(String propertyName) { def value = System.getenv(propertyName) if (project.hasProperty(propertyName)) { value = project.getProperty(propertyName) } if (value == null || value.trim().isEmpty()) { throw new GradleException("Could not find '$propertyName'. " + "Most be provided as either environment variable or " + "a Gradle property.") } return value } // Shared configuration that copies relevant properties from the root level and parse them on to // child projects. def copyProperties = { if (project.hasProperty('buildTargetABIs')) { // Valid options: armeabi-v7a, arm64-v8a, x86, x86_64 startParameter.projectProperties += [buildTargetABIs: project.getProperty('buildTargetABIs')] } if (project.hasProperty('coreSourcePath')) { def absolutePath = file(project.getProperty('coreSourcePath')).absolutePath startParameter.projectProperties += [coreSourcePath: absolutePath] } if (project.hasProperty('s3cfg')) { startParameter.projectProperties += [s3cfg: project.getProperty('s3cfg')] } if (project.hasProperty('enableLTO')) { startParameter.projectProperties += [enableLTO: project.getProperty('enableLTO')] } if (project.hasProperty('buildCore')) { startParameter.projectProperties += [buildCore: project.getProperty('buildCore')] } if (project.hasProperty('signBuild')) { startParameter.projectProperties += [signBuild: project.getProperty('signBuild')] } if (project.hasProperty('signPassword')) { startParameter.projectProperties += [signPassword: project.getProperty('signPassword')] } if (project.hasProperty('signSecretRingFile')) { startParameter.projectProperties += [signSecretRingFile: project.getProperty('signSecretRingFile')] } if (project.hasProperty('ossrhUsername')) { startParameter.projectProperties += [ossrhUsername: project.getProperty('ossrhUsername')] } if (project.hasProperty('ossrhPassword')) { startParameter.projectProperties += [ossrhPassword: project.getProperty('ossrhPassword')] } } task assembleAnnotations(type:GradleBuild) { group = 'Build' description = 'Assemble the Realm annotations' buildFile = file('realm-annotations/build.gradle') tasks = ['assemble'] } task installAnnotations(type:GradleBuild) { group = 'Install' description = 'Install the jar realm-annotations into mavenLocal()' buildFile = file('realm-annotations/build.gradle') tasks = ['publishToMavenLocal'] } task assembleTransformer(type:GradleBuild) { group = 'Build' description = 'Assemble the Realm transformer' dependsOn installAnnotations buildFile = file('realm-transformer/build.gradle') tasks = ['assemble'] } task installTransformer(type:GradleBuild) { group = 'Install' description = 'Install the jar realm-transformer into mavenLocal()' dependsOn installAnnotations buildFile = file('realm-transformer/build.gradle') tasks = ['publishToMavenLocal'] } task installBuildTransformer(type:GradleBuild) { group = 'Install' description = 'Install the jar realm-library-build-transformer into mavenLocal()' buildFile = file('library-build-transformer/build.gradle') tasks = ['publishToMavenLocal'] } task assembleRealm(type:GradleBuild) { group = 'Build' description = 'Assemble the Realm project' dependsOn installAnnotations dependsOn installTransformer dependsOn installBuildTransformer buildFile = file('realm/build.gradle') tasks = ['assemble', 'javadocJar', 'sourcesJar'] configure copyProperties } task checkExamples(type:GradleBuild) { group = 'Test' description = 'Run the JVM tests and checks the examples' buildFile = file('examples/build.gradle') tasks = ['check'] configure copyProperties } task checkRealm(type:GradleBuild) { group = 'Test' description = 'Run the JVM tests and checks Realm project' buildFile = file('realm/build.gradle') tasks = ['check'] configure copyProperties } task check { group = 'Test' description = 'Run the JVM tests and checks in the realm and examples projects' dependsOn checkRealm dependsOn checkExamples } task assembleUnitTests(type:GradleBuild) { group = 'Build' description = 'Assemble Android unit tests of the Realm project' dependsOn installTransformer buildFile = file('realm/build.gradle') tasks = ['assembleAndroidTest'] configure copyProperties } task connectedUnitTests(type:GradleBuild) { group = 'Test' description = 'Run the Android unit tests of the Realm project' dependsOn installTransformer buildFile = file('realm/build.gradle') tasks = ['connectedAndroidTest'] configure copyProperties } task assembleBenchmarks(type:GradleBuild) { group = 'Build' description = 'Assemble benchmark tests for the library ' dependsOn installTransformer buildFile = file('library-benchmarks/build.gradle') tasks = ['assembleAndroidTest'] configure copyProperties } task connectedBenchmarks(type:GradleBuild) { group = 'Test' description = 'Run all the benchmark tests for the library ' dependsOn installTransformer buildFile = file('library-benchmarks/build.gradle') tasks = ['connectedAndroidTest'] configure copyProperties } task installRealm(type:GradleBuild) { group = 'Install' description = 'Install the artifacts of Realm libraries into mavenLocal()' dependsOn installTransformer dependsOn installBuildTransformer buildFile = file('realm/build.gradle') tasks = ['publishToMavenLocal'] configure copyProperties } task assembleGradlePlugin(type:GradleBuild) { group = 'Build' description = 'Assemble the Realm Gradle plugin' dependsOn installRealm dependsOn installTransformer buildFile = file('gradle-plugin/build.gradle') tasks = ['assemble'] } task installGradlePlugin(type:GradleBuild) { description = 'Install the Realm Gradle plugin into mavenLocal()' group = 'Install' dependsOn installRealm dependsOn installTransformer buildFile = file('gradle-plugin/build.gradle') tasks = ['publishToMavenLocal'] } task installRealmJava(type:Task) { dependsOn installGradlePlugin dependsOn installRealm group = 'Install' description = 'Install the Realm library and Gradle plugin into mavenLocal()' } task assembleExamples(type:GradleBuild) { dependsOn installGradlePlugin dependsOn installRealm group = 'Build' description = 'Assemble the Realm examples' buildFile = file('examples/build.gradle') tasks = ['assemble'] } task monkeyExamples(type:GradleBuild) { dependsOn installGradlePlugin dependsOn installRealm group = 'Build' description = 'Run the monkey tests on the Realm examples' buildFile = file('examples/build.gradle') tasks = ['monkeyRelease'] } task javadoc(type:GradleBuild) { description = 'Generate the Javadoc Jar for the Realm project' group = 'Docs' buildFile = file('realm/build.gradle') tasks = ['javadocJar'] configure copyProperties } task uploadJavadoc { group = 'Release' description = 'Upload Java and Kotlin docs to S3' dependsOn javadoc doLast { def awsAccessKey = getPropertyValueOrThrow("SDK_DOCS_AWS_ACCESS_KEY") def awsSecretKey = getPropertyValueOrThrow("SDK_DOCS_AWS_SECRET_KEY") // Upload two copies, to 'latest' and a versioned folder for posterity. // Symlinks would have been safer and faster, but this is not supported by S3. [ "${currentVersion}", "latest"].forEach { version -> exec { commandLine 's3cmd', 'put', '--recursive', '--acl-public', "--access_key=${awsAccessKey}", "--secret_key=${awsSecretKey}", 'realm/realm-library/build/docs/javadoc/', "s3://realm-sdks/realm-sdks/java/${version}/" } // The stylesheet is being uploaded with the wrong Content-Type header, which causes the stylesheet to not be applied in some browsers. // So we need to modify the stylesheet after it has been uploaded. exec { commandLine 's3cmd', 'modify', "--access_key=${awsAccessKey}", "--secret_key=${awsSecretKey}", "--debug", '--add-header=Content-Type: text/css', "s3://realm-sdks/realm-sdks/java/${version}/stylesheet.css" } // Upload Kotlin extension docs to a subdirectory of the Javadoc. This should not conflict with the Javadoc folder layout. exec { commandLine 's3cmd', 'put', '--recursive', '--acl-public', "--access_key=${awsAccessKey}", "--secret_key=${awsSecretKey}", 'realm/kotlin-extensions/build/dokka/', "s3://realm-sdks/realm-sdks/java/${version}/kotlin-extensions/" } } } } task sourcesJar(type:GradleBuild) { description = 'Generate the sources Jar for the Realm project' group = 'Docs' buildFile = file('realm/build.gradle') tasks = ['sourcesJar'] configure copyProperties } task assemble { group 'Build' description = 'Build Realm, the Gradle plugin and the examples' dependsOn assembleExamples } task distributionJniUnstrippedPackage(type:Zip) { description = 'Generate native libs package with debug symbols' group = 'Artifact' archiveName = "realm-java-jni-libs-unstripped-${currentVersion}.zip" destinationDir = file("${buildDir}/outputs/distribution") from("realm/realm-library/build/outputs/jniLibs-unstripped") { include '**/*.so' } } task cleanRealm(type:GradleBuild) { description = 'Clean the Realm project' group = 'Clean' buildFile = file('realm/build.gradle') tasks = ['clean'] configure copyProperties } task cleanGradlePlugin(type:GradleBuild) { description = 'Clean the Realm Gradle plugin project' group = 'Clean' buildFile = file('gradle-plugin/build.gradle') tasks = ['clean'] } task cleanExamples(type:GradleBuild) { description = 'Clean the Realm examples' group = 'Clean' buildFile = file('examples/build.gradle') tasks = ['clean'] } task cleanLocalMavenRepos(type:Delete) { description = 'Remove any Realm artifacts from the local Maven repositories' group = 'Clean' delete "${System.env.HOME}/.m2/repository/io/realm" } task clean { description = 'Perform all the other clean tasks' group = 'Clean' cleanLocalMavenRepos.dependsOn cleanRealm cleanLocalMavenRepos.dependsOn cleanGradlePlugin cleanLocalMavenRepos.dependsOn cleanExamples dependsOn cleanLocalMavenRepos } task manualClean { description = 'Clean build files without using clean tasks defined in sub projects' group = 'Clean' doLast { // clean 'build' directories exec { workingDir "${rootDir}" commandLine 'find', '.', '-type', 'd', '-name', 'build', '-print', '-exec', 'rm', '-rf', '{}', ';', '-prune' } // clean '.externalNativeBuild' directories exec { workingDir "${rootDir}" commandLine 'find', '.', '-type', 'd', '-name', '.externalNativeBuild', '-print', '-exec', 'rm', '-rf', '{}', ';', '-prune' } // clean '.gradle' directories except one in the root exec { workingDir "${rootDir}" commandLine 'find', '.', '-mindepth', '2', '-type', 'd', '-name', '.gradle', '-print', '-exec', 'rm', '-rf', '{}', ';', '-prune' } // clean ${System.env.HOME}/.m2/repository/io/realm exec { workingDir "${rootDir}" commandLine 'sh', '-c', "echo \"${System.env.HOME}/.m2/repository/io/realm\" && rm -rf \"${System.env.HOME}/.m2/repository/io/realm\"" } } } task uploadDistributionPackage { group = 'Release' description = 'Upload the distribution package to S3' dependsOn distributionJniUnstrippedPackage def s3AccessKey = "${ -> getPropertyValueOrThrow('REALM_S3_ACCESS_KEY')}" def s3SecretKey = "${ -> getPropertyValueOrThrow('REALM_S3_SECRET_KEY')}" doLast { // Check that zip file exists. Creating the zip file will silently fail if no files exists, so check here. def zipFile = file("${buildDir}/outputs/distribution/realm-java-jni-libs-unstripped-${currentVersion}.zip") if (!zipFile.exists()) { throw new GradleException("Could not locate unstripped binary zip file in: ${zipFile.getPath()}") } exec { commandLine 's3cmd', "--access_key=${s3AccessKey}", "--secret_key=${s3SecretKey}", 'put', zipFile.getAbsolutePath(), 's3://static.realm.io/downloads/java/' } } } task uploadUpdateVersion(type: Exec) { group = 'Release' description = 'Update the file on S3 containing the latest version' def s3AccessKey = "${ -> getPropertyValueOrThrow('REALM_S3_ACCESS_KEY')}" def s3SecretKey = "${ -> getPropertyValueOrThrow('REALM_S3_SECRET_KEY')}" commandLine 's3cmd', "--access_key=${s3AccessKey}", "--secret_key=${s3SecretKey}", 'put', "${rootDir}/version.txt", 's3://static.realm.io/update/java' } task distribute { group = 'Release' description = 'Distribute release artifacts to S3' dependsOn uploadDistributionPackage dependsOn uploadUpdateVersion } task mavenCentralRealm(type: GradleBuild) { description = 'Publish the Realm AAR and AP to Maven Central' group = 'Publishing' buildFile = file('realm/build.gradle') tasks = ['publishToSonatype'] startParameter.projectProperties = gradle.startParameter.projectProperties configure copyProperties } task mavenCentralAnnotations(type: GradleBuild) { description = 'Publish the Realm Annotations to Maven Central' group = 'Publishing' buildFile = file('realm-annotations/build.gradle') tasks = ['publishToSonatype'] startParameter.projectProperties = gradle.startParameter.projectProperties configure copyProperties } task mavenCentralGradlePlugin(type: GradleBuild) { description = 'Publish the Realm Gradle Plugin to Maven Central' group = 'Publishing' buildFile = file('gradle-plugin/build.gradle') tasks = ['publishToSonatype'] startParameter.projectProperties = gradle.startParameter.projectProperties configure copyProperties } task mavenCentralTransformer(type: GradleBuild) { description = 'Publish the Realm Transformer to Maven Central' group = 'Publishing' buildFile = file('realm-transformer/build.gradle') tasks = ['publishToSonatype'] startParameter.projectProperties = gradle.startParameter.projectProperties configure copyProperties } task mavenCentralUpload { description = 'Publish all the Realm artifacts to Maven Central' group = 'Publishing' dependsOn mavenCentralRealm dependsOn mavenCentralAnnotations dependsOn mavenCentralGradlePlugin dependsOn mavenCentralTransformer } ================================================ FILE: dependencies.list ================================================ # Realm Core release used by Realm Java # https://github.com/realm/realm-core/releases REALM_CORE=13.26.0 # Version of MongoDB Realm used by integration tests # See https://github.com/realm/ci/packages/147854 for available versions MONGODB_REALM_SERVER=2023-05-31 # `BAAS` and `BAAS-UI` projects commit hashes matching MONGODB_REALM_SERVER image version # note that the MONGODB_REALM_SERVER image is a nightly build, find the matching commits # for that date within the following repositories: # https://github.com/10gen/baas/ # https://github.com/10gen/baas-ui/ REALM_BAAS_GIT_HASH=1de3337309b9a89094f739efaa69afa2dbc2daa9 REALM_BAAS_UI_GIT_HASH=9c4ef71f69776651cf0110052ce760920ac8c7da # Common Android settings across projects GRADLE_BUILD_TOOLS=7.4.0 ANDROID_BUILD_TOOLS=30.0.3 KOTLIN=1.6.21 KOTLIN_COROUTINES=1.6.0 # Common classpath dependencies gradle=7.5 ndkVersion=23.1.7779620 BUILD_INFO_EXTRACTOR_GRADLE=4.23.4 GRADLE_NEXUS_PLUGIN=1.0.0 CMAKE=3.27.7 # Bson dependency version BSON_DEPENDENCY=3.12.1 # RxJava dependency version RXJAVA_DEPENDENCY=2.1.5 RXANDROID_DEPENDENCY=2.1.1 ================================================ FILE: docs/README.md ================================================ # Realm SDK for Java Use the Realm SDK for Java to develop Android apps in Java or Kotlin. To develop multiplatform apps using Kotlin Multiplatform (KMP), refer to the Kotlin SDK. ## SDK in Maintenance Mode This SDK is in best-effort maintenance mode and **no longer receives new development or non-critical bug fixes**. To develop your app with new features, use the Kotlin SDK. You can use the Java SDK with the Kotlin SDK. ## Develop Apps with the SDK Use the SDK's open-source database - Realm - as an object store on the device. ### Install the Java SDK Use the Gradle build system to install the Java SDK in your project. ### Define an Object Schema Use Java or Kotlin to idiomatically define an object schema. ### Open a Database The SDK's database - Realm - stores objects in files on your device. Or you can open an in-memory database which does not create a file. To get started reading and writing data, configure and open a database. ### Read and Write Data Create, read, update, and delete objects from the database. Use Android-native queries to filter data. ### React to Changes Live objects mean that your data is always up-to-date. You can register a notification handler to watch for changes and perform some logic, such as updating your UI. ## Examples See the [examples/](..examples/) directory. ================================================ FILE: docs/guides/adapters.md ================================================ # Display Collections - Java SDK Android apps often populate the UI using [RecyclerView](https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView.html) or [ListView](https://developer.android.com/reference/android/widget/ListView) components. Realm offers **adapters** to display realm object collections. These collections implement the `OrderedRealmCollections` interface. RealmResults and RealmList are examples of these adaptors. With these adapters, UI components update when your app changes Realm objects. ## Install Adapters Add these dependencies to your application level `build.gradle` file: ```gradle dependencies { implementation 'io.realm:android-adapters:4.0.0' implementation 'androidx.recyclerview:recyclerview:1.1.0' } ``` Realm hosts these adapters on the [JCenter](https://mvnrepository.com/repos/jcenter) artifact repository. To use `jcenter` in your Android app, add it to your project-level `build.gradle` file: ```gradle buildscript { repositories { jcenter() } } allprojects { repositories { jcenter() } } ``` > Seealso: > Source code: [realm/realm-android-adapters](https://github.com/realm/realm-android-adapters) on GitHub. > ## Example Models The examples on this page use a Realm object named `Item`. This class contains a string named "name" and an identifier number named "id": #### Java ```java import io.realm.RealmObject; public class Item extends RealmObject { int id; String name; public Item() {} public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } ``` #### Kotlin ```kotlin import io.realm.RealmObject open class Item(var id: Int = 0, var name: String? = null): RealmObject() ``` ## Display Collections in a ListView Display Realm objects in a [ListView](https://developer.android.com/reference/android/widget/ListView) by extending [RealmBaseAdapter](https://github.com/realm/realm-android-adapters/blob/master/adapters/src/main/java/io/realm/RealmBaseAdapter.java). The adapter uses the `ListAdapter` interface. Implementation works like any `ListAdapter`. This provides support for automatically-updating Realm objects. Subclass `RealmBaseAdapter` to display Item objects in a `ListView`: #### Java ```java import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.ListAdapter; import android.widget.TextView; import com.mongodb.realm.examples.model.java.Item; import io.realm.OrderedRealmCollection; import io.realm.RealmBaseAdapter; class ExampleListAdapter extends RealmBaseAdapter implements ListAdapter { String TAG = "REALM_LIST_ADAPTER"; ExampleListAdapter(OrderedRealmCollection realmResults) { super(realmResults); } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder; if (convertView == null) { Log.i(TAG, "Creating view holder"); // create a top-level layout for our item views LinearLayout layout = new LinearLayout(parent.getContext()); layout.setLayoutParams( new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); // create a text view to display item names TextView titleView = new TextView(parent.getContext()); titleView.setLayoutParams( new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); // attach the text view to the item view layout layout.addView(titleView); convertView = layout; viewHolder = new ViewHolder(titleView); convertView.setTag(viewHolder); } else { viewHolder = (ViewHolder) convertView.getTag(); } // as long as we if (adapterData != null) { final Item item = adapterData.get(position); viewHolder.title.setText(item.getName()); Log.i(TAG, "Populated view holder with data: " + item.getName()); } else { Log.e(TAG, "No data in adapter! Failed to populate view holder."); } return convertView; } private static class ViewHolder { TextView title; public ViewHolder(TextView textView) { title = textView; } } } ``` To display list data in an activity, instantiate a `ListView`. Then, attach an `ExampleListAdapter`: ```java // instantiate a ListView programmatically ListView listView = new ListView(activity.getApplicationContext()); listView.setLayoutParams( new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); // create an adapter with a RealmResults collection // and attach it to the ListView ExampleListAdapter adapter = new ExampleListAdapter( realm.where(Item.class).findAll()); listView.setAdapter(adapter); ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); activity.addContentView(listView, layoutParams); ``` #### Kotlin ```kotlin import android.util.Log import android.view.View import android.view.ViewGroup import android.widget.LinearLayout import android.widget.ListAdapter import android.widget.TextView import com.mongodb.realm.examples.model.kotlin.Item import io.realm.OrderedRealmCollection import io.realm.RealmBaseAdapter internal class ExampleListAdapter(realmResults: OrderedRealmCollection?) : RealmBaseAdapter(realmResults), ListAdapter { var TAG = "REALM_LIST_ADAPTER" override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { var convertView = convertView val viewHolder: ViewHolder if (convertView == null) { Log.i(TAG, "Creating view holder") // create a top-level layout for our item views val layout = LinearLayout(parent.context) layout.layoutParams = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) // create a text view to display item names val titleView = TextView(parent.context) titleView.layoutParams = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) // attach the text view to the item view layout layout.addView(titleView) convertView = layout viewHolder = ViewHolder(titleView) convertView.tag = viewHolder } else { viewHolder = convertView.tag as ViewHolder } // as long as we if (adapterData != null) { val item = adapterData!![position]!! viewHolder.title.text = item.name Log.i(TAG, "Populated view holder with data: ${item.name}") } else { Log.e(TAG, "No data in adapter! Failed to populate view holder.") } return convertView } private class ViewHolder(var title: TextView) } ``` To display list data in an activity, instantiate a `ListView`. Then, attach an `ExampleListAdapter`: ```kotlin // instantiate a ListView programmatically val listView = ListView(activity!!.applicationContext) listView.layoutParams = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) // create an adapter with a RealmResults collection // and attach it to the ListView val adapter = ExampleListAdapter(realm.where(Item::class.java).findAll()) listView.adapter = adapter val layoutParams = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) activity!!.addContentView(listView, layoutParams) ``` ## Display Collections in a RecyclerView Display Realm objects in a [RecyclerView](https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView.html) by extending [RealmRecyclerViewAdapter](https://github.com/realm/realm-android-adapters/blob/master/adapters/src/main/java/io/realm/RealmRecyclerViewAdapter.java). The adapter extends `RecyclerView.Adapter`. Implementation works like any `RecyclerView` adapter. This provides support for automatically-updating Realm objects. Subclass `RealmRecyclerViewAdapter` to display Item objects in a `RecyclerView`: #### Java ```java import android.util.Log; import android.view.ViewGroup; import android.widget.TextView; import androidx.recyclerview.widget.RecyclerView; import com.mongodb.realm.examples.model.java.Item; import io.realm.OrderedRealmCollection; import io.realm.RealmRecyclerViewAdapter; /* * ExampleRecyclerViewAdapter: extends the Realm-provided * RealmRecyclerViewAdapter to provide data * for a RecyclerView to display * Realm objects on screen to a user. */ class ExampleRecyclerViewAdapter extends RealmRecyclerViewAdapter { String TAG = "REALM_RECYCLER_ADAPTER"; ExampleRecyclerViewAdapter(OrderedRealmCollection data) { super(data, true); Log.i(TAG, "Created RealmRecyclerViewAdapter for " + getData().size() + " items."); } @Override public ExampleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { Log.i(TAG, "Creating view holder"); TextView textView = new TextView(parent.getContext()); textView.setLayoutParams( new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); return new ExampleViewHolder(textView); } @Override public void onBindViewHolder(ExampleViewHolder holder, int position) { final Item obj = getItem(position); Log.i(TAG, "Binding view holder: " + obj.getName()); holder.data = obj; holder.title.setText(obj.getName()); } @Override public long getItemId(int index) { return getItem(index).getId(); } class ExampleViewHolder extends RecyclerView.ViewHolder { TextView title; public Item data; ExampleViewHolder(TextView view) { super(view); title = view; } } } ``` To display list data in an activity, instantiate a `RecyclerView`. Then, attach an `ExampleRecyclerViewAdapter`: ```java // instantiate a RecyclerView programmatically RecyclerView recyclerView = new RecyclerView(activity.getApplicationContext()); recyclerView.setLayoutManager( new LinearLayoutManager(activity.getApplicationContext())); recyclerView.setHasFixedSize(true); recyclerView.addItemDecoration(new DividerItemDecoration( activity.getApplicationContext(), DividerItemDecoration.VERTICAL)); // create an adapter with a RealmResults collection // and attach it to the RecyclerView ExampleRecyclerViewAdapter adapter = new ExampleRecyclerViewAdapter( realm.where(Item.class).findAll()); recyclerView.setAdapter(adapter); ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); activity.addContentView(recyclerView, layoutParams); ``` #### Kotlin ```kotlin import android.util.Log import android.view.ViewGroup import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import com.mongodb.realm.examples.model.kotlin.Item import io.realm.OrderedRealmCollection import io.realm.RealmRecyclerViewAdapter /* * ExampleRecyclerViewAdapter: extends the Realm-provided * RealmRecyclerViewAdapter to provide data * for a RecyclerView to display * Realm objects on screen to a user. */ internal class ExampleRecyclerViewAdapter(data: OrderedRealmCollection?) : RealmRecyclerViewAdapter(data, true) { var TAG = "REALM_RECYCLER_ADAPTER" override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ExampleViewHolder { Log.i(TAG, "Creating view holder") val textView = TextView(parent.context) textView.layoutParams = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ) return ExampleViewHolder(textView) } override fun onBindViewHolder(holder: ExampleViewHolder, position: Int) { val obj = getItem(position) Log.i(TAG, "Binding view holder: ${obj!!.name}") holder.data = obj holder.title.text = obj.name } override fun getItemId(index: Int): Long { return getItem(index)!!.id.toLong() } internal inner class ExampleViewHolder(var title: TextView) : RecyclerView.ViewHolder(title) { var data: Item? = null } init { Log.i(TAG, "Created RealmRecyclerViewAdapter for ${getData()!!.size} items.") } } ``` To display list data in an activity, instantiate a `RecyclerView`. Then, attach an `ExampleRecyclerViewAdapter`: ```kotlin // instantiate a RecyclerView programmatically val recyclerView = RecyclerView(activity!!.applicationContext) recyclerView.layoutManager = LinearLayoutManager(activity!!.applicationContext) recyclerView.setHasFixedSize(true) recyclerView.addItemDecoration( DividerItemDecoration(activity!!.applicationContext, DividerItemDecoration.VERTICAL)) // create an adapter with a RealmResults collection // and attach it to the RecyclerView val adapter = ExampleRecyclerViewAdapter(realm.where(Item::class.java).findAll()) recyclerView.adapter = adapter val layoutParams = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) activity!!.addContentView(recyclerView, layoutParams) ``` ================================================ FILE: docs/guides/async-api.md ================================================ # Asynchronous API - Java SDK The Java SDK lets you access network and disk resources in two ways: **synchronously** and **asynchronously**. While synchronous, or "sync", requests block execution until the request returns success or failure, asynchronous, or "async", requests assign a callback and proceed execution to the next line of code. When the request returns, the callback runs to process results. In the callback, you can check if the request executed successfully and either access the returned results or the returned error. ## Asynchronous Calls Asynchronous API requests in the SDK end with the suffix "Async". There are several different ways an asynchronous request can behave, depending on which part of the SDK you're using. ### Realm.Callback Asynchronous calls to open a realm, use a final parameter of type `Realm.Callback`. To retrieve returned values after the request completes, implement the `onSuccess()` method in the callback object passed as the final parameter to these asynchronous methods. You should also implement the `onError()` method to handle request failures, but it is not required. #### Java ```java Realm.getInstanceAsync(config, new Realm.Callback() { @Override public void onSuccess(@NotNull Realm realm) { Log.v("EXAMPLE", "Successfully fetched realm instance."); } public void onError(Exception e) { Log.e("EXAMPLE", "Failed to get realm instance: " + e); } }); ``` #### Kotlin ```kotlin Realm.getInstanceAsync(config, object : Realm.Callback() { override fun onSuccess(realm: Realm) { Log.v("EXAMPLE", "Successfully fetched realm instance.") } fun onError(e: java.lang.Exception) { Log.e("EXAMPLE", "Failed to get realm instance: $e") } }) ``` ### RealmAsyncTask Asynchronous calls to execute transactions on a realm return an instance of `RealmAsyncTask`. You can optionally specify an error handler or a success notification for `RealmAsyncTask` by passing additional parameters to the asynchronous call. Additionally, you use the `cancel()` method to stop a transaction from completing. The lambda function passed to a `RealmAsyncTask` contains the write operations to include in the transaction. #### Java ```java // transaction logic, success notification, error handler all via lambdas realm.executeTransactionAsync(transactionRealm -> { Item item = transactionRealm.createObject(Item.class); }, () -> { Log.v("EXAMPLE", "Successfully completed the transaction"); }, error -> { Log.e("EXAMPLE", "Failed the transaction: " + error); }); // using class instances for transaction, success, error realm.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm transactionRealm) { Item item = transactionRealm.createObject(Item.class); } }, new Realm.Transaction.OnSuccess() { @Override public void onSuccess() { Log.v("EXAMPLE", "Successfully completed the transaction"); } }, new Realm.Transaction.OnError() { @Override public void onError(Throwable error) { Log.e("EXAMPLE", "Failed the transaction: " + error); } }); ``` #### Kotlin ```kotlin // using class instances for transaction, success, error realm.executeTransactionAsync(Realm.Transaction { transactionRealm -> val item: Item = transactionRealm.createObject() }, Realm.Transaction.OnSuccess { Log.v("EXAMPLE", "Successfully completed the transaction") }, Realm.Transaction.OnError { error -> Log.e("EXAMPLE", "Failed the transaction: $error") }) // transaction logic, success notification, error handler all via lambdas realm.executeTransactionAsync( { transactionRealm -> val item = transactionRealm.createObject() }, { Log.v("EXAMPLE", "Successfully completed the transaction") }, { error -> Log.e("EXAMPLE", "Failed the transaction: $error") }) ``` ### RealmResults Asynchronous reads from a realm using `findAllAsync()` immediately return an empty `[RealmResults` instance. The SDK executes the query on a background thread and populates the `RealmResults` instance with the results when the query completes. You can register a listener with `addChangeListener()` to receive a notification when the query completes. #### Java ```java RealmResults items = realm.where(Item.class).findAllAsync(); // length of items is zero when initially returned items.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults items) { Log.v("EXAMPLE", "Completed the query."); // items results now contains all matched objects (more than zero) } }); ``` #### Kotlin ```kotlin val items = realm.where().findAllAsync() // length of items is zero when initially returned items.addChangeListener(RealmChangeListener { Log.v("EXAMPLE", "Completed the query.") // items results now contains all matched objects (more than zero) }) ``` ### RealmResultTask You can cancel `RealmResultTask` instances just like `RealmAsyncTask`. To access the values returned by your query, you can use: - `get()` to block until the operation completes - `getAsync()` to handle the result via an App.Callback instance #### Java ```java Document queryFilter = new Document("type", "perennial"); mongoCollection.findOne(queryFilter).getAsync(task -> { if (task.isSuccess()) { Plant result = task.get(); Log.v("EXAMPLE", "successfully found a document: " + result); } else { Log.e("EXAMPLE", "failed to find document with: ", task.getError()); } }); ``` #### Kotlin ```kotlin val queryFilter = Document("type", "perennial") mongoCollection.findOne(queryFilter) .getAsync { task -> if (task.isSuccess) { val result = task.get() Log.v("EXAMPLE", "successfully found a document: $result") } else { Log.e("EXAMPLE", "failed to find document with: ${task.error}") } } ``` ## Coroutines The SDK provides a set of Kotlin extensions to request asynchronously using coroutines and flows instead of callbacks. You can use these extensions to execute transactions, watch for changes, read, and write. ```kotlin // open a realm asynchronously Realm.getInstanceAsync(config, object : Realm.Callback() { override fun onSuccess(realm: Realm) { Log.v("EXAMPLE", "Successfully fetched realm instance") CoroutineScope(Dispatchers.Main).launch { // asynchronous transaction realm.executeTransactionAwait(Dispatchers.IO) { transactionRealm: Realm -> if (isActive) { val item = transactionRealm.createObject() } } } // asynchronous query val items: Flow> = realm.where().findAllAsync().toFlow() } fun onError(e: Exception) { Log.e("EXAMPLE", "Failed to get realm instance: $e") } }) ``` > Tip: > The `toFlow()` extension method passes frozen Realm objects to safely communicate between threads. > > Seealso: > The SDK also includes Kotlin extensions that make specifying type parameters for Realm reads and writes easier. > ================================================ FILE: docs/guides/crud/create.md ================================================ # CRUD - Create - Java SDK ## About the Examples on this Page The examples on this page use the data model of a project management app that has two Realm object types: `Project` and `Task`. A `Project` has zero or more `Tasks`. See the schema for these two classes, `Project` and `Task`, below: #### Java ```java import org.bson.types.ObjectId; import io.realm.RealmObject; import io.realm.annotations.PrimaryKey; import io.realm.annotations.RealmClass; import io.realm.annotations.Required; public class ProjectTask extends RealmObject { @PrimaryKey public ObjectId _id; @Required public String name; public String assignee; public int progressMinutes; public boolean isComplete; public int priority; @Required public String _partition; } ``` ```java import org.bson.types.ObjectId; import io.realm.RealmList; import io.realm.RealmObject; import io.realm.annotations.PrimaryKey; import io.realm.annotations.RealmClass; import io.realm.annotations.Required; public class Project extends RealmObject { @PrimaryKey public ObjectId _id; @Required public String name; public RealmList tasks = new RealmList<>(); } ``` #### Kotlin ```kotlin import io.realm.RealmObject import io.realm.annotations.PrimaryKey import io.realm.annotations.Required import org.bson.types.ObjectId open class ProjectTask( @PrimaryKey var _id: ObjectId = ObjectId(), @Required var name: String = "", var assignee: String? = null, var progressMinutes: Int = 0, var isComplete: Boolean = false, var priority: Int = 0, var _partition: String = "" ): RealmObject() ``` ```kotlin import io.realm.RealmList import io.realm.RealmObject import io.realm.annotations.PrimaryKey import io.realm.annotations.Required import org.bson.types.ObjectId open class Project( @PrimaryKey var _id: ObjectId = ObjectId(), @Required var name: String = "", var tasks: RealmList = RealmList(), ): RealmObject() ``` ## Create a New Object Use `realm.createObject()` in a transaction to create a persistent instance of a Realm object in a realm. You can then modify the returned object with other field values using accessors and mutators. The following example demonstrates how to create an object with `createObject()`: #### Java ```java realm.executeTransaction(r -> { // Instantiate the class using the factory function. Turtle turtle = r.createObject(Turtle.class, new ObjectId()); // Configure the instance. turtle.setName("Max"); // Create a TurtleEnthusiast with a primary key. ObjectId primaryKeyValue = new ObjectId(); TurtleEnthusiast turtleEnthusiast = r.createObject(TurtleEnthusiast.class, primaryKeyValue); }); ``` #### Kotlin ```kotlin realm.executeTransaction { r: Realm -> // Instantiate the class using the factory function. val turtle = r.createObject(Turtle::class.java, ObjectId()) // Configure the instance. turtle.name = "Max" // Create a TurtleEnthusiast with a primary key. val primaryKeyValue = ObjectId() val turtleEnthusiast = r.createObject( TurtleEnthusiast::class.java, primaryKeyValue ) } ``` You can also insert objects into a realm from JSON. Realm supports creating objects from `String`, [JSONObject](https://developer.android.com/reference/org/json/JSONObject.html), and [InputStream](https://developer.android.com/reference/java/io/InputStream.html) types. Realm ignores any properties present in the JSON that are not defined in the Realm object schema. The following example demonstrates how to create a single object from JSON with `createObjectFromJson()` or multiple objects from JSON with `createAllFromJson()`: #### Java ```java // Insert from a string realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { realm.createObjectFromJson(Frog.class, "{ name: \"Doctor Cucumber\", age: 1, species: \"bullfrog\", owner: \"Wirt\" }"); } }); // Insert multiple items using an InputStream realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { try { InputStream inputStream = new FileInputStream( new File("path_to_file")); realm.createAllFromJson(Frog.class, inputStream); } catch (IOException e) { throw new RuntimeException(e); } } }); ``` #### Kotlin ```kotlin // Insert from a string realm.executeTransaction { realm -> realm.createObjectFromJson( Frog::class.java, "{ name: \"Doctor Cucumber\", age: 1, species: \"bullfrog\", owner: \"Wirt\" }" ) } // Insert multiple items using an InputStream realm.executeTransaction { realm -> try { val inputStream: InputStream = FileInputStream(File("path_to_file")) realm.createAllFromJson(Frog::class.java, inputStream) } catch (e: IOException) { throw RuntimeException(e) } } ``` ================================================ FILE: docs/guides/crud/delete.md ================================================ # CRUD - Delete - Java SDK ## About the Examples on this Page The examples on this page use the data model of a project management app that has two Realm object types: `Project` and `Task`. A `Project` has zero or more `Tasks`. See the schema for these two classes, `Project` and `Task`, below: #### Java ```java import org.bson.types.ObjectId; import io.realm.RealmObject; import io.realm.annotations.PrimaryKey; import io.realm.annotations.RealmClass; import io.realm.annotations.Required; public class ProjectTask extends RealmObject { @PrimaryKey public ObjectId _id; @Required public String name; public String assignee; public int progressMinutes; public boolean isComplete; public int priority; @Required public String _partition; } ``` ```java import org.bson.types.ObjectId; import io.realm.RealmList; import io.realm.RealmObject; import io.realm.annotations.PrimaryKey; import io.realm.annotations.RealmClass; import io.realm.annotations.Required; public class Project extends RealmObject { @PrimaryKey public ObjectId _id; @Required public String name; public RealmList tasks = new RealmList<>(); } ``` #### Kotlin ```kotlin import io.realm.RealmObject import io.realm.annotations.PrimaryKey import io.realm.annotations.Required import org.bson.types.ObjectId open class ProjectTask( @PrimaryKey var _id: ObjectId = ObjectId(), @Required var name: String = "", var assignee: String? = null, var progressMinutes: Int = 0, var isComplete: Boolean = false, var priority: Int = 0, var _partition: String = "" ): RealmObject() ``` ```kotlin import io.realm.RealmList import io.realm.RealmObject import io.realm.annotations.PrimaryKey import io.realm.annotations.Required import org.bson.types.ObjectId open class Project( @PrimaryKey var _id: ObjectId = ObjectId(), @Required var name: String = "", var tasks: RealmList = RealmList(), ): RealmObject() ``` ## Delete an Object To delete an object from a realm, use either the dynamic or static versions of the `deleteFromRealm()` method of a `RealmObject` subclass. The following example shows how to delete one object from its realm with `deleteFromRealm()`: #### Java ```java realm.executeTransaction(r -> { // Get a turtle named "Tony". Turtle tony = r.where(Turtle.class).equalTo("name", "Tony").findFirst(); tony.deleteFromRealm(); // discard the reference tony = null; }); ``` #### Kotlin ```kotlin realm.executeTransaction { r: Realm -> // Get a turtle named "Tony". var tony = r.where(Turtle::class.java) .equalTo("name", "Tony") .findFirst() tony!!.deleteFromRealm() // discard the reference tony = null } ``` > Tip: > The SDK throws an error if you try to use an object after it has been deleted. > ## Delete Multiple Objects To delete an object from a realm, use the `deleteAllFromRealm()` method of the `RealmResults` instance that contains the objects you would like to delete. You can filter the `RealmResults` down to a subset of objects using the `where()` method. The following example demonstrates how to delete a collection from a realm with `deleteAllFromRealm()`: #### Java ```java realm.executeTransaction(r -> { // Find turtles older than 2 years old. RealmResults oldTurtles = r.where(Turtle.class).greaterThan("age", 2).findAll(); oldTurtles.deleteAllFromRealm(); }); ``` #### Kotlin ```kotlin realm.executeTransaction { r: Realm -> // Find turtles older than 2 years old. val oldTurtles = r.where(Turtle::class.java) .greaterThan("age", 2) .findAll() oldTurtles.deleteAllFromRealm() } ``` ## Delete an Object and its Dependent Objects Sometimes, you have dependent objects that you want to delete when you delete the parent object. We call this a **chaining delete**. Realm does not delete the dependent objects for you. If you do not delete the objects yourself, they will remain orphaned in your realm. Whether or not this is a problem depends on your application's needs. Currently, the best way to delete dependent objects is to iterate through the dependencies and delete them before deleting the parent object. The following example demonstrates how to perform a chaining delete by first deleting all of Ali's turtles, then deleting Ali: #### Java ```java realm.executeTransaction(r -> { // Find a turtle enthusiast named "Ali" TurtleEnthusiast ali = r.where(TurtleEnthusiast.class).equalTo("name", "Ali").findFirst(); // Delete all of ali's turtles ali.getTurtles().deleteAllFromRealm(); ali.deleteFromRealm(); }); ``` #### Kotlin ```kotlin realm.executeTransaction { r: Realm -> // Find a turtle enthusiast named "Ali" val ali = r.where(TurtleEnthusiast::class.java) .equalTo("name", "Ali").findFirst() // Delete all of ali's turtles ali!!.turtles!!.deleteAllFromRealm() ali.deleteFromRealm() } ``` ## Delete All Objects of a Specific Type Realm supports deleting all instances of a Realm type from a realm. The following example demonstrates how to delete all Turtle instances from a realm with `delete()`: #### Java ```java realm.executeTransaction(r -> { r.delete(Turtle.class); }); ``` #### Kotlin ```kotlin realm.executeTransaction { r: Realm -> r.delete(Turtle::class.java) } ``` ## Delete All Objects in a Realm It is possible to delete all objects from the realm. This does not affect the schema of the realm. This is useful for quickly clearing out your realm while prototyping. The following example demonstrates how to delete everything from a realm with `deleteAll()`: #### Java ```java realm.executeTransaction(r -> { r.deleteAll(); }); ``` #### Kotlin ```kotlin realm.executeTransaction { r: Realm -> r.deleteAll() } ``` ## Delete an Object Using an Iterator Because realm collections always reflect the latest state, they can appear, disappear, or change while you iterate over a collection. To get a stable collection you can iterate over, you can create a **snapshot** of a collection's data. A snapshot guarantees the order of elements will not change, even if an element is deleted. For an example, refer to Iteration. ================================================ FILE: docs/guides/crud/filter-data.md ================================================ # Filter Data - Java SDK ## Query Engine To filter data in your realm, use the Realm query engine. There are two ways to access the query engine with the Java SDK: - Fluent interface - Realm Query Language ## Fluent Interface The Java SDK uses a [Fluent interface](https://en.wikipedia.org/wiki/Fluent_interface) to construct multi-clause queries that are passed to the query engine. See RealmQuery API for a complete list of available methods. There are several types of operators available to filter a Realm collection. Filters work by **evaluating** an operator expression for every object in the collection being filtered. If the expression resolves to `true`, Realm Database includes the object in the results collection. An **expression** consists of one of the following: - The name of a property of the object currently being evaluated. - An operator and up to two argument expression(s). - A literal string, number, or date. ### About the Examples In This Section The examples in this section use a simple data set for a task list app. The two Realm object types are `Project` and `Task`. A `Task` has a name, assignee's name, and completed flag. There is also an arbitrary number for priority (higher is more important) and a count of minutes spent working on it. A `Project` has zero or more `Tasks`. See the schema for these two classes, `Project` and `Task`, below: #### Java ```java import org.bson.types.ObjectId; import io.realm.RealmObject; import io.realm.annotations.PrimaryKey; import io.realm.annotations.RealmClass; import io.realm.annotations.Required; public class ProjectTask extends RealmObject { @PrimaryKey public ObjectId _id; @Required public String name; public String assignee; public int progressMinutes; public boolean isComplete; public int priority; @Required public String _partition; } ``` ```java import org.bson.types.ObjectId; import io.realm.RealmList; import io.realm.RealmObject; import io.realm.annotations.PrimaryKey; import io.realm.annotations.RealmClass; import io.realm.annotations.Required; public class Project extends RealmObject { @PrimaryKey public ObjectId _id; @Required public String name; public RealmList tasks = new RealmList<>(); } ``` #### Kotlin ```kotlin import io.realm.RealmObject import io.realm.annotations.PrimaryKey import io.realm.annotations.Required import org.bson.types.ObjectId open class ProjectTask( @PrimaryKey var _id: ObjectId = ObjectId(), @Required var name: String = "", var assignee: String? = null, var progressMinutes: Int = 0, var isComplete: Boolean = false, var priority: Int = 0, var _partition: String = "" ): RealmObject() ``` ```kotlin import io.realm.RealmList import io.realm.RealmObject import io.realm.annotations.PrimaryKey import io.realm.annotations.Required import org.bson.types.ObjectId open class Project( @PrimaryKey var _id: ObjectId = ObjectId(), @Required var name: String = "", var tasks: RealmList = RealmList(), ): RealmObject() ``` ### Comparison Operators The most straightforward operation in a search is to compare values. |Operator|Description| | --- | --- | |`between`|Evaluates to `true` if the left-hand numerical or date expression is between or equal to the right-hand range. For dates, this evaluates to `true` if the left-hand date is within the right-hand date range.| |equalTo|Evaluates to `true` if the left-hand expression is equal to the right-hand expression.| |greaterThan|Evaluates to `true` if the left-hand numerical or date expression is greater than the right-hand numerical or date expression. For dates, this evaluates to `true` if the left-hand date is later than the right-hand date.| |greaterThanOrEqualTo|Evaluates to `true` if the left-hand numerical or date expression is greater than or equal to the right-hand numerical or date expression. For dates, this evaluates to `true` if the left-hand date is later than or the same as the right-hand date.| |`in`|Evaluates to `true` if the left-hand expression is in the right-hand list.| |lessThan|Evaluates to `true` if the left-hand numerical or date expression is less than the right-hand numerical or date expression. For dates, this evaluates to `true` if the left-hand date is earlier than the right-hand date.| |lessThanOrEqualTo|Evaluates to `true` if the left-hand numeric expression is less than or equal to the right-hand numeric expression. For dates, this evaluates to `true` if the left-hand date is earlier than or the same as the right-hand date.| |notEqualTo|Evaluates to `true` if the left-hand expression is not equal to the right-hand expression.| > Example: > The following example uses the query engine's comparison operators to: > > - Find high priority tasks by comparing the value of the `priority` property value with a threshold number, above which priority can be considered high. > - Find just-started or short-running tasks by seeing if the `progressMinutes` property falls within a certain range. > - Find unassigned tasks by finding tasks where the `assignee` property is equal to `null`. > - Find tasks assigned to specific teammates Ali or Jamie by seeing if the `assignee` property is in a list of names. > > #### Java > > ```java > RealmQuery tasksQuery = realm.where(ProjectTask.class); > Log.i("EXAMPLE", "High priority tasks: " + tasksQuery.greaterThan("priority", 5).count()); > Log.i("EXAMPLE", "Just-started or short tasks: " + tasksQuery.between("progressMinutes", 1, 10).count()); > Log.i("EXAMPLE", "Unassigned tasks: " + tasksQuery.isNull("assignee").count()); > Log.i("EXAMPLE", "Ali or Jamie's tasks: " + tasksQuery.in("assignee", new String[]{"Ali", "Jamie"}).count()); > > ``` > > > #### Kotlin > > ```kotlin > val tasksQuery = realm.where(ProjectTask::class.java) > Log.i("EXAMPLE", "High priority tasks: " + tasksQuery.greaterThan("priority", 5).count()) > Log.i("EXAMPLE", "Just-started or short tasks: " + tasksQuery.between("progressMinutes", 1, 10).count()) > Log.i("EXAMPLE", "Unassigned tasks: " + tasksQuery.isNull("assignee").count()) > Log.i("EXAMPLE", "Ali or Jamie's tasks: " + tasksQuery.`in`("assignee", arrayOf("Ali", "Jamie")).count()) > > ``` > > ### Logical Operators You can make compound predicates using logical operators. |Operator|Description| | --- | --- | |and|Evaluates to `true` if both left-hand and right-hand expressions are `true`.| |not|Negates the result of the given expression.| |or|Evaluates to `true` if either expression returns `true`.| > Example: > We can use the query language's logical operators to find all of Ali's completed tasks. That is, we find all tasks where the `assignee` property value is equal to 'Ali' AND the `isComplete` property value is `true`: > > #### Java > > ```java > RealmQuery tasksQuery = realm.where(ProjectTask.class); > Log.i("EXAMPLE", "Ali has completed " + > tasksQuery.equalTo("assignee", "Ali").and().equalTo("isComplete", true).findAll().size() + > " tasks."); > > ``` > > > #### Kotlin > > ```kotlin > val tasksQuery = realm.where(ProjectTask::class.java) > Log.i("EXAMPLE", "Ali has completed " + > tasksQuery.equalTo("assignee", "Ali").and() > .equalTo("isComplete", true).findAll().size + " tasks.") > > ``` > > ### String Operators You can compare string values using these string operators. Regex-like wildcards allow more flexibility in search. |Operator|Description| | --- | --- | |beginsWith|Evaluates to `true` if the left-hand string expression begins with the right-hand string expression. This is similar to `contains`, but only matches if the left-hand string expression is found at the beginning of the right-hand string expression.| |`contains`|Evaluates to `true` if the left-hand string expression is found anywhere in the right-hand string expression.| |endsWith|Evaluates to `true` if the left-hand string expression ends with the right-hand string expression. This is similar to `contains`, but only matches if the left-hand string expression is found at the very end of the right-hand string expression.| |like|Evaluates to `true` if the left-hand string expression matches the right-hand string wildcard string expression. A wildcard string expression is a string that uses normal characters with two special wildcard characters: The `*` wildcard matches zero or more of any character The `?` wildcard matches any character. For example, the wildcard string "d?g" matches "dog", "dig", and "dug", but not "ding", "dg", or "a dog".| |equalTo|Evaluates to `true` if the left-hand string is lexicographically equal to the right-hand string.| > Example: > We use the query engine's string operators to find projects with a name starting with the letter 'e' and projects with names that contain 'ie': > > #### Java > > ```java > RealmQuery projectsQuery = realm.where(Project.class); > // Pass Case.INSENSITIVE as the third argument for case insensitivity. > Log.i("EXAMPLE", "Projects that start with 'e': " > + projectsQuery.beginsWith("name", "e", Case.INSENSITIVE).count()); > Log.i("EXAMPLE", "Projects that contain 'ie': " > + projectsQuery.contains("name", "ie").count()); > > ``` > > > #### Kotlin > > ```kotlin > val projectsQuery = realm.where(Project::class.java) > // Pass Case.INSENSITIVE as the third argument for case insensitivity. > Log.i("EXAMPLE", "Projects that start with 'e': " > + projectsQuery.beginsWith("name", "e", Case.INSENSITIVE).count()) > Log.i("EXAMPLE", "Projects that contain 'ie': " > + projectsQuery.contains("name", "ie").count()) > > ``` > > > Note: > Case-insensitive string operators only support the `Latin Basic`, `Latin Supplement`, `Latin Extended A`, and `Latin Extended B (UTF-8 range 0-591)` character sets. Setting the case insensitive flag in queries when using `equalTo`, `notEqualTo`, `contains`, `endsWith`, `beginsWith`, or `like` only works on English locale characters. > ### Aggregate Operators You can apply an aggregate operator to a collection property of a Realm object. Aggregate operators traverse a collection and reduce it to a single value. |Operator|Description| | --- | --- | |average|Evaluates to the average value of a given numerical property across a collection.| |count|Evaluates to the number of objects in the given collection.| |max|Evaluates to the highest value of a given numerical property across a collection.| |min|Evaluates to the lowest value of a given numerical property across a collection.| |sum|Evaluates to the sum of a given numerical property across a collection.| > Example: > We create a couple of filters to show different facets of the data: > > - Projects with average tasks priority above 5. > - Long running projects. > > #### Java > > ```java > RealmQuery tasksQuery = realm.where(ProjectTask.class); > /* > Aggregate operators do not support dot-notation, so you > cannot directly operate on a property of all of the objects > in a collection property. > > You can operate on a numeric property of the top-level > object, however: > */ > Log.i("EXAMPLE", "Tasks average priority: " + tasksQuery.average("priority")); > > ``` > > > #### Kotlin > > ```kotlin > val tasksQuery = realm.where(ProjectTask::class.java) > /* > Aggregate operators do not support dot-notation, so you > cannot directly operate on a property of all of the objects > in a collection property. > > You can operate on a numeric property of the top-level > object, however: > */Log.i("EXAMPLE", "Tasks average priority: " + tasksQuery.average("priority")) > > ``` > > ## Filter, Sort, Limit, Unique, and Chain Queries ### About the Examples in This Section The examples in this section use two Realm object types: `Teacher` and `Student`. See the schema for these two classes below: #### Java ```java import io.realm.RealmList; import io.realm.RealmObject; public class Teacher extends RealmObject { private String name; private Integer numYearsTeaching; private String subject; private RealmList students; public Teacher() {} public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getNumYearsTeaching() { return numYearsTeaching; } public void setNumYearsTeaching(Integer numYearsTeaching) { this.numYearsTeaching = numYearsTeaching; } public String getSubject() { return subject; } public void setSubject(String subject) { this.subject = subject; } public RealmList getStudents() { return students; } public void setStudents(RealmList students) { this.students = students; } } ``` ```java import io.realm.RealmObject; import io.realm.RealmResults; import io.realm.annotations.LinkingObjects; public class Student extends RealmObject { private String name; private Integer year; @LinkingObjects("students") private final RealmResults teacher = null; public Student() {} public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getYear() { return year; } public void setYear(Integer year) { this.year = year; } public RealmResults getTeacher() { return teacher; } } ``` #### Kotlin ```kotlin import io.realm.RealmList import io.realm.RealmObject open class Teacher : RealmObject() { var name: String? = null var numYearsTeaching: Int? = null var subject: String? = null var students: RealmList? = null } ``` ```kotlin import io.realm.RealmObject import io.realm.RealmResults import io.realm.annotations.LinkingObjects open class Student : RealmObject() { var name: String? = null var year: Int? = null @LinkingObjects("students") val teacher: RealmResults? = null } ``` ### Filters You can build filters using the operator methods of the [fluent interface](https://en.wikipedia.org/wiki/Fluent_interface) exposed by the `RealmQuery` class: #### Java ```java // Build the query looking at all teachers: RealmQuery query = realm.where(Teacher.class); // Add query conditions: query.equalTo("name", "Ms. Langtree"); query.or().equalTo("name", "Mrs. Jacobs"); // Execute the query: RealmResults result1 = query.findAll(); // Or alternatively do the same all at once (the "Fluent interface"): RealmResults result2 = realm.where(Teacher.class) .equalTo("name", "Ms. Langtree") .or() .equalTo("name", "Mrs. Jacobs") .findAll(); ``` #### Kotlin ```kotlin // Build the query looking at all teachers: val query = realm.where(Teacher::class.java) // Add query conditions: query.equalTo("name", "Ms. Langtree") query.or().equalTo("name", "Mrs. Jacobs") // Execute the query: val result1 = query.findAll() // Or alternatively do the same all at once (the "Fluent interface"): val result2 = realm.where(Teacher::class.java) .equalTo("name", "Ms. Langtree") .or() .equalTo("name", "Mrs. Jacobs") .findAll() ``` This gives you a new instance of the class `RealmResults`, containing teachers with the name "Ms. Langtree" or "Mrs. Jacobs". `RealmQuery` includes several methods that can execute queries: - `findAll()` blocks until it finds all objects that meet the query conditions - `findAllAsync()` returns immediately and finds all objects that meet the query conditions asynchronously on a background thread - `findFirst()` blocks until it finds the first object that meets the query conditions - `findFirstAsync()` returns immediately and finds the first object that meets the query conditions asynchronously on a background thread Queries return a list of references to the matching Realm objects using the RealmResults type. #### Link Queries When referring to an object property, you can use **dot notation** to refer to child properties of that object. You can refer to the properties of embedded objects and relationships with dot notation. For example, consider a query for all teachers with a student named "Wirt" or "Greg": #### Java ```java // Find all teachers who have students with the names "Wirt" or "Greg" RealmResults result = realm.where(Teacher.class) .equalTo("students.name", "Wirt") .or() .equalTo("students.name", "Greg") .findAll(); ``` #### Kotlin ```kotlin // Find all teachers who have students with the names "Wirt" or "Greg" val result = realm.where(Teacher::class.java) .equalTo("students.name", "Wirt") .or() .equalTo("students.name", "Greg") .findAll() ``` You can even use dot notation to query inverse relationships: #### Java ```java // Find all students who have teachers with the names "Ms. Langtree" or "Mrs. Jacobs" RealmResults result = realm.where(Student.class) .equalTo("teacher.name", "Ms. Langtree") .or() .equalTo("teacher.name", "Mrs. Jacobs") .findAll(); ``` #### Kotlin ```kotlin // Find all students who have teachers with the names "Ms. Langtree" or "Mrs. Jacobs" val result = realm.where(Student::class.java) .equalTo("teacher.name", "Ms. Langtree") .or() .equalTo("teacher.name", "Mrs. Jacobs") .findAll() ``` ### Sort Results > Important: > Realm applies the `distinct()`, `sort()` and `limit()` methods in the order you specify. Depending on the data set this can alter the query result. Generally, you should apply `limit()` last to avoid unintended result sets. > You can define the order of query results using the `sort()` method: #### Java ```java // Find all students in year 7, and sort them by name RealmResults result = realm.where(Student.class) .equalTo("year", 7) .sort("name") .findAll(); // Alternatively, find all students in year 7 RealmResults unsortedResult = realm.where(Student.class) .equalTo("year", 7) .findAll(); // then sort the results set by name RealmResults sortedResult = unsortedResult.sort("name"); ``` #### Kotlin ```kotlin // Find all students in year 7, and sort them by name val result: RealmResults = realm.where(Student::class.java) .equalTo("year", 7L) .sort("name") .findAll() // Alternatively, find all students in year 7 val unsortedResult: RealmResults = realm.where(Student::class.java) .equalTo("year", 7L) .findAll() // then sort the results set by name val sortedResult = unsortedResult.sort("name") ``` Sorts organize results in ascending order by default. To organize results in descending order, pass `Sort.DESCENDING` as a second argument. You can resolve sort order ties between identical property values by passing an array of properties instead of a single property: in the event of a tie, Realm sorts the tied objects by subsequent properties in order. > Note: > Realm uses non-standard sorting for upper and lowercase letters, sorting them together rather than sorting uppercase first. As a result, `'- !"#0&()*,./:;?_+<=>123aAbBcC...xXyYzZ` is the actual sorting order in Realm. Additionally, sorting strings only supports the `Latin Basic`, `Latin Supplement`, `Latin Extended A`, and `Latin Extended B (UTF-8 range 0–591)` character sets. > ### Limit Results You can cap the number of query results to a specific maximum number using the `limit()` method: #### Java ```java // Find all students in year 8, and limit the results collection to 10 items RealmResults result = realm.where(Student.class) .equalTo("year", 8) .limit(10) .findAll(); ``` #### Kotlin ```kotlin // Find all students in year 8, and limit the results collection to 10 items val result: RealmResults = realm.where(Student::class.java) .equalTo("year", 8L) .limit(10) .findAll() ``` Limited result collections automatically update like any other query result. Consequently, objects might drop out of the collection as underlying data changes. > Tip: > Some databases encourage paginating results with limits to avoid reading unnecessary data from disk or using too much memory. > > Since Realm queries are lazy, there is no need to take such measures. Realm only loads objects from query results when they are explicitly accessed. > > Tip: > Collection notifications report objects as deleted when they drop out of the result set. This does not necessarily mean that they have been deleted from the underlying realm, just that they are no longer part of the query result. > ### Unique Results You can reduce query results to unique values for a given field or fields using the `distinct()` method: #### Java ```java // Find all students in year 9, and cap the result collection at 10 items RealmResults result = realm.where(Student.class) .equalTo("year", 9) .distinct("name") .findAll(); ``` #### Kotlin ```kotlin // Find all students in year 9, and cap the result collection at 10 items val result: RealmResults = realm.where(Student::class.java) .equalTo("year", 9L) .distinct("name") .findAll() ``` You can only call `distinct()` on integer, long, short, and `String` fields; other field types will throw an exception. As with sorting, you can specify multiple fields to resolve ties. ### Chain Queries You can apply additional filters to a results collection by calling the `where()` method: #### Java ```java // Find all students in year 9 and resolve the query into a results collection RealmResults result = realm.where(Student.class) .equalTo("year", 9) .findAll(); // filter the students results again by teacher name RealmResults filteredResults = result.where().equalTo("teacher.name", "Ms. Langtree").findAll(); ``` #### Kotlin ```kotlin // Find all students in year 9 and resolve the query into a results collection val result: RealmResults = realm.where(Student::class.java) .equalTo("year", 9L) .findAll() // filter the students results again by teacher name val filteredResults = result.where().equalTo("teacher.name", "Ms. Langtree").findAll() ``` The `where()` method returns a `RealmQuery` that you can resolve into a `RealmResults` using a `find` method. Filtered results can only return objects of the same type as the original results set, but are otherwise able to use any filters. ## Query with Realm Query Language > Version added: 10.4.0 You can also query realms using Realm Query Language, a string-based query language to constrain searches when retrieving objects from a realm. You can use `RealmQuery.rawPredicate()`. For more information about syntax, usage and limitations, refer to the Realm Query Language reference. Realm Query Language can use either the class and property names defined in your Realm Model classes or the internal names defined with `@RealmField`. You can combine raw predicates with other raw predicates or type-safe predicates created with `RealmQuery`: #### Java ```java // Build a RealmQuery based on the Student type RealmQuery query = realm.where(Student.class); // Simple query RealmResults studentsNamedJane = query.rawPredicate("name = 'Jane'").findAll(); // Multiple predicates RealmResults studentsNamedJaneOrJohn = query.rawPredicate("name = 'Jane' OR name = 'John'").findAll(); // Collection queries RealmResults studentsWithTeachers = query.rawPredicate("teacher.@count > 0").findAll(); RealmResults studentsWithSeniorTeachers = query.rawPredicate("ALL teacher.numYearsTeaching > 5").findAll(); // Sub queries RealmResults studentsWithMathTeachersNamedSteven = query.rawPredicate("SUBQUERY(teacher, $teacher, $teacher.subject = 'Mathematics' AND $teacher.name = 'Mr. Stevens').@count > 0").findAll(); // Sort, Distinct, Limit RealmResults students = query.rawPredicate("teacher.@count > 0 SORT(year ASCENDING) DISTINCT(name) LIMIT(5)").findAll(); // Combine two raw predicates RealmResults studentsNamedJaneOrHenry = query.rawPredicate("name = 'Jane'") .rawPredicate("name = 'Henry'").findAll(); // Combine raw predicate with type-safe predicate RealmResults studentsNamedJaneOrHenryAgain = query.rawPredicate("name = 'Jane'") .equalTo("name", "Henry").findAll(); ``` #### Kotlin ```kotlin // Build a RealmQuery based on the Student type val query = realm.where(Student::class.java) // Simple query val studentsNamedJane = query.rawPredicate("name = 'Jane'").findAll() // Multiple predicates val studentsNamedJaneOrJohn = query.rawPredicate("name = 'Jane' OR name = 'John'").findAll() // Collection queries val studentsWithTeachers = query.rawPredicate("teacher.@count > 0").findAll() val studentsWithSeniorTeachers = query.rawPredicate("ALL teacher.numYearsTeaching > 5").findAll() // Sub queries val studentsWithMathTeachersNamedSteven = query.rawPredicate("SUBQUERY(teacher, \$teacher, \$teacher.subject = 'Mathematics' AND \$teacher.name = 'Mr. Stevens').@count > 0") .findAll() // Sort, Distinct, Limit val students = query.rawPredicate("teacher.@count > 0 SORT(year ASCENDING) DISTINCT(name) LIMIT(5)") .findAll() // Combine two raw predicates val studentsNamedJaneOrHenry = query.rawPredicate("name = 'Jane'") .rawPredicate("name = 'Henry'").findAll() // Combine raw predicate with type-safe predicate val studentsNamedJaneOrHenryAgain = query.rawPredicate("name = 'Jane'") .equalTo("name", "Henry").findAll() ``` ================================================ FILE: docs/guides/crud/read.md ================================================ # CRUD - Read - Java SDK ## Read Operations You can read back the data that you have stored in Realm. The standard data access pattern across Realm SDKs is to find, filter, and sort objects, in that order. To get the best performance from Realm as your app grows and your queries become more complex, design your app's data access patterns around a solid understanding of Realm read characteristics. ### Read Characteristics When you design your app's data access patterns around the following three key characteristics of reads in Realm, you can be confident you are reading data as efficiently as possible. ### Results Are Not Copies Results to a query are not copies of your data: modifying the results of a query will modify the data on disk directly. This memory mapping also means that results are **live**: that is, they always reflect the current state on disk. ### Results Are Lazy Realm defers execution of a query until you access the results. You can chain several filter and sort operations without requiring extra work to process the intermediate state. ### References Are Retained One benefit of Realm's object model is that Realm automatically retains all of an object's relationships as direct references, so you can traverse your graph of relationships directly through the results of a query. A **direct reference**, or pointer, allows you to access a related object's properties directly through the reference. Other databases typically copy objects from database storage into application memory when you need to work with them directly. Because application objects contain direct references, you are left with a choice: copy the object referred to by each direct reference out of the database in case it's needed, or just copy the foreign key for each object and query for the object with that key if it's accessed. If you choose to copy referenced objects into application memory, you can use up a lot of resources for objects that are never accessed, but if you choose to only copy the foreign key, referenced object lookups can cause your application to slow down. Realm bypasses all of this using zero-copy live objects. Realm object accessors point directly into database storage using memory mapping, so there is no distinction between the objects in Realm and the results of your query in application memory. Because of this, you can traverse direct references across an entire realm from any query result. ## About the Examples on this Page The examples on this page use the data model of a project management app that has two Realm object types: `Project` and `Task`. A `Project` has zero or more `Tasks`. See the schema for these two classes, `Project` and `Task`, below: #### Java ```java import org.bson.types.ObjectId; import io.realm.RealmObject; import io.realm.annotations.PrimaryKey; import io.realm.annotations.RealmClass; import io.realm.annotations.Required; public class ProjectTask extends RealmObject { @PrimaryKey public ObjectId _id; @Required public String name; public String assignee; public int progressMinutes; public boolean isComplete; public int priority; @Required public String _partition; } ``` ```java import org.bson.types.ObjectId; import io.realm.RealmList; import io.realm.RealmObject; import io.realm.annotations.PrimaryKey; import io.realm.annotations.RealmClass; import io.realm.annotations.Required; public class Project extends RealmObject { @PrimaryKey public ObjectId _id; @Required public String name; public RealmList tasks = new RealmList<>(); } ``` #### Kotlin ```kotlin import io.realm.RealmObject import io.realm.annotations.PrimaryKey import io.realm.annotations.Required import org.bson.types.ObjectId open class ProjectTask( @PrimaryKey var _id: ObjectId = ObjectId(), @Required var name: String = "", var assignee: String? = null, var progressMinutes: Int = 0, var isComplete: Boolean = false, var priority: Int = 0, var _partition: String = "" ): RealmObject() ``` ```kotlin import io.realm.RealmList import io.realm.RealmObject import io.realm.annotations.PrimaryKey import io.realm.annotations.Required import org.bson.types.ObjectId open class Project( @PrimaryKey var _id: ObjectId = ObjectId(), @Required var name: String = "", var tasks: RealmList = RealmList(), ): RealmObject() ``` ## Read from Realm A read from a realm generally consists of the following steps: - Get all objects of a certain type from the realm. - Optionally, filter the results using the query engine. - Optionally, sort the results. All query, filter, and sort operations return a results collection. The results collections are live, meaning they always contain the latest results of the associated query. > Important: > By default, you can only read or write to a realm in your application's UI thread using asynchronous transactions. That is, you can only use `Realm` methods whose name ends with the word `Async` in the main thread of your Android application unless you explicitly allow the use of synchronous methods. > > This restriction exists for the benefit of your application users: performing read and write operations on the UI thread can lead to unresponsive or slow UI interactions, so it's usually best to handle these operations either asynchronously or in a background thread. ### Find a Specific Object by Primary Key To find an object with a specific primary key value, open a realm and query the primary key field for the desired primary key value using the `RealmQuery.equalTo()` method: #### Java ```java ProjectTask task = realm.where(ProjectTask.class).equalTo("_id", PRIMARY_KEY_VALUE.get()).findFirst(); Log.v("EXAMPLE", "Fetched object by primary key: " + task); ``` #### Kotlin ```kotlin val task = realm.where(ProjectTask::class.java) .equalTo("_id", ObjectId.get()).findFirst() Log.v("EXAMPLE", "Fetched object by primary key: $task") ``` ### Query All Objects of a Given Type The first step of any read is to **get all objects** of a certain type in a realm. With this results collection, you can operate on all instances on a type or filter and sort to refine the results. In order to access all instances of `ProjectTask` and `Project`, use the `where()` method to specify a class: #### Java ```java RealmQuery tasksQuery = realm.where(ProjectTask.class); RealmQuery projectsQuery = realm.where(Project.class); ``` #### Kotlin ```kotlin val tasksQuery = realm.where(ProjectTask::class.java) val projectsQuery = realm.where(Project::class.java) ``` ### Filter Queries Based on Object Properties A **filter** selects a subset of results based on the value(s) of one or more object properties. Realm provides a full-featured query engine you can use to define filters. The most common use case is to find objects where a certain property matches a certain value. Additionally, you can compare strings, aggregate over collections of numbers, and use logical operators to build up complex queries. In the following example, we use the query engine's comparison operators to: - Find high priority tasks by comparing the value of the `priority` property value with a threshold number, above which priority can be considered high. - Find just-started or short-running tasks by seeing if the `progressMinutes` property falls within a certain range. - Find unassigned tasks by finding tasks where the `assignee` property is equal to null. - Find tasks assigned to specific teammates Ali or Jamie by seeing if the `assignee` property is in a list of names. #### Java ```java RealmQuery tasksQuery = realm.where(ProjectTask.class); Log.i("EXAMPLE", "High priority tasks: " + tasksQuery.greaterThan("priority", 5).count()); Log.i("EXAMPLE", "Just-started or short tasks: " + tasksQuery.between("progressMinutes", 1, 10).count()); Log.i("EXAMPLE", "Unassigned tasks: " + tasksQuery.isNull("assignee").count()); Log.i("EXAMPLE", "Ali or Jamie's tasks: " + tasksQuery.in("assignee", new String[]{"Ali", "Jamie"}).count()); ``` #### Kotlin ```kotlin val tasksQuery = realm.where(ProjectTask::class.java) Log.i( "EXAMPLE", "High priority tasks: " + tasksQuery.greaterThan( "priority", 5 ).count() ) Log.i( "EXAMPLE", "Just-started or short tasks: " + tasksQuery.between( "progressMinutes", 1, 10 ).count() ) Log.i( "EXAMPLE", "Unassigned tasks: " + tasksQuery.isNull("assignee").count() ) Log.i( "EXAMPLE", "Ali or Jamie's tasks: " + tasksQuery.`in`( "assignee", arrayOf( "Ali", "Jamie" ) ).count() ) ``` ### Sort Query Results A **sort** operation allows you to configure the order in which Realm returns queried objects. You can sort based on one or more properties of the objects in the results collection. Realm only guarantees a consistent order of results when the results are sorted. The following code sorts the projects by name in reverse alphabetical order (i.e. "descending" order). #### Java ```java RealmQuery projectsQuery = realm.where(Project.class); RealmResults results = projectsQuery.sort("name", Sort.DESCENDING).findAll(); ``` #### Kotlin ```kotlin val projectsQuery = realm.where(Project::class.java) val results = projectsQuery.sort("name", Sort.DESCENDING).findAll() ``` ### Query a Relationship #### Java Consider the following relationship between classes `Human` and `Cat`. This arrangement allows each human to own a single cat: ```java import org.bson.types.ObjectId; import io.realm.RealmObject; import io.realm.annotations.PrimaryKey; public class Human extends RealmObject { @PrimaryKey private ObjectId _id = new ObjectId(); private String name; private Cat cat; public Human(String name) { this.name = name; } public Human() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public Cat getCat() { return cat; } public void setCat(Cat cat) { this.cat = cat; } public ObjectId get_id() { return _id; } } ``` ```java import org.bson.types.ObjectId; import io.realm.RealmObject; import io.realm.RealmResults; import io.realm.annotations.LinkingObjects; import io.realm.annotations.PrimaryKey; public class Cat extends RealmObject { @PrimaryKey private ObjectId _id = new ObjectId(); private String name = null; @LinkingObjects("cat") private final RealmResults owner = null; public Cat(String name) { this.name = name; } public Cat() { } public ObjectId get_id() { return _id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public RealmResults getOwner() { return owner; } } ``` To query this relationship, use dot notation in a query to access any property of the linked object. #### Kotlin Consider the following relationship between classes `Person` and `Dog`. This arrangement allows each person to own a single dog: ```kotlin import io.realm.RealmObject import io.realm.annotations.PrimaryKey import org.bson.types.ObjectId open class Person(var name : String? = null) : RealmObject() { @PrimaryKey var _id : ObjectId = ObjectId() var dog: Dog? = null } ``` ```kotlin import io.realm.RealmObject import io.realm.RealmResults import io.realm.annotations.LinkingObjects import io.realm.annotations.PrimaryKey import org.bson.types.ObjectId open class Dog(var name : String? = null): RealmObject() { @PrimaryKey var _id : ObjectId = ObjectId() @LinkingObjects("dog") val owner: RealmResults? = null } ``` To query this relationship, use dot notation in a query to access any property of the linked object. ### Query an Inverse Relationship #### Java Consider the following relationship between classes `Cat` and `Human`. In this example, all cats link to their human (or multiple humans, if multiple human objects refer to the same cat). Realm calculates the owners of each cat for you based on the field name you provide to the `@LinkingObjects` annotation: ```java import org.bson.types.ObjectId; import io.realm.RealmObject; import io.realm.RealmResults; import io.realm.annotations.LinkingObjects; import io.realm.annotations.PrimaryKey; public class Cat extends RealmObject { @PrimaryKey private ObjectId _id = new ObjectId(); private String name = null; @LinkingObjects("cat") private final RealmResults owner = null; public Cat(String name) { this.name = name; } public Cat() { } public ObjectId get_id() { return _id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public RealmResults getOwner() { return owner; } } ``` ```java import org.bson.types.ObjectId; import io.realm.RealmObject; import io.realm.annotations.PrimaryKey; public class Human extends RealmObject { @PrimaryKey private ObjectId _id = new ObjectId(); private String name; private Cat cat; public Human(String name) { this.name = name; } public Human() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public Cat getCat() { return cat; } public void setCat(Cat cat) { this.cat = cat; } public ObjectId get_id() { return _id; } } ``` To query this relationship, use dot notation in a query to access any property of the linked object. #### Kotlin Consider the following relationship between classes `Dog` and `Person`. In this example, all dogs link to their owner (or multiple owners, if multiple person objects refer to the same dog). Realm calculates the owners of each dog for you based on the field name you provide to the `@LinkingObjects` annotation: ```kotlin import io.realm.RealmObject import io.realm.RealmResults import io.realm.annotations.LinkingObjects import io.realm.annotations.PrimaryKey import org.bson.types.ObjectId open class Dog(var name : String? = null): RealmObject() { @PrimaryKey var _id : ObjectId = ObjectId() @LinkingObjects("dog") val owner: RealmResults? = null } ``` ```kotlin import io.realm.RealmObject import io.realm.annotations.PrimaryKey import org.bson.types.ObjectId open class Person(var name : String? = null) : RealmObject() { @PrimaryKey var _id : ObjectId = ObjectId() var dog: Dog? = null } ``` To query this relationship, use dot notation in a query to access any property of the linked object. ### Aggregate Data #### Java ```java RealmQuery tasksQuery = realm.where(ProjectTask.class); /* Aggregate operators do not support dot-notation, so you cannot directly operate on a property of all of the objects in a collection property. You can operate on a numeric property of the top-level object, however: */ Log.i("EXAMPLE", "Tasks average priority: " + tasksQuery.average("priority")); ``` #### Kotlin ```kotlin val tasksQuery = realm.where(ProjectTask::class.java) /* Aggregate operators do not support dot-notation, so you cannot directly operate on a property of all of the objects in a collection property. You can operate on a numeric property of the top-level object, however: */Log.i("EXAMPLE", "Tasks average priority: " + tasksQuery.average("priority")) ``` ================================================ FILE: docs/guides/crud/threading.md ================================================ # Threading - Java SDK To make your Android apps fast and responsive, you must balance the computing time needed to lay out the visuals and handle user interactions with the time needed to process your data and run your business logic. Typically, app developers spread this work across multiple threads: the main or UI thread for all of the user interface-related work, and one or more background threads to compute heavier workloads before sending it to the UI thread for presentation. By offloading heavy work to background threads, the UI thread can remain highly responsive regardless of the size of the workload. ## Three Rules to Keep in Mind Realm enables simple and safe multithreaded code when you follow these three rules: You can write to a realm from any thread, but there can be only one writer at a time. Consequently, write transactions block each other. A write on the UI thread may result in your app appearing unresponsive while it waits for a write on a background thread to complete. Live objects, collections, and realm instances are **thread-confined**: that is, they are only valid on the thread on which they were created. Practically speaking, this means you cannot pass live instances to other threads. However, Realm offers several mechanisms for sharing objects across threads. Realm's Multiversion Concurrency Control (MVCC) architecture eliminates the need to lock for read operations. The values you read will never be corrupted or in a partially-modified state. You can freely read from realms on any thread without the need for locks or mutexes. Unnecessarily locking would be a performance bottleneck since each thread might need to wait its turn before reading. ## Communication Across Threads Live objects, collections, and realms are **thread-confined**. If you need to work with the same data across multiple threads, you should open the same realm on multiple threads as separate realm instances. The Java SDK consolidates underlying connections across threads where possible to make this pattern more efficient. When you need to communicate across threads, you have several options depending on your use case: - To modify the data on two threads, query for the object on both threads using a primary key. - To send a fast, read-only view of an object to other threads, freeze the object. - To keep and share many read-only views of the object in your app, copy the object from the realm. - To react to changes made on any thread, use notifications. - To see changes from other threads in the realm on the current thread, refresh your realm instance (event loop threads refresh automatically). ### Intents Managed `RealmObject` instances are not thread-safe or `Parcelable`, so you cannot pass them between activities or threads via an `Intent`. Instead, you can pass an object identifier, like a primary key, in the `Intent` extras bundle, and then open a new realm instance in the separate thread to query for that identifier. Alternatively, you can freeze Realm objects. > Seealso: > You can find working examples in the [Passing Objects](https://github.com/realm/realm-java/blob/master/examples/threadExample/src/main/java/io/realm/examples/threads/PassingObjectsFragment.java) portion of the [Java SDK Threading Example](https://github.com/realm/realm-java/tree/master/examples/threadExample). The example shows you how to pass IDs and retrieve a `RealmObject` in common Android use cases. > ### Frozen Objects Live, thread-confined objects work fine in most cases. However, some apps -- those based on reactive, event stream-based architectures, for example -- need to send immutable copies across threads. In this case, you can **freeze** objects, collections, and realms. Freezing creates an immutable view of a specific object, collection, or realm that still exists on disk and does not need to be deeply copied when passed around to other threads. You can freely share a frozen object across threads without concern for thread issues. Frozen objects are not live and do not automatically update. They are effectively snapshots of the object state at the time of freezing. When you freeze a realm all child objects and collections also become frozen. You can't modify frozen objects, but you can read the primary key from a frozen object, query a live realm for the underlying object, and then update that live object instance. Frozen objects remain valid for as long as the realm that spawned them stays open. Avoid closing realms that contain frozen objects until all threads are done working with those frozen objects. > Warning: > When working with frozen objects, an attempt to do any of the following throws an exception: > > - Opening a write transaction on a frozen realm. > - Modifying a frozen object. > - Adding a change listener to a frozen realm, collection, or object. > Once frozen, you cannot unfreeze an object. You can use `isFrozen()` to check if an object is frozen. This method is always thread-safe. To freeze an object, collection, or realm, use the `freeze()` method: #### Java ```java Realm realm = Realm.getInstance(config); // Get an immutable copy of the realm that can be passed across threads Realm frozenRealm = realm.freeze(); Assert.assertTrue(frozenRealm.isFrozen()); RealmResults frogs = realm.where(Frog.class).findAll(); // You can freeze collections RealmResults frozenFrogs = frogs.freeze(); Assert.assertTrue(frozenFrogs.isFrozen()); // You can still read from frozen realms RealmResults frozenFrogs2 = frozenRealm.where(Frog.class).findAll(); Assert.assertTrue(frozenFrogs2.isFrozen()); Frog frog = frogs.first(); Assert.assertTrue(!frog.getRealm().isFrozen()); // You can freeze objects Frog frozenFrog = frog.freeze(); Assert.assertTrue(frozenFrog.isFrozen()); // Frozen objects have a reference to a frozen realm Assert.assertTrue(frozenFrog.getRealm().isFrozen()); ``` #### Kotlin ```kotlin val realm = Realm.getInstance(config) // Get an immutable copy of the realm that can be passed across threads val frozenRealm = realm.freeze() Assert.assertTrue(frozenRealm.isFrozen) val frogs = realm.where(Frog::class.java).findAll() // You can freeze collections val frozenFrogs = frogs.freeze() Assert.assertTrue(frozenFrogs.isFrozen) // You can still read from frozen realms val frozenFrogs2 = frozenRealm.where(Frog::class.java).findAll() Assert.assertTrue(frozenFrogs2.isFrozen) val frog: Frog = frogs.first()!! Assert.assertTrue(!frog.realm.isFrozen) // You can freeze objects val frozenFrog: Frog = frog.freeze() Assert.assertTrue(frozenFrog.isFrozen) Assert.assertTrue(frozenFrog.realm.isFrozen) ``` > Important: > Frozen objects preserve an entire copy of the realm that contains them at the moment they were frozen. As a result, freezing a large number of objects can cause a realm to consume more memory and storage than it might have without frozen objects. If you need to separately freeze a large number of objects for long periods of time, consider copying what you need out of the realm instead. > ## Refreshing Realms When you open a realm, it reflects the most recent successful write commit and remains on that version until it is **refreshed**. This means that the realm will not see changes that happened on another thread until the next refresh. Realms on any event loop thread (including the UI thread) automatically refresh themselves at the beginning of that thread's loop. However, you must manually refresh realm instances that are tied to non-looping threads or that have auto-refresh disabled. To refresh a realm, call `Realm.refresh()`: #### Java ```java if (!realm.isAutoRefresh()) { // manually refresh realm.refresh(); } ``` #### Kotlin ```kotlin if (!realm.isAutoRefresh) { // manually refresh realm.refresh() } ``` > Tip: > Realms also automatically refresh after completing a write transaction. > ## Realm's Threading Model in Depth Realm provides safe, fast, lock-free, and concurrent access across threads with its [Multiversion Concurrency Control (MVCC)](https://en.wikipedia.org/wiki/Multiversion_concurrency_control) architecture. ### Compared and Contrasted with Git If you are familiar with a distributed version control system like [Git](https://git-scm.com/), you may already have an intuitive understanding of MVCC. Two fundamental elements of Git are: - Commits, which are atomic writes. - Branches, which are different versions of the commit history. Similarly, Realm has atomically-committed writes in the form of transactions. Realm also has many different versions of the history at any given time, like branches. Unlike Git, which actively supports distribution and divergence through forking, a realm only has one true latest version at any given time and always writes to the head of that latest version. Realm cannot write to a previous version. This makes sense: your data should converge on one latest version of the truth. ### Internal Structure A realm is implemented using a [B+ tree](https://en.wikipedia.org/wiki/B%2B_tree) data structure. The top-level node represents a version of the realm; child nodes are objects in that version of the realm. The realm has a pointer to its latest version, much like how Git has a pointer to its HEAD commit. Realm uses a copy-on-write technique to ensure [isolation](https://en.wikipedia.org/wiki/Isolation_(database_systems)) and [durability](https://en.wikipedia.org/wiki/Durability_(database_systems)). When you make changes, Realm copies the relevant part of the tree for writing, then commits the changes in two phases: - Write changes to disk and verify success. - Set the latest version pointer to point to the newly-written version. This two-step commit process guarantees that even if the write failed partway, the original version is not corrupted in any way because the changes were made to a copy of the relevant part of the tree. Likewise, the realm's root pointer will point to the original version until the new version is guaranteed to be valid. Realm uses zero-copy techniques like memory mapping to handle data. When you read a value from the realm, you are virtually looking at the value on the actual disk, not a copy of it. This is the basis for live objects. This is also why a realm head pointer can be set to point to the new version after the write to disk has been validated. ## Summary - Realm enables simple and safe multithreaded code when you follow these rules: Don't pass live objects to other threads, and don't lock to read. - In order to see changes made on other threads in your realm instance, you must manually **refresh** realm instances that do not exist on "loop" threads or that have auto-refresh disabled. - For apps based on reactive, event-stream-based architectures, you can **freeze** objects, collections, and realms in order to pass copies around efficiently to different threads for processing. - Realm's multiversion concurrency control (MVCC) architecture is similar to Git's. Unlike Git, Realm has only one true latest version for each realm. - Realm commits in two stages to guarantee isolation and durability. ================================================ FILE: docs/guides/crud/update.md ================================================ # CRUD - Update - Java SDK ## About the Examples on this Page The examples on this page use the data model of a project management app that has two Realm object types: `Project` and `Task`. A `Project` has zero or more `Tasks`. See the schema for these two classes, `Project` and `Task`, below: #### Java ```java import org.bson.types.ObjectId; import io.realm.RealmObject; import io.realm.annotations.PrimaryKey; import io.realm.annotations.RealmClass; import io.realm.annotations.Required; public class ProjectTask extends RealmObject { @PrimaryKey public ObjectId _id; @Required public String name; public String assignee; public int progressMinutes; public boolean isComplete; public int priority; @Required public String _partition; } ``` ```java import org.bson.types.ObjectId; import io.realm.RealmList; import io.realm.RealmObject; import io.realm.annotations.PrimaryKey; import io.realm.annotations.RealmClass; import io.realm.annotations.Required; public class Project extends RealmObject { @PrimaryKey public ObjectId _id; @Required public String name; public RealmList tasks = new RealmList<>(); } ``` #### Kotlin ```kotlin import io.realm.RealmObject import io.realm.annotations.PrimaryKey import io.realm.annotations.Required import org.bson.types.ObjectId open class ProjectTask( @PrimaryKey var _id: ObjectId = ObjectId(), @Required var name: String = "", var assignee: String? = null, var progressMinutes: Int = 0, var isComplete: Boolean = false, var priority: Int = 0, var _partition: String = "" ): RealmObject() ``` ```kotlin import io.realm.RealmList import io.realm.RealmObject import io.realm.annotations.PrimaryKey import io.realm.annotations.Required import org.bson.types.ObjectId open class Project( @PrimaryKey var _id: ObjectId = ObjectId(), @Required var name: String = "", var tasks: RealmList = RealmList(), ): RealmObject() ``` ## Modify an Object Within a transaction, you can update a Realm object the same way you would update any other object in your language of choice. Just assign a new value to the property or update the property. The following example changes the turtle's name to "Archibald" and sets Archibald's age to 101 by assigning new values to properties: #### Java ```java realm.executeTransaction(r -> { // Get a turtle to update. Turtle turtle = r.where(Turtle.class).findFirst(); // Update properties on the instance. // This change is saved to the realm. turtle.setName("Archibald"); turtle.setAge(101); }); ``` #### Kotlin ```kotlin realm.executeTransaction { r: Realm -> // Get a turtle to update. val turtle = r.where(Turtle::class.java).findFirst() // Update properties on the instance. // This change is saved to the realm. turtle!!.name = "Archibald" turtle.age = 101 } ``` ## Upsert an Object An **upsert** is a write operation that either inserts a new object with a given primary key or updates an existing object that already has that primary key. We call this an upsert because it is an "**update** or **insert**" operation. This is useful when an object may or may not already exist, such as when bulk importing a dataset into an existing realm. Upserting is an elegant way to update existing entries while adding any new entries. The following example demonstrates how to upsert an object with realm. We create a new turtle enthusiast named "Drew" and then update their name to "Andy" using `insertOrUpdate()`: #### Java ```java realm.executeTransaction(r -> { ObjectId id = new ObjectId(); TurtleEnthusiast drew = new TurtleEnthusiast(); drew.set_id(id); drew.setName("Drew"); drew.setAge(25); // Add a new turtle enthusiast to the realm. Since nobody with this id // has been added yet, this adds the instance to the realm. r.insertOrUpdate(drew); TurtleEnthusiast andy = new TurtleEnthusiast(); andy.set_id(id); andy.setName("Andy"); andy.setAge(56); // Judging by the ID, it's the same turtle enthusiast, just with a different name. // As a result, you overwrite the original entry, renaming "Drew" to "Andy". r.insertOrUpdate(andy); }); ``` #### Kotlin ```kotlin realm.executeTransaction { r: Realm -> val id = ObjectId() val drew = TurtleEnthusiast() drew._id = id drew.name = "Drew" drew.age = 25 // Add a new turtle enthusiast to the realm. Since nobody with this id // has been added yet, this adds the instance to the realm. r.insertOrUpdate(drew) val andy = TurtleEnthusiast() andy._id = id andy.name = "Andy" andy.age = 56 // Judging by the ID, it's the same turtle enthusiast, just with a different name. // As a result, you overwrite the original entry, renaming "Drew" to "Andy". r.insertOrUpdate(andy) } ``` You can also use `copyToRealmOrUpdate()` to either create a new object based on a supplied object or update an existing object with the same primary key value. Use the `CHECK_SAME_VALUES_BEFORE_SET` `ImportFlag` to only update fields that are different in the supplied object: The following example demonstrates how to insert an object or, if an object already exists with the same primary key, update only those fields that differ: #### Java ```java realm.executeTransaction(r -> { ObjectId id = new ObjectId(); TurtleEnthusiast drew = new TurtleEnthusiast(); drew.set_id(id); drew.setName("Drew"); drew.setAge(25); // Add a new turtle enthusiast to the realm. Since nobody with this id // has been added yet, this adds the instance to the realm. r.insertOrUpdate(drew); TurtleEnthusiast andy = new TurtleEnthusiast(); andy.set_id(id); andy.setName("Andy"); // Judging by the ID, it's the same turtle enthusiast, just with a different name. // As a result, you overwrite the original entry, renaming "Drew" to "Andy". // the flag passed ensures that we only write the updated name field to the db r.copyToRealmOrUpdate(andy, ImportFlag.CHECK_SAME_VALUES_BEFORE_SET); }); ``` #### Kotlin ```kotlin realm.executeTransaction { r: Realm -> val id = ObjectId() val drew = TurtleEnthusiast() drew._id = id drew.name = "Drew" drew.age = 25 // Add a new turtle enthusiast to the realm. Since nobody with this id // has been added yet, this adds the instance to the realm. r.insertOrUpdate(drew) val andy = TurtleEnthusiast() andy._id = id andy.name = "Andy" // Judging by the ID, it's the same turtle enthusiast, just with a different name. // As a result, you overwrite the original entry, renaming "Drew" to "Andy". r.copyToRealmOrUpdate(andy, ImportFlag.CHECK_SAME_VALUES_BEFORE_SET) } ``` ## Update a Collection Realm supports collection-wide updates. A collection update applies the same update to specific properties of several objects in a collection at once. The following example demonstrates how to update a collection. Thanks to the implicit inverse relationship between the Turtle's `owner` property and the TurtleEnthusiast's `turtles` property, Realm automatically updates Josephine's list of turtles when you use `setObject()` to update the "owner" property for all turtles in the collection. #### Java ```java realm.executeTransaction(r -> { // Create a turtle enthusiast named Josephine. TurtleEnthusiast josephine = r.createObject(TurtleEnthusiast.class, new ObjectId()); josephine.setName("Josephine"); // Get all turtles named "Pierogi". RealmResults turtles = r.where(Turtle.class).equalTo("name", "Pierogi").findAll(); // Give all turtles named "Pierogi" to Josephine turtles.setObject("owner", josephine); }); ``` #### Kotlin ```kotlin realm.executeTransaction { r: Realm -> // Create a turtle enthusiast named Josephine. val josephine = realm.createObject( TurtleEnthusiast::class.java, ObjectId() ) josephine.name = "Josephine" // Get all turtles named "Pierogi". val turtles = r.where(Turtle::class.java) .equalTo("name", "Pierogi") .findAll() // Give all turtles named "Pierogi" to Josephine turtles.setObject("owner", josephine) } ``` ## Iteration Because realm collections always reflect the latest state, they can appear, disappear, or change while you iterate over a collection. To get a stable collection you can iterate over, you can create a **snapshot** of a collection's data. A snapshot guarantees the order of elements will not change, even if an element is deleted or modified. `Iterator` objects created from `RealmResults` use snapshots automatically. `Iterator` objects created from `RealmList` instances do *not* use snapshots. Use `RealmList.createSnapshot()` or `RealmResults.createSnapshot()` to manually generate a snapshot you can iterate over manually: The following example demonstrates how to iterate over a collection safely using either an implicit snapshot created from a `RealmResults` `Iterator` or a manual snapshot created from a `RealmList`: #### Java ```java RealmResults frogs = realm.where(Frog.class) .equalTo("species", "bullfrog") .findAll(); // Use an iterator to rename the species of all bullfrogs realm.executeTransaction(r -> { for (Frog frog : frogs) { frog.setSpecies("Lithobates catesbeiana"); } }); // Use a snapshot to rename the species of all bullfrogs realm.executeTransaction(r -> { OrderedRealmCollectionSnapshot frogsSnapshot = frogs.createSnapshot(); for (int i = 0; i < frogsSnapshot.size(); i++) { frogsSnapshot.get(i).setSpecies("Lithobates catesbeiana"); } }); ``` #### Kotlin ```kotlin val frogs = realm.where(Frog::class.java) .equalTo("species", "bullfrog") .findAll() // Use an iterator to rename the species of all bullfrogs realm.executeTransaction { for (frog in frogs) { frog.species = "Lithobates catesbeiana" } } // Use a snapshot to rename the species of all bullfrogs realm.executeTransaction { val frogsSnapshot = frogs.createSnapshot() for (i in frogsSnapshot.indices) { frogsSnapshot[i]!!.species = "Lithobates catesbeiana" } } ``` ================================================ FILE: docs/guides/crud.md ================================================ # CRUD - Java SDK ## Write Operations You can **create** objects in a realm, **update** objects in a realm, and eventually **delete** objects from a realm. Because these operations modify the state of the realm, we call them writes. Realm handles writes in terms of **transactions**. A transaction is a list of read and write operations that Realm treats as a single indivisible operation. In other words, a transaction is *all or nothing*: either all of the operations in the transaction succeed or none of the operations in the transaction take effect. > Note: > All writes must happen in a transaction. > A realm allows only one open write transaction at a time. Realm blocks other writes on other threads until the open transaction is complete. Consequently, there is no race condition when reading values from the realm within a transaction. When you are done with your transaction, Realm either **commits** it or **cancels** it: - When Realm **commits** a transaction, Realm writes all changes to disk. - When Realm **cancels** a write transaction or an operation in the transaction causes an error, all changes are discarded (or "rolled back"). > Tip: > Whenever you create, update, or delete a Realm object, your changes update the representation of that object in Realm and emit notifications to any subscribed listeners. As a result, you should only write to Realm objects when necessary to persist data. > > Important: > By default, you can only read or write to a realm in your application's UI thread using asynchronous transactions. That is, you can only use `Realm` methods whose name ends with the word `Async` in the main thread of your Android application unless you explicitly allow the use of synchronous methods. > > This restriction exists for the benefit of your application users: performing read and write operations on the UI thread can lead to unresponsive or slow UI interactions, so it's usually best to handle these operations either asynchronously or in a background thread. ## Managed Objects **Managed objects** are live Realm objects that update based on changes to underlying data in Realm. Managed objects can only come from an open realm, and receive updates as long as that realm remains open. Managed objects *cannot be passed between threads*. ## Unmanaged objects **Unmanaged objects** are instances of Realm objects that are not live. You can get an unmanaged object by manually constructing a Realm object yourself, or by calling `[Realm.copyFromRealm()`. Unmanaged objects *can be passed between threads*. ## Run a Transaction Realm represents each transaction as a callback function that contains zero or more read and write operations. To run a transaction, define a transaction callback and pass it to the realm's `write` method. Within this callback, you are free to create, read, update, and delete on the realm. If the code in the callback throws an exception when Realm runs it, Realm cancels the transaction. Otherwise, Realm commits the transaction immediately after the callback. > Example: > The following code shows how to run a transaction with `executeTransaction()` or `executeTransactionAsync()`. If the code in the callback throws an exception, Realm cancels the transaction. Otherwise, Realm commits the transaction. > > #### Java > > ```java > realm.executeTransaction(r -> { > // Create a turtle enthusiast named Ali. > TurtleEnthusiast ali = r.createObject(TurtleEnthusiast.class, new ObjectId()); > ali.setName("Ali"); > // Find turtles younger than 2 years old > RealmResults hatchlings = r.where(Turtle.class).lessThan("age", 2).findAll(); > // Give all hatchlings to Ali. > hatchlings.setObject("owner", ali); > }); > > ``` > > > #### Kotlin > > ```kotlin > realm.executeTransaction { r: Realm -> > // Create a turtle enthusiast named Ali. > val ali = r.createObject(TurtleEnthusiast::class.java, ObjectId()) > ali.name = "Ali" > // Find turtles younger than 2 years old > val hatchlings = > r.where(Turtle::class.java).lessThan("age", 2).findAll() > // Give all hatchlings to Ali. > hatchlings.setObject("owner", ali) > } > > ``` > > ================================================ FILE: docs/guides/install.md ================================================ # Install Realm - Java SDK > Note: > The Java SDK is in best-effort maintenance mode and **no longer receives new development or non-critical bug fixes. To develop your app with new features, use the Kotlin SDK. You can use the Java SDK with the Kotlin SDK in the same project. > > Learn more about how to Migrate from the Java SDK to the Kotlin SDK. > ## Overview This page details how to install Realm using the Java SDK in your project and get started. You can use multiple SDKs in your project. Because the Java SDK is no longer receiving new development, this is useful if you want to use new features in your app. ## Prerequisites - [Android Studio](https://developer.android.com/studio/index.html) version 1.5.1 or higher. - Java Development Kit (JDK) 11 or higher. - An emulated or hardware Android device for testing. - Android API Level 16 or higher (Android 4.1 and above). ## Installation Realm only supports the Gradle build system. Follow these steps to add the Realm Java SDK to your project. > Note: > Because Realm provides a ProGuard configuration as part of the Realm library, you do not need to add any Realm-specific rules to your ProGuard configuration. > ### Project Gradle Configuration To add local realm to your application, make the following changes to your project-level Gradle build file, typically found at /build.gradle: #### Gradle Plugin > Tip: > The Java SDK does not yet support the Gradle Plugin syntax. Fortunately, you can still add the SDK to projects that use this syntax. > - Add a `buildscript` block that contains a `repositories` block and a `dependencies` block. - Add the `mavenCentral()` repository to the `buildscript.repositories` block. - Add the `io.realm:realm-gradle-plugin` dependency to the `buildscript.dependencies` block. ```groovy buildscript { repositories { mavenCentral() } dependencies { classpath "io.realm:realm-gradle-plugin:10.18.0" } } plugins { id 'com.android.application' version '7.1.2' apply false id 'com.android.library' version '7.1.2' apply false id 'org.jetbrains.kotlin.android' version '1.6.10' apply false id "org.jetbrains.kotlin.kapt" version "1.6.20" apply false } task clean(type: Delete) { delete rootProject.buildDir } ``` #### Gradle Legacy - Add the `io.realm:realm-gradle-plugin` dependency to the `buildscript.dependencies` block. - Add the `mavenCentral()` repository to the `allprojects.repositories` block. ```groovy buildscript { repositories { google() } dependencies { classpath "com.android.tools.build:gradle:3.5.1" classpath "io.realm:realm-gradle-plugin:10.18.0" } } allprojects { repositories { google() mavenCentral() } dependencies { } } task clean(type: Delete) { delete rootProject.buildDir } ``` ### Application Module Gradle Configuration Then, make the following changes to your application-level Gradle build file, typically found at /app/build.gradle: #### Gradle Plugin - Apply the `kotlin-kapt` plugin if your application uses Kotlin - Beneath the `plugins` block, apply the `realm-android` plugin. ```groovy plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' id 'org.jetbrains.kotlin.kapt' } apply plugin: "realm-android" android { compileSdk 31 defaultConfig { applicationId "com.mongodb.example-realm-application" minSdk 28 targetSdk 31 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 } kotlinOptions { jvmTarget = '11' } } dependencies { implementation 'io.realm:realm-gradle-plugin:10.10.1' implementation 'androidx.core:core-ktx:1.7.0' implementation 'androidx.appcompat:appcompat:1.4.1' implementation 'com.google.android.material:material:1.5.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.3' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' } ``` #### Gradle Legacy - Apply the `kotlin-kapt` plugin if your application uses Kotlin - Apply the `realm-android` plugin ```groovy apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' apply plugin: 'realm-android' android { compileSdkVersion 31 buildToolsVersion "29.0.2" defaultConfig { applicationId "com.mongodb.example-realm-application" minSdkVersion 28 targetSdkVersion 31 } compileOptions { sourceCompatibility 1.11 targetCompatibility 1.11 } kotlinOptions { jvmTarget = '11' } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "androidx.appcompat:appcompat:1.1.0" implementation "androidx.core:core-ktx:1.2.0" } ``` After updating the `build.gradle` files, resolve the dependencies by clicking File > Sync Project with Gradle Files. ## Supported Platforms Realm's Java SDK enables you to build apps for the following platforms: - Android - Wear OS - Android Automotive OS - Android TV - Android Things ================================================ FILE: docs/guides/model-data/data-types/collections.md ================================================ # Collections - Java SDK A Realm collection is an object that contains zero or more instances of one type. Realm collections are homogenous, i.e. all objects in a collection are of the same type. You can filter and sort any collection using Realm's query engine. Collections are live, so they always reflect the current state of the realm instance on the current thread. You can also listen for changes in the collection by subscribing to collection notifications. Realm has two kinds of collections: **lists** and **results**. ## Lists Realm objects can contain lists of non-Realm-object data types. You can model these collections with the type `RealmList`, where `T` can be the following types: - `String` - `Integer` - `UUID` - `ObjectId` - `Boolean` - `Float` - `Double` - `Short` - `Long` - `Byte` - `byte[]` - `Date` > Seealso: > Lists > ### List Collections A **list collection** represents a to-many relationship between two Realm types. Lists are mutable: within a write transaction, you can add and remove elements on a list. Lists are not associated with a query. ### Results Collections A **results collection** represents the lazily-evaluated results of a query operation. Results are immutable: you cannot add or remove elements on the results collection. Results have an associated query that determines their contents. The `RealmResults` class inherits from [AbstractList](https://developer.android.com/reference/java/util/AbstractList) and behaves in similar ways. For example, `RealmResults` are ordered, and you can access the individual objects through an index. If a query has no matches, the returned `RealmResults` object will be a list of length 0, not a `null` object reference. You can only modify or delete objects in a `RealmResults` set in a write transaction. ## Iteration Because Realm collections are live, objects may move as you iterate over a collection. You can use snapshots to iterate over collections safely. ## Adapters Realm offers adapters to help bind data to standard UI widgets. These classes work with any class that implements the `OrderedRealmCollection` interface, which includes the built-in `RealmResults` and `RealmList` classes. For more information on adapters, see the documentation on Displaying Collections. > Important: > The Realm adapters only accept *managed* Realm object instances tied to an instance of a realm. To display non-managed objects, use the general-use Android `RecyclerView.Adapter` for recycler views or `ArrayAdapter` for list views. > ## Collections are Live Like live objects, Realm collections are usually **live**: - Live results collections always reflect the current results of the associated query. - Live lists always reflect the current state of the relationship on the realm instance. There are three cases when a collection is **not** live: - The collection is unmanaged, e.g. a List property of a Realm object that has not been added to a realm yet or that has been copied from a realm. - The collection is frozen. - The collection is part of a snapshot. Combined with collection notifications, live collections enable clean, reactive code. For example, suppose your view displays the results of a query. You can keep a reference to the results collection in your view class, then read the results collection as needed without having to refresh it or validate that it is up-to-date. > Warning: > Results update themselves automatically. If you store the positional index of an object in a collection or the count of objects in a collection, the stored index or count value could be outdated by the time you use it. > ## Results are Lazily Evaluated Realm only runs a query when you actually request the results of that query, e.g. by accessing elements of the results collection. This lazy evaluation enables you to write elegant, highly performant code for handling large data sets and complex queries. ### Limiting Query Results As a result of lazy evaluation, you do not need any special mechanism to limit query results with Realm. For example, if your query matches thousands of objects, but you only want to load the first ten, simply access only the first ten elements of the results collection. ### Pagination Thanks to lazy evaluation, the common task of pagination becomes quite simple. For example, suppose you have a results collection associated with a query that matches thousands of objects in your realm. You display one hundred objects per page. To advance to any page, simply access the elements of the results collection starting at the index that corresponds to the target page. ## List vs. Results When you need a collection, you can use the following rule of thumb to determine whether a list or a results collection is appropriate: - When you define the properties of your Realm objects, use lists to define to-many relationships except implicit inverse relationships. - Use results everywhere else. To understand these different use cases, consider whether you should be able to add or remove objects directly. Lists allow you to add and remove objects directly, because you control the relationships. Results collections do not allow you to add or remove objects directly, because their contents are determined by a query. > Example: > Consider a Realm type called Person with a field called `emails` that is a collection of strings representing email addresses. You control this data. Your application needs to add and remove email addresses from your Person instances. Therefore, use a **list** to define the field type of `emails`. > > On the other hand, when you query the realm for all Persons over the age of 25, it would not make sense for you to add or remove Persons directly to the resulting collection. The contents of that collection only change when the query matches a different set of Persons. Therefore, Realm gives you a **results** collection. > > Note: > Since Realm automatically determines the contents of implicit inverse relationship collections, you may not add or remove objects from such a collection. Therefore, the type of such a one-to-many relationship property is actually a results collection, not a list. > ================================================ FILE: docs/guides/model-data/data-types/counters.md ================================================ # Counters - Java SDK Realm offers `MutableRealmInteger`, a wrapper around numeric values, to help better synchronize numeric changes across multiple clients. Typically, incrementing or decrementing a `byte`, `short`, `int`, or `long` field of a Realm object looks something like this: 1. Read the current value of the field. 2. Update that value in memory to a new value based on the increment or decrement. 3. Write a new value back to the field. When multiple distributed clients attempt this at the same time, updates reaching clients in different orders can result in different values on different clients. `MutableRealmInteger` improves on this by translating numeric updates into sync operations that can be executed in any order to converge to the same value. `MutableRealmInteger` fields are backed by traditional numeric types, so no migration is required when changing a field from `byte`, `short`, `int` or `long` to `MutableRealmInteger`. The following example demonstrates a `MutableRealmInteger` field that counts the number of ghosts found in a haunted house: #### Java ```java import io.realm.MutableRealmInteger; import io.realm.RealmObject; import io.realm.annotations.Required; public class HauntedHouse extends RealmObject { @Required private final MutableRealmInteger ghosts = MutableRealmInteger.valueOf(0); public HauntedHouse() {} public MutableRealmInteger getGhosts() { return ghosts; } } ``` #### Kotlin ```kotlin import io.realm.MutableRealmInteger import io.realm.RealmObject import io.realm.annotations.Required open class HauntedHouse: RealmObject() { @Required val ghosts: MutableRealmInteger = MutableRealmInteger.valueOf(0) } ``` > Important: > `MutableRealmInteger` is a live object like `RealmObject`, `RealmResults` and `RealmList`. This means the value contained inside the `MutableRealmInteger` can change when a realm is written to. For this reason `MutableRealmInteger` fields must be marked final in Java and `val` in Kotlin. > ## Usage The `counter.increment()` and `counter.decrement()` operators ensure that increments and decrements from multiple distributed clients are aggregated correctly. To change a `MutableRealmInteger` value, call `increment()` or `decrement()` within a write transaction: #### Java ```java HauntedHouse house = realm.where(HauntedHouse.class) .findFirst(); realm.executeTransaction(r -> { Log.v("EXAMPLE", "Number of ghosts: " + house.getGhosts().get()); // 0 house.getGhosts().increment(1); Log.v("EXAMPLE", "Number of ghosts: " + house.getGhosts().get()); // 1 house.getGhosts().increment(5); Log.v("EXAMPLE", "Number of ghosts: " + house.getGhosts().get()); // 6 house.getGhosts().decrement(2); Log.v("EXAMPLE", "Number of ghosts: " + house.getGhosts().get()); // 4 }); ``` #### Kotlin ```kotlin val house = realm.where(HauntedHouse::class.java) .findFirst()!! realm.executeTransaction { Log.v("EXAMPLE", "Number of ghosts: ${house.ghosts.get()}") // 0 house.ghosts.increment(1) Log.v("EXAMPLE", "Number of ghosts: ${house.ghosts.get()}") // 1 house.ghosts.increment(5) Log.v("EXAMPLE", "Number of ghosts: ${house.ghosts.get()}") // 6 house.ghosts.decrement(2) Log.v("EXAMPLE", "Number of ghosts: ${house.ghosts.get()}") // 4 } ``` You can assign a `MutableRealmInteger` a new value with a call to `counter.set()` within a write transaction. > Warning: > Use the `set()` operator with extreme care. `set()` ignores the effects of any prior calls to `increment()` or `decrement()`. Although the value of a `MutableRealmInteger` always converges across devices, the specific value on which it converges depends on the actual order in which operations took place. Mixing `set()` with `increment()` and `decrement()` is not advised unless fuzzy counting is acceptable. > #### Java ```java realm.executeTransaction(r -> { house.getGhosts().set(42); }); ``` #### Kotlin ```kotlin realm.executeTransaction { house!!.ghosts.set(42) } ``` Since `MutableRealmInteger` instances retain a reference to their parent object, neither object can be garbage collected while you still retain a reference to the `MutableRealmInteger`. ================================================ FILE: docs/guides/model-data/data-types/embedded-objects.md ================================================ # Embedded Objects - Java SDK An embedded object is a special type of Realm object that models complex data about a specific object. Embedded objects are similar to relationships, but they provide additional constraints and map more naturally to the denormalized document model. Realm enforces unique ownership constraints that treat each embedded object as nested data inside of a single, specific parent object. An embedded object inherits the lifecycle of its parent object and cannot exist as an independent Realm object. Realm automatically deletes embedded objects if their parent object is deleted or when overwritten by a new embedded object instance. > Warning: > When you delete a Realm object, Realm automatically deletes any embedded objects referenced by that object. Any objects that your application must persist after the deletion of their parent object should use relationships instead. > ## Embedded Object Data Models You can define embedded object types using either Realm object models or a server-side document schema. Embedded object types are reusable and composable. You can use the same embedded object type in multiple parent object types and you can embed objects inside of other embedded objects. > Important: > Embedded objects cannot have a primary key. > ### Realm Object Models To define an embedded object, derive a class from `RealmObject` and set the `embedded` property of the `RealmClass` annotation to `true`. You can reference an embedded object type from parent object types in the same way as you would define a relationship: #### Java ```java // Define an embedded object @RealmClass(embedded = true) public class Address extends RealmObject { String street; String city; String country; String postalCode; public Address(String street, String city, String country, String postalCode) { this.street = street; this.city = city; this.country = country; this.postalCode = postalCode; } public Address() {} } // Define an object containing one embedded object public class Contact extends RealmObject { @PrimaryKey private ObjectId _id = new ObjectId(); String name = ""; // Embed a single object. // Embedded object properties must be marked optional Address address; public Contact(String name, Address address) { this.name = name; this.address = address; } public Contact() {} } // Define an object containing an array of embedded objects public class Business extends RealmObject { @PrimaryKey private ObjectId _id = new ObjectId(); String name = ""; // Embed an array of objects RealmList
addresses = new RealmList
(); public Business(String name, RealmList
addresses) { this.name = name; this.addresses = addresses; } public Business() {} } ``` #### Kotlin ```kotlin // Define an embedded object @RealmClass(embedded = true) open class Address( var street: String? = null, var city: String? = null, var country: String? = null, var postalCode: String? = null ): RealmObject() {} // Define an object containing one embedded object open class Contact(_name: String = "", _address: Address? = null) : RealmObject() { @PrimaryKey var _id: ObjectId = ObjectId() var name: String = _name // Embed a single object. // Embedded object properties must be marked optional var address: Address? = _address } // Define an object containing an array of embedded objects open class Business(_name: String = "", _addresses: RealmList
= RealmList()) : RealmObject() { @PrimaryKey var _id: ObjectId = ObjectId() var name: String = _name // Embed an array of objects var addresses: RealmList
= _addresses } ``` ### JSON Schema Embedded objects map to embedded documents in the parent type's schema. ```json { "title": "Contact", "bsonType": "object", "required": ["_id"], "properties": { "_id": { "bsonType": "objectId" }, "name": { "bsonType": "string" }, "address": { "title": "Address", "bsonType": "object", "properties": { "street": { "bsonType": "string" }, "city": { "bsonType": "string" }, "country": { "bsonType": "string" }, "postalCode": { "bsonType": "string" } } } } } ``` ```json { "title": "Business", "bsonType": "object", "required": ["_id", "name"], "properties": { "_id": { "bsonType": "objectId" }, "name": { "bsonType": "string" }, "addresses": { "bsonType": "array", "items": { "title": "Address", "bsonType": "object", "properties": { "street": { "bsonType": "string" }, "city": { "bsonType": "string" }, "country": { "bsonType": "string" }, "postalCode": { "bsonType": "string" } } } } } } ``` ## Read and Write Embedded Objects ### Create an Embedded Object To create an embedded object, assign an instance of the embedded object to a parent object's property. #### Java ```java // open realm Address address = new Address("123 Fake St.", "Springfield", "USA", "90710"); Contact contact = new Contact("Nick Riviera", address); realm.executeTransaction(transactionRealm -> { transactionRealm.insert(contact); }); realm.close(); ``` #### Kotlin ```kotlin // open realm val address = Address("123 Fake St.", "Springfield", "USA", "90710") val contact = Contact("Nick Riviera", address) realm.executeTransaction { transactionRealm -> transactionRealm.insert(contact) } realm.close() ``` ### Update an Embedded Object Property To update a property in an embedded object, modify the property in a write transaction: #### Java ```java // assumes that at least one contact already exists in this partition Contact resultContact = realm.where(Contact.class).findFirst(); realm.executeTransaction(transactionRealm -> { resultContact.address.street = "Hollywood Upstairs Medical College"; resultContact.address.city = "Los Angeles"; resultContact.address.postalCode = "90210"; Log.v("EXAMPLE", "Updated contact: " + resultContact); }); realm.close(); ``` #### Kotlin ```kotlin // assumes that at least one contact already exists in this partition val result = realm.where().findFirst()!! realm.executeTransaction { transactionRealm -> result.address?.street = "Hollywood Upstairs Medical College" result.address?.city = "Los Angeles" result.address?.postalCode = "90210" Log.v("EXAMPLE", "Updated contact: ${result.name}") } realm.close() ``` ### Overwrite an Embedded Object To overwrite an embedded object, reassign the embedded object property of a party to a new instance in a write transaction: #### Java ```java // assumes that at least one contact already exists in this partition Contact oldContact = realm.where(Contact.class).findFirst(); realm.executeTransaction(transactionRealm -> { Address newAddress = new Address( "Hollywood Upstairs Medical College", "Los Angeles", "USA" "90210" ); oldContact.address = newAddress; Log.v("EXAMPLE", "Replaced contact: " + oldContact); }); realm.close(); ``` #### Kotlin ```kotlin // assumes that at least one contact already exists val oldContact = realm.where().findFirst()!! realm.executeTransaction { transactionRealm -> val newAddress = Address( "Hollywood Upstairs Medical College", "Los Angeles", "USA", "90210") oldContact.address = newAddress Log.v("EXAMPLE", "Updated contact: $oldContact") } realm.close() ``` ### Query a Collection on Embedded Object Properties Use dot notation to filter or sort a collection of objects based on an embedded object property value: > Note: > It is not possible to query embedded objects directly. Instead, access embedded objects through a query for the parent object type. > #### Java ```java RealmResults losAngelesContacts = realm.where(Contact.class) .equalTo("address.city", "Los Angeles") .sort("address.street").findAll(); Log.v("EXAMPLE", "Los Angeles contacts: " + losAngelesContacts); ``` #### Kotlin ```kotlin val losAngelesContacts = realm.where() .equalTo("address.city", "Los Angeles") .sort("address.street").findAll() Log.v("EXAMPLE", "Los Angeles Contacts: $losAngelesContacts") ``` ================================================ FILE: docs/guides/model-data/data-types/enums.md ================================================ # Enumerations - Java SDK Enumerations, also known as enums, are not supported natively in the Java SDK. However, you can use Java and Kotlin enums in your Realm objects if you follow these steps. ## Usage To use an enum in a Realm object class, define a field with a type matching the underlying data type of your enum. Create getters and setters for the field that convert the field value between the underlying value and the enum type. You can use the Java's built-in [Enum.valueOf()](https://docs.oracle.com/javase/7/docs/api/java/lang/Enum.html#valueOf(java.lang.Class,%20java.lang.String)) method to convert from the underlying type to the enum type. #### Java ```kotlin public enum FrogState { TADPOLE("Tadpole"), FROG("Frog"), OLD_FROG("Old Frog"); private String state; FrogState(String state) { this.state = state; } public String getState() { return state; } } ``` ```java import io.realm.RealmObject; public class Frog extends RealmObject { String name; String state = FrogState.TADPOLE.getState(); // realm-required empty constructor public Frog() {} public String getName() { return name; } public void setName(String name) { this.name = name; } public FrogState getState() { // because state is actually a String and another client could assign an invalid value, // default the state to "TADPOLE" if the state is unreadable FrogState currentState = null; try { // fetches the FrogState enum value associated with the current internal string value currentState = FrogState.valueOf(state); } catch (IllegalArgumentException e) { currentState = FrogState.TADPOLE; } return currentState; } public void setState(FrogState value) { // users set state using a FrogState, but it is saved as a string internally this.state = value.getState(); } } ``` ```java Frog frog = realm.createObject(Frog.class); frog.setName("Jonathan Livingston Applesauce"); // set the state using the enum frog.setState(FrogState.FROG); // fetching the state returns an enum FrogState currentJonathanState = frog.getState(); ``` #### Kotlin ```kotlin enum class FrogState(val state: String) { TADPOLE("Tadpole"), FROG("Frog"), OLD_FROG("Old Frog"); } ``` ```kotlin import io.realm.RealmObject import java.lang.IllegalArgumentException open class Frog // realm-required empty constructor : RealmObject() { var name: String? = null private var state: String = FrogState.TADPOLE.state var stateEnum: FrogState get() { // because state is actually a String and another client could assign an invalid value, // default the state to "TADPOLE" if the state is unreadable return try { // fetches the FrogState enum value associated with the current internal string value FrogState.valueOf(state) } catch (e: IllegalArgumentException) { FrogState.TADPOLE } } set(value) { // users set state using a FrogState, but it is saved as a string internally state = value.state } } ``` ```kotlin val frog = realm.createObject(Frog::class.java) frog.name = "Jonathan Livingston Applesauce" // set the state using the enum frog.stateEnum = FrogState.FROG // fetching the state returns an enum val currentJonathanState: FrogState = frog.stateEnum ``` ================================================ FILE: docs/guides/model-data/data-types/field-types.md ================================================ # Field Types - Java SDK Realm supports the following field data types: - `Boolean` or `boolean` - `Integer` or `int` - `Short` or `short` - `Long` or `long` - `Byte` or `byte[]` - `Double` or `double` - `Float` or `float` - `String` - `Date` - `Decimal128` from `org.bson.types` - `ObjectId` from `org.bson.types` - `UUID` from `java.util.UUID` - Any `RealmObject` subclass - `RealmList` - `RealmAny` - `RealmSet` - `RealmDictionary` The `Byte`, `Short`, `Integer`, and `Long` types and their lowercase primitive alternatives are all stored as `Long` values within Realm. Similarly, Realm stores objects of the `Float` and `float` types as type `Double`. Realm does not support fields with modifiers `final` and `volatile`, though you can use fields with those modifiers if you ignore them. If you choose to provide custom constructors, you must declare a public constructor with no arguments. ## Updating Strings and Byte Arrays Since Realm operates on fields as a whole, it's not possible to directly update individual elements of strings or byte arrays. Instead, you'll need to read the whole field, make your modification to individual elements, and then write the entire field back again in a transaction block. ## Object IDs and UUIDs `ObjectId` and `UUID` (Universal Unique Identifier) both provide unique values that can be used as identifiers for objects. `ObjectId` is a 12-byte unique value. `UUID` is a [standardized](https://tools.ietf.org/html/rfc4122) 16-byte unique value. Both types are indexable and can be used as primary keys. ================================================ FILE: docs/guides/model-data/data-types/realmany.md ================================================ # RealmAny - Java SDK > Version added: 10.6.0 You can use the `RealmAny` data type to create Realm object fields that can contain any of several underlying types. You can store multiple `RealmAny` instances in `RealmList`, `RealmDictionary`, or `RealmSet` fields. To change the value of a `RealmAny` field, assign a new `RealmAny` instance with a different underlying value. `RealmAny` fields are indexable, but cannot be used as primary keys. > Note: > `RealmAny` objects can refer to any supported field type *except*: > > - `RealmAny` > - `RealmList` > - `RealmSet` > - `RealmDictionary` > ## Usage To create a `RealmAny` instance, use the `RealmAny.valueOf()` method to assign an initial value or `RealmAny.nullValue()` to assign no value. `RealmAny` instances are immutable just like `String` or `Integer` instances; if you want to assign a new value to a `RealmAny` field, you must create a new `RealmAny` instance. > Warning: > `RealmAny` instances are always nullable. Additionally, instances can contain a value of type `RealmAny.Type.NULL`. > #### Java ```java import com.mongodb.realm.examples.model.kotlin.Person; import io.realm.RealmAny; import io.realm.RealmObject; public class Frog extends RealmObject { String name; RealmAny bestFriend; // realm-required empty constructor public Frog() {} public String getName() { return name; } public void setName(String name) { this.name = name; } public RealmAny getBestFriend() { return bestFriend; } public void setBestFriend(RealmAny bestFriend) { this.bestFriend = bestFriend; } public String bestFriendToString() { switch(bestFriend.getType()) { case NULL: { return "no best friend"; } case STRING: { return bestFriend.asString(); } case OBJECT: { if (bestFriend.getValueClass().equals(Person.class)) { Person person = bestFriend.asRealmModel(Person.class); return person.getName(); } } default: { return "unknown type"; } } } } ``` ```java Frog frog = realm.createObject(Frog.class); frog.setName("Jonathan Livingston Applesauce"); // set RealmAny field to a null value frog.setBestFriend(RealmAny.nullValue()); Log.v("EXAMPLE", "Best friend: " + frog.bestFriendToString()); // possible types for RealmAny are defined in RealmAny.Type Assert.assertTrue(frog.getBestFriend().getType() == RealmAny.Type.NULL); // set RealmAny field to a string with RealmAny.valueOf a string value frog.setBestFriend(RealmAny.valueOf("Greg")); Log.v("EXAMPLE", "Best friend: " + frog.bestFriendToString()); // RealmAny instances change type as you reassign to different values Assert.assertTrue(frog.getBestFriend().getType() == RealmAny.Type.STRING); // set RealmAny field to a realm object, also with valueOf Person person = new Person("Jason Funderburker"); frog.setBestFriend(RealmAny.valueOf(person)); Log.v("EXAMPLE", "Best friend: " + frog.bestFriendToString()); // You can also extract underlying Realm Objects from RealmAny with asRealmModel Person bestFriendObject = frog.getBestFriend().asRealmModel(Person.class); Log.v("EXAMPLE", "Best friend: " + bestFriendObject.getName()); // RealmAny fields referring to any Realm Object use the OBJECT type Assert.assertTrue(frog.getBestFriend().getType() == RealmAny.Type.OBJECT); // you can't put a RealmList in a RealmAny field directly, // ...but you can set a RealmAny field to a RealmObject that contains a list GroupOfPeople persons = new GroupOfPeople(); // GroupOfPeople contains a RealmList of people persons.getPeople().add("Rand"); persons.getPeople().add("Perrin"); persons.getPeople().add("Mat"); frog.setBestFriend(RealmAny.valueOf(persons)); Log.v("EXAMPLE", "Best friend: " + frog.getBestFriend().asRealmModel(GroupOfPeople.class).getPeople().toString()); ``` #### Kotlin ```kotlin import io.realm.RealmAny import io.realm.RealmObject open class Frog(var bestFriend: RealmAny? = RealmAny.nullValue()) : RealmObject() { var name: String? = null open fun bestFriendToString(): String { if (bestFriend == null) { return "null" } return when (bestFriend!!.type) { RealmAny.Type.NULL -> { "no best friend" } RealmAny.Type.STRING -> { bestFriend!!.asString() } RealmAny.Type.OBJECT -> { if (bestFriend!!.valueClass == Person::class.java) { val person = bestFriend!!.asRealmModel(Person::class.java) person.name } "unknown type" } else -> { "unknown type" } } } } ``` ```kotlin val frog = realm.createObject(Frog::class.java) frog.name = "George Washington" // set RealmAny field to a null value // set RealmAny field to a null value frog.bestFriend = RealmAny.nullValue() Log.v("EXAMPLE", "Best friend: " + frog.bestFriendToString()) // possible types for RealmAny are defined in RealmAny.Type Assert.assertEquals(frog.bestFriend?.type, RealmAny.Type.NULL) // set RealmAny field to a string with RealmAny.valueOf a string value frog.bestFriend = RealmAny.valueOf("Greg") Log.v("EXAMPLE", "Best friend: " + frog.bestFriendToString()) // RealmAny instances change type as you reassign to different values Assert.assertEquals(frog.bestFriend?.type, RealmAny.Type.STRING) // set RealmAny field to a realm object, also with valueOf val person = Person("Jason Funderburker") frog.bestFriend = RealmAny.valueOf(person) Log.v("EXAMPLE", "Best friend: " + frog.bestFriendToString()) // You can also extract underlying Realm Objects from RealmAny with asRealmModel val bestFriendObject = frog.bestFriend?.asRealmModel(Person::class.java) Log.v("EXAMPLE", "Best friend: " + bestFriendObject?.name) // RealmAny fields referring to any Realm Object use the OBJECT type Assert.assertEquals(frog.bestFriend?.type, RealmAny.Type.OBJECT) // you can't put a RealmList in a RealmAny field directly, // ...but you can set a RealmAny field to a RealmObject that contains a list val persons = GroupOfPeople() // GroupOfPeople contains a RealmList of people persons.people.add("Rand") persons.people.add("Perrin") persons.people.add("Mat") frog.bestFriend = RealmAny.valueOf(persons) Log.v("EXAMPLE", "Best friend: " + frog.bestFriend?.asRealmModel(GroupOfPeople::class.java) ?.people.toString()) ``` ## Queries You can query a `RealmAny` field just like any other data type. Operators that only work with certain types, such as string operators and arithmetic operators, ignore values that do not contain that type. Negating such operators matches values that do not contain the type. Type queries match the underlying type, rather than `RealmAny`. Arithmetic operators convert numeric values implicitly to compare across types. ## Notifications To subscribe to changes to a `RealmAny` field, use the `RealmObject.addChangeListener` method of the enclosing object. You can use the `ObjectChangeSet` parameter to determine if the `RealmAny` field changed. #### Java ```java AtomicReference frog = new AtomicReference(); realm.executeTransaction(r -> { frog.set(realm.createObject(Frog.class)); frog.get().setName("Jonathan Livingston Applesauce"); }); RealmObjectChangeListener objectChangeListener = new RealmObjectChangeListener() { @Override public void onChange(@NotNull Frog frog, @Nullable ObjectChangeSet changeSet) { if (changeSet != null) { Log.v("EXAMPLE", "Changes to fields: " + Arrays.toString(changeSet.getChangedFields())); if (changeSet.isFieldChanged("best_friend")) { Log.v("EXAMPLE", "RealmAny best friend field changed to : " + frog.bestFriendToString()); } } } }; frog.get().addChangeListener(objectChangeListener); realm.executeTransaction(r -> { // set RealmAny field to a null value frog.get().setBestFriend(RealmAny.nullValue()); Log.v("EXAMPLE", "Best friend: " + frog.get().bestFriendToString()); // set RealmAny field to a string with RealmAny.valueOf a string value frog.get().setBestFriend(RealmAny.valueOf("Greg")); }); ``` #### Kotlin ```kotlin var frog: Frog? = null realm.executeTransaction { r: Realm? -> frog = realm.createObject(Frog::class.java) frog?.name = "Jonathan Livingston Applesauce" } val objectChangeListener = RealmObjectChangeListener { frog, changeSet -> if (changeSet != null) { Log.v("EXAMPLE", "Changes to fields: " + changeSet.changedFields) if (changeSet.isFieldChanged("best_friend")) { Log.v("EXAMPLE", "RealmAny best friend field changed to : " + frog.bestFriendToString()) } } } frog?.addChangeListener(objectChangeListener) realm.executeTransaction { r: Realm? -> // set RealmAny field to a null value frog?.bestFriend = RealmAny.nullValue() Log.v("EXAMPLE", "Best friend: " + frog?.bestFriendToString()) // set RealmAny field to a string with RealmAny.valueOf a string value frog?.bestFriend = RealmAny.valueOf("Greg") } ``` ================================================ FILE: docs/guides/model-data/data-types/realmdictionary.md ================================================ # RealmDictionary - Java SDK > Version added: 10.6.0 You can use the `RealmDictionary` data type to manage a collection of unique `String` keys paired with values. `RealmDictionary` implements Java's `Map` interface, so it works just like the built-in `HashMap` class, except managed `RealmDictionary` instances persist their contents to a realm. `RealmDictionary` instances that contain Realm objects store references to those objects. When you delete a Realm object from a realm, any references to that object in a `RealmDictionary` become `null` values. ## Usage To create a field of type `RealmDictionary`, define an object property of type `RealmDictionary`, where `T` defines the values you would like to store in your `RealmDictionary`. Currently, `RealmDictionary` instances can only use keys of type `String`. The following table shows which methods you can use to complete common collection tasks with `RealmDictionary`: |Task|Method| | --- | --- | |Add an object to a `RealmDictionary`|`put()` (or the `[]` operator in Kotlin)| |Add multiple objects to a `RealmDictionary`|`putAll()`| |Check if the dictionary contains an specific key|`containsKey()`| |Check if the dictionary contains a specific value|`containsValue()`| #### Java ```java import io.realm.RealmDictionary; import io.realm.RealmObject; public class Frog extends RealmObject { String name; RealmDictionary nicknamesToFriends; // realm-required empty constructor public Frog() {} public String getName() { return name; } public void setName(String name) { this.name = name; } public RealmDictionary getNicknamesToFriends() { return nicknamesToFriends; } public void setNicknamesToFriends(RealmDictionary nicknamesToFriends) { this.nicknamesToFriends = nicknamesToFriends; } } ``` ```java Frog frog = realm.createObject(Frog.class); frog.setName("George Washington"); // get the RealmDictionary field from the object we just created RealmDictionary dictionary = frog.getNicknamesToFriends(); // add key/value to the dictionary Frog wirt = realm.createObject(Frog.class); wirt.setName("Wirt"); dictionary.put("tall frog", wirt); // add multiple keys/values to the dictionary Frog greg = realm.createObject(Frog.class); greg.setName("Greg"); Frog beatrice = realm.createObject(Frog.class); beatrice.setName("Beatrice"); dictionary.putAll(Map.of("small frog", greg, "feathered frog", beatrice)); // check for the presence of a key Assert.assertTrue(dictionary.containsKey("small frog")); // check for the presence of a value Assert.assertTrue(dictionary.containsValue(greg)); // remove a key dictionary.remove("feathered frog"); Assert.assertFalse(dictionary.containsKey("feathered frog")); // deleting a Realm object does NOT remove it from the dictionary int sizeOfDictionaryBeforeDelete = dictionary.size(); greg.deleteFromRealm(); // deleting greg object did not reduce the size of the dictionary Assert.assertEquals(sizeOfDictionaryBeforeDelete, dictionary.size()); // but greg object IS now null: Assert.assertEquals(dictionary.get("small frog"), null); ``` #### Kotlin ```kotlin import io.realm.RealmDictionary import io.realm.RealmObject open class Frog : RealmObject() { var name: String? = null var nicknamesToFriends: RealmDictionary = RealmDictionary() } ``` ```kotlin val frog = realm.createObject(Frog::class.java) frog.name = "George Washington" // get the RealmDictionary field from the object we just created val dictionary = frog.nicknamesToFriends // add key/value to the dictionary val wirt = realm.createObject(Frog::class.java) wirt.name = "Wirt" dictionary["tall frog"] = wirt // add multiple keys/values to the dictionary val greg = realm.createObject(Frog::class.java) greg.name = "Greg" val beatrice = realm.createObject(Frog::class.java) beatrice.name = "Beatrice" dictionary.putAll(mapOf( Pair("small frog", greg), Pair("feathered frog", beatrice))) // check for the presence of a key Assert.assertTrue(dictionary.containsKey("small frog")) // check for the presence of a value Assert.assertTrue(dictionary.containsValue(greg)) // remove a key dictionary.remove("feathered frog") Assert.assertFalse(dictionary.containsKey("feathered frog")) // deleting a Realm object does NOT remove it from the dictionary val sizeOfDictionaryBeforeDelete = dictionary.size greg.deleteFromRealm() // deleting greg object did not reduce the size of the dictionary Assert.assertEquals( sizeOfDictionaryBeforeDelete.toLong(), dictionary.size.toLong() ) // but greg object IS now null: Assert.assertEquals(dictionary["small frog"], null) ``` ## Notifications To subscribe to changes to a `RealmDictionary`, pass a `MapChangeListener` implementation to the `RealmSet.addChangeListener` method. Your `MapChangeListener` implementation must define an `onChange()` method, which accepts a reference to the changed `RealmDictionary` and a set of changes as parameters. You can access the keys added to the dictionary as well as the keys removed from the dictionary through the `MapChangeSet` parameter. #### Java ```java AtomicReference frog = new AtomicReference(); realm.executeTransaction(r -> { frog.set(realm.createObject(Frog.class)); frog.get().setName("Jonathan Livingston Applesauce"); }); MapChangeListener mapChangeListener = new MapChangeListener() { @Override public void onChange(RealmMap map, MapChangeSet changes) { for (String insertion : changes.getInsertions()) { Log.v("EXAMPLE", "Inserted key: " + insertion + ", Inserted value: " + map.get(insertion).getName()); } } }; frog.get().getNicknamesToFriends().addChangeListener(mapChangeListener); realm.executeTransaction(r -> { // get the RealmDictionary field from the object we just created RealmDictionary dictionary = frog.get().getNicknamesToFriends(); // add key/value to the dictionary Frog wirt = realm.createObject(Frog.class); wirt.setName("Wirt"); dictionary.put("tall frog", wirt); // add multiple keys/values to the dictionary Frog greg = realm.createObject(Frog.class); greg.setName("Greg"); Frog beatrice = realm.createObject(Frog.class); beatrice.setName("Beatrice"); dictionary.putAll(Map.of("small frog", greg, "feathered frog", beatrice)); }); ``` #### Kotlin ```kotlin var frog: Frog? = null realm.executeTransaction { r: Realm? -> frog = realm.createObject(Frog::class.java) frog?.name = "Jonathan Livingston Applesauce" } val mapChangeListener: MapChangeListener = MapChangeListener { map, changes -> for (insertion in changes.insertions) { Log.v("EXAMPLE", "Inserted key: $insertion, Inserted value: ${map[insertion]!!.name}") } } frog?.nicknamesToFriends?.addChangeListener(mapChangeListener) realm.executeTransaction { r: Realm? -> // get the RealmDictionary field from the object we just created val dictionary = frog!!.nicknamesToFriends // add key/value to the dictionary val wirt = realm.createObject(Frog::class.java) wirt.name = "Wirt" dictionary["tall frog"] = wirt // add multiple keys/values to the dictionary val greg = realm.createObject(Frog::class.java) greg.name = "Greg" val beatrice = realm.createObject(Frog::class.java) beatrice.name = "Beatrice" dictionary.putAll(mapOf( Pair("small frog", greg), Pair("feathered frog", beatrice))) } ``` ================================================ FILE: docs/guides/model-data/data-types/realmset.md ================================================ # RealmSet - Java SDK > Version added: 10.6.0 You can use the `RealmSet` data type to manage a collection of unique keys. `RealmSet` implements Java's `Set` interface, so it works just like the built-in `HashSet` class, except managed `RealmSet` instances persist their contents to a realm. `RealmSet` instances that contain Realm objects actually only store references to those objects, so deleting a Realm object from a realm also deletes that object from any `RealmSet` instances that contain the object. Because `RealmSet` implements `RealmCollection`, it has some useful mathematical methods, such as `sum`, `min`, and `max`. For a complete list of available `RealmSet` methods, see: [the RealmSet API reference](https://www.mongodb.com/docs/realm-sdks/java/latest/io/realm/RealmSet.html). ## Method Limitations You cannot use the following `Realm` methods on objects that contain a field of type `RealmSet`: - `Realm.insert()` - `Realm.insertOrUpdate()` - `Realm.createAllFromJson()` - `Realm.createObjectFromJson()` - `Realm.createOrUpdateAllFromJson()` - `Realm.createOrUpdateObjectFromJson()` ## Usage To create a field of type `RealmSet`, define an object property of type `RealmSet`, where `E` defines the keys you would like to store in your `RealmSet`. - Add an object to a `RealmSet` with `RealmSet.add()` - Add multiple objects with `RealmSet.addAll()` - Check if the set contains a specific object with `RealmSet.contains()` - Check if the set contains all of multiple objects with `RealmSet.containsAll()` #### Java ```java import io.realm.RealmObject; import io.realm.RealmSet; public class Frog extends RealmObject { String name; RealmSet favoriteSnacks; // realm-required empty constructor public Frog() {} public String getName() { return name; } public void setName(String name) { this.name = name; } public RealmSet getFavoriteSnacks() { return favoriteSnacks; } public void setFavoriteSnacks(RealmSet favoriteSnacks) { this.favoriteSnacks = favoriteSnacks; } } ``` ```java import io.realm.RealmObject; public class Snack extends RealmObject { private String name; public Snack() {} public String getName() { return name; } public void setName(String name) { this.name = name; } } ``` ```java Frog frog = realm.createObject(Frog.class); frog.setName("George Washington"); // get the RealmSet field from the object we just created RealmSet set = frog.getFavoriteSnacks(); // add value to the RealmSet Snack flies = realm.createObject(Snack.class); flies.setName("flies"); set.add(flies); // add multiple values to the RealmSet Snack water = realm.createObject(Snack.class); water.setName("water"); Snack verySmallRocks = realm.createObject(Snack.class); verySmallRocks.setName("verySmallRocks"); set.addAll(Arrays.asList(water, verySmallRocks)); // check for the presence of a key with contains Assert.assertTrue(set.contains(flies)); // check for the presence of multiple keys with containsAll Snack biscuits = realm.createObject(Snack.class); biscuits.setName("biscuits"); Assert.assertTrue(set.containsAll(Arrays.asList(water, biscuits)) == false); // remove string from a set set.remove(verySmallRocks); // set no longer contains that string Assert.assertTrue(set.contains(verySmallRocks) == false); // deleting a Realm object also removes it from any RealmSets int sizeOfSetBeforeDelete = set.size(); flies.deleteFromRealm(); // deleting flies object reduced the size of the set by one Assert.assertTrue(sizeOfSetBeforeDelete == set.size() + 1); ``` #### Kotlin ```kotlin import io.realm.RealmObject import io.realm.RealmSet open class Frog : RealmObject() { var name: String = "" var favoriteSnacks: RealmSet = RealmSet(); } ``` ```kotlin import io.realm.RealmObject open class Snack : RealmObject() { var name: String? = null } ``` ```kotlin val frog = realm.createObject(Frog::class.java) frog.name = "Jonathan Livingston Applesauce" // get the RealmSet field from the object we just created val set = frog.favoriteSnacks // add value to the RealmSet val flies = realm.createObject(Snack::class.java) flies.name = "flies" set.add(flies) // add multiple values to the RealmSet val water = realm.createObject(Snack::class.java) water.name = "water" val verySmallRocks = realm.createObject(Snack::class.java) verySmallRocks.name = "verySmallRocks" set.addAll(listOf(water, verySmallRocks)) // check for the presence of a key with contains Assert.assertTrue(set.contains(flies)) // check for the presence of multiple keys with containsAll val biscuits = realm.createObject(Snack::class.java) biscuits.name = "biscuits" Assert.assertTrue(set.containsAll(Arrays.asList(water, biscuits)) == false) // remove string from a set set.remove(verySmallRocks) // set no longer contains that string Assert.assertTrue(set.contains(verySmallRocks) == false) // deleting a Realm object also removes it from any RealmSets val sizeOfSetBeforeDelete = set.size flies.deleteFromRealm() // deleting flies object reduced the size of the set by one Assert.assertTrue(sizeOfSetBeforeDelete == set.size + 1) ``` ## Notifications To subscribe to changes to a `RealmSet`, pass a `SetChangeListener` implementation to the `RealmSet.addChangeListener` method. Your `SetChangeListener` implementation must define an `onChange()` method, which accepts a reference to the changed `RealmSet` and a set of changes as parameters. You can access the number of items added to the set as well as the number of items removed from the set through the `SetChangeSet` parameter. #### Java ```java AtomicReference frog = new AtomicReference(); realm.executeTransaction(r -> { frog.set(realm.createObject(Frog.class)); frog.get().setName("Jonathan Livingston Applesauce"); }); SetChangeListener setChangeListener = new SetChangeListener() { @Override public void onChange(@NotNull RealmSet set, SetChangeSet changes) { Log.v("EXAMPLE", "Set changed: " + changes.getNumberOfInsertions() + " new items, " + changes.getNumberOfDeletions() + " items removed."); } }; frog.get().getFavoriteSnacks().addChangeListener(setChangeListener); realm.executeTransaction(r -> { // get the RealmSet field from the object we just created RealmSet set = frog.get().getFavoriteSnacks(); // add value to the RealmSet Snack flies = realm.createObject(Snack.class); flies.setName("flies"); set.add(flies); // add multiple values to the RealmSet Snack water = realm.createObject(Snack.class); water.setName("water"); Snack verySmallRocks = realm.createObject(Snack.class); verySmallRocks.setName("verySmallRocks"); set.addAll(Arrays.asList(water, verySmallRocks)); }); ``` #### Kotlin ```kotlin var frog :Frog? = null realm.executeTransaction { r: Realm? -> frog = realm.createObject(Frog::class.java) frog?.name = "Jonathan Livingston Applesauce" } val setChangeListener: SetChangeListener = SetChangeListener { set, changes -> Log.v("EXAMPLE", "Set changed: " + changes.numberOfInsertions + " new items, " + changes.numberOfDeletions + " items removed.") } frog?.favoriteSnacks?.addChangeListener(setChangeListener) realm.executeTransaction { r: Realm? -> // get the RealmSet field from the object we just created val set = frog!!.favoriteSnacks // add value to the RealmSet val flies = realm.createObject(Snack::class.java) flies.name = "flies" set.add(flies) // add multiple values to the RealmSet val water = realm.createObject(Snack::class.java) water.name = "water" val verySmallRocks = realm.createObject(Snack::class.java) verySmallRocks.name = "verySmallRocks" set.addAll(Arrays.asList(water, verySmallRocks)) } ``` ================================================ FILE: docs/guides/model-data/data-types.md ================================================ # Realm Data Types - Java SDK Explore detailed guidance for each Realm Java SDK data type and related concepts: - [Field Types](./data-types/field-types.md) - [Collections](./data-types/collections.md) - [Counters](./data-types/counters.md) - [Dictionaries](./data-types/realmdictionary.md) - [Sets](./data-types/realmset.md) - [Mixed (RealmAny)](./data-types/realmany.md) - [Enums](./data-types/enums.md) - [Embedded Objects](./data-types/embedded-objects.md) Each linked page covers usage details, constraints, and example code where applicable. ================================================ FILE: docs/guides/model-data/define-a-realm-object-model.md ================================================ # Define a Realm Object Model - Java SDK ## Define a Realm Object To define a Realm object in your application, create a subclass of `RealmObject` or implement `RealmModel`. > Important: > - All Realm objects must provide an empty constructor. > - All Realm objects must use the `public` visibility modifier in Java or the `open` visibility modifier in Kotlin. > > Note: > Class names are limited to a maximum of 57 UTF-8 characters. > ### Extend RealmObject The following code block shows a Realm object that describes a Frog. This Frog class can be stored in Realm because it `extends` the `RealmObject` class. #### Java ```java import io.realm.RealmObject; // To add an object to your Realm Schema, extend RealmObject public class Frog extends RealmObject { private String name; private int age; private String species; private String owner; public Frog(String name, int age, String species, String owner) { this.name = name; this.age = age; this.species = species; this.owner = owner; } public Frog(){} // RealmObject subclasses must provide an empty constructor public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getSpecies() { return species; } public void setSpecies(String species) { this.species = species; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } } ``` #### Kotlin ```kotlin import io.realm.RealmObject // providing default values for each constructor parameter // fulfills the need for an empty constructor open class Frog( var name: String? = null, var age: Int = 0, var species: String? = null, var owner: String? = null ) : RealmObject() // To add an object to your Realm Schema, extend RealmObject ``` ### Implement RealmModel The following code block shows a Realm object that describes a Frog. This Frog class can be stored in Realm because it `implements` the `RealmModel` class and uses the `@RealmClass` annotation: #### Java ```java import io.realm.RealmModel; import io.realm.annotations.RealmClass; @RealmClass public class Frog implements RealmModel { private String name; private int age; private String species; private String owner; public Frog(String name, int age, String species, String owner) { this.name = name; this.age = age; this.species = species; this.owner = owner; } public Frog() {} // RealmObject subclasses must provide an empty constructor public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getSpecies() { return species; } public void setSpecies(String species) { this.species = species; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } } ``` > Important: > All Realm objects must use the `public` visibility modifier. > #### Kotlin ```kotlin import io.realm.RealmModel import io.realm.annotations.RealmClass @RealmClass open class Frog : RealmModel { var name: String? = null var age = 0 var species: String? = null var owner: String? = null constructor(name: String?, age: Int, species: String?, owner: String?) { this.name = name this.age = age this.species = species this.owner = owner } constructor() {} // RealmObject subclasses must provide an empty constructor } ``` > Important: > All Realm objects must use the `open` visibility modifier. > > Tip: > When you create a Realm object by extending the `RealmObject` class, you can access `RealmObject` class methods dynamically on instances of your Realm object. Realm objects created by implementing `RealmModel` can access those same methods statically through the `RealmObject` class: > > #### Java > > ```java > // With RealmObject > frogRealmObject.isValid(); > frogRealmObject.addChangeListener(listener); > > // With RealmModel > RealmObject.isValid(frogRealmModel); > RealmObject.addChangeListener(frogRealmModel, listener); > > ``` > > > #### Kotlin > > ```kotlin > // With RealmObject > frogRealmObject?.isValid > frogRealmObject?.addChangeListener(listener) > > // With RealmModel > RealmObject.isValid(frogRealmModel) > RealmObject.addChangeListener(frogRealmModel, listener) > > ``` > > ## Lists Realm objects can contain lists of non-Realm-object data types: #### Java Unlike lists of Realm objects, these lists can contain null values. If null values shouldn't be allowed, use the @Required annotation. ```java import io.realm.RealmList; import io.realm.RealmObject; public class Frog extends RealmObject { private String name; private int age; private String species; private String owner; private RealmList favoriteColors; public Frog(String name, int age, String species, String owner, RealmList favoriteColors) { this.name = name; this.age = age; this.species = species; this.owner = owner; this.favoriteColors = favoriteColors; } public Frog(){} // RealmObject subclasses must provide an empty constructor public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getSpecies() { return species; } public void setSpecies(String species) { this.species = species; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } public RealmList getFavoriteColors() { return favoriteColors; } public void setFavoriteColors(RealmList favoriteColors) { this.favoriteColors = favoriteColors; } } ``` #### Kotlin ```kotlin import io.realm.RealmList import io.realm.RealmObject open class Frog : RealmObject { var name: String? = null var age = 0 var species: String? = null var owner: String? = null var favoriteColors : RealmList? = null constructor( name: String?, age: Int, species: String?, owner: String?, favoriteColors: RealmList? ) { this.name = name this.age = age this.species = species this.owner = owner this.favoriteColors = favoriteColors } constructor() {} // RealmObject subclasses must provide an empty constructor } ``` > Seealso: > Data Types: Lists > ## Define an Embedded Object Field Realm provides the ability to nest objects within other objects. This has several advantages: - When you delete an object that contains another object, the delete operation removes both objects from the realm, so unused objects don't accumulate in your realm file, taking up valuable space on user's mobile devices. To embed an object, set the `embedded` property of the `@RealmClass` annotation to `true` on the class that you'd like to nest within another class: #### Java ```java import io.realm.RealmObject; import io.realm.annotations.RealmClass; @RealmClass(embedded=true) public class Fly extends RealmObject { private String name; public Fly(String name) { this.name = name; } public Fly() {} // RealmObject subclasses must provide an empty constructor } ``` #### Kotlin ```kotlin import io.realm.RealmObject import io.realm.annotations.RealmClass @RealmClass(embedded = true) open class Fly : RealmObject { private var name: String? = null constructor(name: String?) { this.name = name } constructor() {} // RealmObject subclasses must provide an empty constructor } ``` Then, any time you reference that class from another class, Realm will embed the referenced class within the enclosing class, as in the following example: #### Java ```java import io.realm.RealmObject; public class Frog extends RealmObject { private String name; private int age; private String species; private String owner; private Fly lastMeal; public Frog(String name, int age, String species, String owner, Fly lastMeal) { this.name = name; this.age = age; this.species = species; this.owner = owner; this.lastMeal = lastMeal; } public Frog(){} // RealmObject subclasses must provide an empty constructor public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getSpecies() { return species; } public void setSpecies(String species) { this.species = species; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } public Fly getLastMeal() { return lastMeal; } public void setLastMeal(Fly lastMeal) { this.lastMeal = lastMeal; } } ``` #### Kotlin ```kotlin import io.realm.RealmObject open class Frog : RealmObject { var name: String? = null var age = 0 var species: String? = null var owner: String? = null var lastMeal: Fly? = null constructor( name: String?, age: Int, species: String?, owner: String?, lastMeal: Fly? ) { this.name = name this.age = age this.species = species this.owner = owner this.lastMeal = lastMeal } constructor() {} // RealmObject subclasses must provide an empty constructor } ``` > Seealso: > Data Types: Embedded Objects > ## Annotations Use annotations to customize your Realm object models. ### Primary Key > Version added: 10.6.0 > Realm automatically indexes primary key fields. Previously, Realm only indexed `String` primary keys automatically. > Realm treats fields marked with the `@PrimaryKey` annotation as primary keys for their corresponding object schema. Primary keys are subject to the following limitations: - You can define only one primary key per object schema. - Primary key values must be unique across all instances of an object in a realm. Attempting to insert a duplicate primary key value results in a `RealmPrimaryKeyConstraintException`. - Primary key values are immutable. To change the primary key value of an object, you must delete the original object and insert a new object with a different primary key value. - Embedded objects cannot define a primary key. You can create a primary key with any of the following types: - `String` - `UUID` - `ObjectId` - `Integer` or `int` - `Long` or `long` - `Short` or `short` - `Byte` or `byte[]` Non-primitive types can contain a value of `null` as a primary key value, but only for one object of a particular type, since each primary key value must be unique. Attempting to insert an object with an existing primary key into a realm will result in a `[RealmPrimaryKeyConstraintException`. Realm automatically indexes primary key fields, which allows you to efficiently read and modify objects based on their primary key. You cannot change the primary key field for an object type after adding any object of that type to a realm. Embedded objects cannot contain primary keys. You may optionally define a primary key for an object type as part of the object schema with the `@PrimaryKey` annotation: #### Java ```java import io.realm.RealmObject; import io.realm.annotations.PrimaryKey; public class Frog extends RealmObject { @PrimaryKey private String name; private int age; private String species; private String owner; public Frog(String name, int age, String species, String owner) { this.name = name; this.age = age; this.species = species; this.owner = owner; } public Frog(){} // RealmObject subclasses must provide an empty constructor public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getSpecies() { return species; } public void setSpecies(String species) { this.species = species; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } } ``` #### Kotlin ```kotlin import io.realm.RealmObject import io.realm.annotations.PrimaryKey open class Frog : RealmObject { @PrimaryKey var name : String? = null var age = 0 var species: String? = null var owner: String? = null constructor(name: String?, age: Int, species: String?, owner: String?) { this.name = name this.age = age this.species = species this.owner = owner } constructor() {} // RealmObject subclasses must provide an empty constructor } ``` ### Required Fields #### Java ```java import io.realm.RealmObject; import io.realm.annotations.Required; public class Frog extends RealmObject { @Required private String name; private int age; private String species; private String owner; public Frog(String name, int age, String species, String owner) { this.name = name; this.age = age; this.species = species; this.owner = owner; } public Frog(){} // RealmObject subclasses must provide an empty constructor public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getSpecies() { return species; } public void setSpecies(String species) { this.species = species; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } } ``` #### Kotlin ```kotlin import io.realm.RealmObject import io.realm.annotations.Required open class Frog : RealmObject { @Required var name: String? = null var age = 0 var species: String? = null var owner: String? = null constructor(name: String?, age: Int, species: String?, owner: String?) { this.name = name this.age = age this.species = species this.owner = owner } constructor() {} // RealmObject subclasses must provide an empty constructor } ``` ### Optional Fields Fields marked with Java object types and Kotlin nullable types (ending with `?`) are nullable by default. All other types (primitives, non-nullable Kotlin object types) are required by default. You can mark a nullable field with the `@Required` annotation to prevent that field from holding a null value. `RealmLists` are never nullable, but you can use the `@Required` annotation to prevent objects in a list from holding a null value, even if the base type would otherwise allow it. You cannot mark a `RealmList` of `RealmObject` subtypes as required. You can make any of the following types required: - `String` - `UUID` - `ObjectId` - `Integer` - `Long` - `Short` - `Byte` or `byte[]` - `Boolean` - `Float` - `Double` - `Date` - `RealmList` Primitive types such as `int` and the `RealmList` type are implicitly required. Fields with the `RealmObject` type are always nullable, and cannot be made required. > Important: > In Kotlin, types are non-nullable by default unless you explicitly add a `?` suffix to the type. You can only annotate nullable types. Using the `@Required` annotation on non-nullable types will fail compilation. > #### Java Nullable fields are optional by default in Realm, unless otherwise specified with the @Required annotation. The following types are nullable: - `String` - `Date` - `UUID` - `ObjectId` - `Integer` - `Long` - `Short` - `Byte` or `byte[]` - `Boolean` - `Float` - `Double` Primitive types like `int` and `long` are non-nullable by default and cannot be made nullable, as they cannot be set to a null value. #### Kotlin In Kotlin, fields are considered nullable only if a field is marked nullable with the Kotlin [? operator](https://kotlinlang.org/docs/reference/null-safety.html) except for the following types: - `String` - `Date` - `UUID` - `ObjectId` - `Decimal128` - `RealmAny` You can require any type that ends with the Kotlin `?` operator, such as `Int?`. The `RealmList` type is non-nullable by default and cannot be made nullable. ### Default Field Values To assign a default value to a field, use the built-in language features to assign default values. #### Java Use the class constructor(s) to assign default values: ```java import io.realm.RealmObject; public class Frog extends RealmObject { private String name = "Kitty"; private int age; private String species; private String owner; public Frog(String name, int age, String species, String owner) { this.name = name; this.age = age; this.species = species; this.owner = owner; } public Frog(){} // RealmObject subclasses must provide an empty constructor public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getSpecies() { return species; } public void setSpecies(String species) { this.species = species; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } } ``` #### Kotlin Assign default values in the field declaration: ```kotlin import io.realm.RealmObject open class Frog : RealmObject { var name = "Kitty" var age = 0 var species: String? = null var owner: String? = null constructor(name: String, age: Int, species: String?, owner: String?) { this.name = name this.age = age this.species = species this.owner = owner } constructor() {} // RealmObject subclasses must provide an empty constructor } ``` > Note: > While default values ensure that a newly created object cannot contain a value of `null` (unless you specify a default value of `null`), they do not impact the nullability of a field. To make a field non-nullable, see Required Fields. > ### Index a Field **Indexes** support the efficient execution of queries in Realm. Without indexes, Realm must perform a *collection scan*, i.e. scan every document in a collection, to select those documents that match a query. If an appropriate index exists for a query, Realm can use the index to limit the number of documents that it must inspect. Indexes are special data structures that store a small portion of a realm's data in an easy to traverse form. The index stores the value of a specific field ordered by the value of the field. The ordering of the index entries supports efficient equality matches and range-based query operations. Adding an index can speed up some queries at the cost of slightly slower write times and additional storage and memory overhead. Indexes require space in your realm file, so adding an index to a property will increase disk space consumed by your realm file. Each index entry is a minimum of 12 bytes. You can index fields with the following types: - `String` - `UUID` - `ObjectId` - `Integer` or `int` - `Long` or `long` - `Short` or `short` - `Byte` or `byte[]` - `Boolean` or `bool` - `Date` - `RealmAny` Realm creates indexes for fields annotated with `@Index`. To index a field, use the `@Index` annotation: #### Java ```java import io.realm.RealmObject; import io.realm.annotations.Index; public class Frog extends RealmObject { private String name; private int age; @Index private String species; private String owner; public Frog(String name, int age, String species, String owner) { this.name = name; this.age = age; this.species = species; this.owner = owner; } public Frog(){} // RealmObject subclasses must provide an empty constructor public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getSpecies() { return species; } public void setSpecies(String species) { this.species = species; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } } ``` #### Kotlin ```kotlin import io.realm.RealmObject import io.realm.annotations.Index open class Frog : RealmObject { var name: String? = null var age = 0 @Index var species : String? = null var owner: String? = null constructor(name: String?, age: Int, species: String?, owner: String?) { this.name = name this.age = age this.species = species this.owner = owner } constructor() {} // RealmObject subclasses must provide an empty constructor } ``` ### Ignore a Field If you don't want to save a field in your model to a realm, you can ignore a field. Ignore a field from a Realm object model with the `@Ignore` annotation: #### Java ```java import io.realm.RealmObject; import io.realm.annotations.Ignore; public class Frog extends RealmObject { private String name; private int age; private String species; // can you ever really own a frog persistently? @Ignore private String owner; public Frog(String name, int age, String species, String owner) { this.name = name; this.age = age; this.species = species; this.owner = owner; } public Frog(){} // RealmObject subclasses must provide an empty constructor public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getSpecies() { return species; } public void setSpecies(String species) { this.species = species; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } } ``` #### Kotlin ```kotlin import io.realm.RealmObject import io.realm.annotations.Ignore open class Frog : RealmObject { var name: String? = null var age = 0 var species: String? = null // can you ever really own a frog persistently? @Ignore var owner : String? = null constructor(name: String?, age: Int, species: String?, owner: String?) { this.name = name this.age = age this.species = species this.owner = owner } constructor() {} // RealmObject subclasses must provide an empty constructor } ``` > Note: > Fields marked `static` or `transient` are always ignored, and do not need the `@Ignore` annotation. > ### Rename a Field By default, Realm uses the name defined in the model class to represent fields internally. In some cases you might want to change this behavior: - To make it easier to work across platforms, since naming conventions differ. - To change a field name in Kotlin without forcing a migration. Choosing an internal name that differs from the name used in model classes has the following implications: - Migrations must use the internal name when creating classes and fields. - Schema errors reported will use the internal name. Use the `@RealmField` annotation to rename a field: #### Java ```java import io.realm.RealmObject; import io.realm.annotations.RealmField; public class Frog extends RealmObject { private String name; private int age; @RealmField("latinName") private String species; private String owner; public Frog(String name, int age, String species, String owner) { this.name = name; this.age = age; this.species = species; this.owner = owner; } public Frog(){} // RealmObject subclasses must provide an empty constructor public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getSpecies() { return species; } public void setSpecies(String species) { this.species = species; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } } ``` #### Kotlin ```kotlin import io.realm.RealmObject import io.realm.annotations.RealmField open class Frog : RealmObject { var name: String? = null var age = 0 @RealmField("latinName") var species: String? = null var owner: String? = null constructor(name: String?, age: Int, species: String?, owner: String?) { this.name = name this.age = age this.species = species this.owner = owner } constructor() {} // RealmObject subclasses must provide an empty constructor } ``` Alternatively, you can also assign a naming policy at the module or class levels to change the way that Realm interprets field names. You can define a `naming policy` at the module level, which will affect all classes included in the module: #### Java ```java import io.realm.annotations.RealmModule; import io.realm.annotations.RealmNamingPolicy; @RealmModule( allClasses = true, classNamingPolicy = RealmNamingPolicy.LOWER_CASE_WITH_UNDERSCORES, fieldNamingPolicy = RealmNamingPolicy.LOWER_CASE_WITH_UNDERSCORES ) public class MyModule { } ``` #### Kotlin ```kotlin import io.realm.annotations.RealmModule import io.realm.annotations.RealmNamingPolicy @RealmModule( allClasses = true, classNamingPolicy = RealmNamingPolicy.LOWER_CASE_WITH_UNDERSCORES, fieldNamingPolicy = RealmNamingPolicy.LOWER_CASE_WITH_UNDERSCORES ) open class MyModule ``` You can also define a `naming policy` at the class level, which overrides module level settings: #### Java ```java import io.realm.RealmObject; import io.realm.annotations.RealmClass; import io.realm.annotations.RealmNamingPolicy; @RealmClass(fieldNamingPolicy = RealmNamingPolicy.PASCAL_CASE) public class Frog extends RealmObject { private String name; private int age; private String species; private String owner; public Frog(String name, int age, String species, String owner) { this.name = name; this.age = age; this.species = species; this.owner = owner; } public Frog(){} // RealmObject subclasses must provide an empty constructor public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getSpecies() { return species; } public void setSpecies(String species) { this.species = species; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } } ``` #### Kotlin ```kotlin import io.realm.RealmObject import io.realm.annotations.RealmClass import io.realm.annotations.RealmNamingPolicy @RealmClass(fieldNamingPolicy = RealmNamingPolicy.PASCAL_CASE) open class Frog : RealmObject { var name: String? = null var age = 0 var species: String? = null var owner: String? = null constructor(name: String?, age: Int, species: String?, owner: String?) { this.name = name this.age = age this.species = species this.owner = owner } constructor() {} // RealmObject subclasses must provide an empty constructor } ``` ### Rename a Class By default, Realm uses the name defined in the model class to represent classes internally. In some cases you might want to change this behavior: - To support multiple model classes with the same simple name in different packages. - To make it easier to work across platforms, since naming conventions differ. - To use a class name that is longer than the 57 character limit enforced by Realm. - To change a class name in Kotlin without forcing a migration. Use the `@RealmClass` annotation to rename a class: #### Java ```java import io.realm.RealmObject; import io.realm.annotations.RealmClass; @RealmClass(name = "ShortBodiedTaillessAmphibian") public class Frog extends RealmObject { private String name; private int age; private String species; private String owner; public Frog(String name, int age, String species, String owner) { this.name = name; this.age = age; this.species = species; this.owner = owner; } public Frog(){} // RealmObject subclasses must provide an empty constructor public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getSpecies() { return species; } public void setSpecies(String species) { this.species = species; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } } ``` #### Kotlin ```kotlin import io.realm.RealmObject import io.realm.annotations.RealmClass @RealmClass(name = "Short_Bodied_Tailless_Amphibian") open class Frog : RealmObject { var name: String? = null var age = 0 var species: String? = null var owner: String? = null constructor(name: String?, age: Int, species: String?, owner: String?) { this.name = name this.age = age this.species = species this.owner = owner } constructor() {} // RealmObject subclasses must provide an empty constructor } ``` ## Omit Classes from your Realm Schema By default, your application's Realm Schema includes all classes that extend `RealmObject`. If you only want to include a subset of classes that extend `RealmObject` in your Realm Schema, you can include that subset of classes in a module and open your realm using that module: #### Java ```java import io.realm.annotations.RealmModule; @RealmModule(classes = { Frog.class, Fly.class }) public class MyModule { } ``` #### Kotlin ```kotlin import io.realm.annotations.RealmModule @RealmModule(classes = [Frog::class, Fly::class]) open class MyModule ``` ================================================ FILE: docs/guides/model-data/modify-an-object-schema.md ================================================ # Change an Object Model - Java SDK #### Local The following examples demonstrate how to add, delete, and modify properties in a schema. First, make the required schema change. Then, increment the schema version. Finally, if the change is breaking (destructive) create a corresponding migration function to move data from the original schema to the updated schema. > Note: > Assume that each schema change shown in the following example occurs after the application has used the existing schema. The new schema version numbers apply only after you open the realm and explicitly specify the new version number. In other words, you can't specify version 3 without previously specifying and using versions 0, 1, and 2. > A realm using schema version `0` has a `Person` object type: #### Java ```java public class Person extends RealmObject { // Realm schema version 0 @Required public String firstName; @Required public int age; } ``` #### Kotlin ```kotlin class Person: RealmObject { // Realm schema version 0 var firstName: String = "" var age: int = 0 } ``` ### A. Add a Property The following example adds a `lastName` property to the original Person schema: #### Java ```java public class Person extends RealmObject { // Realm schema version 1 @Required public String firstName; @Required public String lastName; @Required public int age; } ``` #### Kotlin ```kotlin class Person: RealmObject { // Realm schema version 1 var firstName: String = "" var lastName: String = "" var age: int = 0 } ``` ### B. Delete a Property The following example uses a combined `fullName` property instead of the separate `firstName` and `lastName` property in the original Person schema: #### Java ```java public class Person extends RealmObject { // Realm schema version 2 @Required public String fullName; @Required public int age; } ``` #### Kotlin ```kotlin class Person: RealmObject { // Realm schema version 2 var fullName: String = "" var age: int = 0 } ``` ### C. Modify a Property Type or Rename a Property The following example modifies the `age` property in the original Person schema by renaming it to `birthday` and changing the type to `Date`: #### Java ```java public class Person extends RealmObject { // Realm schema version 3 @Required public String fullName; @Required public Date birthday = new Date(); } ``` #### Kotlin ```kotlin class Person: RealmObject { // Realm schema version 3 var fullName: String = "" var birthday: Date = Date() } ``` ### D. Migration Functions To migrate the realm to conform to the updated `Person` schema, set the realm's schema version to `3` and define a migration function to set the value of `fullName` based on the existing `firstName` and `lastName` properties and the value of `birthday` based on `age`: #### Java ```java public class Migration implements RealmMigration { @Override public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { Long version = oldVersion; // DynamicRealm exposes an editable schema RealmSchema schema = realm.getSchema(); // Changes from version 0 to 1: Adding lastName. // All properties will be initialized with the default value "". if (version == 0L) { schema.get("Person") .addField("lastName", String.class, FieldAttribute.REQUIRED); version++; } // Changes from version 1 to 2: combine firstName/lastName into fullName if (version == 1L) { schema.get("Person") .addField("fullName", String.class, FieldAttribute.REQUIRED) .transform( DynamicRealmObject obj -> { String name = "${obj.getString("firstName")} ${obj.getString("lastName")}"; obj.setString("fullName", name); }) .removeField("firstName") .removeField("lastName"); version++; } // Changes from version 2 to 3: replace age with birthday if (version == 2L) { schema.get("Person") .addField("birthday", Date::class.java, FieldAttribute.REQUIRED) .transform(DynamicRealmObject obj -> { Int birthYear = Date().year - obj.getInt("age"); obj.setDate("birthday", Date(birthYear, 1, 1)); }) .removeField("age"); version++; } } }; @RealmModule(classes = { Person.class }) public class Module {} RealmConfiguration config = new RealmConfiguration.Builder() .modules(new Module()) .schemaVersion(3) // Must be bumped when the schema changes .migration(new Migration()) // Migration to run instead of throwing an exception .build(); ``` #### Kotlin ```kotlin val migration = object: RealmMigration { override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { var version: Long = oldVersion // DynamicRealm exposes an editable schema val schema: RealmSchema = realm.schema // Changes from version 0 to 1: Adding lastName. // All properties will be initialized with the default value "". if (version == 0L) { schema.get("Person")!! .addField("lastName", String::class.java, FieldAttribute.REQUIRED) version++ } // Changes from version 1 to 2: Combining firstName/lastName into fullName if (version == 1L) { schema.get("Person")!! .addField("fullName", String::class.java, FieldAttribute.REQUIRED) .transform { obj: DynamicRealmObject -> val name = "${obj.getString("firstName")} ${obj.getString("lastName")}" obj.setString("fullName", name) } .removeField("firstName") .removeField("lastName") version++ } // Changes from version 2 to 3: Replace age with birthday if (version == 2L) { schema.get("Person")!! .addField("birthday", Date::class.java, FieldAttribute.REQUIRED) .transform { obj: DynamicRealmObject -> var birthYear = Date().year - obj.getInt("age") obj.setDate("birthday", Date(birthYear, 1, 1)) } .removeField("age") version++ } } } @RealmModule(classes = { Person::class.java }) class Module val config = RealmConfiguration.Builder() .schemaVersion(3) // Must be bumped when the schema changes .migration(migration) // Migration to run instead of throwing an exception .build() ``` ================================================ FILE: docs/guides/model-data/relationships.md ================================================ # Relationships - Java SDK ## Relationships Realm allows you to define explicit relationships between the types of objects in an App. A relationship is an object property that references another Realm object rather than one of the primitive data types. You define relationships by setting the type of an object property to another object type in the property schema. Relationships are direct references to other objects in a realm, which means that you don't need bridge tables or explicit joins to define a relationship like you would in a relational database. Instead you can access related objects by reading and writing to the property that defines the relationship. Realm executes read operations lazily as they come in, so querying a relationship is just as performant as reading a regular property. There are three primary types of relationships between objects: - One-to-One Relationship - One-to-Many Relationship - Inverse Relationship You can define relationships, collections, and embedded objects in your object schema using the following types: - `RealmObject` - `RealmList ` Use annotations to indicate whether a given field represents a foreign key relationship or an embedded object relationship. For more information, see Relationship Annotations. ### To-One Relationship A **to-one** relationship means that an object is related in a specific way to no more than one other object. You define a to-one relationship for an object type in its object schema by specifying a property where the type is the related Realm object type. Setting a relationship field to null removes the connection between objects, but Realm does not delete the referenced object unless that object is embedded. ### To-Many Relationship A **to-many** relationship means that an object is related in a specific way to multiple objects. You can create a relationship between one object and any number of objects using a field of type `RealmList` where `T` is a Realm object in your application: ### Inverse Relationship An **inverse relationship** links an object back to any other objects that refer to it in a defined to-one or to-many relationship. Relationship definitions are unidirectional, so you must explicitly define a property in the object's model as an inverse relationship. For example, the to-many relationship "User has many Tasks" does not automatically create the inverse relationship "Task belongs to User". If you don't specify the inverse relationship in the object model, you would need to run a separate query to look up the user that is assigned to a given task. To define an inverse relationship, define a `LinkingObjects` property in your object model. The `LinkingObjects` definition specifies the object type and property name of the relationship that it inverts. Realm automatically updates implicit relationships whenever an object is added or removed in the specified relationship. You cannot manually set the value of an inverse relationship property. Fields annotated with `@LinkingObjects` must be: - marked `final` - of type `RealmResults` where `T` is the type at the opposite end of the relationship Since relationships are many-to-one or many-to-many, following inverse relationships can result in zero, one, or many objects. Like any other `RealmResults` set, you can query an inverse relationship. ## Define a Relationship Field > Warning: > Realm objects use getters and setters to persist updated field values to your realms. Always use getters and setters for updates. > ### Many-to-One To set up a many-to-one or one-to-one relationship, create a field whose type is a Realm object in your application: #### Java ```java import io.realm.RealmObject; public class Frog extends RealmObject { private String name; private int age; private String species; private String owner; private Frog bestFriend; public Frog(String name, int age, String species, String owner, Frog bestFriend) { this.name = name; this.age = age; this.species = species; this.owner = owner; this.bestFriend = bestFriend; } public Frog(){} // RealmObject subclasses must provide an empty constructor public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getSpecies() { return species; } public void setSpecies(String species) { this.species = species; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } public Frog getBestFriend() { return bestFriend; } public void setBestFriend(Frog bestFriend) { this.bestFriend = bestFriend; } } ``` #### Kotlin ```kotlin import io.realm.RealmObject open class Frog : RealmObject { var name: String? = null var age = 0 var species: String? = null var owner: String? = null var bestFriend: Frog? = null constructor( name: String?, age: Int, species: String?, owner: String?, bestFriend: Frog? ) { this.name = name this.age = age this.species = species this.owner = owner this.bestFriend = bestFriend } constructor() {} // RealmObject subclasses must provide an empty constructor } ``` > Important: > When you declare a to-one relationship in your object model, it must be an optional property. If you try to make a to-one relationship required, Realm throws an exception at runtime. > Each `Frog` references either zero `Frog` instances or one other `Frog` instance. Nothing prevents multiple `Frog` instances from referencing the same `Frog` as a best friend; the distinction between a many-to-one and a one-to-one relationship is up to your application. ### Many-to-Many #### Java ```java import io.realm.RealmList; import io.realm.RealmObject; public class Frog extends RealmObject { private String name; private int age; private String species; private String owner; private RealmList bestFriends; public Frog(String name, int age, String species, String owner, RealmList bestFriends) { this.name = name; this.age = age; this.species = species; this.owner = owner; this.bestFriends = bestFriends; } public Frog(){} // RealmObject subclasses must provide an empty constructor public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getSpecies() { return species; } public void setSpecies(String species) { this.species = species; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } public RealmList getBestFriends() { return bestFriends; } public void setBestFriends(RealmList bestFriends) { this.bestFriends = bestFriends; } } ``` #### Kotlin ```kotlin import io.realm.RealmList import io.realm.RealmObject open class Frog : RealmObject { var name: String? = null var age = 0 var species: String? = null var owner: String? = null var bestFriends: RealmList? = null constructor( name: String?, age: Int, species: String?, owner: String?, bestFriends: RealmList? ) { this.name = name this.age = age this.species = species this.owner = owner this.bestFriends = bestFriends } constructor() {} // RealmObject subclasses must provide an empty constructor } ``` `RealmList` s are containers of `RealmObject` s, but otherwise behave like a regular collection. You can use the same object in multiple `RealmList` s. ### Inverse Relationships By default, Realm relationships are unidirectional. You can follow a link from one class to a referenced class, but not in the opposite direction. Consider the following class defining a `Toad` with a list of `frogFriends`: #### Java ```java import io.realm.RealmList; import io.realm.RealmObject; public class Toad extends RealmObject { private RealmList frogFriends; public Toad(RealmList frogFriends) { this.frogFriends = frogFriends; } public Toad() {} public RealmList getFrogFriends() { return frogFriends; } public void setFrogFriends(RealmList frogFriends) { this.frogFriends = frogFriends; } } ``` #### Kotlin ```kotlin import io.realm.RealmList import io.realm.RealmObject open class Toad : RealmObject { var frogFriends: RealmList? = null constructor(frogFriends: RealmList?) { this.frogFriends = frogFriends } constructor() {} } ``` You can provide a link in the opposite direction, from `Frog` to `Toad`, with the `@LinkingObjects` annotation on a `final` (in Java) or `val` (in Kotlin) field of type `RealmResults`: #### Java ```java import io.realm.RealmObject; import io.realm.RealmResults; import io.realm.annotations.LinkingObjects; public class Frog extends RealmObject { private String name; private int age; private String species; private String owner; @LinkingObjects("frogFriends") private final RealmResults toadFriends = null; public Frog(String name, int age, String species, String owner) { this.name = name; this.age = age; this.species = species; this.owner = owner; } public Frog(){} // RealmObject subclasses must provide an empty constructor public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getSpecies() { return species; } public void setSpecies(String species) { this.species = species; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } } ``` #### Kotlin ```kotlin import io.realm.RealmObject import io.realm.RealmResults import io.realm.annotations.LinkingObjects open class Frog : RealmObject { var name: String? = null var age = 0 var species: String? = null var owner: String? = null @LinkingObjects("frogFriends") private val toadFriends: RealmResults? = null constructor(name: String?, age: Int, species: String?, owner: String?) { this.name = name this.age = age this.species = species this.owner = owner } constructor() {} // RealmObject subclasses must provide an empty constructor } ``` > Important: > Inverse relationship fields must be marked `final`. > ================================================ FILE: docs/guides/model-data.md ================================================ # Model Data - Java SDK An **object schema** is a configuration object that defines the fields and relationships of a Realm object type. Android Realm applications define object schemas with Java or Kotlin classes using Realm Schemas. Object schemas specify constraints on object fields such as the data type of each field, whether a field is required, and default field values. Schemas can also define relationships between object types in a realm. Modifying your application's Realm Schema requires you to migrate data from older versions of your Realm Schema to the new version. ## Realm Apps Every App has a Realm Schema composed of a list of object schemas for each type of object that the realms in that application may contain. Realm guarantees that all objects in a realm conform to the schema for their object type and validates objects whenever they're created, modified, or deleted. ## Relationships You can model **one-to-one** relationships in realm with `RealmObject` fields. You can model **one-to-many** and **many-to-one** relationships `RealmList` fields. Inverse relationships are the opposite end of a **one-to-many** or **many-to-one** relationship. You can make **inverse** relationships traversable with the `@LinkingObjects` annotation on a `RealmResults` field. In an instance of a `RealmObject`, inverse relationship fields contain the set of Realm objects that point to that object instance through the described relationship. You can find the same set of Realm objects with a manual query, but the inverse relationship field reduces boilerplate query code and capacity for error. ## Realm Objects Unlike normal Java objects, which contain their own data, a Realm object doesn't contain data. Instead, Realm objects read and write properties directly to Realm. Instances of Realm objects can be either **managed** or **unmanaged**. - **Managed** objects are: persisted in Realmalways up to datethread-confinedgenerally more lightweight than the unmanaged version, as they take up less space on the Java heap. - **Unmanaged** objects are just like ordinary Java objects, since they are not persisted and never update automatically. You can move unmanaged objects freely across threads. You can convert between the two states using `realm.copyToRealm()` and `realm.copyFromRealm()`. ### RealmProxy The `RealmProxy` classes are the Realm SDK's way of ensuring that Realm objects don't contain any data themselves. Instead, each class's `RealmProxy` accesses data directly in the database. For every model class in your project, the Realm annotation processor generates a corresponding `RealmProxy` class. This class extends your model class and is returned when you call `Realm.createObject()`. In your code, this object works just like your model class. ### Realm Object Limitations Realm objects: - cannot contain fields that use the `final` or `volatile` modifiers (except for inverse relationship fields). - cannot extend any object other than `RealmObject`. - must contain an empty constructor (if your class does not include any constructor, the automatically generated empty constructor will suffice) Naming limitations: - Class names cannot exceed 57 characters. - Class names must be unique within realm modules - Field names cannot exceed 63 characters. Size limitations: - `String` or `byte[]` fields cannot exceed 16 MB. Usage limitations: - Because Realm objects are live and can change at any time, their `hashCode()` value can change over time. As a result, you should not use `RealmObject` instances as a key in any map or set. ## Incremental Builds The bytecode transformer used by Realm supports incremental builds, but your application requires a full rebuild when adding or removing the following from a Realm object field: - an `@Ignore` annotation - the `static` keyword - the `transient` keyword You can perform a full rebuild with Build > Clean Project and Build > Rebuild Project in these cases. ## Schema Version A **schema version** identifies the state of a Realm Schema at some point in time. Realm tracks the schema version of each realm and uses it to map the objects in each realm to the correct schema. Schema versions are integers that you may include in the realm configuration when you open a realm. If a client application does not specify a version number when it opens a realm then the realm defaults to version `0`. > Important: > Migrations must update a realm to a higher schema version. Realm throws an error if a client application opens a realm with a schema version that is lower than the realm's current version or if the specified schema version is the same as the realm's current version but includes different object schemas. > ## Migrations A **local migration** is a migration for a realm with another realm. Local migrations have access to the existing Realm Schema, version, and objects and define logic that incrementally updates the realm to its new schema version. To perform a local migration you must specify a new schema version that is higher than the current version and provide a migration function when you open the out-of-date realm. With the SDK, you can update underlying data to reflect schema changes using manual migrations. During such a manual migration, you can define new and deleted properties when they are added or removed from your schema. The editable schema exposed via a `DynamicRealm` provides convenience functions for renaming fields. This gives you full control over the behavior of your data during complex schema migrations. > Tip: > During development of an application, `RealmObject` classes can change frequently. You can use `Realm.deleteRealm()`to delete the database file and eliminate the need to write a full migration for testing data. > ================================================ FILE: docs/guides/quick-start-local.md ================================================ # Quick Start - Java SDK This page contains information to quickly get Realm integrated into your app. Before you begin, ensure you have: - Installed the Java SDK ## Initialize Realm Before you can use Realm in your app, you must initialize the Realm library. Your application should initialize Realm just once each time the application runs. To initialize the Realm library, provide an Android `context` to the `Realm.init()` static function. You can provide an Activity, Fragment, or Application `context` for initialization with no difference in behavior. You can initialize the Realm library in the `onCreate()` method of an [application subclass](https://developer.android.com/reference/android/app/Application) to ensure that you only initialize Realm once each time the application runs. #### Java ```java Realm.init(this); // context, usually an Activity or Application ``` #### Kotlin ```kotlin Realm.init(this) // context, usually an Activity or Application ``` > Tip: > If you create your own `Application` subclass, you must add it to your application's `AndroidManifest.xml` to execute your custom application logic. Set the `android.name` property of your manifest's application definition to ensure that Android instantiates your `Application` subclass before any other class when a user launches your application. > > ```xml > > package="com.mongodb.example"> > > android:name=".MyApplicationSubclass" > ... > /> > > ``` > ## Define Your Object Model Your application's **data model** defines the structure of data stored within Realm. You can define your application's data model via Kotlin or Java classes in your application code with Realm Object Models. To define your application's data model, add the following class definitions to your application code: #### Java ```java import io.realm.RealmObject; import io.realm.annotations.PrimaryKey; import io.realm.annotations.Required; public class Task extends RealmObject { @PrimaryKey private String name; @Required private String status = TaskStatus.Open.name(); public void setStatus(TaskStatus status) { this.status = status.name(); } public String getStatus() { return this.status; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Task(String _name) { this.name = _name; } public Task() {} } ``` ```java public enum TaskStatus { Open("Open"), InProgress("In Progress"), Complete("Complete"); String displayName; TaskStatus(String displayName) { this.displayName = displayName; } } ``` #### Kotlin ```kotlin enum class TaskStatus(val displayName: String) { Open("Open"), InProgress("In Progress"), Complete("Complete"), } open class Task() : RealmObject() { @PrimaryKey var name: String = "task" @Required var status: String = TaskStatus.Open.name var statusEnum: TaskStatus get() { // because status is actually a String and another client could assign an invalid value, // default the status to "Open" if the status is unreadable return try { TaskStatus.valueOf(status) } catch (e: IllegalArgumentException) { TaskStatus.Open } } set(value) { status = value.name } } ``` ## Open a Realm Use `RealmConfiguration` to control the specifics of the realm you would like to open, including the name or location of the realm, whether to allow synchronous reads or writes to a realm on the UI thread, and more. #### Java ```java String realmName = "My Project"; RealmConfiguration config = new RealmConfiguration.Builder().name(realmName).build(); Realm backgroundThreadRealm = Realm.getInstance(config); ``` #### Kotlin ```kotlin val realmName: String = "My Project" val config = RealmConfiguration.Builder().name(realmName).build() val backgroundThreadRealm : Realm = Realm.getInstance(config) ``` ## Create, Read, Update, and Delete Objects Once you have opened a realm, you can modify the objects within that realm in a write transaction block. > Important: > By default, you can only read or write to a realm in your application's UI thread using asynchronous transactions. That is, you can only use `Realm` methods whose name ends with the word `Async` in the main thread of your Android application unless you explicitly allow the use of synchronous methods. > > This restriction exists for the benefit of your application users: performing read and write operations on the UI thread can lead to unresponsive or slow UI interactions, so it's usually best to handle these operations either asynchronously or in a background thread. To create a new `Task`, instantiate an instance of the `Task` class and add it to the realm in a write block: #### Java ```java Task Task = new Task("New Task"); backgroundThreadRealm.executeTransaction (transactionRealm -> { transactionRealm.insert(Task); }); ``` #### Kotlin ```kotlin val task : Task = Task() task.name = "New Task" backgroundThreadRealm.executeTransaction { transactionRealm -> transactionRealm.insert(task) } ``` You can retrieve a live collection of all items in the realm: #### Java ```java // all Tasks in the realm RealmResults Tasks = backgroundThreadRealm.where(Task.class).findAll(); ``` #### Kotlin ```kotlin // all tasks in the realm val tasks : RealmResults = backgroundThreadRealm.where().findAll() ``` You can also filter that collection using a filter: #### Java ```java // you can also filter a collection RealmResults TasksThatBeginWithN = Tasks.where().beginsWith("name", "N").findAll(); RealmResults openTasks = Tasks.where().equalTo("status", TaskStatus.Open.name()).findAll(); ``` #### Kotlin ```kotlin // you can also filter a collection val tasksThatBeginWithN : List = tasks.where().beginsWith("name", "N").findAll() val openTasks : List = tasks.where().equalTo("status", TaskStatus.Open.name).findAll() ``` To modify a task, update its properties in a write transaction block: #### Java ```java Task otherTask = Tasks.get(0); // all modifications to a realm must happen inside of a write block backgroundThreadRealm.executeTransaction( transactionRealm -> { Task innerOtherTask = transactionRealm.where(Task.class).equalTo("_id", otherTask.getName()).findFirst(); innerOtherTask.setStatus(TaskStatus.Complete); }); ``` #### Kotlin ```kotlin val otherTask: Task = tasks[0]!! // all modifications to a realm must happen inside of a write block backgroundThreadRealm.executeTransaction { transactionRealm -> val innerOtherTask : Task = transactionRealm.where().equalTo("name", otherTask.name).findFirst()!! innerOtherTask.status = TaskStatus.Complete.name } ``` Finally, you can delete a task by calling the `deleteFromRealm()` method in a write transaction block: #### Java ```java Task yetAnotherTask = Tasks.get(0); String yetAnotherTaskName = yetAnotherTask.getName(); // all modifications to a realm must happen inside of a write block backgroundThreadRealm.executeTransaction( transactionRealm -> { Task innerYetAnotherTask = transactionRealm.where(Task.class).equalTo("_id", yetAnotherTaskName).findFirst(); innerYetAnotherTask.deleteFromRealm(); }); ``` #### Kotlin ```kotlin val yetAnotherTask: Task = tasks.get(0)!! val yetAnotherTaskName: String = yetAnotherTask.name // all modifications to a realm must happen inside of a write block backgroundThreadRealm.executeTransaction { transactionRealm -> val innerYetAnotherTask : Task = transactionRealm.where().equalTo("name", yetAnotherTaskName).findFirst()!! innerYetAnotherTask.deleteFromRealm() } ``` ## Watch for Changes You can watch a realm, collection, or object for changes by attaching a custom `OrderedRealmCollectionChangeListener` with the `addChangeListener()` method: #### Java ```java // all Tasks in the realm RealmResults Tasks = uiThreadRealm.where(Task.class).findAllAsync(); Tasks.addChangeListener(new OrderedRealmCollectionChangeListener>() { @Override public void onChange(RealmResults collection, OrderedCollectionChangeSet changeSet) { // process deletions in reverse order if maintaining parallel data structures so indices don't change as you iterate OrderedCollectionChangeSet.Range[] deletions = changeSet.getDeletionRanges(); for (OrderedCollectionChangeSet.Range range : deletions) { Log.v("QUICKSTART", "Deleted range: " + range.startIndex + " to " + (range.startIndex + range.length - 1)); } OrderedCollectionChangeSet.Range[] insertions = changeSet.getInsertionRanges(); for (OrderedCollectionChangeSet.Range range : insertions) { Log.v("QUICKSTART", "Inserted range: " + range.startIndex + " to " + (range.startIndex + range.length - 1)); } OrderedCollectionChangeSet.Range[] modifications = changeSet.getChangeRanges(); for (OrderedCollectionChangeSet.Range range : modifications) { Log.v("QUICKSTART", "Updated range: " + range.startIndex + " to " + (range.startIndex + range.length - 1)); } } }); ``` #### Kotlin ```kotlin // all tasks in the realm val tasks : RealmResults = realm.where().findAllAsync() tasks.addChangeListener(OrderedRealmCollectionChangeListener> { collection, changeSet -> // process deletions in reverse order if maintaining parallel data structures so indices don't change as you iterate val deletions = changeSet.deletionRanges for (i in deletions.indices.reversed()) { val range = deletions[i] Log.v("QUICKSTART", "Deleted range: ${range.startIndex} to ${range.startIndex + range.length - 1}") } val insertions = changeSet.insertionRanges for (range in insertions) { Log.v("QUICKSTART", "Inserted range: ${range.startIndex} to ${range.startIndex + range.length - 1}") } val modifications = changeSet.changeRanges for (range in modifications) { Log.v("QUICKSTART", "Updated range: ${range.startIndex} to ${range.startIndex + range.length - 1}") } }) ``` ## Complete Example If you're running this project in a fresh Android Studio project, you can copy and paste this file into your application's `MainActivity` -- just remember to: - use a package declaration at the top of the file for your own project - update the `import` statements for `Task` and `TaskStatus` if you're using java #### Java ```java import io.realm.RealmObject; import io.realm.annotations.PrimaryKey; import io.realm.annotations.Required; public class Task extends RealmObject { @PrimaryKey private String name; @Required private String status = TaskStatus.Open.name(); public void setStatus(TaskStatus status) { this.status = status.name(); } public String getStatus() { return this.status; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Task(String _name) { this.name = _name; } public Task() {} } ``` ```java public enum TaskStatus { Open("Open"), InProgress("In Progress"), Complete("Complete"); String displayName; TaskStatus(String displayName) { this.displayName = displayName; } } ``` ```java import io.realm.OrderedCollectionChangeSet; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import android.util.Log; import io.realm.OrderedRealmCollectionChangeListener; import io.realm.Realm; import io.realm.RealmConfiguration; import io.realm.RealmResults; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.FutureTask; import com.mongodb.realm.examples.model.java.Task; import com.mongodb.realm.examples.model.java.TaskStatus; public class MainActivity extends AppCompatActivity { Realm uiThreadRealm; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Realm.init(this); // context, usually an Activity or Application String realmName = "My Project"; RealmConfiguration config = new RealmConfiguration.Builder().name(realmName).build(); uiThreadRealm = Realm.getInstance(config); addChangeListenerToRealm(uiThreadRealm); FutureTask Task = new FutureTask(new BackgroundQuickStart(), "test"); ExecutorService executorService = Executors.newFixedThreadPool(2); executorService.execute(Task); } private void addChangeListenerToRealm(Realm realm) { // all Tasks in the realm RealmResults Tasks = uiThreadRealm.where(Task.class).findAllAsync(); Tasks.addChangeListener(new OrderedRealmCollectionChangeListener>() { @Override public void onChange(RealmResults collection, OrderedCollectionChangeSet changeSet) { // process deletions in reverse order if maintaining parallel data structures so indices don't change as you iterate OrderedCollectionChangeSet.Range[] deletions = changeSet.getDeletionRanges(); for (OrderedCollectionChangeSet.Range range : deletions) { Log.v("QUICKSTART", "Deleted range: " + range.startIndex + " to " + (range.startIndex + range.length - 1)); } OrderedCollectionChangeSet.Range[] insertions = changeSet.getInsertionRanges(); for (OrderedCollectionChangeSet.Range range : insertions) { Log.v("QUICKSTART", "Inserted range: " + range.startIndex + " to " + (range.startIndex + range.length - 1)); } OrderedCollectionChangeSet.Range[] modifications = changeSet.getChangeRanges(); for (OrderedCollectionChangeSet.Range range : modifications) { Log.v("QUICKSTART", "Updated range: " + range.startIndex + " to " + (range.startIndex + range.length - 1)); } } }); } @Override protected void onDestroy() { super.onDestroy(); // the ui thread realm uses asynchronous transactions, so we can only safely close the realm // when the activity ends and we can safely assume that those transactions have completed uiThreadRealm.close(); } public class BackgroundQuickStart implements Runnable { @Override public void run() { String realmName = "My Project"; RealmConfiguration config = new RealmConfiguration.Builder().name(realmName).build(); Realm backgroundThreadRealm = Realm.getInstance(config); Task Task = new Task("New Task"); backgroundThreadRealm.executeTransaction (transactionRealm -> { transactionRealm.insert(Task); }); // all Tasks in the realm RealmResults Tasks = backgroundThreadRealm.where(Task.class).findAll(); // you can also filter a collection RealmResults TasksThatBeginWithN = Tasks.where().beginsWith("name", "N").findAll(); RealmResults openTasks = Tasks.where().equalTo("status", TaskStatus.Open.name()).findAll(); Task otherTask = Tasks.get(0); // all modifications to a realm must happen inside of a write block backgroundThreadRealm.executeTransaction( transactionRealm -> { Task innerOtherTask = transactionRealm.where(Task.class).equalTo("_id", otherTask.getName()).findFirst(); innerOtherTask.setStatus(TaskStatus.Complete); }); Task yetAnotherTask = Tasks.get(0); String yetAnotherTaskName = yetAnotherTask.getName(); // all modifications to a realm must happen inside of a write block backgroundThreadRealm.executeTransaction( transactionRealm -> { Task innerYetAnotherTask = transactionRealm.where(Task.class).equalTo("_id", yetAnotherTaskName).findFirst(); innerYetAnotherTask.deleteFromRealm(); }); // because this background thread uses synchronous realm transactions, at this point all // transactions have completed and we can safely close the realm backgroundThreadRealm.close(); } } } ``` #### Kotlin ```kotlin import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import android.util.Log import io.realm.* import io.realm.annotations.PrimaryKey import io.realm.annotations.Required import io.realm.kotlin.where import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.concurrent.FutureTask class MainActivity : AppCompatActivity() { lateinit var uiThreadRealm: Realm override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Realm.init(this) // context, usually an Activity or Application val realmName: String = "My Project" val config = RealmConfiguration.Builder() .name(realmName) .build() uiThreadRealm = Realm.getInstance(config) addChangeListenerToRealm(uiThreadRealm) val task : FutureTask = FutureTask(BackgroundQuickStart(), "test") val executorService: ExecutorService = Executors.newFixedThreadPool(2) executorService.execute(task) } fun addChangeListenerToRealm(realm : Realm) { // all tasks in the realm val tasks : RealmResults = realm.where().findAllAsync() tasks.addChangeListener(OrderedRealmCollectionChangeListener> { collection, changeSet -> // process deletions in reverse order if maintaining parallel data structures so indices don't change as you iterate val deletions = changeSet.deletionRanges for (i in deletions.indices.reversed()) { val range = deletions[i] Log.v("QUICKSTART", "Deleted range: ${range.startIndex} to ${range.startIndex + range.length - 1}") } val insertions = changeSet.insertionRanges for (range in insertions) { Log.v("QUICKSTART", "Inserted range: ${range.startIndex} to ${range.startIndex + range.length - 1}") } val modifications = changeSet.changeRanges for (range in modifications) { Log.v("QUICKSTART", "Updated range: ${range.startIndex} to ${range.startIndex + range.length - 1}") } }) } override fun onDestroy() { super.onDestroy() // the ui thread realm uses asynchronous transactions, so we can only safely close the realm // when the activity ends and we can safely assume that those transactions have completed uiThreadRealm.close() } class BackgroundQuickStart : Runnable { override fun run() { val realmName: String = "My Project" val config = RealmConfiguration.Builder().name(realmName).build() val backgroundThreadRealm : Realm = Realm.getInstance(config) val task : Task = Task() task.name = "New Task" backgroundThreadRealm.executeTransaction { transactionRealm -> transactionRealm.insert(task) } // all tasks in the realm val tasks : RealmResults = backgroundThreadRealm.where().findAll() // you can also filter a collection val tasksThatBeginWithN : List = tasks.where().beginsWith("name", "N").findAll() val openTasks : List = tasks.where().equalTo("status", TaskStatus.Open.name).findAll() val otherTask: Task = tasks[0]!! // all modifications to a realm must happen inside of a write block backgroundThreadRealm.executeTransaction { transactionRealm -> val innerOtherTask : Task = transactionRealm.where().equalTo("name", otherTask.name).findFirst()!! innerOtherTask.status = TaskStatus.Complete.name } val yetAnotherTask: Task = tasks.get(0)!! val yetAnotherTaskName: String = yetAnotherTask.name // all modifications to a realm must happen inside of a write block backgroundThreadRealm.executeTransaction { transactionRealm -> val innerYetAnotherTask : Task = transactionRealm.where().equalTo("name", yetAnotherTaskName).findFirst()!! innerYetAnotherTask.deleteFromRealm() } // because this background thread uses synchronous realm transactions, at this point all // transactions have completed and we can safely close the realm backgroundThreadRealm.close() } } } enum class TaskStatus(val displayName: String) { Open("Open"), InProgress("In Progress"), Complete("Complete"), } open class Task() : RealmObject() { @PrimaryKey var name: String = "task" @Required var status: String = TaskStatus.Open.name var statusEnum: TaskStatus get() { // because status is actually a String and another client could assign an invalid value, // default the status to "Open" if the status is unreadable return try { TaskStatus.valueOf(status) } catch (e: IllegalArgumentException) { TaskStatus.Open } } set(value) { status = value.name } } ``` ## Output Running the above code should produce output resembling the following: ```shell Successfully authenticated anonymously. Updated range: 0 to 1 Deleted range: 0 to 1 Successfully logged out. ``` ================================================ FILE: docs/guides/react-to-changes.md ================================================ # React to Changes - Java SDK Objects in Realm clients are **live objects** that update automatically to reflect data changes and emit notification events that you can subscribe to whenever their underlying data changes. Any modern app should be able to react when data changes, regardless of where that change originated. When a user adds a new item to a list, you may want to update the UI, show a notification, or log a message. When someone updates that item, you may want to change its visual state or fire off a network request. Finally, when someone deletes the item, you probably want to remove it from the UI. Realm's notification system allows you to watch for and react to changes in your data, independent of the writes that caused the changes. Realm emits three kinds of notifications: - Realm notifications whenever a specific realm commits a write transaction. - Collection notifications whenever any Realm object in a collection changes, including inserts, updates, and deletes. - Object notifications whenever a specific Realm object changes, including updates and deletes. ## Auto-Refresh Realm objects accessed on a thread associated with a [Looper](https://developer.android.com/reference/android/os/Looper.html) automatically update periodically to reflect changes to underlying data. The Android UI thread always contains a `Looper` instance. If you need to keep Realm objects around for long periods of time on any other thread, you should configure a `Looper` for that thread. > Warning: > Realms on a thread without a [Looper](https://developer.android.com/reference/android/os/Looper) do not automatically advance their version. This can increase the size of the realm in memory and on disk. Avoid using realm instances on non-Looper threads when possible. If you *do* open a realm on a non-Looper thread, close the realm when you're done using it. > ## Register a Realm Change Listener You can register a notification handler on an entire realm. Realm calls the notification handler whenever any write transaction involving that realm is committed. The handler receives no information about the change. This is useful when you want to know that there has been a change but do not care to know specifically what changed. For example, proof of concept apps often use this notification type and simply refresh the entire UI when anything changes. As the app becomes more sophisticated and performance-sensitive, the app developers shift to more granular notifications. > Example: > Suppose you are writing a real-time collaborative app. To give the sense that your app is buzzing with collaborative activity, you want to have an indicator that lights up when any change is made. In that case, a realm notification handler would be a great way to drive the code that controls the indicator. The following code shows how to observe a realm for changes with with `addChangeListener()`: > > #### Java > > ```java > public class MyActivity extends Activity { > private Realm realm; > private RealmChangeListener realmListener; > > @Override > protected void onCreate(Bundle savedInstanceState) { > super.onCreate(savedInstanceState); > realm = Realm.getDefaultInstance(); > realmListener = new RealmChangeListener() { > @Override > public void onChange(Realm realm) { > // ... do something with the updates (UI, etc.) ... > } > }; > // Observe realm notifications. > realm.addChangeListener(realmListener); > } > > @Override > protected void onDestroy() { > super.onDestroy(); > // Remove the listener. > realm.removeChangeListener(realmListener); > // Close the Realm instance. > realm.close(); > } > } > > ``` > > > #### Kotlin > > ```kotlin > class MyActivity : Activity() { > private lateinit var realm: Realm > private lateinit var realmListener: RealmChangeListener > > override fun onCreate(savedInstanceState: Bundle?) { > super.onCreate(savedInstanceState) > realm = Realm.getDefaultInstance() > realmListener = RealmChangeListener { > // ... do something with the updates (UI, etc.) ... > } > // Observe realm notifications. > realm.addChangeListener(realmListener) > } > > override fun onDestroy() { > super.onDestroy() > // Remove the listener. > realm.removeChangeListener(realmListener) > // Close the Realm instance. > realm.close() > } > } > > ``` > > > Important: > All threads that contain a `Looper` automatically refresh `RealmObject` and `RealmResult` instances when new changes are written to the realm. As a result, it isn't necessary to fetch those objects again when reacting to a `RealmChangeListener`, since those objects are already updated and ready to be redrawn to the screen. > ## Register a Collection Change Listener You can register a notification handler on a specific collection within a realm. The handler receives a description of changes since the last notification. Specifically, this description consists of three lists of indices: - The indices of the objects that were deleted. - The indices of the objects that were inserted. - The indices of the objects that were modified. Stop notification delivery by calling the `removeChangeListener()` or `removeAllChangeListeners()` methods. Notifications also stop if: - the object on which the listener is registered gets garbage collected. - the realm instance closes. Keep a strong reference to the object you're listening to for as long as you need the notifications. > Important: > In collection notification handlers, always apply changes in the following order: deletions, insertions, then modifications. Handling insertions before deletions may result in unexpected behavior. > Realm emits an initial notification after retrieving the collection. After that, Realm delivers collection notifications asynchronously whenever a write transaction adds, changes, or removes objects in the collection. Unlike realm notifications, collection notifications contain detailed information about the change. This enables sophisticated and selective reactions to changes. Collection notifications provide all the information needed to manage a list or other view that represents the collection in the UI. The following code shows how to observe a collection for changes with `addChangeListener()`: #### Java ```java RealmResults dogs = realm.where(Dog.class).findAll(); // Set up the collection notification handler. OrderedRealmCollectionChangeListener> changeListener = (collection, changeSet) -> { // For deletions, notify the UI in reverse order if removing elements the UI OrderedCollectionChangeSet.Range[] deletions = changeSet.getDeletionRanges(); for (int i = deletions.length - 1; i >= 0; i--) { OrderedCollectionChangeSet.Range range = deletions[i]; Log.v("EXAMPLE", range.length + " dogs deleted at " + range.startIndex); } OrderedCollectionChangeSet.Range[] insertions = changeSet.getInsertionRanges(); for (OrderedCollectionChangeSet.Range range : insertions) { Log.v("EXAMPLE", range.length + " dogs inserted at " + range.startIndex); } OrderedCollectionChangeSet.Range[] modifications = changeSet.getChangeRanges(); for (OrderedCollectionChangeSet.Range range : modifications) { Log.v("EXAMPLE", range.length + " dogs modified at " + range.startIndex); } }; // Observe collection notifications. dogs.addChangeListener(changeListener); ``` #### Kotlin ```kotlin val dogs = realm.where(Dog::class.java).findAll() // Set up the collection notification handler. val changeListener = OrderedRealmCollectionChangeListener { collection: RealmResults?, changeSet: OrderedCollectionChangeSet -> // For deletions, notify the UI in reverse order if removing elements the UI val deletions = changeSet.deletionRanges for (i in deletions.indices.reversed()) { val range = deletions[i] Log.v("EXAMPLE", "${range.length} dogs deleted at ${range.startIndex}") } val insertions = changeSet.insertionRanges for (range in insertions) { Log.v("EXAMPLE", "${range.length} dogs inserted at ${range.startIndex}") } val modifications = changeSet.changeRanges for (range in modifications) { Log.v("EXAMPLE", "${range.length} dogs modified at ${range.startIndex}") } } // Observe collection notifications. dogs.addChangeListener(changeListener) ``` ## Register an Object Change Listener You can register a notification handler on a specific object within a realm. Realm notifies your handler: - When the object is deleted. - When any of the object's properties change. The handler receives information about what fields changed and whether the object was deleted. Stop notification delivery by calling the `removeChangeListener()` or `removeAllChangeListeners()` methods. Notifications also stop if: - the object on which the listener is registered gets garbage collected. - the realm instance closes. Keep a strong reference of the object you're listening to for as long as you need the notifications. The following code shows how create a new instance of a class in a realm and observe that instance for changes with `addChangeListener()`: #### Java ```java // Create a dog in the realm. AtomicReference dog = new AtomicReference(); realm.executeTransaction(transactionRealm -> { dog.set(transactionRealm.createObject(Dog.class, new ObjectId())); dog.get().setName("Max"); }); // Set up the listener. RealmObjectChangeListener listener = (changedDog, changeSet) -> { if (changeSet.isDeleted()) { Log.i("EXAMPLE", "The dog was deleted"); return; } for (String fieldName : changeSet.getChangedFields()) { Log.i("EXAMPLE", "Field '" + fieldName + "' changed."); } }; // Observe object notifications. dog.get().addChangeListener(listener); // Update the dog to see the effect. realm.executeTransaction(r -> { dog.get().setName("Wolfie"); // -> "Field 'name' was changed." }); ``` #### Kotlin ```kotlin // Create a dog in the realm. var dog = Dog() realm.executeTransaction { transactionRealm -> dog = transactionRealm.createObject(Dog::class.java, ObjectId()) dog.name = "Max" } // Set up the listener. val listener = RealmObjectChangeListener { changedDog: Dog?, changeSet: ObjectChangeSet? -> if (changeSet!!.isDeleted) { Log.i("EXAMPLE", "The dog was deleted") } else { for (fieldName in changeSet.changedFields) { Log.i( "EXAMPLE", "Field '$fieldName' changed." ) } } } // Observe object notifications. dog.addChangeListener(listener) // Update the dog to see the effect. realm.executeTransaction { r: Realm? -> dog.name = "Wolfie" // -> "Field 'name' was changed." } ``` ## Unregister a Change Listener You can unregister a change listener by passing your change listener to `Realm.removeChangeListener()`. You can unregister all change listeners currently subscribed to changes in a realm or any of its linked objects or collections with `Realm.removeAllChangeListeners()`. ## Use Realm in System Apps on Custom ROMs Realm uses named pipes in order to support notifications and access to the realm file from multiple processes. While this is allowed by default for normal user apps, it is disallowed for system apps. You can define a system apps by setting `android:sharedUserId="android.uid.system"` in the Android manifest. When working with a system app, you may see a security violation in Logcat that looks something like this: ``` 05-24 14:08:08.984 6921 6921 W .realmsystemapp: type=1400 audit(0.0:99): avc: denied { write } for name="realm.testapp.com.realmsystemapp-Bfqpnjj4mUvxWtfMcOXBCA==" dev="vdc" ino=14660 scontext=u:r:system_app:s0 tcontext=u:object_r:apk_data_file:s0 tclass=dir permissive=0 05-24 14:08:08.984 6921 6921 W .realmsystemapp: type=1400 audit(0.0:100): avc: denied { write } for name="realm.testapp.com.realmsystemapp-Bfqpnjj4mUvxWtfMcOXBCA==" dev="vdc" ino=14660 scontext=u:r:system_app:s0 tcontext=u:object_r:apk_data_file:s0 tclass=dir permissive=0 ``` In order to fix this you need to adjust the SELinux security rules in the ROM. This can be done by using the tool `audit2allow`, which ships as part of AOSP: 1. Pull the current policy from the device: `adb pull /sys/fs/selinux/policy` 2. Copy the SELinux error inside a text file called input.txt. 3. Run the `audit2allow` tool: `audit2allow -p policy -i input.txt` 4. The tool should output a rule you can add to your existing policy to enable the use of Realm. An example of such a policy is provided below: ``` # Allow system_app to create named pipes required by Realm # Credit: https://github.com/mikalackis/platform_vendor_ariel/blob/master_oreo/sepolicy/system_app.te allow system_app fuse:fifo_file create; allow system_app system_app_data_file:fifo_file create; allow system_app system_app_data_file:fifo_file { read write }; allow system_app system_app_data_file:fifo_file open; ``` > Seealso: > `audit2allow` is produced when compiling AOSP/ROM and only runs on Linux. You can read more about it [here](https://source.android.com/security/selinux/validate#using_audit2allow). > > Note: > Since Android Oreo, Google changed the way it configures SELinux. The default security policies are now much more modularized. Read more about that [here](https://source.android.com/security/selinux/images/SELinux_Treble.pdf). > ## Change Notification Limits Changes in nested documents deeper than four levels down do not trigger change notifications. If you have a data structure where you need to listen for changes five levels down or deeper, workarounds include: - Refactor the schema to reduce nesting. - Add something like "push-to-refresh" to enable users to manually refresh data. ================================================ FILE: docs/guides/realm-files/bundle-a-realm.md ================================================ # Bundle a Realm File - Java SDK Realm supports **bundling** realm files. When you bundle a realm file, you include a database and all of its data in your application download. This allows users to start applications for the first time with a set of initial data. ## Overview To create and bundle a realm file with your application: 1. Create a realm file that contains the data you'd like to bundle. 2. Bundle the realm file in the //src/main/assets folder of your production application. 3. In your production application, open the realm from the bundled asset file. ## Create a Realm File for Bundling 1. Build a temporary realm app that shares the data model of your application. 2. Open a realm and add the data you wish to bundle. 3. Use the `writeCopyTo()` method to copy the realm to a new file. `writeCopyTo()` automatically compacts your realm to the smallest possible size before copying. ## Bundle a Realm File in Your Production Application Now that you have a copy of the realm that contains the initial data, bundle it with your production application. 1. Search your application logs to find the location of the realm file copy you just created. 2. Using the "Device File Explorer" widget in the bottom right of your Android Studio window, navigate to the file. 3. Right click on the file and select "Save As". Navigate to the //src/main/assets folder of your production application. Save a copy of the realm file there. > Tip: > If your application does not already contain an asset folder, you can create one by right clicking on your top-level application folder () in Android Studio and selecting New > Folder > Assets Folder in the menu. > ## Open a Realm from a Bundled Realm File Now that you have a copy of the realm included with your production application, you need to add code to use it. Use the `assetFile()` method when configuring your realm to open the realm from the bundled file. ================================================ FILE: docs/guides/realm-files/encryption.md ================================================ # Encrypt a Realm - Java SDK ## Overview You can encrypt your realms to ensure that the data stored to disk can't be read outside of your application. You encrypt the realm file on disk with AES-256 + SHA-2 by supplying a 64-byte encryption key when opening a realm. Realm transparently encrypts and decrypts data with standard [AES-256 encryption](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard) using the first 256 bits of the given 512-bit encryption key. Realm uses the other 256 bits of the 512-bit encryption key to validate integrity using a [hash-based message authentication code (HMAC)](https://en.wikipedia.org/wiki/HMAC). > Warning: > Do not use cryptographically-weak hashes for realm encryption keys. For optimal security, we recommend generating random rather than derived encryption keys. > ## Considerations The following are key impacts to consider when encrypting a realm. ### Storing & Reusing Keys You **must** pass the same encryption key to `RealmConfiguration.Builder.encryptionKey()` each time you open the realm. If you don't provide a key or specify the wrong key for an encrypted realm, the Realm SDK throws an error. Apps should store the encryption key in the [Android KeyStore](https://developer.android.com/training/articles/keystore.html) so that other apps cannot read the key. ### Performance Impact Reads and writes on encrypted realms can be up to 10% slower than unencrypted realms. ### Accessing an Encrypted Realm from Multiple Processes > Version changed: 10.14.0 Starting with Realm Java SDK version 10.14.0, Realm supports opening the same encrypted realm in multiple processes. If your app uses Realm Java SDK version 10.14.0 or earlier, attempting to open an encrypted realm from multiple processes throws this error: `Encrypted interprocess sharing is currently unsupported.` ## Example The following steps describe the recommended way to use the [Android KeyStore](https://developer.android.com/training/articles/keystore.html) for encryption with Realm: 1. Generate an asymmetric RSA key that Android can securely store and retrieve using the Android KeyStore. Versions M and above require user PIN or fingerprint to unlock the KeyStore. 2. Generate a symmetric key (AES) you can use to encrypt the realm. 3. Encrypt the symmetric AES key using your private RSA key. 4. Store the encrypted AES key on filesystem (in a `SharedPreferences`, for example). When you need to use your encrypted realm: 1. Retrieve your encrypted AES key. 2. Decrypt your encrypted AES key using the public RSA key. 3. Use the decrypted AES key in the `RealmConfiguration` to open the encrypted realm. > Seealso: > For an end-to-end example of storing and reusing encryption keys, see the [store_password](https://github.com/realm/realm-java/tree/feature/example/store_password/examples/StoreEncryptionPassword) example project, which demonstrates the fingerprint API. > ### Generate and Store an Encryption Key The following code demonstrates how to securely generate and store an encryption key for a realm: #### Java ```java // Create a key to encrypt a realm and save it securely in the keystore public byte[] getNewKey() { // open a connection to the android keystore KeyStore keyStore; try { keyStore = KeyStore.getInstance("AndroidKeyStore"); keyStore.load(null); } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) { Log.v("EXAMPLE", "Failed to open the keystore."); throw new RuntimeException(e); } // create a securely generated random asymmetric RSA key byte[] realmKey = new byte[Realm.ENCRYPTION_KEY_LENGTH]; new SecureRandom().nextBytes(realmKey); // create a cipher that uses AES encryption -- we'll use this to encrypt our key Cipher cipher; try { cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7); } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { Log.e("EXAMPLE", "Failed to create a cipher."); throw new RuntimeException(e); } // generate secret key KeyGenerator keyGenerator; try { keyGenerator = KeyGenerator.getInstance( KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); } catch (NoSuchAlgorithmException | NoSuchProviderException e) { Log.e("EXAMPLE", "Failed to access the key generator."); throw new RuntimeException(e); } KeyGenParameterSpec keySpec = new KeyGenParameterSpec.Builder( "realm_key", KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_CBC) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) .setUserAuthenticationRequired(true) .setUserAuthenticationValidityDurationSeconds( AUTH_VALID_DURATION_IN_SECOND) .build(); try { keyGenerator.init(keySpec); } catch (InvalidAlgorithmParameterException e) { Log.e("EXAMPLE", "Failed to generate a secret key."); throw new RuntimeException(e); } keyGenerator.generateKey(); // access the generated key in the android keystore, then // use the cipher to create an encrypted version of the key byte[] initializationVector; byte[] encryptedKeyForRealm; try { SecretKey secretKey = (SecretKey) keyStore.getKey("realm_key", null); cipher.init(Cipher.ENCRYPT_MODE, secretKey); encryptedKeyForRealm = cipher.doFinal(realmKey); initializationVector = cipher.getIV(); } catch (InvalidKeyException | UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException | BadPaddingException | IllegalBlockSizeException e) { Log.e("EXAMPLE", "Failed encrypting the key with the secret key."); throw new RuntimeException(e); } // keep the encrypted key in shared preferences // to persist it across application runs byte[] initializationVectorAndEncryptedKey = new byte[Integer.BYTES + initializationVector.length + encryptedKeyForRealm.length]; ByteBuffer buffer = ByteBuffer.wrap(initializationVectorAndEncryptedKey); buffer.order(ByteOrder.BIG_ENDIAN); buffer.putInt(initializationVector.length); buffer.put(initializationVector); buffer.put(encryptedKeyForRealm); activity.getSharedPreferences("realm_key", Context.MODE_PRIVATE).edit() .putString("iv_and_encrypted_key", Base64.encodeToString(initializationVectorAndEncryptedKey, Base64.NO_WRAP)) .apply(); return realmKey; // pass to a realm configuration via encryptionKey() } ``` #### Kotlin ```kotlin // Create a key to encrypt a realm and save it securely in the keystore fun getNewKey(): ByteArray { // open a connection to the android keystore val keyStore: KeyStore try { keyStore = KeyStore.getInstance("AndroidKeyStore") keyStore.load(null) } catch (e: Exception) { Log.v("EXAMPLE", "Failed to open the keystore.") throw RuntimeException(e) } // create a securely generated random asymmetric RSA key val realmKey = ByteArray(Realm.ENCRYPTION_KEY_LENGTH) SecureRandom().nextBytes(realmKey) // create a cipher that uses AES encryption -- we'll use this to encrypt our key val cipher: Cipher cipher = try { Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7) } catch (e: Exception) { Log.e("EXAMPLE", "Failed to create a cipher.") throw RuntimeException(e) } // generate secret key val keyGenerator: KeyGenerator keyGenerator = try { KeyGenerator.getInstance( KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore") } catch (e: NoSuchAlgorithmException) { Log.e("EXAMPLE", "Failed to access the key generator.") throw RuntimeException(e) } val keySpec = KeyGenParameterSpec.Builder( "realm_key", KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_CBC) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) .setUserAuthenticationRequired(true) .setUserAuthenticationValidityDurationSeconds( AUTH_VALID_DURATION_IN_SECOND) .build() try { keyGenerator.init(keySpec) } catch (e: InvalidAlgorithmParameterException) { Log.e("EXAMPLE", "Failed to generate a secret key.") throw RuntimeException(e) } keyGenerator.generateKey() // access the generated key in the android keystore, then // use the cipher to create an encrypted version of the key val initializationVector: ByteArray val encryptedKeyForRealm: ByteArray try { val secretKey = keyStore.getKey("realm_key", null) as SecretKey cipher.init(Cipher.ENCRYPT_MODE, secretKey) encryptedKeyForRealm = cipher.doFinal(realmKey) initializationVector = cipher.iv } catch (e: Exception) { Log.e("EXAMPLE", "Failed encrypting the key with the secret key.") throw RuntimeException(e) } // keep the encrypted key in shared preferences // to persist it across application runs val initializationVectorAndEncryptedKey = ByteArray(Integer.BYTES + initializationVector.size + encryptedKeyForRealm.size) val buffer = ByteBuffer.wrap(initializationVectorAndEncryptedKey) buffer.order(ByteOrder.BIG_ENDIAN) buffer.putInt(initializationVector.size) buffer.put(initializationVector) buffer.put(encryptedKeyForRealm) activity!!.getSharedPreferences("realm_key", Context.MODE_PRIVATE).edit() .putString("iv_and_encrypted_key", Base64.encodeToString(initializationVectorAndEncryptedKey, Base64.NO_WRAP)) .apply() return realmKey // pass to a realm configuration via encryptionKey() } ``` ### Access an Existing Encryption Key The following code demonstrates how to access and decrypt a securely stored encryption key for a realm: #### Java ```java // Access the encrypted key in the keystore, decrypt it with the secret, // and use it to open and read from the realm again public byte[] getExistingKey() { // open a connection to the android keystore KeyStore keyStore; try { keyStore = KeyStore.getInstance("AndroidKeyStore"); keyStore.load(null); } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) { Log.e("EXAMPLE", "Failed to open the keystore."); throw new RuntimeException(e); } // access the encrypted key that's stored in shared preferences byte[] initializationVectorAndEncryptedKey = Base64.decode(activity .getSharedPreferences("realm_key", Context.MODE_PRIVATE) .getString("iv_and_encrypted_key", null), Base64.DEFAULT); ByteBuffer buffer = ByteBuffer.wrap(initializationVectorAndEncryptedKey); buffer.order(ByteOrder.BIG_ENDIAN); // extract the length of the initialization vector from the buffer int initializationVectorLength = buffer.getInt(); // extract the initialization vector based on that length byte[] initializationVector = new byte[initializationVectorLength]; buffer.get(initializationVector); // extract the encrypted key byte[] encryptedKey = new byte[initializationVectorAndEncryptedKey.length - Integer.BYTES - initializationVectorLength]; buffer.get(encryptedKey); // create a cipher that uses AES encryption to decrypt our key Cipher cipher; try { cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7); } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { Log.e("EXAMPLE", "Failed to create cipher."); throw new RuntimeException(e); } // decrypt the encrypted key with the secret key stored in the keystore byte[] decryptedKey; try { final SecretKey secretKey = (SecretKey) keyStore.getKey("realm_key", null); final IvParameterSpec initializationVectorSpec = new IvParameterSpec(initializationVector); cipher.init(Cipher.DECRYPT_MODE, secretKey, initializationVectorSpec); decryptedKey = cipher.doFinal(encryptedKey); } catch (InvalidKeyException e) { Log.e("EXAMPLE", "Failed to decrypt. Invalid key."); throw new RuntimeException(e); } catch (UnrecoverableKeyException | NoSuchAlgorithmException | BadPaddingException | KeyStoreException | IllegalBlockSizeException | InvalidAlgorithmParameterException e) { Log.e("EXAMPLE", "Failed to decrypt the encrypted realm key with the secret key."); throw new RuntimeException(e); } return decryptedKey; // pass to a realm configuration via encryptionKey() } ``` #### Kotlin ```kotlin // Access the encrypted key in the keystore, decrypt it with the secret, // and use it to open and read from the realm again fun getExistingKey(): ByteArray { // open a connection to the android keystore val keyStore: KeyStore try { keyStore = KeyStore.getInstance("AndroidKeyStore") keyStore.load(null) } catch (e: Exception) { Log.e("EXAMPLE", "Failed to open the keystore.") throw RuntimeException(e) } // access the encrypted key that's stored in shared preferences val initializationVectorAndEncryptedKey = Base64.decode(activity ?.getSharedPreferences("realm_key", Context.MODE_PRIVATE) ?.getString("iv_and_encrypted_key", null), Base64.DEFAULT) val buffer = ByteBuffer.wrap(initializationVectorAndEncryptedKey) buffer.order(ByteOrder.BIG_ENDIAN) // extract the length of the initialization vector from the buffer val initializationVectorLength = buffer.int // extract the initialization vector based on that length val initializationVector = ByteArray(initializationVectorLength) buffer[initializationVector] // extract the encrypted key val encryptedKey = ByteArray(initializationVectorAndEncryptedKey.size - Integer.BYTES - initializationVectorLength) buffer[encryptedKey] // create a cipher that uses AES encryption to decrypt our key val cipher: Cipher cipher = try { Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7) } catch (e: Exception) { Log.e("EXAMPLE", "Failed to create cipher.") throw RuntimeException(e) } // decrypt the encrypted key with the secret key stored in the keystore val decryptedKey: ByteArray decryptedKey = try { val secretKey = keyStore.getKey("realm_key", null) as SecretKey val initializationVectorSpec = IvParameterSpec(initializationVector) cipher.init(Cipher.DECRYPT_MODE, secretKey, initializationVectorSpec) cipher.doFinal(encryptedKey) } catch (e: InvalidKeyException) { Log.e("EXAMPLE", "Failed to decrypt. Invalid key.") throw RuntimeException(e) } catch (e: Exception ) { Log.e("EXAMPLE", "Failed to decrypt the encrypted realm key with the secret key.") throw RuntimeException(e) } return decryptedKey // pass to a realm configuration via encryptionKey() } ``` ================================================ FILE: docs/guides/realm-files/open-and-close-a-realm.md ================================================ # Open & Close a Realm - Java SDK Interacting with realms in an Android application uses the following high-level series of steps: 1. Create a configuration for the realm you want to open. 2. Open the realm using the config. 3. Close the realm to free up resources when you're finished. ## The Default Realm You can save any `RealmConfiguration` as the default for your application using the `setDefaultConfiguration()` method: #### Java ```java RealmConfiguration config = new RealmConfiguration.Builder() .name("default-realm") .allowQueriesOnUiThread(true) .allowWritesOnUiThread(true) .compactOnLaunch() .inMemory() .build(); // set this config as the default realm Realm.setDefaultConfiguration(config); ``` #### Kotlin ```kotlin val config = RealmConfiguration.Builder() .name("default-realm") .allowQueriesOnUiThread(true) .allowWritesOnUiThread(true) .compactOnLaunch() .inMemory() .build() // set this config as the default realm Realm.setDefaultConfiguration(config) ``` You can then use `getDefaultConfiguration()` to access that configuration, or `getDefaultInstance()` to open a realm with that configuration: #### Java ```java Realm realm = Realm.getDefaultInstance(); Log.v("EXAMPLE","Successfully opened the default realm at: " + realm.getPath()); ``` #### Kotlin ```kotlin val realm = Realm.getDefaultInstance() Log.v("EXAMPLE","Successfully opened the default realm at: ${realm.path}") ``` ## Local Realms Local realms store data only on the client device. You can customize the settings for a local realm with `RealmConfiguration`. ### Local Realm Configuration To configure settings for a realm, create a `RealmConfiguration` with a `RealmConfiguration.Builder`. The following example configures a local realm with: - the file name "alternate-realm" - synchronous reads explicitly allowed on the UI thread - synchronous writes explicitly allowed on the UI thread - automatic compaction when launching the realm to save file space #### Java ```java RealmConfiguration config = new RealmConfiguration.Builder() .name("alternate-realm") .allowQueriesOnUiThread(true) .allowWritesOnUiThread(true) .compactOnLaunch() .build(); Realm realm = Realm.getInstance(config); Log.v("EXAMPLE", "Successfully opened a realm at: " + realm.getPath()); ``` #### Kotlin ```kotlin val config = RealmConfiguration.Builder() .name("alternate-realm") .allowQueriesOnUiThread(true) .allowWritesOnUiThread(true) .compactOnLaunch() .build() val realm = Realm.getInstance(config) Log.v("EXAMPLE", "Successfully opened a realm at: ${realm.path}") ``` > Important: > By default, you can only read or write to a realm in your application's UI thread using asynchronous transactions. That is, you can only use `Realm` methods whose name ends with the word `Async` in the main thread of your Android application unless you explicitly allow the use of synchronous methods. > > This restriction exists for the benefit of your application users: performing read and write operations on the UI thread can lead to unresponsive or slow UI interactions, so it's usually best to handle these operations either asynchronously or in a background thread. ### Open a Local Realm To open a realm, create a `RealmConfiguration` with `RealmConfiguration.Builder` and pass the resulting `RealmConfiguration` to `getInstance()` or `getInstanceAsync()`: #### Java ```java RealmConfiguration config = new RealmConfiguration.Builder() .allowQueriesOnUiThread(true) .allowWritesOnUiThread(true) .build(); Realm realm; try { realm = Realm.getInstance(config); Log.v("EXAMPLE", "Successfully opened a realm at: " + realm.getPath()); } catch (RealmFileException ex) { Log.v("EXAMPLE", "Error opening the realm."); Log.v("EXAMPLE", ex.toString()); } ``` #### Kotlin ```kotlin val config = RealmConfiguration.Builder() .allowQueriesOnUiThread(true) .allowWritesOnUiThread(true) .build() var realm: Realm try { realm = Realm.getInstance(config) Log.v("EXAMPLE", "Successfully opened a realm at: ${realm.path}") } catch(ex: RealmFileException) { Log.v("EXAMPLE", "Error opening the realm.") Log.v("EXAMPLE", ex.toString()) } ``` ### Read-Only Realms It's sometimes useful to ship a prepared realm file with your app that contains shared data that does not frequently change. You can use the `readOnly()` method when configuring your realm to make it read-only. This can prevent accidental writes to the realm and causes the realm to throw an `IllegalStateException` if a write occurs. > Warning: > Read-only realms are only enforced as read-only in process. The realm file itself is still writeable. > #### Java ```java RealmConfiguration config = new RealmConfiguration.Builder() .assetFile("bundled.realm") .readOnly() .modules(new BundledRealmModule()) .build(); ``` #### Kotlin ```kotlin val config = RealmConfiguration.Builder() .assetFile("readonly.realm") .readOnly() .modules(BundledRealmModule()) .build() ``` ### In-Memory Realms You can create a realm that runs entirely in memory without being written to a file. When memory runs low on an Android device, in-memory realms may [swap](https://en.wikipedia.org/wiki/Memory_paging#Terminology) temporarily from main memory to disk space. The SDK deletes all files created by an in-memory realm when: - the realm closes - all references to that realm fall out of scope To create an in-memory realm, use `inMemory()` when configuring your realm: #### Java ```java RealmConfiguration config = new RealmConfiguration.Builder() .inMemory() .name("java.transient.realm") .build(); Realm realm = Realm.getInstance(config); ``` #### Kotlin ```kotlin val config = RealmConfiguration.Builder() .inMemory() .name("kt.transient.realm") .build() val realm = Realm.getInstance(config) ``` ### Dynamic Realms Conventional realms define a schema using `RealmObject` subclasses or the `RealmModel` interface. A `DynamicRealm` uses strings to define a schema at runtime. Opening a dynamic realm uses the same configuration as a conventional realm, but dynamic realms ignore all configured schema, migration, and schema versions. Dynamic realms offer flexibility at the expense of type safety and performance. As a result, only use dynamic realms when that flexibility is required, such as during migrations, manual client resets, and when working with string-based data like CSV files or JSON. To open a Dynamic Realm with a mutable schema, use `DynamicRealm`: #### Java ```java RealmConfiguration config = new RealmConfiguration.Builder() .allowWritesOnUiThread(true) .allowQueriesOnUiThread(true) .name("java.dynamic.realm") .build(); DynamicRealm dynamicRealm = DynamicRealm.getInstance(config); // all objects in a DynamicRealm are DynamicRealmObjects AtomicReference frog = new AtomicReference<>(); dynamicRealm.executeTransaction(transactionDynamicRealm -> { // add type Frog to the schema with name and age fields dynamicRealm.getSchema() .create("Frog") .addField("name", String.class) .addField("age", int.class); frog.set(transactionDynamicRealm.createObject("Frog")); frog.get().set("name", "Wirt Jr."); frog.get().set("age", 42); }); // access all fields in a DynamicRealm using strings String name = frog.get().getString("name"); int age = frog.get().getInt("age"); // because an underlying schema still exists, // accessing a field that does not exist throws an exception try { frog.get().getString("doesn't exist"); } catch (IllegalArgumentException e) { Log.e("EXAMPLE", "That field doesn't exist."); } // Queries still work normally RealmResults frogs = dynamicRealm.where("Frog") .equalTo("name", "Wirt Jr.") .findAll(); ``` #### Kotlin ```kotlin val config = RealmConfiguration.Builder() .allowWritesOnUiThread(true) .allowQueriesOnUiThread(true) .name("kt.dynamic.realm") .build() val dynamicRealm = DynamicRealm.getInstance(config) // all objects in a DynamicRealm are DynamicRealmObjects var frog: DynamicRealmObject? = null dynamicRealm.executeTransaction { transactionDynamicRealm: DynamicRealm -> // add type Frog to the schema with name and age fields dynamicRealm.schema .create("Frog") .addField("name", String::class.java) .addField("age", Integer::class.java) frog = transactionDynamicRealm.createObject("Frog") frog?.set("name", "Wirt Jr.") frog?.set("age", 42) } // access all fields in a DynamicRealm using strings val name = frog?.getString("name") val age = frog?.getInt("age") // because an underlying schema still exists, // accessing a field that does not exist throws an exception try { frog?.getString("doesn't exist") } catch (e: IllegalArgumentException) { Log.e("EXAMPLE", "That field doesn't exist.") } // Queries still work normally val frogs = dynamicRealm.where("Frog") .equalTo("name", "Wirt Jr.") .findAll() ``` ## Close a Realm It is important to remember to call the `close()` method when done with a realm instance to free resources. Neglecting to close realms can lead to an `OutOfMemoryError`. #### Java ```java realm.close(); ``` #### Kotlin ```kotlin realm.close() ``` ## Configure Which Classes to Include in Your Realm Schema Realm modules are collections of Realm object models. Specify a module or modules when opening a realm to control which classes Realm should include in your schema. If you do not specify a module, Realm uses the default module, which includes all Realm objects defined in your application. > Note: > Libraries that include Realm must expose and use their schema through a module. Doing so prevents the library from generating the default `RealmModule`, which would conflict with the default `RealmModule` used by any app that includes the library. Apps using the library access library classes through the module. > ================================================ FILE: docs/guides/realm-files.md ================================================ # Work with Realm Files - Java SDK A **realm** is a set of related objects that conform to a pre-defined schema. Realms may contain more than one type of data as long as a schema exists for each type. Every realm stores data in a separate realm file that contains a binary encoding of each object in the realm. You can automatically synchronize realm across multiple devices and set up reactive event handlers that call a function any time an object in a realm is created, modified, or deleted. ## The Realm Lifecycle Every realm instance consumes a significant amount of resources. Opening and closing a realm are both expensive operations, but keeping a realm open also incurs significant resource overhead. To maximize the performance of your application, you should minimize the number of open realms at any given time and limit the number of open and close operations used. However, opening a realm is not always consistently expensive. If the realm is already open within the same process or thread, opening an additional instance requires fewer resources: - If the realm is not open within the same process, opening the realm is expensive. - If the realm is already open on a different thread within the same process, opening the realm is less expensive, but still nontrivial. - If the realm is already open on the same thread within the same process, opening the realm requires minimal additional resources. When you open a realm for the first time, Realm performs the memory-mapping and schema validation required to read and write data to the realm. Additional instances of that realm on the same thread use the same underlying resources. Instances of that realm on separate threads use some of the same underlying resources. When all connections to a realm are closed in a thread, Realm frees the thread resources used to connect to that realm. When all connections to a realm are closed in a process, Realm frees all resources used to connect to that realm. As a best practice, we recommend tying the realm instance lifecycle to the lifecycles of the views that observe the realm. For instance, consider a `RecyclerView` that displays `RealmResults` data via a `Fragment`. You could: - Open a single realm that contains the data for that view in the `Fragment.onCreateView()` lifecycle method. - Close that same realm in the `Fragment.onDestroyView()` lifecycle method. > Note: > If your realm is especially large, fetching a realm instance in `Fragment.onCreateView()` may briefly block rendering. If opening your realm in `onCreateView()` causes performance issues, consider managing the realm from `Fragment.onStart()` and `Fragment.onStop()` instead. > If multiple `Fragment` instances require access to the same dataset, you could manage a single realm in the enclosing `Activity`: - Open the realm in the `Activity.onCreate()` lifecycle method. - Close the realm in the `Activity.onDestroy()` lifecycle method. ## Multi-process You cannot access encrypted or [delete me]s simultaneously from different processes. However, local realms function normally across processes, so you can read, write, and receive notifications from multiple APKs. ## Realm Schema A **Realm Schema** is a list of valid object schemas that each define an object type that an App may persist. All objects in a realm must conform to the Realm Schema. By default, the SDK automatically adds all classes in your project that derive from `RealmObject` to the realm schema. Client applications provide a Realm Schema when they open a realm. If a realm already contains data, then Realm validates each existing object to ensure that an object schema was provided for its type and that it meets all of the constraints specified in the schema. > Example: > A realm that contains basic data about books in libraries might use a schema like the following: > > ```json > [ > { > "type": "Library", > "properties": { > "address": "string", > "books": "Book[]" > } > }, > { > "type": "Book", > "primaryKey": "isbn", > "properties": { > "isbn": "string", > "title": "string", > "author": "string", > "numberOwned": { "type": "int?", "default": 0 }, > "numberLoaned": { "type": "int?", "default": 0 } > } > } > ] > ``` > ## Find Your Realm File Realm stores a binary encoded version of every object and type in a realm in a single `.realm` file. The filesystem used by Android emulators is not directly accessible from the machine running Realm Studio. You must download the file from the emulator before you can access it. First, find the path of the file on the emulator: ```java // Run this on the device to find the path on the emulator Realm realm = Realm.getDefaultInstance(); Log.i("Realm", realm.getPath()); ``` Then, download the file using ADB. You can do this while the app is running. ```java > adb pull ``` You can also upload the modified file again using ADB, but only when the app isn't running. Uploading a modified file while the app is running can corrupt the file. ```java > adb push ``` > Seealso: > Realm creates additional files for each realm. To learn more about these files, see Realm Internals. > ## Realm File Size Realm usually takes up less space on disk than an equivalent SQLite database. However, in order to give you a consistent view of your data, Realm operates on multiple versions of a realm. If many versions of a realm are opened simultaneously, the realm file can require additional space on disk. These versions take up an amount of space dependent on the amount of changes in each transaction. Many small transactions have the same overhead as a small number of large transactions. Unexpected file size growth usually happens for one of three reasons: 1. *You open a realm on a background thread and forget to close it again.* As a result, Realm retains a reference to the older version of data on the background thread. Because Realm automatically updates realms to the most recent version on threads with loopers, the UI thread and other Looper threads do not have this problem. 2. *You hold references to too many versions of frozen objects.* Frozen objects preserve the version of a realm that existed when the object was first frozen. If you need to freeze a large number of objects, consider using `Realm.copyFromRealm()` instead to only preserve the data you need. 3. *You read some data from a realm. Then, you block the thread with a long-running operation. Meanwhile, you write many times to the realm on other threads.* This causes Realm to create many intermediate versions. You can avoid this by: batching the writes, avoiding leaving the realm open while otherwise blocking the background thread. ### Limit the Maximum Number of Active Versions You can set `maxNumberOfActiveVersions()` when building your `RealmConfiguration` to throw an `IllegalStateException` if your application opens more versions of a realm than the permitted number. Versions are created when executing a write transaction. Realm automatically removes older versions of data once they are no longer used by your application. However, Realm does not free the space used by older versions of data; instead, that space is used for new writes to the realm. ### Compact a Realm You can remove unused space by **compacting** the realm file: - Manually: call `compactRealm()` - Automatically: specify the `compactOnLaunch()` builder option when opening the first connection to a realm in your Android application > Important: > Every production application should implement compacting to periodically reduce realm file size. > ## Backup and Restore Realms Realm persists realms to disk using files on your Android device. To back up a realm, find your realm file and copy it to a safe location. You should close all instances of the realm before copying it. Alternatively, you can also use `realm.writeCopyTo()` to write a compacted version of a realm to a destination file. > Seealso: > If you want to back up a realm to an external location like Google Drive, see the following article series: ([Part 1](https://medium.com/glucosio-project/example-class-to-export-import-a-realm-database-on-java-c429ade2b4ed#.80ibsc7wm), [Part 2](https://medium.com/glucosio-project/backup-restore-a-realm-database-on-google-drive-with-drive-api-c238515a5975#.qbuugb322), [Part 3](https://medium.com/glucosio-project/build-a-nice-ux-to-backup-and-sync-your-app-data-on-google-drive-3-3-a3b598cab68b#.5mjk4w4se)). > ## Modules Realm Modules describe the set of Realm objects that can be stored in a realm. By default, Realm automatically creates a Realm Module that contains all Realm objects defined in your application. You can define a `RealmModule` to restrict a realm to a subset of classes defined in an application. If you produce a library that uses Realm, you can use a Realm Module to explicitly include only the Realm objects defined in your library in your realm. This allows applications that include your library to also use Realm without managing object name conflicts and migrations with your library's defined Realm objects. ================================================ FILE: docs/guides/realm-query-language.md ================================================ # Realm Query Language Realm Query Language (RQL) is a string-based query language to constrain searches when retrieving objects from a realm. SDK-specific methods pass queries to the Realm query engine, which retrieves matching objects from the realm. Realm Query Language syntax is based on [NSPredicate](https://developer.apple.com/documentation/foundation/nspredicate). Queries evaluate a predicate for every object in the collection being queried. If the predicate resolves to `true`, the results collection includes the object. You can use Realm Query Language in most Realm SDKs with your SDK's filter or query methods. The Swift SDK is the exception, as it uses the NSPredicate query API. Some SDKs also support idiomatic APIs for querying realms in their language. You can also use Realm Query Language to browse for data in Realm Studio. Realm Studio is a visual tool to view, edit, and design Realm files. ## Examples on This Page Many of the examples in this page use a simple data set for a to-do list app. The two Realm object types are `Project` and `Item`. - An `Item` has a name, assignee's name, and completed flag. There is also an arbitrary number for priority (higher is more important) and a count of minutes spent working on it. - A `Project` has zero or more `Items` and an optional quota for minimum number of to-do items expected to be completed. See the schema for these two classes, `Project` and `Item`, below: ### Java ```java public class Item extends RealmObject { ObjectId id = new ObjectId(); String name; Boolean isComplete = false; String assignee; Integer priority = 0; Integer progressMinutes = 0; @LinkingObjects("items") final RealmResults projects = null; } public class Project extends RealmObject { ObjectId id = new ObjectId(); String name; RealmList items; Integer quota = null; } ``` ### Kotlin ```kotlin open class Item(): RealmObject() { var id: ObjectId = new ObjectId() @FullText lateinit var name: String var isComplete: Boolean = false var assignee: String? = null var priority: Int = 0 var progressMinutes: Int = 0 } open class Project(): RealmObject() { var id: ObjectId = new ObjectId() lateinit var name: String lateinit var items: RealmList var quota: Int? = null } ``` ## Expressions Filters consist of **expressions** in a predicate. An expression consists of one of the following: - The name of a property of the object currently being evaluated. - An operator and up to two argument expression(s). For example, in the expression `A + B`, the entirety of `A + B` is an expression, but `A` and `B` are also argument expressions to the operator `+`. - A value, such as a string (`'hello'`) or a number (`5`). ```javascript "progressMinutes > 1 AND assignee == $0", "Ali" ``` ## Parameterized Queries Create parameterized queries to interpolate variables into prepared Realm Query Language statements. The syntax for interpolated variables is `$`, starting at `0`. Pass the positional arguments as additional arguments to Realm SDK methods that use Realm Query Language. Include just one parameter with `$0`. ```js "progressMinutes > 1 AND assignee == $0", "Ali" ``` Include multiple parameters with ascending integers starting at `$0`. ```js "progressMinutes > $0 AND assignee == $1", 1, "Alex" ``` ### Query Formats The following table shows how a query should be formatted when serialized and parameterized for the following data types: |Type|Parameterized Example|Serialized Example|Note| | --- | --- | --- | --- | |Boolean|"setting == $0", false|"setting == false"|`true` or `false` values.| |String|"name == $0", "George"|"name == 'George'"|Applies to `string` and `char` data type.| |Number|"age > $0", 5.50|"age > 5.50"|Applies to `int`, `short`, `long`, `double`, `Decimal128`, and `float` data types.| |Date|"date < $0", dateObject|"date < 2021-02-20@17:30:15:0"|For parameterized date queries, you must pass in a date object. For serialized date queries, you can represented the date in the following formats: As an explicit date and time- YYYY-MM-DD@HH:mm:ss:nn (year-month-day@hours:minutes:seconds:nanoseconds) As a `datetime` relative to the [Unix epoch](https://en.wikipedia.org/wiki/Unix_time)- Ts:n (T, designates the start of the time; `s`, seconds; `n`, nanoseconds) Parameterized `Date` object| |ObjectID|"_id == $0", oidValue|"_id == oid(507f1f77bcf86cd799439011)"|For parameterized ObjectId queries, you must pass in an ObjectId. For serialized ObjectId queries, the string representation is `oid()`.| |UUID|"id == $0", uuidValue|"id == uuid(d1b186e1-e9e0-4768-a1a7-c492519d47ee)"|For parameterized UUID queries, you must pass in a UUID. For serialized UUID queries, the string representation is `uuid()`.| |Binary|"value == $0", "binary"|"value == 'binary'"|For ASCII characters, RQL serializes the binary value like a string, with quotes. For non-printable characters, RQL serializes the binary to a base 64 value.| |List|"ANY items.name == {$0, $1}", "milk", "bread"|"ANY items.name == {'milk', 'bread'}"|Applies for list, collections, and sets. A parameterized value should be used for each member of the list.| |RealmObject|"ANY items == $0", obj("Item", oid(6489f036f7bd0546377303ab))|"ANY items == obj('Item', oid(6489f036f7bd0546377303ab))"|To pass in a RealmObject, you need the class and primary key of the object.| ## Dot Notation When referring to an object property, you can use **dot notation** to refer to child properties of that object. You can even refer to the properties of embedded objects and relationships with dot notation. For example, consider a query on an object with a `workplace` property that refers to a Workplace object. The Workplace object has an embedded object property, `address`. You can chain dot notations to refer to the zipcode property of that address: ```js "workplace.address.zipcode == 10019" ``` ## Nil Type Realm Query Language include the `nil` type to represent a null pointer. You can either reference `nil` directly in your queries or with a parameterized query. If you're using a parameterized query, each SDK maps its respective null pointer to `nil`. ```js "assignee == nil" ``` ```js // comparison to language null pointer "assignee == $0", null ``` ## Comparison Operators The most straightforward operation in a search is to compare values. > Important: > The type on both sides of the operator must be equivalent. For example, comparing an ObjectId with string will result in a precondition failure with a message like: > > ``` > "Expected object of type object id for property 'id' on object of type > 'User', but received: 11223344556677889900aabb (Invalid value)" > ``` > > You can compare any numeric type with any other numeric type, including decimal, float, and Decimal128. > |Operator|Description| | --- | --- | |`BETWEEN {number1, number2}`|Evaluates to `true` if the left-hand numerical or date expression is between or equal to the right-hand range. For dates, this evaluates to `true` if the left-hand date is within the right-hand date range.| |== , =|Evaluates to `true` if the left-hand expression is equal to the right-hand expression.| |>|Evaluates to `true` if the left-hand numerical or date expression is greater than the right-hand numerical or date expression. For dates, this evaluates to `true` if the left-hand date is later than the right-hand date.| |>=|Evaluates to `true` if the left-hand numerical or date expression is greater than or equal to the right-hand numerical or date expression. For dates, this evaluates to `true` if the left-hand date is later than or the same as the right-hand date.| |IN|Evaluates to `true` if the left-hand expression is in the right-hand list. This is equivalent to and used as a shorthand for `== ANY`.| |<|Evaluates to `true` if the left-hand numerical or date expression is less than the right-hand numerical or date expression. For dates, this evaluates to `true` if the left-hand date is earlier than the right-hand date.| |<=|Evaluates to `true` if the left-hand numeric expression is less than or equal to the right-hand numeric expression. For dates, this evaluates to `true` if the left-hand date is earlier than or the same as the right-hand date.| |!= , <>|Evaluates to `true` if the left-hand expression is not equal to the right-hand expression.| > Example: > The following example uses Realm Query Language's comparison operators to: > > - Find high priority to-do items by comparing the value of the `priority` property value with a threshold number, above which priority can be considered high. > - Find long-running to-do items by seeing if the `progressMinutes` property is at or above a certain value. > - Find unassigned to-do items by finding items where the `assignee` property is equal to `null`. > - Find to-do items within a certain time range by finding items where the `progressMinutes` property is between two numbers. > - Find to-do items with a certain amount of `progressMinutes` from the given list. > > ```javascript > // Find high priority to-do items by comparing the value of the ``priority`` > // property value with a threshold number, above which priority can be considered high. > "priority > $0", 5 > > // Find long-running to-do items by seeing if the progressMinutes property is at or above a certain value. > "progressMinutes > $0", 120 > > // Find unassigned to-do items by finding items where the assignee property is equal to null. > "assignee == $0", null > > // Find to-do items within a certain time range by finding items > // where the progressMinutes property is between two numbers. > "progressMinutes BETWEEN { $0 , $1 }", 30, 60 > > // Find to-do items with a certain amount of progressMinutes from the given list. > "progressMinutes IN { $0, $1, $2, $3, $4, $5 }", 10, 20, 30, 40, 50, 60 > > ``` > ## Logical Operators Make compound predicates using logical operators. |Operator|Description| | --- | --- | |AND &&|Evaluates to `true` if both left-hand and right-hand expressions are `true`.| |NOT !|Negates the result of the given expression.| |OR \\|\\||Evaluates to `true` if either expression returns `true`.| > Example: > We can use the query language's logical operators to find all of Ali's completed to-do items. That is, we find all items where the `assignee` property value is equal to 'Ali' AND the `isComplete` property value is `true`: > > ```javascript > "assignee == $0 AND isComplete == $1", "Ali", true > > ``` > ## String Operators Compare string values using these string operators. Regex-like wildcards allow more flexibility in search. > Note: > You can use the following modifiers with the string operators: > > - `[c]` for case insensitivity. `"name CONTAINS[c] $0", 'a'` > |Operator|Description| | --- | --- | |BEGINSWITH|Evaluates to `true` if the left-hand string expression begins with the right-hand string expression. This is similar to `contains`, but only matches if the right-hand string expression is found at the beginning of the left-hand string expression.| |CONTAINS|Evaluates to `true` if the right-hand string expression is found anywhere in the left-hand string expression.| |ENDSWITH|Evaluates to `true` if the left-hand string expression ends with the right-hand string expression. This is similar to `contains`, but only matches if the left-hand string expression is found at the very end of the right-hand string expression.| |LIKE|Evaluates to `true` if the left-hand string expression matches the right-hand string wildcard string expression. A wildcard string expression is a string that uses normal characters with two special wildcard characters: The `*` wildcard matches zero or more of any character The `?` wildcard matches any character. For example, the wildcard string "d?g" matches "dog", "dig", and "dug", but not "ding", "dg", or "a dog".| |== , =|Evaluates to `true` if the left-hand string is lexicographically equal to the right-hand string.| |!= , <>|Evaluates to `true` if the left-hand string is not lexicographically equal to the right-hand string.| > Example: > We use the query engine's string operators to find: > > - Projects with a name starting with the letter 'e' > - Projects with names that contain 'ie' > > ```javascript > "name BEGINSWITH[c] $0", 'e' > > "name CONTAINS $0", 'ie' > > ``` > ## ObjectId and UUID Operators Query [BSON ObjectIds](https://www.mongodb.com/docs/manual/reference/method/ObjectId/) and [UUIDs](https://www.mongodb.com/docs/manual/reference/method/UUID/). These data types are often used as primary keys. To query with ObjectIds, use a parameterized query. Pass the ObjectId or UUID you're querying against as the argument. ```js "_id == $0", oidValue ``` You can also put a string representation of the ObjectId you're evaluating in `oid()`. ```js "_id == oid(6001c033600510df3bbfd864)" ``` To query with UUIDs, put a string representation of the UUID you're evaluating in `uuid()`. ```js "id == uuid(d1b186e1-e9e0-4768-a1a7-c492519d47ee)" ``` |Operator|Description| | --- | --- | |== , =|Evaluates to `true` if the left-hand value is equal to the right-hand value.| |!= , <>|Evaluates to `true` if the left-hand value is not equal to the right-hand value.| ## Arithmetic Operators Perform basic arithmetic in one side of a RQL expression when evaluating numeric data types. ```js "2 * priority > 6" // Is equivalent to "priority >= 2 * (2 - 1) + 2" ``` You can also use multiple object properties together in a mathematic operation. ```js "progressMinutes * priority == 90" ``` |Operator|Description| | --- | --- | |*|Multiplication.| |/|Division.| |+|Addition.| |-|Subtraction.| |()|Group expressions together.| ## Type Operator Check the type of a property using the `@type` operator. You can only use the type operator with mixed types and dictionaries. Evaluate the property against a string representation of the data type name. Refer to SDK documentation on the mapping from the SDK language's data types to Realm data types. |Operator|Description| | --- | --- | |`@type`|Check if type of a property is the property name as a string. Use `==` and `!=` to compare equality.| ```js "mixedType.@type == 'string'" "mixedType.@type == 'bool'" ``` ## Dictionary Operators Compare dictionary values using these dictionary operators. |Operator|Description| | --- | --- | |`@values`|Returns objects that have the value specified in the right-hand expression.| |`@keys`|Returns objects that have the key specified in the right-hand expression.| |`@size`, `@count`|The number of elements in a dictionary.| |`Dictionary['key']`|Access the value at a key of a dictionary.| |`ALL \| ANY \| NONE .@type`|Checks if the dictionary contains properties of certain type.| You can also use dictionary operators in combination with comparison operators to filter objects based on dictionary keys and values. The following examples show some ways to use dictionary operators with comparison operators. All examples query a collection of Realm objects with a dictionary property named `dict`. > Example: > The following examples use various dictionary operators. > > ```js > // Evaluates if there is a dictionary key with the name 'foo' > "ANY dict.@keys == $0", 'foo' > > // Evaluates if there is a dictionary key with key 'foo' and value 'bar > "dict['foo'] == $0", 'bar' > > // Evaluates if there is greater than one key-value pair in the dictionary > "dict.@count > $0", 1 > > // Evaluates if dictionary has property of type 'string' > "ANY dict.@type == 'string'" > > // Evaluates if all the dictionary's values are integers > "ALL dict.@type == 'bool'" > > // Evaluates if dictionary does not have any values of type int > "NONE dict.@type == 'double'" > > // ANY is implied. > "dict.@type == 'string'" > > ``` > ## Date Operators Query date types in a realm. Generally, you should use a parameterized query to pass a date data type from the SDK language you are using to a query. ```js "timeCompleted < $0", someDate ``` You can also specify dates in the following two ways: - As a specific date (in UTC)- `YYYY-MM-DD@HH:mm:ss:nnnnnnnnnn` (year-month-day@hours:minutes:seconds:nanoseconds), UTC. You can also use `T` instead of `@` to separate the date from the time. - As a time in seconds since the [Unix epoch](https://en.wikipedia.org/wiki/Unix_time)- `Ts:n`, where `T` designates the start of the time, `s` is the number of seconds, and `n` is the number of nanoseconds. Date supports comparison operators. > Example: > The following example shows how to use a parameterized query with a date object: > > ```js > var date = new Date("2021-02-20@17:30:15:0"); > > "timeCompleted > $0", date > ``` > ## Aggregate Operators Apply an aggregate operator to a collection property of a Realm object. Aggregate operators traverse a collection and reduce it to a single value. |Operator|Description| | --- | --- | |@avg|Evaluates to the average value of a given numerical property across a collection. If any values are `null`, they are not counted in the result.| |@count|Evaluates to the number of objects in the given collection.| |@max|Evaluates to the highest value of a given numerical property across a collection. `null` values are ignored.| |@min|Evaluates to the lowest value of a given numerical property across a collection. `null` values are ignored.| |@sum|Evaluates to the sum of a given numerical property across a collection, excluding `null` values.| > Example: > These examples all query for projects containing to-do items that meet this criteria: > > - Projects with average item priority above 5. > - Projects with an item whose priority is less than 5. > - Projects with an item whose priority is greater than 5. > - Projects with more than 5 items. > - Projects with long-running items. > > ```javascript > var priorityNum = 5; > > "items.@avg.priority > $0", priorityNum > > "items.@max.priority < $0", priorityNum > > "items.@min.priority > $0", priorityNum > > "items.@count > $0", 5 > > "items.@sum.progressMinutes > $0", 100 > > ``` > ## Collection Operators A **collection operator** lets you query list properties within a collection of objects. Collection operators filter a collection by applying a predicate to every element of a given list property of the object. If the predicate returns true, the object is included in the output collection. |Operator|Description| | --- | --- | |`ALL`|Returns objects where the predicate evaluates to `true` for all objects in the collection.| |`ANY`, `SOME`|Returns objects where the predicate evaluates to `true` for any objects in the collection.| |`NONE`|Returns objects where the predicate evaluates to false for all objects in the collection.| > Example: > This example uses collection operators to find projects that contain to-do items matching certain criteria: > > ```js > // Projects with no complete items. > "NONE items.isComplete == $0", true > > // Projects that contain a item with priority 10 > "ANY items.priority == $0", 10 > > // Projects that only contain completed items > "ALL items.isComplete == $0", true > > // Projects with at least one item assigned to either Alex or Ali > "ANY items.assignee IN { $0 , $1 }", "Alex", "Ali" > > // Projects with no items assigned to either Alex or Ali > "NONE items.assignee IN { $0 , $1 }", "Alex", "Ali" > > ``` > ## List Comparisons You can use comparison operators and collection operators to filter based on lists of data. You can compare any type of valid list. This includes: - collections of Realm objects, which let you filter against other data in the realm. `"oid(631a072f75120729dc9223d9) IN items.id" ` - lists defined directly in the query, which let you filter against static data. You define static lists as a comma-separated list of literal values enclosed in opening (`{`) and closing (`}`) braces. `"priority IN {0, 1, 2}" ` - native list objects passed in a parameterized expression, which let you pass application data directly to your queries. `const ids = [ new BSON.ObjectId("631a072f75120729dc9223d9"), new BSON.ObjectId("631a0737c98f89f5b81cd24d"), new BSON.ObjectId("631a073c833a34ade21db2b2"), ]; const parameterizedQuery = realm.objects("Item").filtered("id IN $0", ids); ` If you do not define a collection operator, a list expression defaults to the `ANY` operator. > Example: > These two list queries are equivalent: > > - `age == ANY {18, 21}` > - `age == {18, 21}` > > Both of these queries return objects with an age property equal to either 18 or 21. You could also do the opposite by returning objects only if the age is not equal to either 18 or 21: > > - `age == NONE {18, 21}` > The following table includes examples that illustrate how collection operators interact with lists and comparison operators: |Expression|Match?|Reason| | --- | --- | --- | |`ANY {1, 2, 3} > ALL {1, 2}`|true|A value on the left (3) is greater than some value on the right (both 1 and 2)| |`ANY {1, 2, 3} == NONE {1, 2}`|true|3 does not match either of 1 or 2| |`ANY {4, 8} == ANY {5, 9, 11}`|false|Neither 4 nor 8 matches any value on the right (5, 9 or 11)| |`ANY {1, 2, 7} <= NONE {1, 2}`|true|A value on the left (7) is not less than or equal to both 1 and 2| |`ALL {1, 2} IN ANY {1, 2, 3}`|true|Every value on the left (1 and 2) is equal to 1, 2 or 3| |`ALL {3, 1, 4, 3} == NONE {1, 2}`|false|1 matches a value in the NONE list (1 or 2)| |`ALL {} in ALL {1, 2}`|true|An empty list matches all lists| |`NONE {1, 2, 3, 12} > ALL {5, 9, 11}`|false|12 is bigger than all values on the right (5, 9, and 11)| |`NONE {4, 8} > ALL {5, 9, 11}`|true|4 and 8 are both less than some value on the right (5, 9, or 11)| |`NONE {0, 1} < NONE {1, 2}`|true|0 and 1 are both less than none of 1 and 2| ## Full Text Search You can use RQL to query on properties that have a full-text search (FTS) annotation. FTS supports boolean match word searches, rather than searches for relevance. For information on enabling FTS on a property, see the FTS documentation for your SDK: - Flutter SDK - Kotlin SDK - .NET SDK - Node.js SDK - React Native SDK - Swift SDK does not yet support Full-Text Search. To query these properties, use the `TEXT` predicate in your query. You can search for entire words or phrases, or limit your results with the following characters: - Exclude results for a word by placing the `-` character in front of the word. - Specify prefixes by placing the `*` character at the end of a prefix. Suffix searching is not currently supported. In the following example, we query the `Item.name` property: ```js // Filter for items with 'write' in the name "name TEXT $0", "write" // Find items with 'write' but not 'tests' using '-' "name TEXT $0", "write -tests" // Find items starting with 'wri-' using '*' "name TEXT $0", "wri*" ``` ### Full-Text Search Tokenizer Details Full-Text Search (FTS) indexes support: - Tokens are diacritics- and case-insensitive. - Tokens can only consist of characters from ASCII and the Latin-1 supplement (western languages). All other characters are considered whitespace. - Words split by a hyphen (-) are split into two tokens. For example, `full-text` splits into `full` and `text`. ## Backlink Queries A backlink is an inverse relationship link that lets you look up objects that reference another object. Backlinks use the to-one and to-many relationships defined in your object schemas but reverse the direction. Every relationship that you define in your schema implicitly has a corresponding backlink. You can access backlinks in queries using the `@links..` syntax, where `` and `` refer to a specific property on an object type that references the queried object type. ```js // Find items that belong to a project with a quota greater than 10 (@links) "@links.Project.items.quota > 10" ``` You can also define a `linkingObjects` property to explicitly include the backlink in your data model. This lets you reference the backlink through an assigned property name using standard dot notation. ```js // Find items that belong to a project with a quota greater than 10 (LinkingObjects) "projects.quota > 10" ``` The result of a backlink is treated like a collection and supports collection operators. ```js // Find items where any project that references the item has a quota greater than 0 "ANY @links.Project.items.quota > 0" // Find items where all projects that reference the item have a quota greater than 0 "ALL @links.Project.items.quota > 0" ``` You can use aggregate operators on the backlink collection. ```js // Find items that are referenced by multiple projects "projects.@count > 1" // Find items that are not referenced by any project "@links.Project.items.@count == 0" // Find items that belong to a project where the average item has // been worked on for at least 5 minutes "@links.Project.items.items.@avg.progressMinutes > 10" ``` You can query the count of all relationships that point to an object by using the `@count` operator directly on `@links`. ```js // Find items that are not referenced by another object of any type "@links.@count == 0" ``` ## Subqueries Iterate through list properties with another query using the `SUBQUERY()` predicate function. Subqueries are useful for the following scenarios: - Matching each object in a list property on multiple conditions - Counting the number of objects that match a subquery `SUBQUERY()` has the following structure: ```js SUBQUERY(, , ) ``` - `collection`: The name of the property to iterate through - `variableName`: A variable name of the element to use in the subquery - `predicate`: The subquery predicate. Use the variable specified by `variableName` to refer to the currently-iterated element. A subquery iterates through the given collection and checks the given predicate against each object in the collection. The predicate can refer to the current iterated object with the variable name passed to `SUBQUERY()`. A subquery expression resolves to a list of objects. Realm only supports the `@count` aggregate operator on the result of a subquery. This allows you to count how many objects in the subquery input collection matched the predicate. You can use the count of the subquery result as you would any other number in a valid expression. In particular, you can compare the count with the number `0` to return all matching objects. > Example: > The following example shows two subquery filters on a collection of projects. > > ```js > // Returns projects with items that have not been completed > // by a user named Alex. > "SUBQUERY(items, $item, $item.isComplete == false AND $item.assignee == 'Alex').@count > 0" > > // Returns the projects where the number of completed items is > // greater than or equal to the value of a project's `quota` property. > "SUBQUERY(items, $item, $item.isComplete == true).@count >= quota" > > ``` > ## Sort, Distinct & Limit Sort and limit the results collection of your query using additional operators. |Operator|Description| | --- | --- | |`SORT`|Specify the name of the property to compare, and whether to sort by ascending (`ASC`) or descending (`DESC`) order. If you specify multiple SORT fields, you must specify sort order for each field. With multiple sort fields, the query sorts by the first field, and then the second. For example, if you `SORT (priority DESC, name DESC)`, the query returns sorted by priority, and then by name when priority value is the same.| |`DISTINCT`|Specify a name of the property to compare. Remove duplicates for that property in the results collection. If you specify multiple DISTINCT fields, the query removes duplicates by the first field, and then the second. For example, if you `DISTINCT (name, assignee)`, the query only removes duplicates where the values of both properties are the same.| |`LIMIT`|Limit the results collection to the specified number.| > Example: > Use the query engine's sort, distinct, and limit operators to find to-do items where the assignee is Ali: > > - Sorted by priority in descending order > - Enforcing uniqueness by name > - Limiting the results to 5 items > > ```javascript > "assignee == 'Ali' SORT(priority DESC) DISTINCT(name) LIMIT(5)" > > ``` > ================================================ FILE: docs/guides/test-and-debug/debugging.md ================================================ # Debugging - Java SDK ## Android Studio Debugging > Important: > The Android Studio debugger can provide misleading values for Realm object fields. For correct values, you can watch accessor values instead, or use the Realm object `toString()` method to see the latest field values. > This section details information you should keep in mind when debugging Realm applications with Android Studio to avoid incorrect value reporting. When you watch a Realm object, you'll see values displayed in the object's fields. These values are incorrect because the field values themselves are not used. This is because Realm creates a proxy object behind the scenes, overriding the getters and setters to access the persisted data in the realm. To see the correct values, add a watch on the accessors. In the image above, the debugger has stopped on line `113`. There are three watch values: - The `person` variable - The `person.getName()` accessor - The `person.getAge()` accessor The code from lines `107` to `111` alters the `person` instance by changing the name and age in a write transaction. On line `113`, the `person` watch instance reports incorrect values for the *field* watch values. The watch values that use the *accessors* report values that are correct. ## NDK Debugging The Realm Java SDK library contains native code. Debugging NDK crashes can be cumbersome, as the default stack trace provides minimal information. We recommend you use a crash reporting tool such as [Crashlytics](http://www.crashlytics.com/). This gives you the ability to track native errors and gather other valuable information. We can help with your issues faster if you have this information. To enable NDK crash reporting in Crashlytics for your project, add the following to the root of your application `build.gradle` file: ```groovy crashlytics { enableNdk true } ``` > Note: > The values `androidNdkOut` and `androidNdkLibsOut` are not needed. > ================================================ FILE: docs/guides/test-and-debug/log-realm-events.md ================================================ # Log Realm Events - Java SDK The SDK logs events to the Android system log automatically. You can view these events using [Logcat](https://developer.android.com//studio/debug/am-logcat). ## Set the Client Log Level Realm uses the log levels defined by [Log4J](https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/Level.html). To configure the log level for Realm logs in your application, pass a `LogLevel` to `RealmLog.setLevel()`: #### Java ```java RealmLog.setLevel(LogLevel.ALL); ``` #### Kotlin ```kotlin RealmLog.setLevel(LogLevel.ALL) ``` > Tip: > To diagnose and troubleshoot errors while developing your application, set the log level to `debug` or `trace`. For production deployments, decrease the log level for improved performance. > ================================================ FILE: docs/guides/test-and-debug/testing.md ================================================ # Testing - Java SDK You can test your application using unit tests or integration tests. **Unit tests** only assess the logic written in your application's code. **Integration tests** assess your application logic, database queries and writes, and calls to your application's backend, if you have one. Unit tests run on your development machine using the JVM, while integration tests run on a physical or emulated Android device. You can run integration tests by communicating with actual instances of Realm or an App backend using Android's built-in instrumented tests. Android uses specific file paths and folder names in Android projects for unit tests and instrumented tests: |Test Type|Path| | --- | --- | |Unit Tests|/app/src/test| |Instrumented Tests|/app/src/androidTest| Because the SDK uses C++ code via Android Native for data storage, unit testing requires you to entirely mock interactions with Realm. Prefer integration tests for logic that requires extensive interaction with the database. ## Integration Tests This section shows how to integration test an application that uses the Realm SDK. It covers the following concepts in the test environment: - acquiring an application context - executing logic on a `Looper` thread - how to delay test execution while asynchronous method calls complete ### Application Context To initialize the SDK, you'll need to provide an application or activity [context](https://developer.android.com/reference/android/content/Context). This isn't available by default in Android integration tests. However, you can use Android's built-in testing [ActivityScenario](https://developer.android.com/reference/androidx/test/core/app/ActivityScenario) class to start an activity in your tests. You can use any activity from your application, or you can create an empty activity just for testing. Call `ActivityScenario.launch()` with your activity class as a parameter to start the simulated activity. Next, use the `ActivityScenario.onActivity()` method to run a lambda on the simulated activity's main thread. In this lambda, you should call the `Realm.init()` function to initialize the SDK with your activity as a parameter. Additionally, you should save the parameter passed to your lambda (the newly created instance of your activity) for future use. Because the `onActivity()` method runs on a different thread, you should block your test from executing further until this initial setup completes. The following example uses an `ActivityScenario`, an empty testing activity, and a `CountDownLatch` to demonstrate how to set up an environment where you can test your Realm application: #### Java ```java AtomicReference testActivity = new AtomicReference(); ActivityScenario scenario = ActivityScenario.launch(BasicActivity.class); // create a latch to force blocking for an async call to initialize realm CountDownLatch setupLatch = new CountDownLatch(1); scenario.onActivity(activity -> { Realm.init(activity); testActivity.set(activity); setupLatch.countDown(); // unblock the latch await }); // block until we have an activity to run tests on try { Assert.assertTrue(setupLatch.await(1, TimeUnit.SECONDS)); } catch (InterruptedException e) { Log.e("EXAMPLE", e.getMessage()); } ``` #### Kotlin ```kotlin var testActivity: Activity? = null val scenario: ActivityScenario? = ActivityScenario.launch(BasicActivity::class.java) // create a latch to force blocking for an async call to initialize realm val setupLatch = CountDownLatch(1) scenario?.onActivity{ activity: BasicActivity -> Realm.init(activity) testActivity = activity setupLatch.countDown() // unblock the latch await } ``` ### Looper Thread Realm functionality such as Live objects and change notifications only work on [Looper](https://developer.android.com/reference/android/os/Looper) threads. Threads configured with a `Looper` object pass events over a message loop coordinated by the `Looper`. Test functions normally don't have a `Looper` object, and configuring one to work in your tests can be very error-prone. Instead, you can use the [Activity.runOnUiThread()](https://developer.android.com/reference/android/app/Activity#runOnUiThread(java.lang.Runnable)) method of your test activity to execute logic on a thread that already has a `Looper` configured. Combine `Activity.runOnUiThread()` with a `CountDownLatch` as described in the delay section to prevent your test from completing and exiting before your logic has executed. Within the `runOnUiThread()` call, you can interact with the SDK just like you normally would in your application code: #### Java ```java testActivity.get().runOnUiThread(() -> { // instantiate an app connection String appID = YOUR_APP_ID; // replace this with your test application App ID App app = new App(new AppConfiguration.Builder(appID).build()); // authenticate a user Credentials credentials = Credentials.anonymous(); app.loginAsync(credentials, it -> { if (it.isSuccess()) { Log.v("EXAMPLE", "Successfully authenticated."); Realm.getInstanceAsync(config, new Realm.Callback() { @Override public void onSuccess(@NonNull Realm realm) { Log.v("EXAMPLE", "Successfully opened a realm."); // read and write to realm here via transactions testLatch.countDown(); realm.executeTransaction(new Realm.Transaction() { @Override public void execute(@NonNull Realm realm) { realm.createObjectFromJson(Frog.class, "{ name: \"Doctor Cucumber\", age: 1, species: \"bullfrog\", owner: \"Wirt\", _id: 0 }"); } }); realm.close(); } @Override public void onError(@NonNull Throwable exception) { Log.e("EXAMPLE", "Failed to open the realm: " + exception.getLocalizedMessage()); } }); } else { Log.e("EXAMPLE", "Failed login: " + it.getError().getErrorMessage()); } }); }); ``` #### Kotlin ```kotlin testActivity?.runOnUiThread { // instantiate an app connection val appID: String = YOUR_APP_ID // replace this with your App ID val app = App(AppConfiguration.Builder(appID).build()) // authenticate a user val credentials = Credentials.anonymous() app.loginAsync(credentials) { if (it.isSuccess) { Log.v("EXAMPLE", "Successfully authenticated.") Realm.getInstanceAsync(config, object : Realm.Callback() { override fun onSuccess(realm: Realm) { Log.v("EXAMPLE", "Successfully opened a realm.") // read and write to realm here via transactions realm.executeTransaction { realm.createObjectFromJson( Frog::class.java, "{ name: \"Doctor Cucumber\", age: 1, species: \"bullfrog\", owner: \"Wirt\", _id:0 }" ) } testLatch.countDown() realm.close() } override fun onError(exception: Throwable) { Log.e("EXAMPLE", "Failed to open the realm: " + exception.localizedMessage) } }) } else { Log.e("EXAMPLE", "Failed login: " + it.error.errorMessage) } } } ``` ### Delay Test Execution While Async Calls Complete Because the SDK uses asynchronous calls for common operations, tests need a way to wait for those async calls to complete. Otherwise, your tests will exit before your asynchronous (or multi-threaded) calls run. This example uses Java's built-in [CountDownLatch](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/CountDownLatch.html). Follow these steps to use a `CountDownLatch` in your own tests: 1. Instantiate a `CountDownLatch` with a count of 1. 2. After running the async logic your test needs to wait for, call that `CountDownLatch` instance's `countDown()` method. 3. When you need to wait for async logic, add a `try`/`catch` block that handles an `InterruptedException`. In that block, call that `CountDownLatch` instance's `await()` method. 4. Pass a timeout interval and unit to `await()`, and wrap the call in a `Assert.assertTrue()` assertion. If the logic takes too long, the `await()` call times out, returning false and failing the test. ### Testing Backend Applications that use an App backend should not connect to the production backend for testing purposes for the following reasons: - you should always keep test users and production users separate for security and privacy reasons - tests often require a clean initial state, so there's a good chance your tests will include a setup or teardown method that deletes all users or large chunks of data You can use environments to manage separate apps for testing and production. ## Unit Tests To unit test Realm applications that use Realm, you must [mock](https://en.wikipedia.org/wiki/Mock_object) Realm (and your application backend, if you use one). Use the following libraries to mock SDK functionality: - [Robolectric](http://robolectric.org/) - [PowerMock](https://powermock.github.io/) - [Mockito](https://site.mockito.org/) To make these libraries available for unit testing in your Android project, add the following to the `dependencies` block of your application `build.gradle` file: ``` testImplementation "org.robolectric:robolectric:4.1" testImplementation "org.mockito:mockito-core:3.3.3" testImplementation "org.powermock:powermock-module-junit4:2.0.9" testImplementation "org.powermock:powermock-module-junit4-rule:2.0.9" testImplementation "org.powermock:powermock-api-mockito2:2.0.9" testImplementation "org.powermock:powermock-classloading-xstream:2.0.9" ``` > Note: > Mocking the SDK in unit tests requires Robolectric, Mockito, and Powermock because the SDK uses Android Native C++ method calls to interact with Realm. Because the frameworks required to override these method calls can be delicate, you should use the versions listed above to ensure that your mocking is successful. Some recent version updates (particularly Robolectric version 4.2+) can break compiliation of unit tests using the SDK. > To configure your unit tests to use Robolectric, PowerMock, and Mockito with the SDK, add the following annotations to each unit test class that mocks the SDK: #### Java ```java @RunWith(RobolectricTestRunner.class) @Config(sdk = 28) @PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*", "jdk.internal.reflect.*", "androidx.*"}) @SuppressStaticInitializationFor("io.realm.internal.Util") @PrepareForTest({Realm.class, RealmConfiguration.class, RealmQuery.class, RealmResults.class, RealmCore.class, RealmLog.class}) ``` #### Kotlin ```kotlin @RunWith(RobolectricTestRunner::class) @Config(sdk = [28]) @PowerMockIgnore( "org.mockito.*", "org.robolectric.*", "android.*", "jdk.internal.reflect.*", "androidx.*" ) @SuppressStaticInitializationFor("io.realm.internal.Util") @PrepareForTest( Realm::class, RealmConfiguration::class, RealmQuery::class, RealmResults::class, RealmCore::class, RealmLog::class ) ``` Then, bootstrap Powermock globally in the test class: #### Java ```java // bootstrap powermock @Rule public PowerMockRule rule = new PowerMockRule(); ``` #### Kotlin ```kotlin // bootstrap powermock @Rule var rule = PowerMockRule() ``` Next, mock the components of the SDK that might query native C++ code so we don't hit the limitations of the test environment: #### Java ```java // set up realm SDK components to be mocked. The order of these matters mockStatic(RealmCore.class); mockStatic(RealmLog.class); mockStatic(Realm.class); mockStatic(RealmConfiguration.class); Realm.init(RuntimeEnvironment.application); // boilerplate to mock realm components -- this prevents us from hitting any // native code doNothing().when(RealmCore.class); RealmCore.loadLibrary(any(Context.class)); ``` #### Kotlin ```kotlin // set up realm SDK components to be mocked. The order of these matters PowerMockito.mockStatic(RealmCore::class.java) PowerMockito.mockStatic(RealmLog::class.java) PowerMockito.mockStatic(Realm::class.java) PowerMockito.mockStatic(RealmConfiguration::class.java) Realm.init(RuntimeEnvironment.application) PowerMockito.doNothing().`when`(RealmCore::class.java) RealmCore.loadLibrary(ArgumentMatchers.any(Context::class.java)) ``` Once you've completed the setup required for mocking, you can start mocking components and wiring up behavior for your tests. You can also configure PowerMockito to return specific objects when new objects of a type are instantiated, so even code that references the default realm in your application won't break your tests: #### Java ```java // create the mocked realm final Realm mockRealm = mock(Realm.class); final RealmConfiguration mockRealmConfig = mock(RealmConfiguration.class); // use this mock realm config for all new realm configurations whenNew(RealmConfiguration.class).withAnyArguments().thenReturn(mockRealmConfig); // use this mock realm for all new default realms when(Realm.getDefaultInstance()).thenReturn(mockRealm); ``` #### Kotlin ```kotlin // create the mocked realm val mockRealm = PowerMockito.mock(Realm::class.java) val mockRealmConfig = PowerMockito.mock( RealmConfiguration::class.java ) // use this mock realm config for all new realm configurations PowerMockito.whenNew(RealmConfiguration::class.java).withAnyArguments() .thenReturn(mockRealmConfig) // use this mock realm for all new default realms PowerMockito.`when`(Realm.getDefaultInstance()).thenReturn(mockRealm) ``` After mocking a realm, you'll have to configure data for your test cases. See the full example below for some examples of how you can provide testing data in unit tests. ### Full Example The following example shows a full JUnit `test` example mocking Realm in unit tests. This example tests an activity that performs some basic Realm operations. The tests use mocking to simulate those operations when that activity is started during a unit test: #### Java ```java package com.mongodb.realm.examples.java; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.os.AsyncTask; import android.util.Log; import android.widget.LinearLayout; import android.widget.TextView; import com.mongodb.realm.examples.R; import com.mongodb.realm.examples.model.java.Cat; import io.realm.Realm; import io.realm.RealmResults; public class UnitTestActivity extends AppCompatActivity { public static final String TAG = UnitTestActivity.class.getName(); private LinearLayout rootLayout = null; private Realm realm; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Realm.init(getApplicationContext()); setContentView(R.layout.activity_unit_test); rootLayout = findViewById(R.id.container); rootLayout.removeAllViews(); // open the default Realm for the UI thread. realm = Realm.getDefaultInstance(); // clean up from previous run cleanUp(); // small operation that is ok to run on the main thread basicCRUD(realm); // more complex operations can be executed on another thread. AsyncTask foo = new AsyncTask() { @Override protected String doInBackground(Void... voids) { String info = ""; info += complexQuery(); return info; } @Override protected void onPostExecute(String result) { showStatus(result); } }; foo.execute(); findViewById(R.id.clean_up).setOnClickListener(view -> { view.setEnabled(false); Log.d("TAG", "clean up"); cleanUp(); view.setEnabled(true); }); } private void cleanUp() { // delete all cats realm.executeTransaction(r -> r.delete(Cat.class)); } @Override public void onDestroy() { super.onDestroy(); realm.close(); // remember to close realm when done. } private void showStatus(String txt) { Log.i(TAG, txt); TextView tv = new TextView(this); tv.setText(txt); rootLayout.addView(tv); } private void basicCRUD(Realm realm) { showStatus("Perform basic Create/Read/Update/Delete (CRUD) operations..."); // all writes must be wrapped in a transaction to facilitate safe multi threading realm.executeTransaction(r -> { // add a cat Cat cat = r.createObject(Cat.class); cat.setName("John Young"); }); // find the first cat (no query conditions) and read a field final Cat cat = realm.where(Cat.class).findFirst(); showStatus(cat.getName()); // update cat in a transaction realm.executeTransaction(r -> { cat.setName("John Senior"); }); showStatus(cat.getName()); // add two more cats realm.executeTransaction(r -> { Cat jane = r.createObject(Cat.class); jane.setName("Jane"); Cat doug = r.createObject(Cat.class); doug.setName("Robert"); }); RealmResults cats = realm.where(Cat.class).findAll(); showStatus(String.format("Found %s cats", cats.size())); for (Cat p : cats) { showStatus("Found " + p.getName()); } } private String complexQuery() { String status = "\n\nPerforming complex Query operation..."; Realm realm = Realm.getDefaultInstance(); status += "\nNumber of cats in the DB: " + realm.where(Cat.class).count(); // find all cats where name begins with "J". RealmResults results = realm.where(Cat.class) .beginsWith("name", "J") .findAll(); status += "\nNumber of cats whose name begins with 'J': " + results.size(); realm.close(); return status; } } ``` ```java import android.content.Context; import com.mongodb.realm.examples.java.UnitTestActivity; import com.mongodb.realm.examples.model.java.Cat; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor; import org.powermock.modules.junit4.rule.PowerMockRule; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import java.util.Arrays; import java.util.List; import io.realm.Realm; import io.realm.RealmConfiguration; import io.realm.RealmObject; import io.realm.RealmQuery; import io.realm.RealmResults; import io.realm.internal.RealmCore; import io.realm.log.RealmLog; import com.mongodb.realm.examples.R; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.powermock.api.mockito.PowerMockito.doNothing; import static org.powermock.api.mockito.PowerMockito.mock; import static org.powermock.api.mockito.PowerMockito.mockStatic; import static org.powermock.api.mockito.PowerMockito.when; import static org.powermock.api.mockito.PowerMockito.whenNew; @RunWith(RobolectricTestRunner.class) @Config(sdk = 28) @PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*", "jdk.internal.reflect.*", "androidx.*"}) @SuppressStaticInitializationFor("io.realm.internal.Util") @PrepareForTest({Realm.class, RealmConfiguration.class, RealmQuery.class, RealmResults.class, RealmCore.class, RealmLog.class}) public class TestTest { // bootstrap powermock @Rule public PowerMockRule rule = new PowerMockRule(); // mocked realm SDK components for tests private Realm mockRealm; private RealmResults cats; @Before public void setup() throws Exception { // set up realm SDK components to be mocked. The order of these matters mockStatic(RealmCore.class); mockStatic(RealmLog.class); mockStatic(Realm.class); mockStatic(RealmConfiguration.class); Realm.init(RuntimeEnvironment.application); // boilerplate to mock realm components -- this prevents us from hitting any // native code doNothing().when(RealmCore.class); RealmCore.loadLibrary(any(Context.class)); // create the mocked realm final Realm mockRealm = mock(Realm.class); final RealmConfiguration mockRealmConfig = mock(RealmConfiguration.class); // use this mock realm config for all new realm configurations whenNew(RealmConfiguration.class).withAnyArguments().thenReturn(mockRealmConfig); // use this mock realm for all new default realms when(Realm.getDefaultInstance()).thenReturn(mockRealm); // any time we ask Realm to create a Cat, return a new instance. when(mockRealm.createObject(Cat.class)).thenReturn(new Cat()); // set up test data Cat p1 = new Cat(); p1.setName("Enoch"); Cat p2 = new Cat(); p2.setName("Quincy Endicott"); Cat p3 = new Cat(); p3.setName("Sara"); Cat p4 = new Cat(); p4.setName("Jimmy Brown"); List catList = Arrays.asList(p1, p2, p3, p4); // create a mocked RealmQuery RealmQuery catQuery = mockRealmQuery(); // when the RealmQuery performs findFirst, return the first record in the list. when(catQuery.findFirst()).thenReturn(catList.get(0)); // when the where clause is called on the Realm, return the mock query. when(mockRealm.where(Cat.class)).thenReturn(catQuery); // when the RealmQuery is filtered on any string and any integer, return the query when(catQuery.equalTo(anyString(), anyInt())).thenReturn(catQuery); // when a between query is performed with any string as the field and any int as the // value, then return the catQuery itself when(catQuery.between(anyString(), anyInt(), anyInt())).thenReturn(catQuery); // When a beginsWith clause is performed with any string field and any string value // return the same cat query when(catQuery.beginsWith(anyString(), anyString())).thenReturn(catQuery); // RealmResults is final, must mock static and also place this in the PrepareForTest // annotation array. mockStatic(RealmResults.class); // create a mock RealmResults RealmResults cats = mockRealmResults(); // the for(...) loop in Java needs an iterator, so we're giving it one that has items, // since the mock RealmResults does not provide an implementation. Therefore, any time // anyone asks for the RealmResults Iterator, give them a functioning iterator from the // ArrayList of Cats we created above. This will allow the loop to execute. when(cats.iterator()).thenReturn(catList.iterator()); // Return the size of the mock list. when(cats.size()).thenReturn(catList.size()); // when we ask Realm for all of the Cat instances, return the mock RealmResults when(mockRealm.where(Cat.class).findAll()).thenReturn(cats); // when we ask the RealmQuery for all of the Cat objects, return the mock RealmResults when(catQuery.findAll()).thenReturn(cats); this.mockRealm = mockRealm; this.cats = cats; } @Test public void shouldBeAbleToAccessActivityAndVerifyRealmInteractions() { doCallRealMethod().when(mockRealm) .executeTransaction(any(Realm.Transaction.class)); // create test activity -- onCreate method calls methods that // query/write to realm UnitTestActivity activity = Robolectric .buildActivity(UnitTestActivity.class) .create() .start() .resume() .visible() .get(); // click the clean up button activity.findViewById(R.id.clean_up).performClick(); // verify that we queried for Cat instances five times in this run // (2 in basicCrud(), 2 in complexQuery() and 1 in the button click) verify(mockRealm, times(5)).where(Cat.class); // verify that the delete method was called. We also call delete at // the start of the activity to ensure we start with a clean db. verify(mockRealm, times(2)).delete(Cat.class); // call the destroy method so we can verify that the .close() method // was called (below) activity.onDestroy(); // verify that the realm got closed 2 separate times. Once in the // AsyncTask, once in onDestroy verify(mockRealm, times(2)).close(); } @SuppressWarnings("unchecked") private RealmQuery mockRealmQuery() { return mock(RealmQuery.class); } @SuppressWarnings("unchecked") private RealmResults mockRealmResults() { return mock(RealmResults.class); } } ``` #### Kotlin ```kotlin package com.mongodb.realm.examples.kotlin import android.os.AsyncTask import android.os.Bundle import android.util.Log import android.view.View import android.widget.LinearLayout import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import com.mongodb.realm.examples.R import com.mongodb.realm.examples.model.java.Cat import io.realm.Realm class UnitTestActivity : AppCompatActivity() { private var rootLayout: LinearLayout? = null private var realm: Realm? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Realm.init(applicationContext) setContentView(R.layout.activity_unit_test) rootLayout = findViewById(R.id.container) rootLayout!!.removeAllViews() // open the default Realm for the UI thread. realm = Realm.getDefaultInstance() // clean up from previous run cleanUp() // small operation that is ok to run on the main thread basicCRUD(realm) // more complex operations can be executed on another thread. val foo: AsyncTask = object : AsyncTask() { protected override fun doInBackground(vararg params: Void?): String? { var info = "" info += complexQuery() return info } override fun onPostExecute(result: String) { showStatus(result) } } foo.execute() findViewById(R.id.clean_up).setOnClickListener { view: View -> view.isEnabled = false Log.d("TAG", "clean up") cleanUp() view.isEnabled = true } } private fun cleanUp() { // delete all cats realm!!.executeTransaction { r: Realm -> r.delete(Cat::class.java) } } public override fun onDestroy() { super.onDestroy() realm!!.close() // remember to close realm when done. } private fun showStatus(txt: String) { Log.i(TAG, txt) val tv = TextView(this) tv.text = txt rootLayout!!.addView(tv) } private fun basicCRUD(realm: Realm?) { showStatus("Perform basic Create/Read/Update/Delete (CRUD) operations...") // all writes must be wrapped in a transaction to facilitate safe multi threading realm!!.executeTransaction { r: Realm -> // add a cat val cat = r.createObject(Cat::class.java) cat.name = "John Young" } // find the first cat (no query conditions) and read a field val cat = realm.where(Cat::class.java).findFirst() showStatus(cat!!.name) // update cat in a transaction realm.executeTransaction { r: Realm? -> cat.name = "John Senior" } showStatus(cat.name) // add two more cats realm.executeTransaction { r: Realm -> val jane = r.createObject(Cat::class.java) jane.name = "Jane" val doug = r.createObject(Cat::class.java) doug.name = "Robert" } val cats = realm.where(Cat::class.java).findAll() showStatus(String.format("Found %s cats", cats.size)) for (p in cats) { showStatus("Found " + p.name) } } private fun complexQuery(): String { var status = "\n\nPerforming complex Query operation..." val realm = Realm.getDefaultInstance() status += """ Number of cats in the DB: ${realm.where(Cat::class.java).count()} """.trimIndent() // find all cats where name begins with "J". val results = realm.where(Cat::class.java) .beginsWith("name", "J") .findAll() status += """ Number of cats whose name begins with 'J': ${results.size} """.trimIndent() realm.close() return status } companion object { val TAG = UnitTestActivity::class.java.name } } ``` ```kotlin import android.content.Context import android.view.View import com.mongodb.realm.examples.R import com.mongodb.realm.examples.kotlin.UnitTestActivity import com.mongodb.realm.examples.model.java.Cat import io.realm.Realm import io.realm.RealmConfiguration import io.realm.RealmObject import io.realm.RealmQuery import io.realm.RealmResults import io.realm.internal.RealmCore import io.realm.log.RealmLog import java.lang.Exception import java.util.* import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers import org.mockito.Mockito import org.powermock.api.mockito.PowerMockito import org.powermock.core.classloader.annotations.PowerMockIgnore import org.powermock.core.classloader.annotations.PrepareForTest import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor import org.powermock.modules.junit4.rule.PowerMockRule import org.robolectric.Robolectric import org.robolectric.RobolectricTestRunner import org.robolectric.RuntimeEnvironment import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(sdk = [28]) @PowerMockIgnore( "org.mockito.*", "org.robolectric.*", "android.*", "jdk.internal.reflect.*", "androidx.*" ) @SuppressStaticInitializationFor("io.realm.internal.Util") @PrepareForTest( Realm::class, RealmConfiguration::class, RealmQuery::class, RealmResults::class, RealmCore::class, RealmLog::class ) class TestTest { // bootstrap powermock @Rule var rule = PowerMockRule() // mocked realm SDK components for tests private var mockRealm: Realm? = null private var cats: RealmResults? = null @Before @Throws(Exception::class) fun setup() { // set up realm SDK components to be mocked. The order of these matters PowerMockito.mockStatic(RealmCore::class.java) PowerMockito.mockStatic(RealmLog::class.java) PowerMockito.mockStatic(Realm::class.java) PowerMockito.mockStatic(RealmConfiguration::class.java) Realm.init(RuntimeEnvironment.application) PowerMockito.doNothing().`when`(RealmCore::class.java) RealmCore.loadLibrary(ArgumentMatchers.any(Context::class.java)) // create the mocked realm val mockRealm = PowerMockito.mock(Realm::class.java) val mockRealmConfig = PowerMockito.mock( RealmConfiguration::class.java ) // use this mock realm config for all new realm configurations PowerMockito.whenNew(RealmConfiguration::class.java).withAnyArguments() .thenReturn(mockRealmConfig) // use this mock realm for all new default realms PowerMockito.`when`(Realm.getDefaultInstance()).thenReturn(mockRealm) // any time we ask Realm to create a Cat, return a new instance. PowerMockito.`when`(mockRealm.createObject(Cat::class.java)).thenReturn(Cat()) // set up test data val p1 = Cat() p1.name = "Enoch" val p2 = Cat() p2.name = "Quincy Endicott" val p3 = Cat() p3.name = "Sara" val p4 = Cat() p4.name = "Jimmy Brown" val catList = Arrays.asList(p1, p2, p3, p4) // create a mocked RealmQuery val catQuery = mockRealmQuery() // when the RealmQuery performs findFirst, return the first record in the list. PowerMockito.`when`(catQuery!!.findFirst()).thenReturn(catList[0]) // when the where clause is called on the Realm, return the mock query. PowerMockito.`when`(mockRealm.where(Cat::class.java)).thenReturn(catQuery) // when the RealmQuery is filtered on any string and any integer, return the query PowerMockito.`when`( catQuery.equalTo( ArgumentMatchers.anyString(), ArgumentMatchers.anyInt() ) ).thenReturn(catQuery) // when a between query is performed with any string as the field and any int as the // value, then return the catQuery itself PowerMockito.`when`( catQuery.between( ArgumentMatchers.anyString(), ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt() ) ).thenReturn(catQuery) // When a beginsWith clause is performed with any string field and any string value // return the same cat query PowerMockito.`when`( catQuery.beginsWith( ArgumentMatchers.anyString(), ArgumentMatchers.anyString() ) ).thenReturn(catQuery) // RealmResults is final, must mock static and also place this in the PrepareForTest // annotation array. PowerMockito.mockStatic(RealmResults::class.java) // create a mock RealmResults val cats = mockRealmResults() // the for(...) loop in Java needs an iterator, so we're giving it one that has items, // since the mock RealmResults does not provide an implementation. Therefore, any time // anyone asks for the RealmResults Iterator, give them a functioning iterator from the // ArrayList of Cats we created above. This will allow the loop to execute. PowerMockito.`when`>(cats!!.iterator()).thenReturn(catList.iterator()) // Return the size of the mock list. PowerMockito.`when`(cats.size).thenReturn(catList.size) // when we ask Realm for all of the Cat instances, return the mock RealmResults PowerMockito.`when`(mockRealm.where(Cat::class.java).findAll()).thenReturn(cats) // when we ask the RealmQuery for all of the Cat objects, return the mock RealmResults PowerMockito.`when`(catQuery.findAll()).thenReturn(cats) this.mockRealm = mockRealm this.cats = cats } @Test fun shouldBeAbleToAccessActivityAndVerifyRealmInteractions() { Mockito.doCallRealMethod().`when`(mockRealm)!! .executeTransaction(ArgumentMatchers.any(Realm.Transaction::class.java)) // create test activity -- onCreate method calls methods that // query/write to realm val activity = Robolectric .buildActivity(UnitTestActivity::class.java) .create() .start() .resume() .visible() .get() // click the clean up button activity.findViewById(R.id.clean_up).performClick() // verify that we queried for Cat instances five times in this run // (2 in basicCrud(), 2 in complexQuery() and 1 in the button click) Mockito.verify(mockRealm, Mockito.times(5))!!.where(Cat::class.java) // verify that the delete method was called. We also call delete at // the start of the activity to ensure we start with a clean db. Mockito.verify(mockRealm, Mockito.times(2))!!.delete(Cat::class.java) // call the destroy method so we can verify that the .close() method // was called (below) activity.onDestroy() // verify that the realm got closed 2 separate times. Once in the // AsyncTask, once in onDestroy Mockito.verify(mockRealm, Mockito.times(2))!!.close() } private fun mockRealmQuery(): RealmQuery? { @Suppress("UNCHECKED_CAST") return PowerMockito.mock(RealmQuery::class.java) as RealmQuery } private fun mockRealmResults(): RealmResults? { @Suppress("UNCHECKED_CAST") return PowerMockito.mock(RealmResults::class.java) as RealmResults } } ``` > Seealso: > See the [Unit Testing Example App](https://github.com/realm/realm-java/tree/master/examples/unitTestExample) for an example of unit testing an application that uses Realm. > ================================================ FILE: docs/guides/test-and-debug/troubleshooting.md ================================================ # Troubleshooting - Java SDK ## Couldn't load "librealm-jni.so" If your app uses native libraries that don't ship with support for 64-bit architectures, Android will fail to load Realm's `librealm-jni.so` file on ARM64 devices. This happens because Android cannot load 32-bit and 64-bit native libraries concurrently. Ideally, all libraries could provide the same set of supported ABIs, but sometimes that may not be doable when using a 3rd-party library. To work around this issue, you can exclude Realm's ARM64 library from the APK file by adding the following code to the application's `build.gradle`. You can refer to Mixing 32- and 64-bit Dependencies in Android for more information. ```gradle android { //... packagingOptions { exclude "lib/arm64-v8a/librealm-jni.so" } //... } ``` > Seealso: > For more information, see [Mixing 32- and 64-bit Dependencies in Android](https://corbt.com/posts/2015/09/18/mixing-32-and-64bit-dependencies-in-android.html). > ## Network Calls to Mixpanel Realm collects anonymous analytics when you run the Realm bytecode transformer on your source code. This is completely anonymous and helps us improve the product by flagging: - which version of the SDK you use - which operating system you use - if your application uses Kotlin Analytics do not run when your application runs on user devices - only when you compile your source code. To opt out of analytics, you can set the `REALM_DISABLE_ANALYTICS` environment variable to `true`. ## Change Listeners in Android 12 with SDK Versions Below 10.5.1 Due to a change in the Linux kernel, object, collection, and realm notifications do not work in SDK versions below 10.5.1 on devices running certain early versions of Android 12. This change effects Linux kernel versions beginning with `5.5`. Linux kernel version `5.14-rc4` fixed the issue. The fix was also backported to `LTS 5.10.56` and `LTS 5.13.8`. All mainline and LTS Android 12 branches contain the fix or a backport of it. If you experience this issue, you can restore notification functionality with the following fixes: - upgrade to a version of the SDK later than 10.5.1. - upgrade to a version of Android 12 that uses a Linux kernel release that contains the fix (kernel commit `3a34b13a88caeb2800ab44a4918f230041b37dd9`) or the backport of the fix (kernel commit `4b20d2de0b367bca627b49efd8d2e9e01bb66753`). ## Configurations Cannot be Different if Used to Open the Same File Realm runs checks whenever you open a realm file to avoid corruption. In order to avoid accidentally opening a realm file with incompatible settings, the SDK uses Java's `equals()` method to compare `RealmConfiguration` objects. This prevents the SDK from opening a single realm file with different schemas, durability levels, or writability settings. However, configurations that include lambda functions, such as those passed to `initialData()` and `compactOnLaunch()`, can break this `equals()` comparison, since two different lambdas are never considered equal using Java's built-in comparison. To avoid this error when using lambdas, you can either: 1. Store a single configuration statically in your application, so that separate realm instances use the exact same `RealmConfiguration` object and it passes the check. 2. Override the default equals check of the `RealmConfiguration`: `val config = RealmConfiguration.Builder() .initialData(object: Realm.Transaction { override fun execute(realm: Realm) { TODO("Not yet implemented") } override fun equals(other: Any?): Boolean { return true } override fun hashCode(): Int { return 37 } }).build()` ## Kapt Exceptions During Builds If you experience an exception in the Kapt library with a description like the following: ``` A failure occurred while executing org.jetbrains.kotlin.gradle.internal.KaptWithoutKotlincTask$KaptExecutionWorkAction ``` This most likely means there is an issue with one of your model classes. Possible causes include: - introducing a field type that is not supported by the SDK - using a visibility type other than `open` or `public` for a realm object model class - using a Realm annotation on an incompatible field If you experience this error, check any recent updates to your schema for problems. ## Installation Size Once your app is built for release and split for distribution, the SDK should only add about 800KB to your APK in most cases. The releases are significantly larger because they include support for more architectures, such as ARM7, ARMv7, ARM64, x86, and MIPS. The APK file contains all supported architectures, but the Android installer only installs native code for the device's architecture. This means that the installed app is smaller than the size of the APK file. You can reduce the size of the Android APK itself by splitting the APK into a version for each architecture. Use the Android Build Tool ABI Split support by adding the following to your build.gradle: ```gradle android { splits { abi { enable true reset() include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' } } } ``` Select the architectures that you'd like to include to build a separate APK for each. > Seealso: > See the [Android Tools documentation about ABI Splits](https://developer.android.com/studio/build/configure-apk-splits.html) for more information, or the [example on GitHub](https://github.com/realm/realm-java/tree/master/examples/gridViewExample). > If you don't want to handle multiple APKs, you can restrict the number of architectures supported in a single APK. This is done by adding `abiFilters` to your build.gradle: ```gradle android { defaultConfig { ndk { abiFilters 'armeabi-v7a', 'arm64-v8a', 'mips', 'x86', 'x86_64' } } } ``` > Seealso: > [Controlling APK Size When Using Native Libraries](https://medium.com/android-news/controlling-apk-size-when-using-native-libraries-45c6c0e5b70a). > ## Customize Dependencies Defined by the Realm Gradle Plugin Realm uses a Gradle plugin because it makes it easier to set up a large number of dependencies. Unfortunately this also makes it a bit harder to ignore specific transitive dependencies. If you want to customize Realm beyond what is exposed by the plugin, you can manually set up all the dependencies and ignore the Gradle plugin. The following example demonstrates how to set up the SDK for an Android application using Kotlin manually: ```gradle buildscript { ext.kotlin_version = '1.5.21' ext.realm_version = '10.18.0' repositories { jcenter() mavenCentral() } dependencies { classpath "io.realm:realm-transformer:$realm_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' import io.realm.transformer.RealmTransformer android.registerTransform(new RealmTransformer(project)) dependencies { api "io.realm:realm-annotations:$realm_version" api "io.realm:realm-android-library:$realm_version" api "io.realm:realm-android-kotlin-extensions:$realm_version" kapt "io.realm:realm-annotations-processor:$realm_version" } ``` ================================================ FILE: docs/guides/troubleshooting.md ================================================ # Troubleshooting - Java SDK ## Use in System Apps on Custom Android ROMs Realm SDKs use named pipes to support notifications and access to the Realm file from multiple processes. While this is allowed by default for normal user apps, it is disallowed for system apps. System apps are defined by setting `android:sharedUserId="android.uid.system"` in the Android manifest. For system apps, you may see a security violation in Logcat that looks something like this: ```bash 05-24 14:08:08.984 6921 6921 W .realmsystemapp: type=1400 audit(0.0:99): avc: denied { write } for name="realm.testapp.com.realmsystemapp-Bfqpnjj4mUvxWtfMcOXBCA==" dev="vdc" ino=14660 scontext=u:r:system_app:s0 tcontext=u:object_r:apk_data_file:s0 tclass=dir permissive=0 05-24 14:08:08.984 6921 6921 W .realmsystemapp: type=1400 audit(0.0:100): avc: denied { write } for name="realm.testapp.com.realmsystemapp-Bfqpnjj4mUvxWtfMcOXBCA==" dev="vdc" ino=14660 scontext=u:r:system_app:s0 tcontext=u:object_r:apk_data_file:s0 tclass=dir permissive=0 ``` To fix this, you need to adjust the SELinux security rules in the ROM. This can be done by using the tool `audit2allow`. This tool ships as part of [AOSP](https://source.android.com/). 1. Pull the current policy from the device: `adb pull /sys/fs/selinux/policy`. 2. Copy the SELinux error inside a text file called `input.txt`. 3. Run the `audit2allow` tool: `audit2allow -p policy -i input.txt`. 4. The tool should output a rule you can add to your existing policy. The rule allows you to access the Realm file from multiple processes. `audit2allow` is produced when compiling AOSP/ROM and only runs on Linux. Check out the details in the [Android Source documentation](https://source.android.com/security/selinux/validate#using_audit2allow). Also note that since Android Oreo, Google changed the way it configures SELinux and the default security policies are now more modularized. More details are in the [Android Source documentation](https://source.android.com/security/selinux/images/SELinux_Treble.pdf). ================================================ FILE: examples/architectureComponentsExample/build.gradle ================================================ apply plugin: 'com.android.application' apply plugin: 'realm-android' android { compileSdkVersion rootProject.sdkVersion buildToolsVersion rootProject.buildTools compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } defaultConfig { applicationId 'io.realm.examples.arch' targetSdkVersion rootProject.sdkVersion minSdkVersion rootProject.minSdkVersion versionCode 1 versionName "1.0" vectorDrawables.useSupportLibrary = true } buildTypes { release { minifyEnabled true signingConfig signingConfigs.debug } debug { minifyEnabled false } } } dependencies { implementation "android.arch.lifecycle:runtime:1.1.1" implementation "android.arch.lifecycle:extensions:1.1.1" annotationProcessor "android.arch.lifecycle:compiler:1.1.1" implementation 'com.android.support:appcompat-v7:27.1.1' implementation 'com.android.support:recyclerview-v7:27.1.1' implementation 'com.android.support:design:27.1.1' } ================================================ FILE: examples/architectureComponentsExample/lint.xml ================================================ ================================================ FILE: examples/architectureComponentsExample/src/main/AndroidManifest.xml ================================================ ================================================ FILE: examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/ArchExampleActivity.java ================================================ /* * Copyright 2018 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.arch; import android.os.Bundle; import android.support.annotation.MainThread; import android.support.design.widget.FloatingActionButton; import android.support.v7.app.AppCompatActivity; public class ArchExampleActivity extends AppCompatActivity { private FloatingActionButton backgroundJobStartStop; private BackgroundTask backgroundTask; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_arch_example); setupViews(); backgroundTask = (BackgroundTask) getLastCustomNonConfigurationInstance(); if (backgroundTask == null) { // this could also live inside a ViewModel, a singleton job queue, etc. backgroundTask = new BackgroundTask(); backgroundTask.start(); // this task will update items in Realm on a background thread. } updateJobButton(); if (savedInstanceState == null) { getSupportFragmentManager().beginTransaction() .add(R.id.container, PersonListFragment.create()) .addToBackStack(null) .commit(); } } @Override public Object onRetainCustomNonConfigurationInstance() { return backgroundTask; // retain background task through config changes without ViewModel. } @Override protected void onDestroy() { super.onDestroy(); if (isFinishing()) { if(backgroundTask.isStarted()) { backgroundTask.stop(); // make sure job is stopped when exiting the app } } } @Override public void onBackPressed() { if (getSupportFragmentManager().getBackStackEntryCount() <= 1) { finish(); } else { super.onBackPressed(); } } @MainThread private void setupViews() { backgroundJobStartStop = findViewById(R.id.backgroundJobStartStop); backgroundJobStartStop.setOnClickListener(v -> { if (!backgroundTask.isStarted()) { backgroundTask.start(); } else { backgroundTask.stop(); } updateJobButton(); }); } private void updateJobButton() { if (backgroundTask.isStarted()) { backgroundJobStartStop.setImageResource(R.drawable.ic_stop_black_24dp); } else { backgroundJobStartStop.setImageResource(R.drawable.ic_play_arrow_black_24dp); } } } ================================================ FILE: examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/BackgroundTask.java ================================================ /* * Copyright 2018 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.arch; import android.annotation.SuppressLint; import android.os.SystemClock; import android.support.annotation.MainThread; import android.util.Log; import io.realm.Realm; import io.realm.RealmResults; import io.realm.examples.arch.model.Person; public class BackgroundTask { private static final Object lock = new Object(); private static final String TAG = "BackgroundTask"; private boolean isStarted; private volatile Thread thread; @MainThread public boolean isStarted() { return isStarted; } @MainThread public void start() { synchronized (lock) { if (isStarted) { return; } thread = new IncrementThread(); thread.start(); isStarted = true; Log.i(TAG, "Background job started."); } } @MainThread public void stop() { synchronized (lock) { if (thread != null) { thread.interrupt(); thread = null; } isStarted = false; } } private static final class IncrementThread extends Thread { IncrementThread() { super("Aging thread"); } @Override @SuppressLint("NewApi") public void run() { try (Realm realm = Realm.getDefaultInstance()) { final RealmResults persons = realm.where(Person.class).findAll(); Realm.Transaction transaction = (Realm r) -> { for (Person person : persons) { person.setAge(person.getAge() + 1); // updates the Persons in the Realm. } }; while (!isInterrupted()) { realm.executeTransaction(transaction); SystemClock.sleep(1000L); } } Log.i(TAG, "Background job stopped."); } } } ================================================ FILE: examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/CustomApplication.java ================================================ /* * Copyright 2018 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.arch; import android.app.Application; import android.support.annotation.NonNull; import io.realm.Realm; import io.realm.RealmConfiguration; import io.realm.examples.arch.model.Person; public class CustomApplication extends Application { @Override public void onCreate() { super.onCreate(); Realm.init(this); Realm.setDefaultConfiguration(new RealmConfiguration.Builder() .deleteRealmIfMigrationNeeded() .initialData(new Realm.Transaction() { @Override public void execute(@NonNull Realm realm) { Person person = realm.createObject(Person.class); person.setName("Makoto Yamazaki"); person.setAge(32); person = realm.createObject(Person.class); person.setName("Christian Melchior"); person.setAge(34); person = realm.createObject(Person.class); person.setName("Chen Mulong"); person.setAge(29); person = realm.createObject(Person.class); person.setName("Nabil Hachicha"); person.setAge(31); } }) .build()); } } ================================================ FILE: examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonFragment.java ================================================ /* * Copyright 2018 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.arch; import android.arch.lifecycle.ViewModel; import android.arch.lifecycle.ViewModelProvider; import android.arch.lifecycle.ViewModelProviders; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; public class PersonFragment extends Fragment { private static final String ARG_PERSON_NAME = "personName"; public static PersonFragment create(String personName) { PersonFragment personFragment = new PersonFragment(); Bundle bundle = new Bundle(); bundle.putString(ARG_PERSON_NAME, personName); personFragment.setArguments(bundle); return personFragment; } private PersonViewModel personViewModel; private TextView name; private TextView age; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @SuppressWarnings("ConstantConditions") final String personName = getArguments().getString(ARG_PERSON_NAME); personViewModel = ViewModelProviders.of(this, new ViewModelProvider.Factory() { @NonNull @Override public T create(@NonNull Class modelClass) { if (modelClass == PersonViewModel.class) { PersonViewModel personViewModel = new PersonViewModel(); personViewModel.setup(personName); // we use a Factory to ensure `setup` is called before use. //noinspection unchecked return (T) personViewModel; } //noinspection ConstantConditions return null; } }).get(PersonViewModel.class); personViewModel.getPerson().observe(this, person -> { if (person != null) { // null would mean the object was deleted. name.setText(person.getName()); age.setText(String.valueOf(person.getAge())); } }); } @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_person, container, false); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); name = view.findViewById(R.id.personName); age = view.findViewById(R.id.personAge); } } ================================================ FILE: examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonListFragment.java ================================================ /* * Copyright 2018 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.arch; import android.arch.lifecycle.ViewModelProviders; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import java.util.Collections; import java.util.List; import io.realm.examples.arch.model.Person; import io.realm.examples.arch.utils.ContextUtils; public class PersonListFragment extends Fragment { public static PersonListFragment create() { return new PersonListFragment(); } private RecyclerView recyclerView; private Adapter adapter; private PersonListViewModel personListViewModel; private List personList = Collections.emptyList(); @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Fragments should start listening in `onCreate()` // to ensure single observer instance, even if detached (for example in FragmentPagerAdapter). personListViewModel = ViewModelProviders.of(this).get(PersonListViewModel.class); personListViewModel.getPersons().observe(this, people -> { personList = people; if (adapter != null) { adapter.updateItems(people); } }); } @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_person_list, container, false); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); recyclerView = view.findViewById(R.id.recyclerView); recyclerView.setHasFixedSize(true); recyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false)); adapter = new Adapter(personList); recyclerView.setAdapter(adapter); } static class Adapter extends RecyclerView.Adapter { private List persons; public Adapter(List persons) { this.persons = persons; } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_person, parent, false)); } @Override public void onBindViewHolder(ViewHolder holder, int position) { holder.bind(persons.get(position)); } @Override public int getItemCount() { return persons == null ? 0 : persons.size(); } public void updateItems(List persons) { this.persons = persons; notifyDataSetChanged(); } static class ViewHolder extends RecyclerView.ViewHolder { TextView name; TextView age; Person person; private final View.OnClickListener onClick = (view) -> { if (person == null) { return; } AppCompatActivity activity = ContextUtils.findActivity(view.getContext()); PersonFragment personFragment = PersonFragment.create(person.name); activity.getSupportFragmentManager() .beginTransaction() .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) .replace(R.id.container, personFragment) .addToBackStack(null) .commit(); }; public ViewHolder(View itemView) { super(itemView); name = itemView.findViewById(R.id.personName); age = itemView.findViewById(R.id.personAge); itemView.setOnClickListener(onClick); } public void bind(Person person) { this.person = person; name.setText(person.getName()); age.setText(String.valueOf(person.getAge())); } } } } ================================================ FILE: examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonListViewModel.java ================================================ /* * Copyright 2018 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.arch; import android.arch.lifecycle.LiveData; import android.arch.lifecycle.ViewModel; import java.util.List; import io.realm.Realm; import io.realm.examples.arch.livemodel.LiveRealmResults; import io.realm.examples.arch.model.Person; public class PersonListViewModel extends ViewModel { private final Realm realm; private final LiveData> persons; public PersonListViewModel() { realm = Realm.getDefaultInstance(); // Realm is bound to the lifecycle of the ViewModel, and stays alive as long as it is needed. persons = new LiveRealmResults<>(realm.where(Person.class).sort("age").findAllAsync()); } public LiveData> getPersons() { return persons; } @Override protected void onCleared() { realm.close(); // Realm is bound to the lifecycle of the ViewModel, and is destroyed when no longer needed. super.onCleared(); } } ================================================ FILE: examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonViewModel.java ================================================ /* * Copyright 2018 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.arch; import android.arch.lifecycle.LiveData; import android.arch.lifecycle.ViewModel; import io.realm.Realm; import io.realm.examples.arch.livemodel.LiveRealmObject; import io.realm.examples.arch.model.Person; public class PersonViewModel extends ViewModel { private final Realm realm; private LiveData livePerson; public PersonViewModel() { realm = Realm.getDefaultInstance(); // Realm is bound to the lifecycle of the ViewModel, and stays alive as long as it is needed. } public LiveData getPerson() { return livePerson; } @Override protected void onCleared() { realm.close(); // Realm is bound to the lifecycle of the ViewModel, and is destroyed when no longer needed. super.onCleared(); } public void setup(String personName) { Person person = realm.where(Person.class).equalTo("name", personName).findFirst(); if (person == null) { throw new IllegalStateException("The person was not found, it shouldn't be deleted!"); } livePerson = new LiveRealmObject<>(person); } } ================================================ FILE: examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/livemodel/LiveRealmObject.java ================================================ /* * Copyright 2018 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.arch.livemodel; import android.arch.lifecycle.LiveData; import android.support.annotation.MainThread; import android.support.annotation.NonNull; import io.realm.ObjectChangeSet; import io.realm.RealmModel; import io.realm.RealmObject; import io.realm.RealmObjectChangeListener; /** * This class represents a RealmObject wrapped inside a LiveData. * * It is expected that the provided RealmObject is a managed object, and exists in the Realm on creation. * * This allows observing the RealmObject in such a way, that the listener that will be automatically unsubscribed when the enclosing LifecycleOwner is killed. * * Realm will keep the managed RealmObject up-to-date whenever a change occurs on any thread, * and when that happens, the observer will be notified. * * The object will be observed until it is invalidated - deleted, or all local Realm instances are closed. * * @param the type of the RealmModel */ public class LiveRealmObject extends LiveData { // The listener will listen until the object is deleted. // An invalidated object shouldn't be set in LiveData, null is set instead. private RealmObjectChangeListener listener = new RealmObjectChangeListener() { @Override public void onChange(@NonNull T object, ObjectChangeSet objectChangeSet) { if (!objectChangeSet.isDeleted()) { setValue(object); } else { setValue(null); } } }; /** * Wraps the provided managed RealmObject as a LiveData. * * The provided object should not be null, should be managed, and should be valid. * * @param object the managed RealmModel to wrap as LiveData */ @MainThread public LiveRealmObject(@NonNull T object) { //noinspection ConstantConditions if (object == null) { throw new IllegalArgumentException("The object cannot be null!"); } if (!RealmObject.isManaged(object)) { throw new IllegalArgumentException("LiveRealmObject only supports managed RealmModel instances!"); } if (!RealmObject.isValid(object)) { throw new IllegalArgumentException("The provided RealmObject is no longer valid, and therefore cannot be observed for changes."); } setValue(object); } // We should start observing and stop observing, depending on whether we have observers. // Deleted objects can no longer be observed. // We can also no longer observe the object if all local Realm instances on this thread (the UI thread) are closed. /** * Starts observing the RealmObject, if it is still valid. */ @Override protected void onActive() { super.onActive(); T object = getValue(); if (object != null && RealmObject.isValid(object)) { RealmObject.addChangeListener(object, listener); } } /** * Stops observing the RealmObject. */ @Override protected void onInactive() { super.onInactive(); T object = getValue(); if (object != null && RealmObject.isValid(object)) { RealmObject.removeChangeListener(object, listener); } } } ================================================ FILE: examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/livemodel/LiveRealmResults.java ================================================ /* * Copyright 2018 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.arch.livemodel; import android.arch.lifecycle.LiveData; import android.support.annotation.MainThread; import android.support.annotation.NonNull; import java.util.List; import javax.annotation.Nullable; import io.realm.OrderedCollectionChangeSet; import io.realm.OrderedRealmCollectionChangeListener; import io.realm.RealmModel; import io.realm.RealmResults; /** * This class represents a RealmResults wrapped inside a LiveData. * * Realm will always keep the RealmResults up-to-date whenever a change occurs on any thread, * and when that happens, the observer will be notified. * * The RealmResults will be observed until it is invalidated - meaning all local Realm instances on this thread are closed. * * @param the type of the RealmModel */ public class LiveRealmResults extends LiveData> { private final RealmResults results; // The listener will notify the observers whenever a change occurs. // The results are modified in change. This could be expanded to also return the change set in a pair. private OrderedRealmCollectionChangeListener> listener = new OrderedRealmCollectionChangeListener>() { @Override public void onChange(@NonNull RealmResults results, @Nullable OrderedCollectionChangeSet changeSet) { LiveRealmResults.this.setValue(results); } }; @MainThread public LiveRealmResults(@NonNull RealmResults results) { //noinspection ConstantConditions if (results == null) { throw new IllegalArgumentException("Results cannot be null!"); } if (!results.isValid()) { throw new IllegalArgumentException("The provided RealmResults is no longer valid, the Realm instance it belongs to is closed. It can no longer be observed for changes."); } this.results = results; if (results.isLoaded()) { // we should not notify observers when results aren't ready yet (async query). // however, synchronous query should be set explicitly. setValue(results); } } // We should start observing and stop observing, depending on whether we have observers. /** * Starts observing the RealmResults, if it is still valid. */ @Override protected void onActive() { super.onActive(); if (results.isValid()) { // invalidated results can no longer be observed. results.addChangeListener(listener); } } /** * Stops observing the RealmResults. */ @Override protected void onInactive() { super.onInactive(); if (results.isValid()) { results.removeChangeListener(listener); } } } ================================================ FILE: examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/model/Person.java ================================================ /* * Copyright 2018 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.arch.model; import io.realm.RealmObject; import io.realm.annotations.Index; public class Person extends RealmObject { @Index public String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } ================================================ FILE: examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/utils/ContextUtils.java ================================================ /* * Copyright 2018 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.arch.utils; import android.app.Activity; import android.content.Context; import android.content.ContextWrapper; /** * This is a helper class to look up an Activity inside a View's context chain in a reliable/safe manner. */ public class ContextUtils { private ContextUtils() { } /** * Finds the Activity inside the hierarchy of the provided Context. * * @param context the context * @param the expected type of the Activity * @return the activity * * @throws IllegalArgumentException if the context has no Activity in its base context hierarchy */ public static T findActivity(Context context) { if (context instanceof Activity) { //noinspection unchecked return (T) context; } while (context != null && context instanceof ContextWrapper) { context = ((ContextWrapper) context).getBaseContext(); if (context instanceof Activity) { //noinspection unchecked return (T) context; } } throw new IllegalArgumentException("No activity found in context hierarchy."); } } ================================================ FILE: examples/architectureComponentsExample/src/main/res/drawable/ic_play_arrow_black_24dp.xml ================================================ ================================================ FILE: examples/architectureComponentsExample/src/main/res/drawable/ic_stop_black_24dp.xml ================================================ ================================================ FILE: examples/architectureComponentsExample/src/main/res/layout/activity_arch_example.xml ================================================ ================================================ FILE: examples/architectureComponentsExample/src/main/res/layout/fragment_person.xml ================================================ ================================================ FILE: examples/architectureComponentsExample/src/main/res/layout/fragment_person_list.xml ================================================ ================================================ FILE: examples/architectureComponentsExample/src/main/res/layout/item_person.xml ================================================ ================================================ FILE: examples/architectureComponentsExample/src/main/res/values/dimens.xml ================================================ 16dp 16dp ================================================ FILE: examples/architectureComponentsExample/src/main/res/values/strings.xml ================================================ Lifecycle example ================================================ FILE: examples/architectureComponentsExample/src/main/res/values/styles.xml ================================================ ================================================ FILE: examples/architectureComponentsExample/src/main/res/values-w820dp/dimens.xml ================================================ 64dp ================================================ FILE: examples/build.gradle ================================================ def projectDependencies = new Properties() projectDependencies.load(new FileInputStream("${rootDir}/../dependencies.list")) project.ext.sdkVersion = 29 project.ext.minSdkVersion = 16 project.ext.buildTools = projectDependencies.get("ANDROID_BUILD_TOOLS") project.ext.kotlinVersion = projectDependencies.get('KOTLIN') // Don't cache SNAPSHOT (changing) dependencies. configurations.all { resolutionStrategy.cacheChangingModulesFor 0, 'seconds' } static String getAppId (path) { String build = new File(path).text def matcher = build =~ 'applicationId.*' def appId = matcher.size() > 0 ? matcher[0].trim() - 'applicationId' - ~/\s/ : '' String myappId = appId.replaceAll('"', '') myappId = myappId.replaceAll('\'', '') return myappId } allprojects { def currentVersion = file("${rootDir}/../version.txt").text.trim() def props = new Properties() props.load(new FileInputStream("${rootDir}/../dependencies.list")) props.each { key, val -> project.ext.set(key, val) } buildscript { ext { kotlin_version = projectDependencies.get('KOTLIN') } repositories { mavenLocal() mavenCentral() maven { url 'https://jitpack.io' } google() jcenter() } dependencies { classpath "com.android.tools.build:gradle:${props.get("GRADLE_BUILD_TOOLS")}" classpath "io.realm:realm-gradle-plugin:${currentVersion}" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } group = 'io.realm' version = currentVersion repositories { mavenLocal() mavenCentral() google() jcenter() } if (!project.name.startsWith("realm-examples") && !project.name.startsWith("library") && !project.name.startsWith("moduleExample")) { // exclude root and library project ["Debug", "Release"].each { task "monkey${it}"(dependsOn: "install${it}") { doLast { def numberOfEvents = 2000 def appId = getAppId("${project.projectDir}/build.gradle") def process = "adb shell monkey -p ${appId} --pct-syskeys 0 ${numberOfEvents}".execute([], project.rootDir) def sout = new StringBuilder(), serr = new StringBuilder() process.consumeProcessOutput(sout, serr) process.waitFor() if (process.exitValue() != 0 || !sout?.toString()?.trim()?.contains("Events injected: ${numberOfEvents}")) { // fail Gradle build throw new GradleException("monkey failed for AppID: ${appId} \nExit code: ${process.exitValue()}\nStd out: ${sout}\nStd err: ${serr}") } } } } } } ================================================ FILE: examples/compatibilityExample/README.md ================================================ # Using this example This example is not really meant for use, but only as a compatibility check to ensure that we can build projects without AndroidX and Java 8 features. ================================================ FILE: examples/compatibilityExample/build.gradle ================================================ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-android-extensions' apply plugin: 'realm-android' android { compileSdkVersion rootProject.sdkVersion buildToolsVersion rootProject.buildTools defaultConfig { applicationId 'io.realm.examples.compatibility' targetSdkVersion rootProject.sdkVersion minSdkVersion rootProject.minSdkVersion versionCode 1 versionName "1.0" } buildTypes { // Configure server and App Id. // The default server is https://realm-dev.mongodb.com/ . Go to that and copy the MongoDB // Realm App Id. // // If you are running a local version of MongoDB Realm, modify endpoint accordingly. Most // likely it is "http://localhost:9090" def mongodbRealmUrl = "https://realm-dev.mongodb.com" def appId = "my-app-id" debug { buildConfigField "String", "MONGODB_REALM_URL", "\"${mongodbRealmUrl}\"" buildConfigField "String", "MONGODB_REALM_APP_ID", "\"${appId}\"" } release { buildConfigField "String", "MONGODB_REALM_URL", "\"${mongodbRealmUrl}\"" buildConfigField "String", "MONGODB_REALM_APP_ID", "\"${appId}\"" minifyEnabled true signingConfig signingConfigs.debug } } // Ensure that we can compile an app without Java 8 features compileOptions { sourceCompatibility 1.8 targetCompatibility 1.8 } } realm { syncEnabled = true } 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 "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'com.android.support.constraint:constraint-layout:1.1.3' } if ((project.findProperty("android.useAndroidX") ?: false).toBoolean()) throw new RuntimeException("Compatibility project should run without AndroidX") ================================================ FILE: examples/compatibilityExample/gradle.properties ================================================ # Ensure that we do not use AndroidX for this project android.useAndroidX=false ================================================ FILE: examples/compatibilityExample/lint.xml ================================================ ================================================ FILE: examples/compatibilityExample/src/main/AndroidManifest.xml ================================================ ================================================ FILE: examples/compatibilityExample/src/main/java/io/realm/examples/compatibility/MyActivity.kt ================================================ package io.realm.examples.compatibility import android.support.v7.app.AppCompatActivity import android.os.Bundle class MyActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_my_activty) } } ================================================ FILE: examples/compatibilityExample/src/main/java/io/realm/examples/compatibility/MyApplication.kt ================================================ /* * 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.compatibility import android.app.Application import io.realm.Realm import io.realm.log.LogLevel import io.realm.log.RealmLog import io.realm.mongodb.App import io.realm.mongodb.AppConfiguration lateinit var APP: App class MyApplication : Application() { override fun onCreate() { super.onCreate() Realm.init(this) APP = App(AppConfiguration.Builder(BuildConfig.MONGODB_REALM_APP_ID) .baseUrl(BuildConfig.MONGODB_REALM_URL) .appName(BuildConfig.VERSION_NAME) .appVersion(BuildConfig.VERSION_CODE.toString()) .build()) // Enable more logging in debug mode if (BuildConfig.DEBUG) { RealmLog.setLevel(LogLevel.DEBUG) } } } ================================================ FILE: examples/compatibilityExample/src/main/res/layout/activity_my_activty.xml ================================================ ================================================ FILE: examples/compatibilityExample/src/main/res/values/dimens.xml ================================================ 16dp 16dp ================================================ FILE: examples/compatibilityExample/src/main/res/values/realm_colors.xml ================================================ #1C233F #9A9BA5 #b1b3bf #EBEBF2 #39477F #59569E #9A59A5 #D34CA3 #F25192 #F77C88 #FC9F95 #FCC397 #d64881 #dadada #EF5350 #9CCC65 #FFA726 ================================================ FILE: examples/compatibilityExample/src/main/res/values/strings.xml ================================================ Realm Compatibility Example Realm Logo ================================================ FILE: examples/compatibilityExample/src/main/res/values/styles.xml ================================================ ================================================ FILE: examples/coroutinesExample/build.gradle ================================================ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' apply plugin: 'realm-android' android { // androidx.lifecycle dependencies requires Android APIs 31 or later compileSdkVersion 31 buildToolsVersion rootProject.buildTools defaultConfig { applicationId "io.realm.examples.coroutinesexample" targetSdkVersion rootProject.sdkVersion minSdkVersion rootProject.minSdkVersion versionCode 1 versionName "1.0" multiDexEnabled true testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } buildFeatures { dataBinding true } packagingOptions { exclude 'META-INF/LICENSE' } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = JavaVersion.VERSION_1_8.toString() } } dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "androidx.activity:activity-ktx:1.1.0" implementation "androidx.appcompat:appcompat:1.2.0" implementation "androidx.constraintlayout:constraintlayout:2.0.4" implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0" implementation "androidx.fragment:fragment-ktx:1.2.5" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0" implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0" implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0" implementation "androidx.lifecycle:lifecycle-common-java8:2.4.0" implementation "androidx.legacy:legacy-support-v4:1.0.0" implementation "androidx.multidex:multidex:2.0.1" implementation "androidx.recyclerview:recyclerview:1.1.0" implementation "com.dropbox.mobile.store:store4:4.0.5" implementation "com.google.android.material:material:1.2.1" implementation "com.squareup.retrofit2:adapter-rxjava2:2.3.0" implementation "com.squareup.retrofit2:retrofit:2.8.1" implementation "com.squareup.retrofit2:converter-moshi:2.8.1" implementation "com.squareup.okhttp3:logging-interceptor:4.9.0" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:1.4.0" testImplementation 'junit:junit:4.13' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' } ================================================ FILE: examples/coroutinesExample/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile ================================================ FILE: examples/coroutinesExample/src/main/AndroidManifest.xml ================================================ ================================================ FILE: examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/MainActivity.kt ================================================ /* * Copyright 2020 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.coroutinesexample import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.fragment.app.commit import io.realm.examples.coroutinesexample.ui.details.DetailsFragment import io.realm.examples.coroutinesexample.ui.main.MainFragment import kotlin.time.ExperimentalTime @ExperimentalTime class MainActivity : AppCompatActivity(), MainFragment.OnItemClicked { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) if (savedInstanceState == null) { showMainFragment() } } override fun onAttachFragment(fragment: Fragment) { when (fragment) { is MainFragment -> fragment.onItemclickedCallback = this } } override fun onBackPressed() { val detailsFragment = supportFragmentManager.findFragmentByTag(DetailsFragment.TAG) if (detailsFragment != null) { supportFragmentManager.popBackStackImmediate() } else { super.onBackPressed() } } override fun onItemClicked(id: String) { val mainFragment = supportFragmentManager.findFragmentByTag(MainFragment.TAG) val detailsFragment = DetailsFragment.instantiate(DetailsFragment.ArgsBundle(id)) supportFragmentManager.commit { setCustomAnimations(R.anim.fragment_open_enter, R.anim.fragment_open_exit) add(R.id.container, detailsFragment, DetailsFragment.TAG) hide(requireNotNull(mainFragment)) addToBackStack(null) } } private fun showMainFragment() { supportFragmentManager.commit { replace(R.id.container, MainFragment.newInstance(), MainFragment.TAG) } } } ================================================ FILE: examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/MainApplication.kt ================================================ /* * Copyright 2020 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.coroutinesexample import androidx.multidex.MultiDexApplication import io.realm.Realm import io.realm.examples.coroutinesexample.data.newsreader.local.repository.NewsReaderRepository import io.realm.examples.coroutinesexample.di.DependencyGraph const val TAG = "--- CoroutinesExample" class MainApplication : MultiDexApplication() { override fun onCreate() { super.onCreate() Realm.init(this) repository = DependencyGraph.provideNewsReaderRepository() } companion object { lateinit var repository: NewsReaderRepository } } ================================================ FILE: examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/data/newsreader/local/RealmNYTDao.kt ================================================ /* * Copyright 2020 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.coroutinesexample.data.newsreader.local import io.realm.Realm import io.realm.RealmConfiguration import io.realm.RealmList import io.realm.RealmResults import io.realm.examples.coroutinesexample.data.newsreader.network.model.NYTMultimedium import io.realm.examples.coroutinesexample.data.newsreader.network.model.NYTimesArticle import io.realm.kotlin.executeTransactionAwait import io.realm.kotlin.toFlow import io.realm.kotlin.where import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.withContext import java.io.Closeable import java.util.concurrent.Executors /** * Data Access Object interface used to gain access to Realm. * * It implements [Closeable] to allow proper Realm instance housekeeping linked to handling the * Android activity/fragment lifecycle. */ interface RealmNYTDao : Closeable { suspend fun insertArticles(articles: List) suspend fun updateArticle(id: String) suspend fun deleteArticles(section: String) suspend fun deleteAllArticles() fun getArticlesBlocking(section: String): RealmResults fun getArticles(section: String): Flow> fun getArticleBlocking(id: String): RealmNYTimesArticle? fun getArticle(id: String): Flow fun countArticles(section: String): Long } class RealmNYTDaoImpl( private val realmConfiguration: RealmConfiguration ) : RealmNYTDao { /** * Dispatcher used to run suspendable functions that run Realm transactions. This is needed to * confine Realm instances within the same thread as long as the coroutine is running to avoid * accessing said instances from different threads and thus (potentially) triggering a thread * violation. */ private val monoThreadDispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher() /** * [Realm] instance used to fire queries. It must not be used for other than firing queries and * has to be closed when no longer in use. */ private val realm = Realm.getInstance(realmConfiguration) override suspend fun insertArticles(articles: List) { withContext(monoThreadDispatcher) { runCloseableTransaction(realmConfiguration) { transactionRealm -> transactionRealm.insertOrUpdate(articles) } } } override suspend fun updateArticle(id: String) { withContext(monoThreadDispatcher) { runCloseableTransaction(realmConfiguration) { transactionRealm -> val article = transactionRealm.where() .equalTo(RealmNYTimesArticle.COLUMN_URL, id) .findFirst() checkNotNull(article).read = true } } } override suspend fun deleteArticles(section: String) { withContext(monoThreadDispatcher) { runCloseableTransaction(realmConfiguration) { transactionRealm -> transactionRealm.deleteAll() } } } override suspend fun deleteAllArticles() { withContext(monoThreadDispatcher) { runCloseableTransaction(realmConfiguration) { transactionRealm -> transactionRealm.deleteAll() } } } override fun getArticlesBlocking(section: String): RealmResults { return realm.where() .equalTo(RealmNYTimesArticle.COLUMN_API_SECTION, section) .findAll() } override fun getArticles(section: String): Flow> { return realm.where() .equalTo(RealmNYTimesArticle.COLUMN_API_SECTION, section) .findAllAsync() .toFlow() } override fun getArticleBlocking(id: String): RealmNYTimesArticle? { return realm.where() .equalTo(RealmNYTimesArticle.COLUMN_URL, id) .findFirst() } override fun getArticle(id: String): Flow { return realm.where() .equalTo(RealmNYTimesArticle.COLUMN_URL, id) .findFirstAsync() .toFlow() } override fun countArticles(section: String): Long { return realm.where() .equalTo(RealmNYTimesArticle.COLUMN_API_SECTION, section) .count() } override fun close() { realm.close() } } /** * Inserts a [List] of [NYTimesArticle]s after they have been mapped to [RealmNYTimesArticle] * instances. */ suspend fun RealmNYTDao.insertArticles(apiSection: String, articles: List) { val realmArticles = articles.toRealmArticles(apiSection) insertArticles(realmArticles) } private fun List.toRealmArticles(apiQuerySection: String): List { val timestamp = System.currentTimeMillis() return map { article -> RealmNYTimesArticle().apply { updateTime = timestamp apiSection = apiQuerySection section = article.section subsection = article.subsection title = article.title abstractText = article.abstractText url = article.url uri = article.uri byline = article.byline itemType = article.itemType updatedDate = article.updatedDate createDate = article.createDate publishedDate = article.publishedDate materialTypeFacet = article.materialTypeFacet kicker = article.kicker desFacet = RealmList().apply { addAll(article.desFacet ?: listOf()) } orgFacet = RealmList().apply { addAll(article.orgFacet ?: listOf()) } perFacet = RealmList().apply { addAll(article.perFacet ?: listOf()) } geoFacet = RealmList().apply { addAll(article.geoFacet ?: listOf()) } orgFacet = RealmList().apply { addAll(article.orgFacet ?: listOf()) } perFacet = RealmList().apply { addAll(article.perFacet ?: listOf()) } geoFacet = RealmList().apply { addAll(article.geoFacet ?: listOf()) } multimedia = article.multimedia.toRealmMultimediumRealmList() shortUrl = article.shortUrl } } } private fun List?.toRealmMultimediumRealmList(): RealmList { return RealmList().also { realmList -> realmList.addAll( this?.map { multimedium -> multimedium.toRealmMultimedium() } ?: listOf() ) } } private fun NYTMultimedium.toRealmMultimedium(): RealmNYTMultimedium { return RealmNYTMultimedium().also { it.url = url it.format = format it.height = height it.width = width it.type = type it.subtype = subtype it.caption = caption it.copyright = copyright } } private suspend fun runCloseableTransaction( realmConfiguration: RealmConfiguration, transaction: (realm: Realm) -> Unit ) { Realm.getInstance(realmConfiguration).use { realmInstance -> realmInstance.executeTransactionAwait(transaction = transaction) } } ================================================ FILE: examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/data/newsreader/local/RealmNYTimes.kt ================================================ /* * Copyright 2020 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.coroutinesexample.data.newsreader.local import io.realm.RealmList import io.realm.RealmObject import io.realm.annotations.LinkingObjects import io.realm.annotations.PrimaryKey import io.realm.annotations.RealmClass import java.time.LocalDateTime import java.util.* open class RealmNYTimesArticle : RealmObject() { var read: Boolean = false var updateTime: Long = 0 var apiSection: String = "" var section: String = "" var subsection: String? = null var title: String = "" var abstractText: String? = null @PrimaryKey var url: String = UUID.randomUUID().toString() var uri: String? = null var byline: String? = null var itemType: String? = null var updatedDate: String? = null var createDate: String? = null var publishedDate: String? = null var materialTypeFacet: String? = null var kicker: String? = null var desFacet: RealmList = RealmList() var orgFacet: RealmList = RealmList() var perFacet: RealmList = RealmList() var geoFacet: RealmList = RealmList() var multimedia: RealmList = RealmList() var shortUrl: String? = null companion object { const val EMBEDDED_MULTIMEDIA = "multimedia" const val COLUMN_URL = "url" const val COLUMN_API_SECTION = "apiSection" } } @RealmClass(embedded = true) open class RealmNYTMultimedium : RealmObject() { var url: String? = null var format: String? = null var height: Int = 0 var width: Int = 0 var type: String? = null var subtype: String? = null var caption: String? = null var copyright: String? = null @LinkingObjects(RealmNYTimesArticle.EMBEDDED_MULTIMEDIA) val parent = RealmNYTimesArticle() } ================================================ FILE: examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/data/newsreader/local/repository/NewsReaderRepository.kt ================================================ /* * Copyright 2020 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.coroutinesexample.data.newsreader.local.repository import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import com.dropbox.android.external.store4.* import io.realm.examples.coroutinesexample.data.newsreader.local.RealmNYTDao import io.realm.examples.coroutinesexample.data.newsreader.local.RealmNYTimesArticle import io.realm.examples.coroutinesexample.ui.main.NewsReaderState import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch private const val THIRTY_MINUTES = 30 * 60 * 1000 class NewsReaderRepository( private val dao: RealmNYTDao, private val store: Store> ) { private val _newsReaderState = MutableLiveData() val newsReaderState: LiveData get() = _newsReaderState private val sectionRefreshJobs = mutableMapOf() fun getTopStories(scope: CoroutineScope, apiSection: String, refresh: Boolean = false) { scope.launch { if (refresh) { store.fresh(apiSection) } else { getFromStream(scope, apiSection) } } } fun getStory(id: String): Flow { return dao.getArticle(id) } fun updateArticle(scope: CoroutineScope, id: String) { scope.launch { dao.updateArticle(id) } } fun close() { dao.close() sectionRefreshJobs.values.forEach { it.cancel() } sectionRefreshJobs.clear() } private fun getFromStream(scope: CoroutineScope, apiSection: String) { store.stream(StoreRequest.cached( key = apiSection, refresh = false )).onEach { response -> val origin = response.origin.toString() when (response) { is StoreResponse.Loading -> NewsReaderState.Loading(origin) is StoreResponse.Data -> getNewsReaderState(response, store, apiSection, origin) is StoreResponse.NoNewData -> NewsReaderState.NoNewData(origin) is StoreResponse.Error.Exception -> NewsReaderState.ErrorException(origin, response.error) is StoreResponse.Error.Message -> NewsReaderState.ErrorMessage(origin, response.message) }.also { _newsReaderState.postValue(it) } }.launchIn( scope ).also { job -> scope.launch { sectionRefreshJobs.values.forEach { it.cancelAndJoin() } sectionRefreshJobs.clear() sectionRefreshJobs[apiSection] = job } } } private suspend fun getNewsReaderState( response: StoreResponse.Data>, store: Store>, apiSection: String, origin: String ): NewsReaderState { val data = response.value return if (data.isNotEmpty()) { data.first() .let { firstElement -> val now = System.currentTimeMillis() val entryExpired = (firstElement.updateTime + THIRTY_MINUTES) < now if (!(response.origin == ResponseOrigin.Fetcher || !entryExpired)) { store.fresh(apiSection) NewsReaderState.Loading(origin) } else { NewsReaderState.Data(origin, response.value) } } } else { NewsReaderState.Data(origin, response.value) } } } ================================================ FILE: examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/data/newsreader/network/NYTimesApiClient.kt ================================================ /* * Copyright 2020 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.coroutinesexample.data.newsreader.network import io.realm.examples.coroutinesexample.data.newsreader.network.model.NYTimesResponse import okhttp3.HttpUrl import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory import retrofit2.converter.moshi.MoshiConverterFactory private const val API_KEY = "YUPmyj0Q09Fm2VlCHmD9FU7rpCcI5dUD" interface NYTimesApiClient { suspend fun getTopStories(section: String): NYTimesResponse } class NYTimesApiClientImpl : NYTimesApiClient { private val service: NYTimesService init { val okHttpClient = OkHttpClient.Builder() .addInterceptor( HttpLoggingInterceptor() .apply { setLevel(HttpLoggingInterceptor.Level.BASIC) } ) .addInterceptor { chain -> val original = chain.request() val originalHttpUrl = original.url val url = originalHttpUrl.newBuilder() .addEncodedQueryParameter("api-key", API_KEY) .build() val requestBuilder: Request.Builder = original.newBuilder() .url(url) val request: Request = requestBuilder.build() chain.proceed(request) } .build() service = Retrofit.Builder() .client(okHttpClient) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addConverterFactory(MoshiConverterFactory.create()) .baseUrl("https://api.nytimes.com/") .build() .create(NYTimesService::class.java) } override suspend fun getTopStories(section: String): NYTimesResponse { return service.topStories(section) } } ================================================ FILE: examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/data/newsreader/network/NYTimesService.kt ================================================ /* * Copyright 2020 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.coroutinesexample.data.newsreader.network import io.realm.examples.coroutinesexample.data.newsreader.network.model.NYTimesResponse import retrofit2.http.GET import retrofit2.http.Path interface NYTimesService { @GET("svc/topstories/v2/{section}.json") suspend fun topStories(@Path("section") section: String): NYTimesResponse } val sectionsToNames = mapOf( "Home" to "home", "World" to "world", "National" to "national", "Politics" to "politics", "NY Region" to "nyregion", "Business" to "business", "Opinion" to "opinion", "Technology" to "technology", "Science" to "science", "Health" to "health", "Sports" to "sports", "Arts" to "arts", "Fashion" to "fashion", "Dining" to "dining", "Travel" to "travel", "Magazine" to "magazine", "Real Estate" to "realestate" ) ================================================ FILE: examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/data/newsreader/network/model/NYTimes.kt ================================================ /* * Copyright 2020 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.coroutinesexample.data.newsreader.network.model import com.squareup.moshi.Json data class NYTimesResponse( val status: String, val copyright: String, val section: String, @field:Json(name = "last_updated") val lastUpdated: String, @field:Json(name = "num_results") val numResults: Int, val results: List ) data class NYTimesArticle( val section: String, val subsection: String, val title: String, @field:Json(name = "abstract") val abstractText: String?, val url: String, val uri: String, val byline: String, @field:Json(name = "item_type") val itemType: String?, @field:Json(name = "updated_date") val updatedDate: String?, @field:Json(name = "created_date") val createDate: String?, @field:Json(name = "published_date") val publishedDate: String?, @field:Json(name = "material_type_facet") val materialTypeFacet: String?, val kicker: String, @field:Json(name = "des_facet") val desFacet: List?, @field:Json(name = "org_facet") val orgFacet: List?, @field:Json(name = "per_facet") val perFacet: List?, @field:Json(name = "geo_facet") val geoFacet: List?, val multimedia: List, @field:Json(name = "short_url") val shortUrl: String? ) data class NYTMultimedium( val url: String, val format: String, val height: Int, val width: Int, val type: String, val subtype: String, val caption: String, val copyright: String ) ================================================ FILE: examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/di/DependencyGraph.kt ================================================ /* * Copyright 2020 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.coroutinesexample.di import com.dropbox.android.external.store4.Fetcher import com.dropbox.android.external.store4.SourceOfTruth import com.dropbox.android.external.store4.Store import com.dropbox.android.external.store4.StoreBuilder import io.realm.RealmConfiguration import io.realm.examples.coroutinesexample.data.newsreader.local.RealmNYTDao import io.realm.examples.coroutinesexample.data.newsreader.local.RealmNYTDaoImpl import io.realm.examples.coroutinesexample.data.newsreader.local.RealmNYTimesArticle import io.realm.examples.coroutinesexample.data.newsreader.local.insertArticles import io.realm.examples.coroutinesexample.data.newsreader.local.repository.NewsReaderRepository import io.realm.examples.coroutinesexample.data.newsreader.network.NYTimesApiClient import io.realm.examples.coroutinesexample.data.newsreader.network.NYTimesApiClientImpl import io.realm.examples.coroutinesexample.data.newsreader.network.model.NYTimesArticle import io.realm.examples.coroutinesexample.util.NewsReaderFlowFactory import kotlinx.coroutines.flow.map /** * Homemade, simple DI solution - ideally, we should use a proper DI framework instead. */ object DependencyGraph { // Repository dependencies fun provideNewsReaderRepository(): NewsReaderRepository = NewsReaderRepository(provideRealmDao(), provideStore()) private fun provideStore(): Store> = StoreBuilder.from( fetcher = provideFetcher(provideApiClient()), sourceOfTruth = provideSourceOfTruth(provideRealmDao()) ).build() private fun provideFetcher(nytApiClient: NYTimesApiClient): Fetcher> = Fetcher.of { apiSection -> nytApiClient.getTopStories(apiSection).results } private fun provideSourceOfTruth(realmDao: RealmNYTDao): SourceOfTruth, List> = SourceOfTruth.of( reader = { apiSection -> realmDao.getArticles(apiSection) .map { articles -> if (articles.isEmpty()) null else articles } }, writer = { apiSection, articles -> realmDao.insertArticles(apiSection, articles) }, delete = { apiSection -> realmDao.deleteArticles(apiSection) }, deleteAll = { realmDao.deleteAllArticles() } ) // Database dependencies private fun provideRealmDao(): RealmNYTDao = RealmNYTDaoImpl(provideRealmConfig()) private fun provideRealmConfig(): RealmConfiguration = RealmConfiguration.Builder() .flowFactory(NewsReaderFlowFactory()) .build() // Network dependencies private fun provideApiClient(): NYTimesApiClient = NYTimesApiClientImpl() } ================================================ FILE: examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/ui/details/DetailsFragment.kt ================================================ /* * Copyright 2020 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.coroutinesexample.ui.details import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.lifecycle.Observer import io.realm.examples.coroutinesexample.databinding.FragmentDetailsBinding import kotlin.time.ExperimentalTime @ExperimentalTime class DetailsFragment : Fragment() { private val viewModel: DetailsViewModel by viewModels() private lateinit var binding: FragmentDetailsBinding override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return FragmentDetailsBinding.inflate(inflater, container, false) .also { binding -> this.binding = binding binding.lifecycleOwner = viewLifecycleOwner binding.viewModel = viewModel setupLiveData() }.root } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) val id = requireNotNull(requireArguments().getString(ARG_ID)) viewModel.loadDetails(id) } private fun setupLiveData() { viewModel.read.observe(viewLifecycleOwner, Observer { setRead() }) } private fun setRead() { with(binding.read) { animate().alpha(1.0f) } } data class ArgsBundle(val id: String) companion object { const val TAG = "DetailsFragment" private const val ARG_ID = "id" fun instantiate(argsBundle: ArgsBundle): DetailsFragment { return DetailsFragment().apply { arguments = Bundle().apply { putString(ARG_ID, argsBundle.id) } } } } } ================================================ FILE: examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/ui/details/DetailsViewModel.kt ================================================ /* * Copyright 2020 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.coroutinesexample.ui.details import androidx.lifecycle.* import io.realm.examples.coroutinesexample.data.newsreader.local.RealmNYTimesArticle import io.realm.examples.coroutinesexample.di.DependencyGraph import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.delay import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.isActive import kotlin.time.ExperimentalTime import kotlin.time.seconds @ExperimentalTime class DetailsViewModel : ViewModel() { private val repository = DependencyGraph.provideNewsReaderRepository() private val article = MutableLiveData() private val _read = MutableLiveData() val read: LiveData get() = _read val date = article.map { it.updatedDate.toString() } val title = article.map { it.title } val articleText = article.map { it.abstractText } override fun onCleared() { repository.close() } fun loadDetails(id: String) { repository.getStory(id) .onEach { realmArticle -> checkNotNull(realmArticle) .also { if (article.value == null) { article.postValue(it) if (!it.read) { markAsRead(it) } else { markAsRead(it, true) } } } }.launchIn(viewModelScope) } private fun markAsRead(article: RealmNYTimesArticle, immediately: Boolean = false) { if (immediately) { _read.postValue(true) } else { flow { delay(2.seconds) repository.updateArticle(viewModelScope, article.url) _read.postValue(true) }.launchIn(viewModelScope) } } } ================================================ FILE: examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/ui/main/MainAdapter.kt ================================================ /* * Copyright 2020 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.coroutinesexample.ui.main import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import io.realm.examples.coroutinesexample.R import io.realm.examples.coroutinesexample.data.newsreader.local.RealmNYTimesArticle class MainAdapter( private val onClick: (String) -> Unit ) : ListAdapter(DIFF_CALLBACK) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticleViewHolder = LayoutInflater.from(parent.context) .inflate(R.layout.item_article, parent, false) .let { view -> ArticleViewHolder(view) } override fun onBindViewHolder(holder: ArticleViewHolder, position: Int) { with(holder.title) { val article = getItem(position) text = article.title isEnabled = !article.read } } inner class ArticleViewHolder(view: View) : RecyclerView.ViewHolder(view) { val title: TextView = view.findViewById(R.id.title) init { view.setOnClickListener { onClick.invoke(getItem(adapterPosition).url) } } } companion object { val DIFF_CALLBACK = object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: RealmNYTimesArticle, newItem: RealmNYTimesArticle): Boolean = oldItem == newItem override fun areContentsTheSame(oldItem: RealmNYTimesArticle, newItem: RealmNYTimesArticle): Boolean = oldItem.read == newItem.read && oldItem.title == newItem.title && oldItem.abstractText == newItem.abstractText } } } ================================================ FILE: examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/ui/main/MainFragment.kt ================================================ /* * Copyright 2020 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.coroutinesexample.ui.main import android.os.Bundle import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.AdapterView import android.widget.ArrayAdapter import android.widget.SpinnerAdapter import android.widget.Toast import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager import io.realm.examples.coroutinesexample.R import io.realm.examples.coroutinesexample.data.newsreader.local.RealmNYTimesArticle import io.realm.examples.coroutinesexample.data.newsreader.network.sectionsToNames import io.realm.examples.coroutinesexample.databinding.FragmentMainBinding import java.util.* import kotlin.Comparator class MainFragment : Fragment() { interface OnItemClicked { fun onItemClicked(id: String) } internal lateinit var onItemclickedCallback: OnItemClicked private val viewModel: MainViewModel by viewModels() private val newsReaderAdapter = MainAdapter { id -> onItemclickedCallback.onItemClicked(id) } private lateinit var binding: FragmentMainBinding override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? = FragmentMainBinding.inflate(inflater, container, false) .also { binding -> binding.lifecycleOwner = viewLifecycleOwner this.binding = binding setupSpinner() setupRecyclerView() setupLiveData() }.root private fun setupSpinner() { with(binding.spinner) { adapter = ArrayAdapter( context, android.R.layout.simple_spinner_dropdown_item, sectionsToNames.keys.sortedWith( Comparator { o1, o2 -> if (o1.toLowerCase(Locale.ROOT) == "home") return@Comparator -1 if (o2.toLowerCase(Locale.ROOT) == "home") return@Comparator 1 return@Comparator o1.compareTo(o2, ignoreCase = true) } ) ) onItemSelectedListener = object : AdapterView.OnItemSelectedListener { override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { viewModel.getTopStories(getApiSection(adapter, position)) } override fun onNothingSelected(parent: AdapterView<*>?) { // No-op } } } } private fun setupRecyclerView() { with(binding.list) { layoutManager = LinearLayoutManager(context) adapter = newsReaderAdapter } with(binding.refresh) { setOnRefreshListener { with(binding.spinner) { viewModel.getTopStories(getApiSection(adapter, selectedItemPosition), true) } } } } private fun setupLiveData() { viewModel.newsReaderState.observe(viewLifecycleOwner, Observer { viewState -> when (viewState) { is NewsReaderState.Loading -> { Log.d(TAG, "--- origin: ${viewState.origin}, loading") RealmStateHelper.loading(binding) } is NewsReaderState.Data -> { Log.d(TAG, "--- origin: ${viewState.origin}, elements: ${viewState.data.size}") RealmStateHelper.data(binding, viewState.data, newsReaderAdapter) } is NewsReaderState.NoNewData -> { Log.d(TAG, "--- origin: ${viewState.origin}, no new data") RealmStateHelper.noNewData(binding) } is NewsReaderState.ErrorException -> { val stacktrace = viewState.throwable.cause?.stackTrace?.joinToString { "$it\n" } Log.e(TAG, "--- error (exception): ${viewState.throwable.message} - ${viewState.throwable.cause?.message}: $stacktrace") RealmStateHelper.error(binding) } is NewsReaderState.ErrorMessage -> { Log.e(TAG, "--- error (message): ${viewState.message}") RealmStateHelper.error(binding) } } }) } private fun getApiSection(adapter: SpinnerAdapter, position: Int): String { val apiSection = adapter.getItem(position) as String return requireNotNull(sectionsToNames[apiSection]) } companion object { const val TAG = "MainFragment" fun newInstance() = MainFragment() } } sealed class NewsReaderState { abstract val origin: String data class Loading(override val origin: String) : NewsReaderState() data class Data(override val origin: String, val data: List) : NewsReaderState() data class NoNewData(override val origin: String) : NewsReaderState() data class ErrorException(override val origin: String, val throwable: Throwable) : NewsReaderState() data class ErrorMessage(override val origin: String, val message: String) : NewsReaderState() } private object RealmStateHelper { fun loading(binding: FragmentMainBinding) { if (!binding.refresh.isRefreshing) { binding.refresh.setRefreshing(true) } } fun data( binding: FragmentMainBinding, data: List, newsReaderAdapter: MainAdapter ) { hideLoadingSpinner(binding) newsReaderAdapter.submitList(data) } fun noNewData(binding: FragmentMainBinding) { hideLoadingSpinner(binding) } fun error(binding: FragmentMainBinding) { hideLoadingSpinner(binding) Toast.makeText(binding.root.context, R.string.error_generic, Toast.LENGTH_SHORT).show() } private fun hideLoadingSpinner(binding: FragmentMainBinding) { if (binding.refresh.isRefreshing) { binding.refresh.setRefreshing(false) } } private fun showLoadingSpinner(binding: FragmentMainBinding) { if (!binding.refresh.isRefreshing) { binding.refresh.setRefreshing(true) } } } ================================================ FILE: examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/ui/main/MainViewModel.kt ================================================ /* * Copyright 2020 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.coroutinesexample.ui.main import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import io.realm.examples.coroutinesexample.TAG import io.realm.examples.coroutinesexample.di.DependencyGraph class MainViewModel : ViewModel() { private val repository = DependencyGraph.provideNewsReaderRepository() val newsReaderState: LiveData get() = repository.newsReaderState override fun onCleared() { repository.close() } fun getTopStories(apiSection: String, refresh: Boolean = false) { Log.d(TAG, "------ apiSection: $apiSection - refresh '$refresh'") repository.getTopStories(viewModelScope, apiSection, refresh) } } ================================================ FILE: examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/util/NewsReaderFlowFactory.kt ================================================ /* * Copyright 2020 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.coroutinesexample.util import io.realm.DynamicRealm import io.realm.Realm import io.realm.RealmResults import io.realm.coroutines.RealmFlowFactory import io.realm.rx.CollectionChange import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.drop /** * Similar to [io.realm.coroutines.RealmFlowFactory] but it will not emit the current value * immediately. This is needed by Store to function properly or else it will receive updates with * empty [RealmResults] that will make it think existing values for the current key are present. * * There is no need to override the methods for [io.realm.RealmModel] since the internal factory * does check whether or not an object is loaded before the first emission. */ class NewsReaderFlowFactory : RealmFlowFactory(true) { override fun from( realm: Realm, results: RealmResults ): Flow> = super.from(realm, results) .drop(1) override fun from( dynamicRealm: DynamicRealm, results: RealmResults ): Flow> = super.from(dynamicRealm, results) .drop(1) override fun changesetFrom( realm: Realm, results: RealmResults ): Flow>> = super.changesetFrom(realm, results) .drop(1) override fun changesetFrom( dynamicRealm: DynamicRealm, results: RealmResults ): Flow>> = super.changesetFrom(dynamicRealm, results) .drop(1) } ================================================ FILE: examples/coroutinesExample/src/main/res/drawable/ic_launcher_background.xml ================================================ ================================================ FILE: examples/coroutinesExample/src/main/res/drawable-v24/ic_launcher_foreground.xml ================================================ ================================================ FILE: examples/coroutinesExample/src/main/res/layout/activity_main.xml ================================================ ================================================ FILE: examples/coroutinesExample/src/main/res/layout/fragment_details.xml ================================================ ================================================ FILE: examples/coroutinesExample/src/main/res/layout/fragment_main.xml ================================================ ================================================ FILE: examples/coroutinesExample/src/main/res/layout/item_article.xml ================================================ ================================================ FILE: examples/coroutinesExample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml ================================================ ================================================ FILE: examples/coroutinesExample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml ================================================ ================================================ FILE: examples/coroutinesExample/src/main/res/values/colors.xml ================================================ #6200EE #3700B3 #03DAC5 ================================================ FILE: examples/coroutinesExample/src/main/res/values/dimens.xml ================================================ 8dp 16dp 8dp 40dp 16dp ================================================ FILE: examples/coroutinesExample/src/main/res/values/strings.xml ================================================ Coroutines Example An error has occurred. Check Logcat for more details. Read ================================================ FILE: examples/coroutinesExample/src/main/res/values/styles.xml ================================================ ================================================ FILE: examples/coroutinesExample/src/main/res/values/themes.xml ================================================ ================================================ FILE: examples/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: examples/gradle.properties ================================================ org.gradle.jvmargs=-Xmx2048M -XX:MaxMetaspaceSize=1024M org.gradle.caching=true android.enableD8=true # Gradle sync failed: Due to a limitation of Gradle’s new variant-aware dependency management, loading the Android Gradle plugin in different class loaders leads to a build error. # This can occur when the buildscript classpaths that contain the Android Gradle plugin in sub-projects, or included projects in the case of composite builds, are set differently. # To resolve this issue, add the Android Gradle plugin to only the buildscript classpath of the top-level build.gradle file. # In the case of composite builds, also make sure the build script classpaths that contain the Android Gradle plugin are identical across the main and included projects. # If you are using a version of Gradle that has fixed the issue, you can disable this check by setting android.enableBuildScriptClasspathCheck=false in the gradle.properties file. # To learn more about this issue, go to https://d.android.com/r/tools/buildscript-classpath-check.html. android.enableBuildScriptClasspathCheck=false # See https://developer.android.com/studio/build/optimize-your-build#configuration_on_demand org.gradle.configureondemand=false android.useAndroidX=true ================================================ FILE: examples/gradlew ================================================ #!/bin/sh # # Copyright © 2015-2021 the original authors. # # 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 # # https://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. # ############################################################################## # # Gradle start up script for POSIX generated by Gradle. # # Important for running: # # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is # noncompliant, but you have some other compliant shell such as ksh or # bash, then to run this script, type that shell name before the whole # command line, like: # # ksh Gradle # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: # * functions; # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», # «${var#prefix}», «${var%suffix}», and «$( cmd )»; # * compound commands having a testable exit status, especially «case»; # * various built-in commands including «command», «set», and «ulimit». # # Important for patching: # # (2) This script targets any POSIX shell, so it avoids extensions provided # by Bash, Ksh, etc; in particular arrays are avoided. # # The "traditional" practice of packing multiple parameters into a # space-separated string is a well documented source of bugs and security # problems, so this is (mostly) avoided, by progressively accumulating # options in "$@", and eventually passing that to Java. # # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; # see the in-line comments for details. # # There are tweaks for specific operating systems such as AIX, CygWin, # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. # ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link app_path=$0 # Need this for daisy-chained symlinks. while APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path [ -h "$app_path" ] do ls=$( ls -ld "$app_path" ) link=${ls#*' -> '} case $link in #( /*) app_path=$link ;; #( *) app_path=$APP_HOME$link ;; esac done APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum warn () { echo "$*" } >&2 die () { echo echo "$*" echo exit 1 } >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "$( uname )" in #( CYGWIN* ) cygwin=true ;; #( Darwin* ) darwin=true ;; #( MSYS* | MINGW* ) msys=true ;; #( NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD=$JAVA_HOME/jre/sh/java else JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi # Collect all arguments for the java command, stacking in reverse order: # * args from the command line # * the main class name # * -classpath # * -D...appname settings # * --module-path (only if needed) # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) # Now convert the arguments - kludge to limit ourselves to /bin/sh for arg do if case $arg in #( -*) false ;; # don't mess with options #( /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath [ -e "$t" ] ;; #( *) false ;; esac then arg=$( cygpath --path --ignore --mixed "$arg" ) fi # Roll the args list around exactly as many times as the number of # args, so each arg winds up back in the position where it started, but # possibly modified. # # NB: a `for` loop captures its iteration list before it begins, so # changing the positional parameters here affects neither the number of # iterations, nor the values presented in `arg`. shift # remove old arg set -- "$@" "$arg" # push replacement arg done fi # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in # double quotes to make sure that they get re-expanded; and # * put everything else in single quotes, so that it's not re-expanded. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ org.gradle.wrapper.GradleWrapperMain \ "$@" # Stop when "xargs" is not available. if ! command -v xargs >/dev/null 2>&1 then die "xargs is not available" fi # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. # # In Bash we could simply go: # # readarray ARGS < <( xargs -n1 <<<"$var" ) && # set -- "${ARGS[@]}" "$@" # # but POSIX shell has neither arrays nor command substitution, so instead we # post-process each arg (as a line of input to sed) to backslash-escape any # character that might be a shell metacharacter, then use eval to reverse # that process (while maintaining the separation between arguments), and wrap # the whole thing up as a single "set" statement. # # This will of course break if any of these variables contains a newline or # an unmatched quote. # eval "set -- $( printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | xargs -n1 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | tr '\n' ' ' )" '"$@"' exec "$JAVACMD" "$@" ================================================ FILE: examples/gradlew.bat ================================================ @rem @rem Copyright 2015 the original author or authors. @rem @rem Licensed under the Apache License, Version 2.0 (the "License"); @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem @rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Resolve any "." and ".." in APP_HOME to make it shorter. for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! set EXIT_CODE=%ERRORLEVEL% if %EXIT_CODE% equ 0 set EXIT_CODE=1 if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: examples/gridViewExample/build.gradle ================================================ apply plugin: 'com.android.application' apply plugin: 'realm-android' android { compileSdkVersion rootProject.sdkVersion buildToolsVersion rootProject.buildTools compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } defaultConfig { applicationId 'io.realm.examples.realmgridview' targetSdkVersion rootProject.sdkVersion minSdkVersion rootProject.minSdkVersion versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled true signingConfig signingConfigs.debug proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } debug { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } productFlavors { } splits { // Split apks on build target ABI, view all options for the splits here: // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits abi { enable true reset() include 'armeabi', 'armeabi-v7a', 'arm64-v8a', 'mips', 'x86', 'x86_64' } } } dependencies { implementation 'com.google.code.gson:gson:2.8.2' } ================================================ FILE: examples/gridViewExample/lint.xml ================================================ ================================================ FILE: examples/gridViewExample/proguard-rules.pro ================================================ -keep class io.realm.examples.realmgridview.City { ; } ================================================ FILE: examples/gridViewExample/src/main/AndroidManifest.xml ================================================ ================================================ FILE: examples/gridViewExample/src/main/assets/cities.json ================================================ [ { "name" : "Barcelona", "votes" : 23 }, { "name" : "San Francisco", "votes" : 21 }, { "name" : "Venice", "votes" : 19 }, { "name" : "Melbourne", "votes" : 18 }, { "name" : "Paris", "votes" : 17 }, { "name" : "Seville", "votes" : 14 }, { "name" : "Sydney", "votes" : 12 }, { "name" : "New York", "votes" : 11 }, { "name" : "Krakow", "votes" : 9 }, { "name" : "Peoria", "votes" : 72 }, { "name" : "Springfield", "votes" : 88 }, { "name" : "Kansas City", "votes" : 100 }, { "name" : "Tokyo", "votes" : 20 }, { "name" : "Boise", "votes" : 17 }, { "name" : "Los Angeles", "votes" : 14 }, { "name" : "Newark", "votes" : 12 }, { "name" : "Chicago", "votes" : 11 }, { "name" : "Detroit", "votes" : 91 }, { "name" : "Florence", "votes" : 4 }, { "name" : "Madrid", "votes" : 1 }, { "name" : "London", "votes" : 9 } ] ================================================ FILE: examples/gridViewExample/src/main/java/io/realm/examples/realmgridview/City.java ================================================ /* * Copyright 2018 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.realmgridview; import io.realm.RealmObject; import io.realm.annotations.PrimaryKey; public class City extends RealmObject { // If you are using GSON, field names should not be obfuscated. // Add either the proguard rule in proguard-rules.pro or the @SerializedName annotation. @PrimaryKey private String name; private long votes; public String getName() { return name; } public void setName(String name) { this.name = name; } public long getVotes() { return votes; } public void setVotes(long votes) { this.votes = votes; } } ================================================ FILE: examples/gridViewExample/src/main/java/io/realm/examples/realmgridview/CityAdapter.java ================================================ /* * Copyright 2018 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.realmgridview; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.TextView; import java.util.Collections; import java.util.List; import java.util.Locale; // This adapter is strictly to interface with the GridView and doesn't // particular show much interesting Realm functionality. // Alternatively from this example, // a developer could update the getView() to pull items from the Realm. public class CityAdapter extends BaseAdapter { private List cities = Collections.emptyList(); public CityAdapter() { } public void setData(List details) { if (details == null) { details = Collections.emptyList(); } this.cities = details; notifyDataSetChanged(); } @Override public int getCount() { return cities.size(); } @Override public City getItem(int position) { return cities.get(position); } @Override public long getItemId(int i) { return i; } // ViewHolder caches view resources so that `findViewById` is not called for each row private static class ViewHolder { private TextView name; private TextView vote; public ViewHolder(View view) { name = view.findViewById(R.id.name); vote = view.findViewById(R.id.votes); } public void bind(City city) { name.setText(city.getName()); vote.setText(String.format(Locale.US, "%d", city.getVotes())); } } @Override public View getView(int position, View currentView, ViewGroup parent) { // GridView requires ViewHolder pattern to ensure optimal performance ViewHolder viewHolder; if (currentView == null) { currentView = LayoutInflater.from(parent.getContext()).inflate(R.layout.city_listitem, parent, false); viewHolder = new ViewHolder(currentView); currentView.setTag(viewHolder); } else { viewHolder = (ViewHolder)currentView.getTag(); } City city = cities.get(position); viewHolder.bind(city); return currentView; } } ================================================ FILE: examples/gridViewExample/src/main/java/io/realm/examples/realmgridview/GridViewExampleActivity.java ================================================ /* * Copyright 2018 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.realmgridview; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; import android.widget.GridView; import io.realm.Realm; import io.realm.RealmChangeListener; import io.realm.RealmResults; public class GridViewExampleActivity extends Activity implements AdapterView.OnItemClickListener { private GridView gridView; private CityAdapter adapter; private Realm realm; private RealmResults cities; private RealmChangeListener> realmChangeListener = cities -> { // Set the cities to the adapter only when async query is loaded. // It will also be called for any future writes made to the Realm. adapter.setData(cities); }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_realm_example); // This is the GridView adapter adapter = new CityAdapter(); //This is the GridView which will display the list of cities gridView = findViewById(R.id.cities_list); gridView.setAdapter(adapter); gridView.setOnItemClickListener(GridViewExampleActivity.this); // Clear the realm from last time //noinspection ConstantConditions Realm.deleteRealm(Realm.getDefaultConfiguration()); // Create a new empty instance of Realm realm = Realm.getDefaultInstance(); // Obtain the cities in the Realm with asynchronous query. cities = realm.where(City.class).findAllAsync(); // The RealmChangeListener will be called when the results are asynchronously loaded, and available for use. cities.addChangeListener(realmChangeListener); } @Override protected void onDestroy() { super.onDestroy(); cities.removeAllChangeListeners(); // Remove change listeners to prevent updating views not yet GCed. realm.close(); // Remember to close Realm when done. } @Override public void onItemClick(AdapterView parent, View view, int position, long id) { City modifiedCity = adapter.getItem(position); // Acquire the name of the clicked City, in order to be able to query for it. final String name = modifiedCity.getName(); // Create an asynchronous transaction to increment the vote count for the selected City in the Realm. // The write will happen on a background thread, and the RealmChangeListener will update the GridView automatically. realm.executeTransactionAsync(bgRealm -> { // We need to find the City we want to modify from the background thread's Realm City city = bgRealm.where(City.class).equalTo("name", name).findFirst(); if (city != null) { // Let's increase the votes of the selected city! city.setVotes(city.getVotes() + 1); } }); } } ================================================ FILE: examples/gridViewExample/src/main/java/io/realm/examples/realmgridview/MyApplication.java ================================================ /* * Copyright 2018 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.realmgridview; import android.app.Application; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import com.google.gson.JsonParser; import com.google.gson.reflect.TypeToken; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.List; import io.realm.Realm; import io.realm.RealmConfiguration; public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); Realm.init(this); Realm.setDefaultConfiguration(new RealmConfiguration.Builder() .initialData(realm -> { // Load from file "cities.json" first time List cities = loadCities(); if (cities != null) { // Use insertOrUpdate() to convert the objects into proper RealmObjects managed by Realm. realm.insertOrUpdate(cities); } }) .deleteRealmIfMigrationNeeded() .build() ); } private List loadCities() { // In this case we're loading from local assets. // NOTE: could alternatively easily load from network. // However, that would need to happen on a background thread. InputStream stream; try { stream = getAssets().open("cities.json"); } catch (IOException e) { return null; } Gson gson = new GsonBuilder().create(); JsonElement json = new JsonParser().parse(new InputStreamReader(stream)); return gson.fromJson(json, new TypeToken>() { }.getType()); } } ================================================ FILE: examples/gridViewExample/src/main/res/layout/activity_realm_example.xml ================================================ ================================================ FILE: examples/gridViewExample/src/main/res/layout/city_listitem.xml ================================================ ================================================ FILE: examples/gridViewExample/src/main/res/values/strings.xml ================================================ GridView example My Favorite City Click to Add A Vote ================================================ FILE: examples/gridViewExample/src/main/res/values/styles.xml ================================================ ================================================ FILE: examples/introExample/build.gradle ================================================ apply plugin: 'com.android.application' apply plugin: 'realm-android' android { compileSdkVersion rootProject.sdkVersion buildToolsVersion rootProject.buildTools compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } defaultConfig { applicationId 'io.realm.examples.intro' targetSdkVersion rootProject.sdkVersion minSdkVersion rootProject.minSdkVersion versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled true signingConfig signingConfigs.debug } debug { minifyEnabled true } } productFlavors { } } ================================================ FILE: examples/introExample/lint.xml ================================================ ================================================ FILE: examples/introExample/src/main/AndroidManifest.xml ================================================ ================================================ FILE: examples/introExample/src/main/java/io/realm/examples/intro/IntroExampleActivity.java ================================================ /* * Copyright 2018 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.intro; import android.app.Activity; import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; import android.widget.LinearLayout; import android.widget.TextView; import java.lang.ref.WeakReference; import java.util.Arrays; import io.realm.OrderedRealmCollectionChangeListener; import io.realm.Realm; import io.realm.RealmConfiguration; import io.realm.RealmResults; import io.realm.Sort; import io.realm.examples.intro.model.Cat; import io.realm.examples.intro.model.Dog; import io.realm.examples.intro.model.Person; public class IntroExampleActivity extends Activity { public static final String TAG = "IntroExampleActivity"; private LinearLayout rootLayout; private Realm realm; // Results obtained from a Realm are live, and can be observed on looper threads (like the UI thread). // Note that if you want to observe the RealmResults for a long time, then it should be a field reference. // Otherwise, the RealmResults can no longer be notified if the GC has cleared the reference to it. private RealmResults persons; // OrderedRealmCollectionChangeListener receives fine-grained changes - insertions, deletions, and changes. // If the change set isn't needed, then RealmChangeListener can also be used. private final OrderedRealmCollectionChangeListener> realmChangeListener = (people, changeSet) -> { String insertions = changeSet.getInsertions().length == 0 ? "" : "\n - Insertions: " + Arrays.toString(changeSet.getInsertions()); String deletions = changeSet.getDeletions().length == 0 ? "" : "\n - Deletions: " + Arrays.toString(changeSet.getDeletions()); String changes = changeSet.getChanges().length == 0 ? "" : "\n - Changes: " + Arrays.toString(changeSet.getChanges()); showStatus("Person was loaded, or written to. " + insertions + deletions + changes); }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_realm_basic_example); rootLayout = findViewById(R.id.container); rootLayout.removeAllViews(); // Clear the Realm if the example was previously run. Realm.deleteRealm(Realm.getDefaultConfiguration()); // Create the Realm instance RealmConfiguration build = new RealmConfiguration.Builder() .allowWritesOnUiThread(true) .allowQueriesOnUiThread(true).build(); realm = Realm.getInstance(build); // Asynchronous queries are evaluated on a background thread, // and passed to the registered change listener when it's done. // The change listener is also called on any future writes that change the result set. persons = realm.where(Person.class).findAllAsync(); // The change listener will be notified when the data is loaded, // or the Realm is written to from any threads (and the result set is modified). persons.addChangeListener(realmChangeListener); // These operations are small enough that // we can generally safely run them on the UI thread. basicCRUD(realm); basicQuery(realm); basicLinkQuery(realm); // More complex operations can be executed on another thread. new ComplexBackgroundOperations(this).execute(); } @Override protected void onDestroy() { super.onDestroy(); persons.removeAllChangeListeners(); // Remove the change listener when no longer needed. realm.close(); // Remember to close Realm when done. } private void showStatus(String text) { Log.i(TAG, text); TextView textView = new TextView(this); textView.setText(text); rootLayout.addView(textView); } private void basicCRUD(Realm realm) { showStatus("Perform basic Create/Read/Update/Delete (CRUD) operations..."); // All writes must be wrapped in a transaction to facilitate safe multi threading realm.executeTransaction(r -> { // Add a person. // RealmObjects with primary keys created with `createObject()` must specify the primary key value as an argument. Person person = r.createObject(Person.class, 1); person.setName("Young Person"); person.setAge(14); // Even young people have at least one phone in this day and age. // Please note that this is a RealmList that contains primitive values. person.getPhoneNumbers().add("+1 123 4567"); }); // Find the first person (no query conditions) and read a field final Person person = realm.where(Person.class).findFirst(); showStatus(person.getName() + ":" + person.getAge()); // Update person in a transaction realm.executeTransaction(r -> { // Managed objects can be modified inside transactions. person.setName("Senior Person"); person.setAge(99); showStatus(person.getName() + " got older: " + person.getAge()); }); // Delete all persons showStatus("Deleting all persons"); realm.executeTransaction(r -> r.delete(Person.class)); } private void basicQuery(Realm realm) { showStatus("\nPerforming basic Query operation..."); // Let's add a person so that the query returns something. realm.executeTransaction(r -> { Person oldPerson = new Person(); oldPerson.setId(99); oldPerson.setAge(99); oldPerson.setName("George"); realm.insertOrUpdate(oldPerson); }); showStatus("Number of persons: " + realm.where(Person.class).count()); RealmResults results = realm.where(Person.class).equalTo("age", 99).findAll(); showStatus("Size of result set: " + results.size()); } private void basicLinkQuery(Realm realm) { showStatus("\nPerforming basic Link Query operation..."); // Let's add a person with a cat so that the query returns something. realm.executeTransaction(r -> { Person catLady = realm.createObject(Person.class, 24); catLady.setAge(52); catLady.setName("Mary"); Cat tiger = realm.createObject(Cat.class); tiger.name = "Tiger"; catLady.getCats().add(tiger); }); showStatus("Number of persons: " + realm.where(Person.class).count()); RealmResults results = realm.where(Person.class).equalTo("cats.name", "Tiger").findAll(); showStatus("Size of result set: " + results.size()); } // This AsyncTask shows how to use Realm in background thread operations. // // AsyncTasks should be static inner classes to avoid memory leaks. // In this example, WeakReference is used for the sake of simplicity. private static class ComplexBackgroundOperations extends AsyncTask { private WeakReference weakReference; public ComplexBackgroundOperations(IntroExampleActivity introExampleActivity) { this.weakReference = new WeakReference<>(introExampleActivity); } @Override protected void onPreExecute() { IntroExampleActivity activity = weakReference.get(); if (activity == null) { return; } activity.showStatus("\n\nBeginning complex operations on background thread."); } @Override protected String doInBackground(Void... voids) { IntroExampleActivity activity = weakReference.get(); if (activity == null) { return ""; } // Open the default realm. Uses `try-with-resources` to automatically close Realm when done. // All threads must use their own reference to the realm. // Realm instances, RealmResults, and managed RealmObjects can not be transferred across threads. try (Realm realm = Realm.getDefaultInstance()) { String info; info = activity.complexReadWrite(realm); info += activity.complexQuery(realm); return info; } } @Override protected void onPostExecute(String result) { IntroExampleActivity activity = weakReference.get(); if (activity == null) { return; } activity.showStatus(result); } } private String complexReadWrite(Realm realm) { String status = "\nPerforming complex Read/Write operation..."; // Add ten persons in one transaction realm.executeTransaction(r -> { Dog fido = r.createObject(Dog.class); fido.name = "fido"; for (int i = 0; i < 10; i++) { Person person = r.createObject(Person.class, i); person.setName("Person no. " + i); person.setAge(i); person.setDog(fido); // The field tempReference is annotated with @Ignore. // This means setTempReference sets the Person tempReference // field directly. The tempReference is NOT saved as part of // the RealmObject: person.setTempReference(42); for (int j = 0; j < i; j++) { Cat cat = r.createObject(Cat.class); cat.name = "Cat_" + j; person.getCats().add(cat); } } }); // Implicit read transactions allow you to access your objects status += "\nNumber of persons: " + realm.where(Person.class).count(); // Iterate over all objects, with an iterator for (Person person : realm.where(Person.class).findAll()) { String dogName; if (person.getDog() == null) { dogName = "None"; } else { dogName = person.getDog().name; } status += "\n" + person.getName() + ":" + person.getAge() + " : " + dogName + " : " + person.getCats().size(); } // Sorting RealmResults sortedPersons = realm.where(Person.class).sort("age", Sort.DESCENDING).findAll(); status += "\nSorting " + sortedPersons.last().getName() + " == " + realm.where(Person.class).findFirst() .getName(); return status; } private String complexQuery(Realm realm) { String status = "\n\nPerforming complex Query operation..."; status += "\nNumber of persons: " + realm.where(Person.class).count(); // Find all persons where age between 7 and 9 and name begins with "Person". RealmResults results = realm.where(Person.class) .between("age", 7, 9) // Notice implicit "and" operation .beginsWith("name", "Person").findAll(); status += "\nSize of result set: " + results.size(); return status; } } ================================================ FILE: examples/introExample/src/main/java/io/realm/examples/intro/MyApplication.java ================================================ /* * Copyright 2018 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.intro; import android.app.Application; import io.realm.Realm; public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); // Initialize Realm. Should only be done once when the application starts. Realm.init(this); // In this example, no default configuration is set, // so by default, `RealmConfiguration.Builder().build()` is used. } } ================================================ FILE: examples/introExample/src/main/java/io/realm/examples/intro/model/Cat.java ================================================ /* * Copyright 2018 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.intro.model; import io.realm.RealmObject; import io.realm.RealmResults; import io.realm.annotations.LinkingObjects; public class Cat extends RealmObject { // It is possible to also use public fields, instead of getters/setters. public String name; // You can define inverse relationships. @LinkingObjects("cats") public final RealmResults owners = null; } ================================================ FILE: examples/introExample/src/main/java/io/realm/examples/intro/model/Dog.java ================================================ /* * Copyright 2018 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.intro.model; import io.realm.RealmModel; import io.realm.RealmResults; import io.realm.annotations.LinkingObjects; import io.realm.annotations.RealmClass; // It is possible to use @RealmClass and implement RealmModel, instead of extending RealmObject. @RealmClass public class Dog implements RealmModel { // It is possible to also use public fields, instead of getters/setters. public String name; // You can define inverse relationships. @LinkingObjects("dog") public final RealmResults owners = null; } ================================================ FILE: examples/introExample/src/main/java/io/realm/examples/intro/model/Person.java ================================================ /* * Copyright 2018 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.intro.model; import io.realm.RealmList; import io.realm.RealmObject; import io.realm.annotations.Ignore; import io.realm.annotations.Index; import io.realm.annotations.PrimaryKey; // Your model just have to extend RealmObject. // This will inherit an annotation which produces proxy getters and setters for all fields. // It is also possible to use @RealmClass annotation, and implement RealmModel interface. public class Person extends RealmObject { // All fields are by default persisted. private int age; // Adding an index makes queries execute faster on that field. @Index private String name; // Primary keys are optional, but it allows identifying a specific object // when Realm writes are instructed to update if the object already exists in the Realm @PrimaryKey private long id; // Other objects in a one-to-one relation must also implement RealmModel, or extend RealmObject private Dog dog; // One-to-many relations is simply a RealmList of the objects which also implements RealmModel private RealmList cats; // It is also possible to have list of primitive types (long, String, Date, byte[], etc.) private RealmList phoneNumbers; // You can instruct Realm to ignore a field and not persist it. @Ignore private int tempReference; // Let your IDE generate getters and setters for you! // Or if you like you can even have public fields and no accessors! See Dog.java and Cat.java public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public long getId() { return id; } public void setId(long id) { this.id = id; } public Dog getDog() { return dog; } public void setDog(Dog dog) { this.dog = dog; } public RealmList getCats() { return cats; } public void setCats(RealmList cats) { this.cats = cats; } public int getTempReference() { return tempReference; } public void setTempReference(int tempReference) { this.tempReference = tempReference; } public RealmList getPhoneNumbers() { return phoneNumbers; } public void setPhoneNumbers(RealmList phoneNumbers) { this.phoneNumbers = phoneNumbers; } } ================================================ FILE: examples/introExample/src/main/res/layout/activity_realm_basic_example.xml ================================================ ================================================ FILE: examples/introExample/src/main/res/values/dimens.xml ================================================ 16dp 16dp ================================================ FILE: examples/introExample/src/main/res/values/strings.xml ================================================ Intro example Status Output… ================================================ FILE: examples/introExample/src/main/res/values/styles.xml ================================================ ================================================ FILE: examples/introExample/src/main/res/values-w820dp/dimens.xml ================================================ 64dp ================================================ FILE: examples/jsonExample/build.gradle ================================================ apply plugin: 'com.android.application' apply plugin: 'realm-android' android { compileSdkVersion rootProject.sdkVersion buildToolsVersion rootProject.buildTools compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } defaultConfig { applicationId 'io.realm.examples.json' targetSdkVersion rootProject.sdkVersion minSdkVersion rootProject.minSdkVersion versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled true signingConfig signingConfigs.debug } debug { minifyEnabled true } } productFlavors { } } dependencies { compileOnly 'org.projectlombok:lombok:1.18.22' compileOnly 'javax.annotation:javax.annotation-api:1.3.1' annotationProcessor 'org.projectlombok:lombok:1.18.22' } ================================================ FILE: examples/jsonExample/lint.xml ================================================ ================================================ FILE: examples/jsonExample/lombok.config ================================================ lombok.addGeneratedAnnotation = false ================================================ FILE: examples/jsonExample/src/main/AndroidManifest.xml ================================================ ================================================ FILE: examples/jsonExample/src/main/assets/cities.json ================================================ [ { "name" : "Barcelona", "votes" : 23 }, { "name" : "San Francisco", "votes" : 21 }, { "name" : "Venice", "votes" : 19 }, { "name" : "Melbourne", "votes" : 18 }, { "name" : "Paris", "votes" : 17 }, { "name" : "Seville", "votes" : 14 }, { "name" : "Sydney", "votes" : 12 }, { "name" : "New York", "votes" : 11 }, { "name" : "Krakow", "votes" : 9 }, { "name" : "Peoria", "votes" : 72 }, { "name" : "Springfield", "votes" : 88 }, { "name" : "Kansas City", "votes" : 100 }, { "name" : "Tokyo", "votes" : 20 }, { "name" : "Boise", "votes" : 17 }, { "name" : "Los Angeles", "votes" : 14 }, { "name" : "Newark", "votes" : 12 }, { "name" : "Chicago", "votes" : 11 }, { "name" : "Detroit", "votes" : 91 }, { "name" : "Florence", "votes" : 4 }, { "name" : "Madrid", "votes" : 1 }, { "name" : "London", "votes" : 9 } ] ================================================ FILE: examples/jsonExample/src/main/java/io/realm/examples/json/City.java ================================================ /* * Copyright 2014 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.json; import io.realm.RealmObject; import lombok.Getter; import lombok.Setter; @Getter @Setter public class City extends RealmObject { private String name; private long votes; } ================================================ FILE: examples/jsonExample/src/main/java/io/realm/examples/json/CityAdapter.java ================================================ /* * Copyright 2018 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.json; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.TextView; import java.util.Collections; import java.util.List; // This adapter is strictly to interface with the GridView and doesn't // particular show much interesting Realm functionality. public class CityAdapter extends BaseAdapter { public static final String TAG = "CityAdapter"; private List cities = Collections.emptyList(); public CityAdapter() { } public void setData(List details) { this.cities = details; notifyDataSetChanged(); } @Override public int getCount() { return cities == null ? 0 : cities.size(); } @Override public City getItem(int position) { return cities.get(position); } @Override public long getItemId(int i) { return i; } private static class ViewHolder { private TextView name; private TextView votes; public ViewHolder(View view) { this.name = view.findViewById(R.id.name); this.votes = view.findViewById(R.id.votes); } public void bind(City city) { name.setText(city.getName()); votes.setText(String.valueOf(city.getVotes())); } } @Override public View getView(int position, View currentView, ViewGroup parent) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); ViewHolder viewHolder; if (currentView == null) { currentView = inflater.inflate(R.layout.city_listitem, parent, false); viewHolder = new ViewHolder(currentView); currentView.setTag(viewHolder); } else { viewHolder = (ViewHolder)currentView.getTag(); } City city = cities.get(position); viewHolder.bind(city); return currentView; } } ================================================ FILE: examples/jsonExample/src/main/java/io/realm/examples/json/JsonExampleActivity.java ================================================ /* * Copyright 2018 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.json; import android.app.Activity; import android.os.Bundle; import android.widget.GridView; import org.json.JSONObject; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Map; import io.realm.Realm; import io.realm.RealmChangeListener; import io.realm.RealmConfiguration; import io.realm.RealmResults; /** * This example demonstrates how to import RealmObjects as JSON. Realm supports JSON represented * as Strings, JSONObject, JSONArray or InputStreams (from API 11+) */ public class JsonExampleActivity extends Activity { private GridView gridView; private CityAdapter adapter; private Realm realm; private RealmResults cities; private RealmChangeListener> realmChangeListener = (cities) -> { adapter.setData(cities); }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_realm_example); RealmConfiguration config = new RealmConfiguration.Builder() .allowWritesOnUiThread(true) .allowQueriesOnUiThread(true) .build(); Realm.deleteRealm(config); realm = Realm.getInstance(config); gridView = findViewById(R.id.cities_list); cities = realm.where(City.class).findAllAsync(); cities.addChangeListener(realmChangeListener); adapter = new CityAdapter(); gridView.setAdapter(adapter); // Load from file "cities.json" first time loadCities(); } @Override protected void onDestroy() { super.onDestroy(); cities.removeAllChangeListeners(); realm.close(); } public void loadCities() { try { loadJsonFromStream(); loadJsonFromJsonObject(); loadJsonFromString(); } catch(IOException e) { throw new RuntimeException(e); } } private void loadJsonFromStream() throws IOException { // Use streams if you are worried about the size of the JSON whether it was persisted on disk // or received from the network. try(InputStream stream = getAssets().open("cities.json")) { try { // Open a transaction to store items into the realm realm.beginTransaction(); realm.createAllFromJson(City.class, stream); realm.commitTransaction(); } catch (IOException e) { // Remember to cancel the transaction if anything goes wrong. if(realm.isInTransaction()) { realm.cancelTransaction(); } throw new RuntimeException(e); } } } private void loadJsonFromJsonObject() { Map city = new HashMap(); city.put("name", "København"); city.put("votes", "9"); final JSONObject json = new JSONObject(city); realm.executeTransaction(realm -> realm.createObjectFromJson(City.class, json)); } private void loadJsonFromString() { final String json = "{ name: \"Aarhus\", votes: 99 }"; realm.executeTransaction(realm -> realm.createObjectFromJson(City.class, json)); } } ================================================ FILE: examples/jsonExample/src/main/java/io/realm/examples/json/MyApplication.java ================================================ /* * 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.json; import android.app.Application; import io.realm.Realm; public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); Realm.init(this); } } ================================================ FILE: examples/jsonExample/src/main/res/layout/activity_realm_example.xml ================================================ ================================================ FILE: examples/jsonExample/src/main/res/layout/city_listitem.xml ================================================ ================================================ FILE: examples/jsonExample/src/main/res/values/dimens.xml ================================================ 16dp 16dp ================================================ FILE: examples/jsonExample/src/main/res/values/strings.xml ================================================ JSON Example My Favorite City ================================================ FILE: examples/jsonExample/src/main/res/values/styles.xml ================================================ ================================================ FILE: examples/jsonExample/src/main/res/values-w820dp/dimens.xml ================================================ 64dp ================================================ FILE: examples/kotlinExample/build.gradle ================================================ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' apply plugin: 'realm-android' android { //noinspection GroovyAssignabilityCheck compileSdkVersion rootProject.sdkVersion //noinspection GroovyAssignabilityCheck buildToolsVersion rootProject.buildTools defaultConfig { applicationId 'io.realm.examples.kotlin' //noinspection GroovyAssignabilityCheck targetSdkVersion rootProject.sdkVersion minSdkVersion rootProject.minSdkVersion versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled true signingConfig signingConfigs.debug } debug { minifyEnabled true } } sourceSets { main.java.srcDirs += 'src/main/kotlin' } } // This is added automatically if Kotlin is registered in the project, but Kotlin extension functions // for Realm can be excluded if needed. realm { kotlinExtensionsEnabled = true } // enable @ParametersAreNonnullByDefault annotation. See https://blog.jetbrains.com/kotlin/2017/09/kotlin-1-1-50-is-out/ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { kotlinOptions { freeCompilerArgs = ["-Xjsr305=strict"] } } dependencies { implementation "org.jetbrains.anko:anko-commons:0.10.4" } ================================================ FILE: examples/kotlinExample/lint.xml ================================================ ================================================ FILE: examples/kotlinExample/src/main/AndroidManifest.xml ================================================ ================================================ FILE: examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt ================================================ /* * Copyright 2015 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.kotlin import android.app.Activity import android.os.Bundle import android.util.Log import android.widget.LinearLayout import android.widget.TextView import io.realm.Realm import io.realm.RealmConfiguration import io.realm.Sort import io.realm.examples.kotlin.model.Cat import io.realm.examples.kotlin.model.Dog import io.realm.examples.kotlin.model.Person import io.realm.kotlin.createObject import io.realm.kotlin.where import org.jetbrains.anko.doAsync import org.jetbrains.anko.uiThread class KotlinExampleActivity : Activity() { companion object { const val TAG: String = "KotlinExampleActivity" } private lateinit var rootLayout: LinearLayout private lateinit var realm: Realm override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_realm_basic_example) rootLayout = findViewById(R.id.container) rootLayout.removeAllViews() Realm.setDefaultConfiguration( RealmConfiguration.Builder() .allowQueriesOnUiThread(true) .allowWritesOnUiThread(true) .build() ) // Open the realm for the UI thread. realm = Realm.getDefaultInstance() // Delete all persons // Using executeTransaction with a lambda reduces code size and makes it impossible // to forget to commit the transaction. realm.executeTransaction { realm -> realm.deleteAll() } // These operations are small enough that // we can generally safely run them on the UI thread. basicCRUD(realm) basicQuery(realm) basicLinkQuery(realm) // More complex operations can be executed on another thread, for example using // Anko's doAsync extension method. doAsync { var info = "" // Open the default realm. All threads must use its own reference to the realm. // Those can not be transferred across threads. // Realm implements the Closable interface, therefore // we can make use of Kotlin's built-in extension method 'use' (pun intended). Realm.getDefaultInstance().use { realm -> info += complexReadWrite(realm) info += complexQuery(realm) } uiThread { showStatus(info) } } } override fun onDestroy() { super.onDestroy() realm.close() // Remember to close Realm when done. } private fun showStatus(text: String) { Log.i(TAG, text) val textView = TextView(this) textView.text = text rootLayout.addView(textView) } @Suppress("NAME_SHADOWING") private fun basicCRUD(realm: Realm) { showStatus("Perform basic Create/Read/Update/Delete (CRUD) operations...") // All writes must be wrapped in a transaction to facilitate safe multi threading realm.executeTransaction { realm -> // Add a person val person = realm.createObject(0) person.name = "Young Person" person.age = 14 } // Find the first person (no query conditions) and read a field val person = realm.where().findFirst()!! showStatus(person.name + ": " + person.age) // Update person in a transaction realm.executeTransaction { _ -> person.name = "Senior Person" person.age = 99 showStatus(person.name + " got older: " + person.age) } } private fun basicQuery(realm: Realm) { showStatus("\nPerforming basic Query operation...") showStatus("Number of persons: ${realm.where().count()}") val ageCriteria = 99 val results = realm.where().equalTo("age", ageCriteria).findAll() showStatus("Size of result set: " + results.size) } private fun basicLinkQuery(realm: Realm) { showStatus("\nPerforming basic Link Query operation...") showStatus("Number of persons: ${realm.where().count()}") val results = realm.where().equalTo("cats.name", "Tiger").findAll() showStatus("Size of result set: ${results.size}") } private fun complexReadWrite(realm: Realm): String { var status = "\nPerforming complex Read/Write operation..." // Add ten persons in one transaction realm.executeTransaction { val fido = realm.createObject() fido.name = "fido" for (i in 1..9) { val person = realm.createObject(i.toLong()) person.name = "Person no. $i" person.age = i person.dog = fido // The field tempReference is annotated with @Ignore. // This means setTempReference sets the Person tempReference // field directly. The tempReference is NOT saved as part of // the RealmObject: person.tempReference = 42 for (j in 0..i - 1) { val cat = realm.createObject() cat.name = "Cat_$j" person.cats.add(cat) } } } // Implicit read transactions allow you to access your objects status += "\nNumber of persons: ${realm.where().count()}" // Iterate over all objects for (person in realm.where().findAll()) { val dogName: String = person?.dog?.name ?: "None" status += "\n${person.name}: ${person.age} : $dogName : ${person.cats.size}" // The field tempReference is annotated with @Ignore // Though we initially set its value to 42, it has // not been saved as part of the Person RealmObject: check(person.tempReference == 0) } // Sorting val sortedPersons = realm.where().sort(Person::age.name, Sort.DESCENDING).findAll() status += "\nSorting ${sortedPersons.last()?.name} == ${realm.where().findAll().first()?.name}" return status } private fun complexQuery(realm: Realm): String { var status = "\n\nPerforming complex Query operation..." status += "\nNumber of persons: ${realm.where().count()}" // Find all persons where age between 7 and 9 and name begins with "Person". val results = realm.where() .between("age", 7, 9) // Notice implicit "and" operation .beginsWith("name", "Person") .findAll() status += "\nSize of result set: ${results.size}" return status } } ================================================ FILE: examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/MyApplication.kt ================================================ /* * 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.kotlin import android.app.Application import io.realm.Realm class MyApplication : Application() { override fun onCreate() { super.onCreate() // Initialize Realm. Should only be done once when the application starts. Realm.init(this) } } ================================================ FILE: examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/model/Cat.kt ================================================ /* * Copyright 2015 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.kotlin.model import io.realm.RealmObject import io.realm.RealmResults import io.realm.annotations.LinkingObjects open class Cat : RealmObject() { var name: String? = null @LinkingObjects("cats") val owners: RealmResults? = null } ================================================ FILE: examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/model/Dog.kt ================================================ /* * Copyright 2015 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.kotlin.model import io.realm.RealmObject import io.realm.RealmResults import io.realm.annotations.LinkingObjects open class Dog : RealmObject() { var name: String? = null @LinkingObjects("dog") val owners: RealmResults? = null } ================================================ FILE: examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/model/Person.kt ================================================ /* * Copyright 2015 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.kotlin.model import io.realm.RealmList import io.realm.RealmObject import io.realm.annotations.Ignore import io.realm.annotations.PrimaryKey // Your model has to extend RealmObject. Furthermore, the class must be annotated with open (Kotlin classes are final // by default). open class Person( // You can put properties in the constructor as long as all of them are initialized with // default values. This ensures that an empty constructor is generated. // All properties are by default persisted. // Properties can be annotated with PrimaryKey or Index. // If you use non-nullable types, properties must be initialized with non-null values. @PrimaryKey var id: Long = 0, var name: String = "", var age: Int = 0, // Other objects in a one-to-one relation must also subclass RealmObject var dog: Dog? = null, // One-to-many relations is simply a RealmList of the objects which also subclass RealmObject var cats: RealmList = RealmList(), // You can instruct Realm to ignore a field and not persist it. @Ignore var tempReference: Int = 0 ) : RealmObject() { // The Kotlin compiler generates standard getters and setters. // Realm will overload them and code inside them is ignored. // So if you prefer you can also just have empty abstract methods. } ================================================ FILE: examples/kotlinExample/src/main/res/layout/activity_realm_basic_example.xml ================================================ ================================================ FILE: examples/kotlinExample/src/main/res/values/dimens.xml ================================================ 16dp 16dp ================================================ FILE: examples/kotlinExample/src/main/res/values/strings.xml ================================================ Kotlin example Status Output… ================================================ FILE: examples/kotlinExample/src/main/res/values/styles.xml ================================================ ================================================ FILE: examples/kotlinExample/src/main/res/values-w820dp/dimens.xml ================================================ 64dp ================================================ FILE: examples/migrationExample/build.gradle ================================================ apply plugin: 'com.android.application' apply plugin: 'realm-android' android { compileSdkVersion rootProject.sdkVersion buildToolsVersion rootProject.buildTools defaultConfig { applicationId "examples.realm.io.migration" targetSdkVersion rootProject.sdkVersion minSdkVersion rootProject.minSdkVersion versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled true signingConfig signingConfigs.debug } debug { minifyEnabled true } } } ================================================ FILE: examples/migrationExample/lint.xml ================================================ ================================================ FILE: examples/migrationExample/src/main/AndroidManifest.xml ================================================ ================================================ FILE: examples/migrationExample/src/main/java/io/realm/examples/realmmigrationexample/MigrationExampleActivity.java ================================================ /* * Copyright 2014 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.realmmigrationexample; import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.widget.LinearLayout; import android.widget.TextView; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import io.realm.Realm; import io.realm.RealmConfiguration; import io.realm.examples.realmmigrationexample.model.Migration; import io.realm.examples.realmmigrationexample.model.Person; /* ** This example demonstrates how you can migrate your data through different updates ** of your models. */ public class MigrationExampleActivity extends Activity { public static final String TAG = MigrationExampleActivity.class.getName(); private LinearLayout rootLayout = null; private Realm realm; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_realm_migration_example); rootLayout = ((LinearLayout) findViewById(R.id.container)); rootLayout.removeAllViews(); // 3 versions of the databases for testing. Normally you would only have one. copyBundledRealmFile(this.getResources().openRawResource(R.raw.default0), "default0.realm"); copyBundledRealmFile(this.getResources().openRawResource(R.raw.default1), "default1.realm"); copyBundledRealmFile(this.getResources().openRawResource(R.raw.default2), "default2.realm"); // When you create a RealmConfiguration you can specify the version of the schema. // If the schema does not have that version a RealmMigrationNeededException will be thrown. RealmConfiguration config0 = new RealmConfiguration.Builder() .name("default0.realm") .schemaVersion(3) .build(); // You can then manually call Realm.migrateRealm(). try { Realm.migrateRealm(config0, new Migration()); } catch (FileNotFoundException ignored) { // If the Realm file doesn't exist, just ignore. } realm = Realm.getInstance(config0); showStatus("Default0"); showStatus(realm); realm.close(); // Or you can add the migration code to the configuration. This will run the migration code without throwing // a RealmMigrationNeededException. RealmConfiguration config1 = new RealmConfiguration.Builder() .name("default1.realm") .schemaVersion(3) .migration(new Migration()) .build(); realm = Realm.getInstance(config1); // Automatically run migration if needed showStatus("Default1"); showStatus(realm); realm.close(); // or you can set .deleteRealmIfMigrationNeeded() if you don't want to bother with migrations. // WARNING: This will delete all data in the Realm though. RealmConfiguration config2 = new RealmConfiguration.Builder() .name("default2.realm") .schemaVersion(3) .deleteRealmIfMigrationNeeded() .build(); realm = Realm.getInstance(config2); showStatus("default2"); showStatus(realm); realm.close(); } private String copyBundledRealmFile(InputStream inputStream, String outFileName) { try { File file = new File(this.getFilesDir(), outFileName); FileOutputStream outputStream = new FileOutputStream(file); byte[] buf = new byte[1024]; int bytesRead; while ((bytesRead = inputStream.read(buf)) > 0) { outputStream.write(buf, 0, bytesRead); } outputStream.close(); return file.getAbsolutePath(); } catch (IOException e) { e.printStackTrace(); } return null; } private String realmString(Realm realm) { StringBuilder stringBuilder = new StringBuilder(); for (Person person : realm.where(Person.class).findAll()) { stringBuilder.append(person.toString()).append("\n"); } return (stringBuilder.length() == 0) ? "" : stringBuilder.toString(); } private void showStatus(Realm realm) { showStatus(realmString(realm)); } private void showStatus(String txt) { Log.i(TAG, txt); TextView tv = new TextView(this); tv.setText(txt); rootLayout.addView(tv); } } ================================================ FILE: examples/migrationExample/src/main/java/io/realm/examples/realmmigrationexample/MyApplication.java ================================================ /* * 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.realmmigrationexample; import android.app.Application; import io.realm.Realm; public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); Realm.init(this); } } ================================================ FILE: examples/migrationExample/src/main/java/io/realm/examples/realmmigrationexample/model/Migration.java ================================================ /* * Copyright 2014 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.realmmigrationexample.model; import io.realm.DynamicRealm; import io.realm.DynamicRealmObject; import io.realm.FieldAttribute; import io.realm.RealmMigration; import io.realm.RealmObjectSchema; import io.realm.RealmSchema; /** * Example of migrating a Realm file from version 0 (initial version) to its last version (version 3). */ public class Migration implements RealmMigration { @Override public void migrate(final DynamicRealm realm, long oldVersion, long newVersion) { // During a migration, a DynamicRealm is exposed. A DynamicRealm is an untyped variant of a normal Realm, but // with the same object creation and query capabilities. // A DynamicRealm uses Strings instead of Class references because the Classes might not even exist or have been // renamed. // Access the Realm schema in order to create, modify or delete classes and their fields. RealmSchema schema = realm.getSchema(); /************************************************ // Version 0 class Person @Required String firstName; @Required String lastName; int age; // Version 1 class Person @Required String fullName; // combine firstName and lastName into single field. int age; ************************************************/ // Migrate from version 0 to version 1 if (oldVersion == 0) { RealmObjectSchema personSchema = schema.get("Person"); // Combine 'firstName' and 'lastName' in a new field called 'fullName' personSchema .addField("fullName", String.class, FieldAttribute.REQUIRED) .transform(new RealmObjectSchema.Function() { @Override public void apply(DynamicRealmObject obj) { obj.set("fullName", obj.getString("firstName") + " " + obj.getString("lastName")); } }) .removeField("firstName") .removeField("lastName"); oldVersion++; } /************************************************ // Version 2 class Pet // add a new model class @Required String name; @Required String type; class Person @Required String fullName; int age; RealmList pets; // add an array property ************************************************/ // Migrate from version 1 to version 2 if (oldVersion == 1) { // Create a new class RealmObjectSchema petSchema = schema.create("Pet") .addField("name", String.class, FieldAttribute.REQUIRED) .addField("type", String.class, FieldAttribute.REQUIRED); // Add a new field to an old class and populate it with initial data schema.get("Person") .addRealmListField("pets", petSchema) .transform(new RealmObjectSchema.Function() { @Override public void apply(DynamicRealmObject obj) { if (obj.getString("fullName").equals("JP McDonald")) { DynamicRealmObject pet = realm.createObject("Pet"); pet.setString("name", "Jimbo"); pet.setString("type", "dog"); obj.getList("pets").add(pet); } } }); oldVersion++; } /************************************************ // Version 3 class Pet @Required String name; int type; // type becomes int class Person String fullName; // fullName is nullable now RealmList pets; // age and pets re-ordered (no action needed) int age; ************************************************/ // Migrate from version 2 to version 3 if (oldVersion == 2) { RealmObjectSchema personSchema = schema.get("Person"); personSchema.setNullable("fullName", true); // fullName is nullable now. // Change type from String to int schema.get("Pet") .addField("type_tmp", int.class) .transform(new RealmObjectSchema.Function() { @Override public void apply(DynamicRealmObject obj) { String oldType = obj.getString("type"); if (oldType.equals("dog")) { obj.setLong("type_tmp", 1); } else if (oldType.equals("cat")) { obj.setInt("type_tmp", 2); } else if (oldType.equals("hamster")) { obj.setInt("type_tmp", 3); } } }) .removeField("type") .renameField("type_tmp", "type"); oldVersion++; } } } ================================================ FILE: examples/migrationExample/src/main/java/io/realm/examples/realmmigrationexample/model/Person.java ================================================ /* * Copyright 2014 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.realmmigrationexample.model; import io.realm.RealmList; import io.realm.RealmObject; import io.realm.annotations.Ignore; public class Person extends RealmObject { private String fullName; private int age; private RealmList pets; public String getFullName() { return fullName; } public void setFullName(String fullName) { this.fullName = fullName; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public RealmList getPets() { return pets; } public void setPets(RealmList pets) { this.pets = pets; } } ================================================ FILE: examples/migrationExample/src/main/java/io/realm/examples/realmmigrationexample/model/Pet.java ================================================ /* * Copyright 2014 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.realmmigrationexample.model; import io.realm.RealmObject; import io.realm.annotations.Required; public class Pet extends RealmObject { @Required private String name; private int type; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getType() { return type; } public void setType(int type) { this.type = type; } } ================================================ FILE: examples/migrationExample/src/main/res/layout/activity_realm_migration_example.xml ================================================ ================================================ FILE: examples/migrationExample/src/main/res/values/dimens.xml ================================================ 16dp 16dp ================================================ FILE: examples/migrationExample/src/main/res/values/strings.xml ================================================ Migration example Status Output... ================================================ FILE: examples/migrationExample/src/main/res/values/styles.xml ================================================ ================================================ FILE: examples/migrationExample/src/main/res/values-w820dp/dimens.xml ================================================ 64dp ================================================ FILE: examples/moduleExample/app/build.gradle ================================================ apply plugin: 'com.android.application' apply plugin: 'realm-android' android { compileSdkVersion rootProject.sdkVersion buildToolsVersion rootProject.buildTools defaultConfig { applicationId 'io.realm.examples.appmodules' targetSdkVersion rootProject.sdkVersion minSdkVersion rootProject.minSdkVersion versionCode 1 versionName "1.0" } signingConfigs { release { storeFile file("keystore/release.keystore") storePassword "realm1234" keyAlias "realm-introexample" keyPassword "realm1234" } } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.release } } compileOptions { sourceCompatibility 1.8 targetCompatibility 1.8 } } dependencies { implementation project(':moduleExample:library') } ================================================ FILE: examples/moduleExample/app/lint.xml ================================================ ================================================ FILE: examples/moduleExample/app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /usr/local/Cellar/android-sdk/22.6.2/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: examples/moduleExample/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/ModulesExampleActivity.java ================================================ /* * Copyright 2015 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.appmodules; import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.widget.LinearLayout; import android.widget.TextView; import io.realm.Realm; import io.realm.RealmConfiguration; import io.realm.examples.appmodules.model.Cow; import io.realm.examples.appmodules.model.Pig; import io.realm.examples.appmodules.model.Snake; import io.realm.examples.appmodules.model.Spider; import io.realm.examples.appmodules.modules.CreepyAnimalsModule; import io.realm.examples.librarymodules.Zoo; import io.realm.examples.librarymodules.model.Cat; import io.realm.examples.librarymodules.model.Dog; import io.realm.examples.librarymodules.model.Elephant; import io.realm.examples.librarymodules.model.Lion; import io.realm.examples.librarymodules.model.Zebra; import io.realm.examples.librarymodules.modules.AllAnimalsModule; import io.realm.examples.librarymodules.modules.DomesticAnimalsModule; import io.realm.examples.librarymodules.modules.ZooAnimalsModule; import io.realm.exceptions.RealmException; /** * This example demonstrates how you can use modules to control which classes belong to which Realms and how you can * work with multiple Realms at the same time. */ public class ModulesExampleActivity extends Activity { public static final String TAG = ModulesExampleActivity.class.getName(); private LinearLayout rootLayout = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_modules_example); rootLayout = ((LinearLayout) findViewById(R.id.container)); rootLayout.removeAllViews(); // The default Realm instance implicitly knows about all classes in the realmModuleAppExample Android Studio // module. This does not include the classes from the realmModuleLibraryExample AS module so a Realm using this // configuration would know about the following classes: { Cow, Pig, Snake, Spider } RealmConfiguration defaultConfig = new RealmConfiguration .Builder() .modules(Realm.getDefaultModule(), new AllAnimalsModule()) .allowQueriesOnUiThread(true) .allowWritesOnUiThread(true) .build(); // It is possible to extend the default schema by adding additional Realm modules using modules(). This can // also be Realm modules from libraries. The below Realm contains the following classes: { Cow, Pig, Snake, // Spider, Cat, Dog } RealmConfiguration farmAnimalsConfig = new RealmConfiguration.Builder() .name("farm.realm") .allowQueriesOnUiThread(true) .allowWritesOnUiThread(true) .modules(Realm.getDefaultModule(), new AllAnimalsModule()) .build(); // Or you can completely replace the default schema. // This Realm contains the following classes: { Elephant, Lion, Zebra, Snake, Spider } RealmConfiguration exoticAnimalsConfig = new RealmConfiguration.Builder() .name("exotic.realm") .allowQueriesOnUiThread(true) .allowWritesOnUiThread(true) .modules(new ZooAnimalsModule(), new CreepyAnimalsModule()) .build(); // Multiple Realms can be opened at the same time showStatus("Opening multiple Realms"); Realm defaultRealm = Realm.getInstance(defaultConfig); final Realm farmRealm = Realm.getInstance(farmAnimalsConfig); Realm exoticRealm = Realm.getInstance(exoticAnimalsConfig); // Objects can be added to each Realm independantly showStatus("Create objects in the default Realm"); defaultRealm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { realm.createObject(Cow.class); realm.createObject(Pig.class); realm.createObject(Snake.class); realm.createObject(Spider.class); } }); showStatus("Create objects in the farm Realm"); farmRealm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { realm.createObject(Cow.class); realm.createObject(Pig.class); realm.createObject(Cat.class); realm.createObject(Dog.class); } }); showStatus("Create objects in the exotic Realm"); exoticRealm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { realm.createObject(Elephant.class); realm.createObject(Lion.class); realm.createObject(Zebra.class); realm.createObject(Snake.class); realm.createObject(Spider.class); } }); // You can copy objects between Realms showStatus("Copy objects between Realms"); showStatus("Number of pigs on the farm : " + farmRealm.where(Pig.class).count()); showStatus("Copy pig from defaultRealm to farmRealm"); final Pig defaultPig = defaultRealm.where(Pig.class).findFirst(); farmRealm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { realm.copyToRealm(defaultPig); } }); showStatus("Number of unnamed pigs on the farm : " + farmRealm.where(Pig.class).isNull("name").count()); // Each Realm is restricted to only accept the classes in their schema. showStatus("Trying to add an unsupported class"); defaultRealm.beginTransaction(); try { defaultRealm.createObject(Elephant.class); } catch (RealmException expected) { showStatus("This throws a :" + expected.toString()); } finally { defaultRealm.cancelTransaction(); } // And Realms in library projects are independent from Realms in the app code showStatus("Interacting with library code that uses Realm internally"); int animals = 5; Zoo libraryZoo = new Zoo(); libraryZoo.open(); showStatus("Adding animals: " + animals); libraryZoo.addAnimals(5); showStatus("Number of animals in the library Realm:" + libraryZoo.getNoOfAnimals()); libraryZoo.close(); // Remember to close all open Realms defaultRealm.close(); farmRealm.close(); exoticRealm.close(); } private void showStatus(String txt) { Log.i(TAG, txt); TextView tv = new TextView(this); tv.setText(txt); rootLayout.addView(tv); } } ================================================ FILE: examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/MyApplication.java ================================================ /* * 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.appmodules; import android.app.Application; import io.realm.Realm; public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); Realm.init(this); } } ================================================ FILE: examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/model/Cow.java ================================================ /* * Copyright 2015 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.appmodules.model; import io.realm.RealmObject; public class Cow extends RealmObject { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } ================================================ FILE: examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/model/CrossModuleLinks.java ================================================ package io.realm.examples.appmodules.model; import io.realm.RealmDictionary; import io.realm.RealmList; import io.realm.RealmObject; import io.realm.RealmSet; import io.realm.examples.librarymodules.model.Cat; import io.realm.examples.librarymodules.model.EmbeddedAnimal; // Not used by the app, but merely acts as sanity checks that we can make cross module references, // and generate valid code for it. public class CrossModuleLinks extends RealmObject { public EmbeddedAnimal embeded; public Cat link; public RealmList list; public RealmSet set; public RealmDictionary dictionary; } ================================================ FILE: examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/model/Pig.java ================================================ /* * Copyright 2015 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.appmodules.model; import io.realm.RealmObject; public class Pig extends RealmObject { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } ================================================ FILE: examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/model/Snake.java ================================================ /* * Copyright 2015 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.appmodules.model; import io.realm.RealmObject; public class Snake extends RealmObject { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } ================================================ FILE: examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/model/Spider.java ================================================ /* * Copyright 2015 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.appmodules.model; import io.realm.RealmObject; public class Spider extends RealmObject { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } ================================================ FILE: examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/modules/CreepyAnimalsModule.java ================================================ /* * Copyright 2015 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.appmodules.modules; import io.realm.annotations.RealmModule; import io.realm.examples.appmodules.model.Snake; import io.realm.examples.appmodules.model.Spider; @RealmModule(classes = {Snake.class, Spider.class}) public class CreepyAnimalsModule { } ================================================ FILE: examples/moduleExample/app/src/main/res/layout/activity_modules_example.xml ================================================ ================================================ FILE: examples/moduleExample/app/src/main/res/values/dimens.xml ================================================ 16dp 16dp ================================================ FILE: examples/moduleExample/app/src/main/res/values/strings.xml ================================================ RealmModule example Status Output… ================================================ FILE: examples/moduleExample/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: examples/moduleExample/app/src/main/res/values-w820dp/dimens.xml ================================================ 64dp ================================================ FILE: examples/moduleExample/library/build.gradle ================================================ apply plugin: 'com.android.library' apply plugin: 'realm-android' android { compileSdkVersion rootProject.sdkVersion buildToolsVersion rootProject.buildTools defaultConfig { targetSdkVersion rootProject.sdkVersion minSdkVersion rootProject.minSdkVersion versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false } } } ================================================ FILE: examples/moduleExample/library/lint.xml ================================================ ================================================ FILE: examples/moduleExample/library/src/main/AndroidManifest.xml ================================================ ================================================ FILE: examples/moduleExample/library/src/main/java/io/realm/examples/librarymodules/Zoo.java ================================================ /* * Copyright 2015 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.librarymodules; import android.content.Context; import io.realm.Realm; import io.realm.RealmConfiguration; import io.realm.examples.librarymodules.model.Cat; import io.realm.examples.librarymodules.modules.AllAnimalsModule; /** * Library projects can also use Realms, but some configuration options are mandatory to avoid clashing with Realms used * in the app code. */ public class Zoo { private final RealmConfiguration realmConfig; private Realm realm; public Zoo() { realmConfig = new RealmConfiguration.Builder() // The app is responsible for calling `Realm.init(Context)` .name("library.zoo.realm") // So always use a unique name .allowQueriesOnUiThread(true) .allowWritesOnUiThread(true) .modules(new AllAnimalsModule()) // Always use explicit modules in library projects .build(); // Reset Realm Realm.deleteRealm(realmConfig); } public void open() { // Don't use Realm.setDefaultInstance() in library projects. It is unsafe as app developers can override the // default configuration. So always use explicit configurations in library projects. realm = Realm.getInstance(realmConfig); } public long getNoOfAnimals() { return realm.where(Cat.class).count(); } public void addAnimals(final int count) { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { for (int i = 0; i < count; i++) { Cat cat = realm.createObject(Cat.class); cat.setName("Cat " + i); } } }); } public void close() { realm.close(); } } ================================================ FILE: examples/moduleExample/library/src/main/java/io/realm/examples/librarymodules/model/Cat.java ================================================ /* * Copyright 2015 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.librarymodules.model; import io.realm.RealmObject; public class Cat extends RealmObject { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } ================================================ FILE: examples/moduleExample/library/src/main/java/io/realm/examples/librarymodules/model/Dog.java ================================================ /* * Copyright 2015 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.librarymodules.model; import io.realm.RealmObject; public class Dog extends RealmObject { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } ================================================ FILE: examples/moduleExample/library/src/main/java/io/realm/examples/librarymodules/model/Elephant.java ================================================ /* * Copyright 2015 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.librarymodules.model; import io.realm.RealmObject; public class Elephant extends RealmObject { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } ================================================ FILE: examples/moduleExample/library/src/main/java/io/realm/examples/librarymodules/model/EmbeddedAnimal.java ================================================ package io.realm.examples.librarymodules.model; import io.realm.RealmObject; import io.realm.annotations.RealmClass; // Not used by the app, but merely acts as sanity checks that we can make cross module references, // and generate valid code for it. @RealmClass(embedded = true) public class EmbeddedAnimal extends RealmObject { public String name; } ================================================ FILE: examples/moduleExample/library/src/main/java/io/realm/examples/librarymodules/model/Lion.java ================================================ /* * Copyright 2015 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.librarymodules.model; import io.realm.RealmObject; public class Lion extends RealmObject { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } ================================================ FILE: examples/moduleExample/library/src/main/java/io/realm/examples/librarymodules/model/Zebra.java ================================================ /* * Copyright 2015 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.librarymodules.model; import io.realm.RealmObject; public class Zebra extends RealmObject { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } ================================================ FILE: examples/moduleExample/library/src/main/java/io/realm/examples/librarymodules/modules/AllAnimalsModule.java ================================================ /* * Copyright 2015 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.librarymodules.modules; import io.realm.annotations.RealmModule; @RealmModule(library = true, allClasses = true) public class AllAnimalsModule { } ================================================ FILE: examples/moduleExample/library/src/main/java/io/realm/examples/librarymodules/modules/DomesticAnimalsModule.java ================================================ /* * Copyright 2015 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.librarymodules.modules; import io.realm.annotations.RealmModule; import io.realm.examples.librarymodules.model.Cat; import io.realm.examples.librarymodules.model.Dog; @RealmModule(library = true, classes = {Cat.class, Dog.class}) public class DomesticAnimalsModule { } ================================================ FILE: examples/moduleExample/library/src/main/java/io/realm/examples/librarymodules/modules/ZooAnimalsModule.java ================================================ /* * Copyright 2015 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.librarymodules.modules; import io.realm.annotations.RealmModule; import io.realm.examples.librarymodules.model.Elephant; import io.realm.examples.librarymodules.model.Lion; import io.realm.examples.librarymodules.model.Zebra; @RealmModule(library = true, classes = {Elephant.class, Lion.class, Zebra.class}) public class ZooAnimalsModule { } ================================================ FILE: examples/moduleExample/library/src/main/res/values/strings.xml ================================================ RealmModule Library Example ================================================ FILE: examples/mongoDbRealmExample/README.md ================================================ # Using this example This example is a minimal demonstration of how to connect to and use the Realm Object Server to synchronize changes between devices. The example assumes that the Object Server is running on the machine that built the application: The build machine IP address is automatically 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: def rosUrl = "" For instance: 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/ ================================================ FILE: examples/mongoDbRealmExample/build.gradle ================================================ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-android-extensions' apply plugin: 'realm-android' android { // androidx.lifecycle dependencies requires Android APIs 31 or later compileSdkVersion 31 buildToolsVersion rootProject.buildTools defaultConfig { applicationId 'com.mongodb.realm.example' targetSdkVersion rootProject.sdkVersion minSdkVersion rootProject.minSdkVersion versionCode 1 versionName "1.0" } dataBinding { enabled = true } buildTypes { // Configure server and App Id. // The default server is https://realm-dev.mongodb.com/ . Go to that and copy the MongoDB // Realm App Id. // // If you are running a local version of MongoDB Realm, modify endpoint accordingly. Most // likely it is "http://localhost:9090" def mongodbRealmUrl = "https://realm-dev.mongodb.com" def appId = "my-app-id" debug { buildConfigField "String", "MONGODB_REALM_URL", "\"${mongodbRealmUrl}\"" buildConfigField "String", "MONGODB_REALM_APP_ID", "\"${appId}\"" minifyEnabled true } release { buildConfigField "String", "MONGODB_REALM_URL", "\"${mongodbRealmUrl}\"" buildConfigField "String", "MONGODB_REALM_APP_ID", "\"${appId}\"" minifyEnabled true signingConfig signingConfigs.debug } } compileOptions { sourceCompatibility 1.8 targetCompatibility 1.8 } } realm { syncEnabled = true } dependencies { implementation 'androidx.appcompat:appcompat:1.4.2' implementation 'com.google.android.material:material:1.6.1' implementation 'me.zhanghai.android.materialprogressbar:library:1.6.1' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } ================================================ FILE: examples/mongoDbRealmExample/gradle.properties ================================================ # FIXME: Required as long as we depend on PlayServices for RemoteMongDB API's android.useAndroidX=true ================================================ FILE: examples/mongoDbRealmExample/lint.xml ================================================ ================================================ FILE: examples/mongoDbRealmExample/src/main/AndroidManifest.xml ================================================ ================================================ FILE: examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/CounterActivity.kt ================================================ /* * 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 com.mongodb.realm.example import android.content.Intent import android.graphics.PorterDuff import android.os.Bundle import android.view.Menu import android.view.MenuItem import android.view.View import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.databinding.DataBindingUtil import io.realm.* import com.mongodb.realm.example.model.CRDTCounter import com.mongodb.realm.example.databinding.ActivityCounterBinding import io.realm.kotlin.syncSession import io.realm.kotlin.where import io.realm.log.RealmLog import io.realm.mongodb.User import io.realm.mongodb.sync.ProgressListener import io.realm.mongodb.sync.ProgressMode import io.realm.mongodb.sync.SyncConfiguration import io.realm.mongodb.sync.SyncSession 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 var realm: Realm? = null private lateinit var session: SyncSession private var user: User? = null private lateinit var counterView: TextView private lateinit var progressBar: MaterialProgressBar private lateinit var counter: CRDTCounter // Keep strong reference to counter to keep change listeners alive. private val loggedInUser: User? get() { var user: User? = null try { user = APP.currentUser() } 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 // Use user id as partition value, so each user gets an unique view. // FIXME Right now we are using waitForInitialRemoteData and a more advanced // initialData block due to Sync only supporting ObjectId keys. This should // be changed once natural keys are supported. val config = SyncConfiguration.Builder(user, user.id) .initialData { if (it.isEmpty) { it.insert(CRDTCounter()) } } .waitForInitialRemoteData() .build() // This will automatically sync all changes in the background for as long as the Realm is open Realm.getInstanceAsync(config, object: Realm.Callback() { override fun onSuccess(realm: Realm) { this@CounterActivity.realm = realm counter = realm.where().findFirstAsync() counter.addChangeListener { obj, _ -> if (obj.isValid) { 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) } } }) counterView.text = "-" } } 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 -> { val user = user user?.logOutAsync { if (it.isSuccess) { realm?.close() this.user = loggedInUser } else { RealmLog.error(it.error.toString()) } } true } else -> super.onOptionsItemSelected(item) } } private fun updateProgressBar(downloading: Boolean, uploading: Boolean) { 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()) } } } ================================================ FILE: examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/LoginActivity.kt ================================================ /* * 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 com.mongodb.realm.example import android.app.ProgressDialog import android.os.Bundle import android.widget.Button import android.widget.EditText import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.databinding.DataBindingUtil import com.mongodb.realm.example.databinding.ActivityLoginBinding import io.realm.* import io.realm.log.RealmLog import io.realm.mongodb.Credentials 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() if (createUser) { APP.emailPassword.registerUserAsync(username, password) { progressDialog.dismiss() binding.buttonCreate.isEnabled = true binding.buttonLogin.isEnabled = true if (!it.isSuccess) { onLoginFailed("Could not register user. Check Logcat") } } } else { val creds = Credentials.emailPassword(username, password) APP.loginAsync(creds) { progressDialog.dismiss() if (!it.isSuccess) { RealmLog.error(it.error.toString()) onLoginFailed(it.error.message ?: "An error occurred. Check Logcat") } else { onLoginSuccess() } } } } 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 } } ================================================ FILE: examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/MyApplication.kt ================================================ /* * 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 com.mongodb.realm.example import android.app.Application import io.realm.Realm import io.realm.log.LogLevel import io.realm.log.RealmLog import io.realm.mongodb.App import io.realm.mongodb.AppConfiguration lateinit var APP: App class MyApplication : Application() { override fun onCreate() { super.onCreate() Realm.init(this) APP = App(AppConfiguration.Builder(BuildConfig.MONGODB_REALM_APP_ID) .baseUrl(BuildConfig.MONGODB_REALM_URL) .appName(BuildConfig.VERSION_NAME) .appVersion(BuildConfig.VERSION_CODE.toString()) .build()) // Enable more logging in debug mode if (BuildConfig.DEBUG) { RealmLog.setLevel(LogLevel.DEBUG) } } } ================================================ FILE: examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/model/CRDTCounter.kt ================================================ /* * 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 com.mongodb.realm.example.model import io.realm.MutableRealmInteger import io.realm.RealmObject import io.realm.annotations.PrimaryKey import io.realm.annotations.RealmField import io.realm.annotations.Required import org.bson.types.ObjectId open class CRDTCounter : RealmObject() { @PrimaryKey @RealmField("_id") var id: ObjectId = ObjectId.get() @Required private val counter: MutableRealmInteger = MutableRealmInteger.valueOf(0L) val count: Long get() = this.counter.get()!!.toLong() fun incrementCounter(delta: Long) { counter.increment(delta) } } ================================================ FILE: examples/mongoDbRealmExample/src/main/res/drawable/button_counter.xml ================================================ ================================================ FILE: examples/mongoDbRealmExample/src/main/res/layout/activity_counter.xml ================================================ ================================================ FILE: examples/mongoDbRealmExample/src/main/res/layout/activity_login.xml ================================================ ================================================ FILE: examples/mongoDbRealmExample/src/main/res/menu/menu_counter.xml ================================================ ================================================ FILE: examples/mongoDbRealmExample/src/main/res/values/dimens.xml ================================================ 16dp 16dp ================================================ FILE: examples/mongoDbRealmExample/src/main/res/values/realm_colors.xml ================================================ #1C233F #9A9BA5 #b1b3bf #EBEBF2 #39477F #59569E #9A59A5 #D34CA3 #F25192 #F77C88 #FC9F95 #FCC397 #d64881 #dadada #EF5350 #9CCC65 #FFA726 ================================================ FILE: examples/mongoDbRealmExample/src/main/res/values/strings.xml ================================================ MongoDB Realm Example Realm Logo Username Password Create account and login Login Logout Account does not exist. User name and password do not match. ================================================ FILE: examples/mongoDbRealmExample/src/main/res/values/styles.xml ================================================ ================================================ FILE: examples/mongoDbRealmExample/src/main/res/values-w820dp/dimens.xml ================================================ 64dp ================================================ FILE: examples/multiprocessExample/.gitignore ================================================ /build ================================================ FILE: examples/multiprocessExample/build.gradle ================================================ apply plugin: 'com.android.application' apply plugin: 'realm-android' android { compileSdkVersion rootProject.sdkVersion buildToolsVersion rootProject.buildTools defaultConfig { applicationId "io.realm.examples.realmmultiprocessexample" targetSdkVersion rootProject.sdkVersion minSdkVersion rootProject.minSdkVersion versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled true signingConfig signingConfigs.debug } debug { minifyEnabled true } } } dependencies { implementation 'com.android.support:appcompat-v7:27.1.1' } ================================================ FILE: examples/multiprocessExample/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /home/cc/.android-sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: examples/multiprocessExample/src/main/AndroidManifest.xml ================================================ ================================================ FILE: examples/multiprocessExample/src/main/java/io/realm/examples/realmmultiprocessexample/AnotherProcessService.java ================================================ /* * 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.realmmultiprocessexample; import android.app.Service; import android.content.Intent; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import io.realm.Realm; public class AnotherProcessService extends Service { Handler handler; @Override public void onCreate() { super.onCreate(); handler = new Handler(Looper.myLooper()); final Runnable runnable = new Runnable() { @Override public void run() { Realm realm = Realm.getDefaultInstance(); realm.beginTransaction(); realm.copyToRealmOrUpdate(Utils.createStandaloneProcessInfo(AnotherProcessService.this)); realm.commitTransaction(); realm.close(); handler.postDelayed(this, 1000); } }; handler.postDelayed(runnable, 1000); } @Override public void onDestroy() { super.onDestroy(); } @Override public IBinder onBind(Intent intent) { throw new UnsupportedOperationException("Not yet implemented"); } } ================================================ FILE: examples/multiprocessExample/src/main/java/io/realm/examples/realmmultiprocessexample/MainActivity.java ================================================ /* * 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.realmmultiprocessexample; import android.content.Intent; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.TextView; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Locale; import io.realm.Realm; import io.realm.RealmChangeListener; import io.realm.RealmResults; import io.realm.examples.realmmultiprocessexample.models.ProcessInfo; public class MainActivity extends AppCompatActivity { private TextView textView; private Realm realm; private RealmResults processInfoResults; private DateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss", Locale.ENGLISH); private RealmChangeListener> listener = new RealmChangeListener>() { @Override public void onChange(RealmResults results) { StringBuilder stringBuilder = new StringBuilder(); for (ProcessInfo processInfo : results) { stringBuilder.append(processInfo.getName()); stringBuilder.append("\npid: "); stringBuilder.append(processInfo.getPid()); stringBuilder.append("\nlast response time: "); stringBuilder.append(dateFormat.format(processInfo.getLastResponseDate())); stringBuilder.append("\n------\n"); } textView.setText(stringBuilder.toString()); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView) findViewById(R.id.textView); if (realm == null) { realm = Realm.getDefaultInstance(); processInfoResults = realm.where(ProcessInfo.class).findAllAsync(); processInfoResults.addChangeListener(listener); } realm.beginTransaction(); realm.copyToRealmOrUpdate(Utils.createStandaloneProcessInfo(this)); realm.commitTransaction(); } @Override protected void onDestroy() { super.onDestroy(); if (realm != null) { realm.close(); realm = null; processInfoResults = null; } } public void onStartButton(View button) { Intent intent = new Intent(MainActivity.this, AnotherProcessService.class); startService(intent); button.setEnabled(false); } } ================================================ FILE: examples/multiprocessExample/src/main/java/io/realm/examples/realmmultiprocessexample/MyApplication.java ================================================ /* * 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.realmmultiprocessexample; import android.app.Application; import io.realm.Realm; import io.realm.RealmConfiguration; public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); Realm.init(this); RealmConfiguration configuration = new RealmConfiguration.Builder() .deleteRealmIfMigrationNeeded() .allowWritesOnUiThread(true) .allowQueriesOnUiThread(true) .build(); Realm.setDefaultConfiguration(configuration); } } ================================================ FILE: examples/multiprocessExample/src/main/java/io/realm/examples/realmmultiprocessexample/Utils.java ================================================ /* * 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.realmmultiprocessexample; import android.app.ActivityManager; import android.app.ActivityManager.RunningAppProcessInfo; import android.content.Context; import android.os.Process; import java.util.Date; import java.util.List; import io.realm.examples.realmmultiprocessexample.models.ProcessInfo; public class Utils { public static String getMyProcessName(Context context) { String processName = ""; ActivityManager am = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE); List infoList = am.getRunningAppProcesses(); if (infoList == null) { throw new RuntimeException("getRunningAppProcesses() returns 'null'."); } for (RunningAppProcessInfo info : infoList) { try { if (info.pid == Process.myPid()) { processName = info.processName; break; } } catch (Exception ignored) { } } return processName; } public static ProcessInfo createStandaloneProcessInfo(Context context) { ProcessInfo processInfo = new ProcessInfo(); processInfo.setName(getMyProcessName(context)); processInfo.setPid(android.os.Process.myPid()); processInfo.setLastResponseDate(new Date()); return processInfo; } } ================================================ FILE: examples/multiprocessExample/src/main/java/io/realm/examples/realmmultiprocessexample/models/ProcessInfo.java ================================================ /* * 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.realmmultiprocessexample.models; import java.util.Date; import io.realm.RealmObject; import io.realm.annotations.PrimaryKey; import io.realm.annotations.Required; public class ProcessInfo extends RealmObject { @PrimaryKey private String name; private int pid; @Required private Date lastResponseDate; public int getPid() { return pid; } public void setPid(int pid) { this.pid = pid; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getLastResponseDate() { return lastResponseDate; } public void setLastResponseDate(Date lastResponseDate) { this.lastResponseDate = lastResponseDate; } } ================================================ FILE: examples/multiprocessExample/src/main/res/layout/activity_main.xml ================================================