diff --git a/AppServicesUsageSamples/README.md b/AppServicesUsageSamples/README.md index 16c1ec3..575c210 100644 --- a/AppServicesUsageSamples/README.md +++ b/AppServicesUsageSamples/README.md @@ -18,6 +18,10 @@ This sample demonstrates user presence detection with App Services. A showcase for the different client reset resolution strategies with an sync error handling example. +### [Modelling unstructured data](apps/dynamic-data/README.md) + +A showcase for storing and synchronizing unstructured data through collections in mixed properties. + ## Demo app structure The project has been structured in two main folders: @@ -52,4 +56,4 @@ The `realm-cli import` command will prompt for the app configuration details, th After deploying the Atlas apps, you will need to update [Constants.kt](demo/src/main/java/io/realm/appservicesusagesamples/Constants.kt) with their app ids. -Once you have completed these steps, you would be able to run the samples using the Kotlin demo app. \ No newline at end of file +Once you have completed these steps, you would be able to run the samples using the Kotlin demo app. diff --git a/AppServicesUsageSamples/Screenshots/dynamic-data-browser.png b/AppServicesUsageSamples/Screenshots/dynamic-data-browser.png new file mode 100644 index 0000000..ea9c65e Binary files /dev/null and b/AppServicesUsageSamples/Screenshots/dynamic-data-browser.png differ diff --git a/AppServicesUsageSamples/apps/dynamic-data/.mdb/meta.json b/AppServicesUsageSamples/apps/dynamic-data/.mdb/meta.json new file mode 100644 index 0000000..39af777 --- /dev/null +++ b/AppServicesUsageSamples/apps/dynamic-data/.mdb/meta.json @@ -0,0 +1,7 @@ +{ + "config_version": 20230101, + "app_id": "6655920fc1ed444018db297b", + "group_id": "64c37d22e47344686a325451", + "client_app_id": "dynamic-data-fmzmcph", + "last_pulled": 1717066261 +} diff --git a/AppServicesUsageSamples/apps/dynamic-data/README.md b/AppServicesUsageSamples/apps/dynamic-data/README.md new file mode 100644 index 0000000..83601ca --- /dev/null +++ b/AppServicesUsageSamples/apps/dynamic-data/README.md @@ -0,0 +1,15 @@ +# Dynamic data + +With the newly added ability to store collections in mixed properties, you can now store and synchronized +data without pre-known schema. + +This app just holds a single data class that mixes strictly typed properties and a single mixed property +that will be used as an entry point for the potentially deeply nested, dynamic data. + +The Kotlin UI shows each entity and associated 'configuration' mixed property in a tree view. There is +currently no update options in the UI, so data has to be added through the Atlas UI in the +"Data Service" section: + +![alt text](../../Screenshots/dynamic-data-browser.png "Atlas collection browser") + +Updates will be reflected in the Kotlin UI. diff --git a/AppServicesUsageSamples/apps/dynamic-data/auth/custom_user_data.json b/AppServicesUsageSamples/apps/dynamic-data/auth/custom_user_data.json new file mode 100644 index 0000000..a82d0fb --- /dev/null +++ b/AppServicesUsageSamples/apps/dynamic-data/auth/custom_user_data.json @@ -0,0 +1,3 @@ +{ + "enabled": false +} diff --git a/AppServicesUsageSamples/apps/dynamic-data/auth/providers.json b/AppServicesUsageSamples/apps/dynamic-data/auth/providers.json new file mode 100644 index 0000000..dff103b --- /dev/null +++ b/AppServicesUsageSamples/apps/dynamic-data/auth/providers.json @@ -0,0 +1,12 @@ +{ + "anon-user": { + "name": "anon-user", + "type": "anon-user", + "disabled": false + }, + "api-key": { + "name": "api-key", + "type": "api-key", + "disabled": true + } +} diff --git a/AppServicesUsageSamples/apps/dynamic-data/data_sources/mongodb-atlas/config.json b/AppServicesUsageSamples/apps/dynamic-data/data_sources/mongodb-atlas/config.json new file mode 100644 index 0000000..9913676 --- /dev/null +++ b/AppServicesUsageSamples/apps/dynamic-data/data_sources/mongodb-atlas/config.json @@ -0,0 +1,10 @@ +{ + "name": "mongodb-atlas", + "type": "mongodb-atlas", + "config": { + "clusterName": "Cluster0", + "readPreference": "primary", + "wireProtocolEnabled": false + }, + "version": 1 +} diff --git a/AppServicesUsageSamples/apps/dynamic-data/data_sources/mongodb-atlas/default_rule.json b/AppServicesUsageSamples/apps/dynamic-data/data_sources/mongodb-atlas/default_rule.json new file mode 100644 index 0000000..1d5545f --- /dev/null +++ b/AppServicesUsageSamples/apps/dynamic-data/data_sources/mongodb-atlas/default_rule.json @@ -0,0 +1,17 @@ +{ + "roles": [ + { + "name": "readAndWriteAll", + "apply_when": {}, + "document_filters": { + "read": true, + "write": true + }, + "insert": true, + "delete": true, + "search": true, + "read": true, + "write": true + } + ] +} diff --git a/AppServicesUsageSamples/apps/dynamic-data/data_sources/mongodb-atlas/dynamic_data/DynamicDataEntity/schema.json b/AppServicesUsageSamples/apps/dynamic-data/data_sources/mongodb-atlas/dynamic_data/DynamicDataEntity/schema.json new file mode 100644 index 0000000..25b6afc --- /dev/null +++ b/AppServicesUsageSamples/apps/dynamic-data/data_sources/mongodb-atlas/dynamic_data/DynamicDataEntity/schema.json @@ -0,0 +1,19 @@ +{ + "title": "DynamicDataEntity", + "type": "object", + "required": [ + "_id", + "name" + ], + "properties": { + "_id": { + "bsonType": "objectId" + }, + "configuration": { + "bsonType": "mixed" + }, + "name": { + "bsonType": "string" + } + } +} diff --git a/AppServicesUsageSamples/apps/dynamic-data/environments/development.json b/AppServicesUsageSamples/apps/dynamic-data/environments/development.json new file mode 100644 index 0000000..ad7e98e --- /dev/null +++ b/AppServicesUsageSamples/apps/dynamic-data/environments/development.json @@ -0,0 +1,3 @@ +{ + "values": {} +} diff --git a/AppServicesUsageSamples/apps/dynamic-data/environments/no-environment.json b/AppServicesUsageSamples/apps/dynamic-data/environments/no-environment.json new file mode 100644 index 0000000..ad7e98e --- /dev/null +++ b/AppServicesUsageSamples/apps/dynamic-data/environments/no-environment.json @@ -0,0 +1,3 @@ +{ + "values": {} +} diff --git a/AppServicesUsageSamples/apps/dynamic-data/environments/production.json b/AppServicesUsageSamples/apps/dynamic-data/environments/production.json new file mode 100644 index 0000000..ad7e98e --- /dev/null +++ b/AppServicesUsageSamples/apps/dynamic-data/environments/production.json @@ -0,0 +1,3 @@ +{ + "values": {} +} diff --git a/AppServicesUsageSamples/apps/dynamic-data/environments/qa.json b/AppServicesUsageSamples/apps/dynamic-data/environments/qa.json new file mode 100644 index 0000000..ad7e98e --- /dev/null +++ b/AppServicesUsageSamples/apps/dynamic-data/environments/qa.json @@ -0,0 +1,3 @@ +{ + "values": {} +} diff --git a/AppServicesUsageSamples/apps/dynamic-data/environments/testing.json b/AppServicesUsageSamples/apps/dynamic-data/environments/testing.json new file mode 100644 index 0000000..ad7e98e --- /dev/null +++ b/AppServicesUsageSamples/apps/dynamic-data/environments/testing.json @@ -0,0 +1,3 @@ +{ + "values": {} +} diff --git a/AppServicesUsageSamples/apps/dynamic-data/functions/config.json b/AppServicesUsageSamples/apps/dynamic-data/functions/config.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/AppServicesUsageSamples/apps/dynamic-data/functions/config.json @@ -0,0 +1 @@ +[] diff --git a/AppServicesUsageSamples/apps/dynamic-data/graphql/config.json b/AppServicesUsageSamples/apps/dynamic-data/graphql/config.json new file mode 100644 index 0000000..406b1ab --- /dev/null +++ b/AppServicesUsageSamples/apps/dynamic-data/graphql/config.json @@ -0,0 +1,4 @@ +{ + "use_natural_pluralization": true, + "disable_schema_introspection": false +} diff --git a/AppServicesUsageSamples/apps/dynamic-data/graphql/validation_settings.json b/AppServicesUsageSamples/apps/dynamic-data/graphql/validation_settings.json new file mode 100644 index 0000000..a112d6f --- /dev/null +++ b/AppServicesUsageSamples/apps/dynamic-data/graphql/validation_settings.json @@ -0,0 +1,6 @@ +{ + "read_validation_action": "ERROR", + "read_validation_level": "STRICT", + "write_validation_action": "ERROR", + "write_validation_level": "STRICT" +} diff --git a/AppServicesUsageSamples/apps/dynamic-data/hosting/config.json b/AppServicesUsageSamples/apps/dynamic-data/hosting/config.json new file mode 100644 index 0000000..a82d0fb --- /dev/null +++ b/AppServicesUsageSamples/apps/dynamic-data/hosting/config.json @@ -0,0 +1,3 @@ +{ + "enabled": false +} diff --git a/AppServicesUsageSamples/apps/dynamic-data/https_endpoints/config.json b/AppServicesUsageSamples/apps/dynamic-data/https_endpoints/config.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/AppServicesUsageSamples/apps/dynamic-data/https_endpoints/config.json @@ -0,0 +1 @@ +[] diff --git a/AppServicesUsageSamples/apps/dynamic-data/root_config.json b/AppServicesUsageSamples/apps/dynamic-data/root_config.json new file mode 100644 index 0000000..81530e3 --- /dev/null +++ b/AppServicesUsageSamples/apps/dynamic-data/root_config.json @@ -0,0 +1,5 @@ +{ + "name": "dynamic-data", + "provider_region": "aws-eu-central-1", + "deployment_model": "LOCAL" +} diff --git a/AppServicesUsageSamples/apps/dynamic-data/sync/config.json b/AppServicesUsageSamples/apps/dynamic-data/sync/config.json new file mode 100644 index 0000000..9b7dcf6 --- /dev/null +++ b/AppServicesUsageSamples/apps/dynamic-data/sync/config.json @@ -0,0 +1,9 @@ +{ + "type": "flexible", + "state": "enabled", + "development_mode_enabled": true, + "service_name": "mongodb-atlas", + "database_name": "dynamic_data", + "client_max_offline_days": 30, + "is_recovery_mode_disabled": false +} diff --git a/AppServicesUsageSamples/demo/build.gradle.kts b/AppServicesUsageSamples/demo/build.gradle.kts index 92273e7..e92cfd7 100644 --- a/AppServicesUsageSamples/demo/build.gradle.kts +++ b/AppServicesUsageSamples/demo/build.gradle.kts @@ -5,13 +5,13 @@ plugins { // alias(libs.plugins.realm.kotlin) id(libs.plugins.realm.kotlin.get().pluginId) alias(libs.plugins.kotlin.serialization) -// alias(libs.plugins.jetbrainsCompose) + alias(libs.plugins.jetbrainsCompose) alias(libs.plugins.compose.compiler) } android { namespace = "io.realm.appservicesusagesamples" - compileSdk = 33 + compileSdk = 34 defaultConfig { applicationId = "io.realm.appservicesusagesamples" @@ -76,6 +76,10 @@ dependencies { implementation(libs.koin.android) implementation(libs.koin.androidx.compose) + implementation(libs.bonsai) + // Compile only dependency of libs.bonsai, so need to include it explicitly + implementation(compose.materialIconsExtended) + implementation(libs.realm.library.sync) debugImplementation(composeBom) diff --git a/AppServicesUsageSamples/demo/src/main/AndroidManifest.xml b/AppServicesUsageSamples/demo/src/main/AndroidManifest.xml index b708f50..ea003fb 100644 --- a/AppServicesUsageSamples/demo/src/main/AndroidManifest.xml +++ b/AppServicesUsageSamples/demo/src/main/AndroidManifest.xml @@ -27,6 +27,7 @@ + - \ No newline at end of file + diff --git a/AppServicesUsageSamples/demo/src/main/java/io/realm/appservicesusagesamples/AppServicesUsageSamplesApp.kt b/AppServicesUsageSamples/demo/src/main/java/io/realm/appservicesusagesamples/AppServicesUsageSamplesApp.kt index 2cff5a5..4846b51 100644 --- a/AppServicesUsageSamples/demo/src/main/java/io/realm/appservicesusagesamples/AppServicesUsageSamplesApp.kt +++ b/AppServicesUsageSamples/demo/src/main/java/io/realm/appservicesusagesamples/AppServicesUsageSamplesApp.kt @@ -17,6 +17,7 @@ package io.realm.appservicesusagesamples import android.app.Application +import io.realm.appservicesusagesamples.dynamicdata.dynamicDataViewModel import io.realm.appservicesusagesamples.errorhandling.errorHandlingModule import io.realm.appservicesusagesamples.propertyencryption.propertyEncryptionModule import io.realm.appservicesusagesamples.presence.presenceDetectionModule @@ -42,6 +43,7 @@ class AppServicesUsageSamplesApp: Application() { propertyEncryptionModule, presenceDetectionModule, errorHandlingModule, + dynamicDataViewModel, ) } } diff --git a/AppServicesUsageSamples/demo/src/main/java/io/realm/appservicesusagesamples/Constants.kt b/AppServicesUsageSamples/demo/src/main/java/io/realm/appservicesusagesamples/Constants.kt index 9a969fd..f805e63 100644 --- a/AppServicesUsageSamples/demo/src/main/java/io/realm/appservicesusagesamples/Constants.kt +++ b/AppServicesUsageSamples/demo/src/main/java/io/realm/appservicesusagesamples/Constants.kt @@ -21,3 +21,4 @@ package io.realm.appservicesusagesamples const val PROPERTY_ENCRYPTION_APP_ID = "" const val USER_PRESENCE_APP_ID = "" const val ERROR_HANDLING_APP_ID = "" +const val DYNAMIC_DATA_APP_ID = "" diff --git a/AppServicesUsageSamples/demo/src/main/java/io/realm/appservicesusagesamples/DependencyInjection.kt b/AppServicesUsageSamples/demo/src/main/java/io/realm/appservicesusagesamples/DependencyInjection.kt index 42b2874..2a171de 100644 --- a/AppServicesUsageSamples/demo/src/main/java/io/realm/appservicesusagesamples/DependencyInjection.kt +++ b/AppServicesUsageSamples/demo/src/main/java/io/realm/appservicesusagesamples/DependencyInjection.kt @@ -16,6 +16,7 @@ */ package io.realm.appservicesusagesamples +import io.realm.appservicesusagesamples.dynamicdata.DynamicDataActivity import io.realm.appservicesusagesamples.propertyencryption.PropertyEncryptionActivity import io.realm.appservicesusagesamples.ui.SampleSelectorScreenViewModel import io.realm.appservicesusagesamples.presence.PresenceDetectionActivity @@ -23,6 +24,7 @@ import io.realm.appservicesusagesamples.ui.EntryView import io.realm.appservicesusagesamples.ui.buttonSelector import io.realm.appservicesusagesamples.ui.errorHandlingSelector import io.realm.kotlin.mongodb.App +import io.realm.kotlin.mongodb.AppConfiguration import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.core.qualifier.named import org.koin.dsl.module @@ -52,6 +54,13 @@ enum class Demos( ERROR_HANDLING( appId = ERROR_HANDLING_APP_ID, addView = errorHandlingSelector, + ), + DYNAMIC_DATA( + appId = DYNAMIC_DATA_APP_ID, + addView = buttonSelector( + "Dynamic data", + DynamicDataActivity::class.java, + ) ); val qualifier = named(appId) diff --git a/AppServicesUsageSamples/demo/src/main/java/io/realm/appservicesusagesamples/dynamicdata/DependencyInjection.kt b/AppServicesUsageSamples/demo/src/main/java/io/realm/appservicesusagesamples/dynamicdata/DependencyInjection.kt new file mode 100644 index 0000000..22d98b2 --- /dev/null +++ b/AppServicesUsageSamples/demo/src/main/java/io/realm/appservicesusagesamples/dynamicdata/DependencyInjection.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2024 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.appservicesusagesamples.dynamicdata + +import io.realm.appservicesusagesamples.Demos +import io.realm.appservicesusagesamples.dynamicdata.ui.DynamicDataViewModel +import io.realm.appservicesusagesamples.presence.ui.UserStatusListViewModel +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module + +val dynamicDataViewModel = module { + viewModel { + DynamicDataViewModel( + app = get(qualifier = Demos.DYNAMIC_DATA.qualifier), + ) + } +} diff --git a/AppServicesUsageSamples/demo/src/main/java/io/realm/appservicesusagesamples/dynamicdata/DynamicDataActivity.kt b/AppServicesUsageSamples/demo/src/main/java/io/realm/appservicesusagesamples/dynamicdata/DynamicDataActivity.kt new file mode 100644 index 0000000..15ccbd6 --- /dev/null +++ b/AppServicesUsageSamples/demo/src/main/java/io/realm/appservicesusagesamples/dynamicdata/DynamicDataActivity.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2024 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.appservicesusagesamples.dynamicdata + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import io.realm.appservicesusagesamples.dynamicdata.ui.DynamicDataScreen +import io.realm.appservicesusagesamples.dynamicdata.ui.DynamicDataViewModel +import io.realm.appservicesusagesamples.ui.theme.AppServicesUsageSamplesTheme +import org.koin.android.scope.AndroidScopeComponent +import org.koin.androidx.scope.activityRetainedScope +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.scope.Scope + +/** + * Activity that hosts the views that would demo property level encryption. + */ +class DynamicDataActivity : ComponentActivity(), AndroidScopeComponent { + override val scope: Scope by activityRetainedScope() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val viewModel: DynamicDataViewModel by viewModel() + + + setContent { + AppServicesUsageSamplesTheme { + DynamicDataScreen( + viewModel, + ) + } + } + } +} diff --git a/AppServicesUsageSamples/demo/src/main/java/io/realm/appservicesusagesamples/dynamicdata/models/DynamicDataEntity.kt b/AppServicesUsageSamples/demo/src/main/java/io/realm/appservicesusagesamples/dynamicdata/models/DynamicDataEntity.kt new file mode 100644 index 0000000..7f742b2 --- /dev/null +++ b/AppServicesUsageSamples/demo/src/main/java/io/realm/appservicesusagesamples/dynamicdata/models/DynamicDataEntity.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2024 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.appservicesusagesamples.dynamicdata.models + +import io.realm.kotlin.types.RealmAny +import io.realm.kotlin.types.RealmObject +import io.realm.kotlin.types.annotations.PersistedName +import io.realm.kotlin.types.annotations.PrimaryKey +import org.mongodb.kbson.BsonObjectId +import org.mongodb.kbson.ObjectId + +class DynamicDataEntity: RealmObject { + @PersistedName("_id") + @PrimaryKey + var id: ObjectId = BsonObjectId() + + var name: String = "" + + var configuration: RealmAny? = null +} diff --git a/AppServicesUsageSamples/demo/src/main/java/io/realm/appservicesusagesamples/dynamicdata/ui/DynamicDataScreen.kt b/AppServicesUsageSamples/demo/src/main/java/io/realm/appservicesusagesamples/dynamicdata/ui/DynamicDataScreen.kt new file mode 100644 index 0000000..7a86eac --- /dev/null +++ b/AppServicesUsageSamples/demo/src/main/java/io/realm/appservicesusagesamples/dynamicdata/ui/DynamicDataScreen.kt @@ -0,0 +1,95 @@ +package io.realm.appservicesusagesamples.dynamicdata.ui + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Divider +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import cafe.adriel.bonsai.core.Bonsai +import cafe.adriel.bonsai.core.node.Branch +import cafe.adriel.bonsai.core.node.Leaf +import cafe.adriel.bonsai.core.tree.Tree +import cafe.adriel.bonsai.core.tree.TreeScope +import io.realm.appservicesusagesamples.dynamicdata.models.DynamicDataEntity +import io.realm.kotlin.types.RealmAny +import io.realm.kotlin.types.RealmDictionary +import io.realm.kotlin.types.RealmList + +@Composable +fun DynamicDataScreen( + viewModel: DynamicDataViewModel, +) { + val entities: List by viewModel.instances.observeAsState(emptyList()) + + Column { + entities.forEach { item -> + Column { + ElevatedCard( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp, horizontal = 16.dp), + ) { + Text( + text = "Name: ${item.name}", + modifier = + Modifier + .padding(start = 4.dp) + .align(Alignment.CenterHorizontally), + ) + Text( + text = "Id: ${item.id.toHexString()}", + modifier = Modifier + .padding(start = 4.dp) + .align(Alignment.CenterHorizontally), + ) + Divider() + item.configuration?.let { + Bonsai(tree = Tree { RealmAnyNode("Configuration", it) }) + } + } + } + } + } +} + +@Composable +fun TreeScope.RealmAnyNode( + key: String?, + element: RealmAny?, +) { + when (element?.type) { + RealmAny.Type.LIST -> RealmAnyListNode("$key: ${element.type}", element.asList()) + RealmAny.Type.DICTIONARY -> RealmAnyDictionaryNode( + "$key: ${element.type}", + element.asDictionary() + ) + + else -> RealmAnyPrimitiveNode("$key", element) + } +} + +@Composable +fun TreeScope.RealmAnyListNode(property: String?, element: RealmList) { + Branch(content = element, name = "$property") { + element.forEachIndexed { index, element -> RealmAnyNode(key = "$index", element = element) } + } +} + +@Composable +fun TreeScope.RealmAnyDictionaryNode(property: String?, element: RealmDictionary) { + Branch(content = element, name = "$property") { + element.forEach { (key, value) -> RealmAnyNode(key = "$key", element = value) } + } +} + +@Composable +fun TreeScope.RealmAnyPrimitiveNode(key: String?, element: RealmAny?) { + Leaf(content = element, name = "$key: $element") +} diff --git a/AppServicesUsageSamples/demo/src/main/java/io/realm/appservicesusagesamples/dynamicdata/ui/DynamicDataViewModel.kt b/AppServicesUsageSamples/demo/src/main/java/io/realm/appservicesusagesamples/dynamicdata/ui/DynamicDataViewModel.kt new file mode 100644 index 0000000..dfbba76 --- /dev/null +++ b/AppServicesUsageSamples/demo/src/main/java/io/realm/appservicesusagesamples/dynamicdata/ui/DynamicDataViewModel.kt @@ -0,0 +1,91 @@ +package io.realm.appservicesusagesamples.dynamicdata.ui + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import io.realm.appservicesusagesamples.dynamicdata.models.DynamicDataEntity +import io.realm.kotlin.Realm +import io.realm.kotlin.ext.query +import io.realm.kotlin.ext.realmAnyDictionaryOf +import io.realm.kotlin.ext.realmAnyListOf +import io.realm.kotlin.ext.realmDictionaryOf +import io.realm.kotlin.mongodb.App +import io.realm.kotlin.mongodb.Credentials +import io.realm.kotlin.mongodb.User +import io.realm.kotlin.mongodb.sync.SyncConfiguration +import io.realm.kotlin.types.RealmAny +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.launch +import org.mongodb.kbson.ObjectId + +class DynamicDataViewModel( + private val app: App, +): ViewModel() { + lateinit var user: User + private lateinit var realm: Realm + init { + viewModelScope.launch(Dispatchers.IO) { + app.login(Credentials.anonymous()) + .let { user -> + this@DynamicDataViewModel.user = user + + val syncConfig = SyncConfiguration + .Builder(app.currentUser!!, setOf(DynamicDataEntity::class)) + .initialSubscriptions { + add(it.query()) + } + + .waitForInitialRemoteData() + .initialData { + if (this.query().find().isEmpty()) { + val client1 = copyToRealm(DynamicDataEntity().apply { + name = "Client 1" + configuration = RealmAny.Companion.create("Hello, World!") + }) + copyToRealm(DynamicDataEntity().apply { + name = "Client 2" + configuration = realmAnyListOf( + realmDictionaryOf("key1" to realmAnyListOf(1, ObjectId(), "Nested collection")), + "String", + client1 + ) + }) + copyToRealm(DynamicDataEntity().apply { + name = "Client 3" + configuration = realmAnyDictionaryOf( + "key1" to realmAnyListOf( + 1, + ObjectId(), + "Nested collection" + ) + ) + } ) + } + } + .build() + Realm.deleteRealm(syncConfig) + + realm = Realm.open(syncConfig) + + val job = async { + realm.query() + .sort("name") + .asFlow() + .collect { + instances.postValue(it.list) + } + } + + addCloseable { + job.cancel() + realm.close() + } + } + } + } + + val instances: MutableLiveData> by lazy { + MutableLiveData>() + } +} diff --git a/AppServicesUsageSamples/gradle/libs.versions.toml b/AppServicesUsageSamples/gradle/libs.versions.toml index c5df532..b38a033 100644 --- a/AppServicesUsageSamples/gradle/libs.versions.toml +++ b/AppServicesUsageSamples/gradle/libs.versions.toml @@ -14,6 +14,8 @@ androidxEspresso = "3.5.1" androidxTestExt = "1.1.5" kotlinxCoroutines = "1.6.4" androidxTestRunner = "1.5.1" +compose-plugin = "1.6.10" + [libraries] androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidxAppCompat" } androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "androidxComposeBom" } @@ -25,6 +27,7 @@ androidx-lifecycle-runtimeCompose = { group = "androidx.lifecycle", name = "life androidx-lifecycle-viewModelCompose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "androidxLifecycle" } androidx-compose-ui-tooling-core = { group = "androidx.compose.ui", name = "ui-tooling" } androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } +bonsai = { group = "cafe.adriel.bonsai", name = "bonsai-core", version = "1.2.0" } kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } koin-android = { group = "io.insert-koin", name = "koin-android",version.ref = "koin"} koin-androidx-compose = { group = "io.insert-koin", name = "koin-androidx-compose" ,version.ref = "koinCompose"} @@ -41,3 +44,4 @@ kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } realm-kotlin = { id = "io.realm.kotlin", version.ref = "realm" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" }