移动端Realm与ROS数据同步及离线使用架构咨询
Hey there, let's break down your questions one by one—this is all super common when building offline-first Realm apps, so I’ve got you covered!
For a mobile app that supports online registration then offline use (with shared data between service and UI layers), MVVM (Model-View-ViewModel) is a perfect fit. It keeps your layers decoupled while ensuring data consistency:
- UI Layer: Handles user interaction and display (e.g., Activities/Fragments, Jetpack Compose components). It never directly touches Realm—instead, it observes state from ViewModels, which automatically update when Realm data changes.
- ViewModel Layer: Acts as the middleman between UI and data. It fetches data from the service layer, transforms it into UI-friendly states (loading, success, error), and exposes observables (like LiveData or Kotlin Flow) for the UI to subscribe to.
- Service Layer: Encapsulates all Realm operations and business logic. You can split this into focused services:
AuthService: Handles user registration, login, and authentication with ROS.DataSyncService: Manages sync triggers (though Realm Sync is mostly automatic) and conflict resolution.LocalDataService: Wraps offline Realm CRUD operations for when the device is disconnected.
- Data Layer: Your Realm database. Since Realm is a real-time database, the service layer and UI layer share the same source of truth—any changes to Realm are immediately reflected in the UI via observables, no manual data sync between layers needed.
Pro tip: Use dependency injection (like Dagger Hilt or Koin) to manage Realm instances across the app. This ensures you’re reusing instances correctly and avoiding connection issues.
You don’t need to build sync logic from scratch! Realm Sync (now part of MongoDB Realm) handles all the heavy lifting automatically:
- Automatic Incremental Sync: Once the user is authenticated, the SDK syncs only changed data between the local device and ROS—no full downloads/uploads every time.
- Offline First: Users can read/write to the local Realm even when offline. When connectivity returns, the SDK automatically syncs pending changes to ROS, and pulls down any remote updates.
- Conflict Resolution: Realm has built-in strategies (like "last write wins") to handle conflicting changes, and you can customize this if your app needs specific rules.
Key steps to set this up:
- On ROS, configure sync rules that grant authenticated users access to their own Realm partition (e.g., using the user ID as the partition key).
- After registration, always use the authenticated user instance to open the sync Realm. Never try to open the local
.realmfile directly without logging in first.
Example code (Kotlin):
// Login after registration val user = Realm.login(Credentials.emailPassword(email, password)) // Create sync configuration using the authenticated user val syncConfig = SyncConfiguration.Builder(user, user.id) // User ID as partition key .name("user-realm.realm") .build() // Open the synced Realm val realm = Realm.getInstance(syncConfig)
This error happens when your local Realm’s transaction history doesn’t match the one on ROS. The most common cause is trying to open a synced Realm without first authenticating the user (i.e., skipping User.LoginAsync).
Here’s how to fix it:
- Always authenticate first: You must call
User.LoginAsyncafter registration, then use the returned user object to create your SyncConfiguration. Directly opening the local.realmfile (without the authenticated user) breaks the sync history link. - Clean up unauthenticated local Realms: If you used a local Realm for pre-registration data, migrate that data to the synced Realm after login, then delete the old local Realm to avoid conflicts.
Example migration (Kotlin):
// Old local Realm config (pre-registration) val localConfig = RealmConfiguration.Builder() .name("local-temp.realm") .build() // New synced Realm config (post-login) val syncConfig = SyncConfiguration.Builder(user, user.id) .name("user-realm.realm") .build() // Migrate data from local to synced Realm Realm.getInstance(localConfig).use { localRealm -> Realm.getInstance(syncConfig).use { syncRealm -> syncRealm.executeTransaction { realm -> val tempItems = localRealm.copyFromRealm(localRealm.where<TempItem>().findAll()) realm.insertOrUpdate(tempItems) } } } // Delete the old local Realm to prevent conflicts Realm.deleteRealm(localConfig)
- Avoid mixing configs: Make sure every part of your app uses the same SyncConfiguration (based on the authenticated user) when accessing the synced Realm. Never mix local-only configurations with sync configurations for the same data.
内容的提问来源于stack exchange,提问作者Mauricio

