I have a project with multiple modules using Realm. I was hoping that I could just call deleteAll() on a Realm for one of the modules, but it seems to be trying to delete RealmObjects from the other modules, leading to a runtime exception.
I'd expect
deleteAll()to only delete theRealmObjects within the associatedRealm.
When the code runs, I get the following runtime exception when calling
deleteAll()on the libraryRealminstance:
Caused by: io.realm.exceptions.RealmException: 'AppModel' doesn't exist in current schema.
at io.realm.internal.ColumnIndices.getColumnInfo(ColumnIndices.java:112)
at io.realm.RealmSchema.getColumnInfo(RealmSchema.java:250)
at io.realm.ImmutableRealmSchema.get(ImmutableRealmSchema.java:41)
at io.realm.RealmSchema.getAll(RealmSchema.java:88)
at io.realm.BaseRealm.deleteAll(BaseRealm.java:602)
at io.realm.Realm.deleteAll(Realm.java:135)
at com.johnpetitto.realmbug.MainActivity.lambda$onCreate$1$MainActivity(MainActivity.java:30)
at com.johnpetitto.realmbug.MainActivity$$Lambda$1.execute(Unknown Source:0)
at io.realm.Realm.executeTransaction(Realm.java:1394)
at com.johnpetitto.realmbug.MainActivity.onCreate(MainActivity.java:30)
at android.app.Activity.performCreate(Activity.java:6998)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1230)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2899)
I created a sample project to reproduce this. Basically there is an app module and a library module. The library module has a
@RealmModulein order to differentiate itself from the app code. Both modules contain a singleRealmObject. Here is a repository with the full code.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Realm.init(this);
try (Realm realm = Realm.getDefaultInstance()) {
realm.executeTransaction(r -> r.deleteAll());
}
RealmConfiguration libraryConfig = new RealmConfiguration.Builder()
.deleteRealmIfMigrationNeeded()
.modules(new LibraryModule())
.build();
try (Realm realm = Realm.getInstance(libraryConfig)) {
realm.executeTransaction(r -> r.deleteAll());
}
}
Realm version(s): 4.3.3
Realm sync feature enabled: no
Android Studio version: 3.0
Which Android version and device: Android 8 on a OnePlus 3T
I would think that currently, Realm.deleteAll() tries to delete the items for all currently existing classes that are in the schema of the existing Realm file.
I'm actually more surprised that
try (Realm realm = Realm.getInstance(libraryConfig)) {
I would have expected this to have a schema mismatch and delete the Realm because migration was needed, after which it SHOULD allow deleting items. So there could be a bug there with schema caching
Hi @jpetitto Thanks for the nice example project. I'm able to reproduce the behaviour and something indeed looks fishy. I'm looking into it.
I found the issue(s).
1) You are re-using the underlying file between the two Realm instances. This means that when you open the library instance, the class "AppModel" is already in there. Currently, we don't treat extra tables as an error as long as they don't conflict with the schema you define.
2) The problem arises when you call schema.getAll() (which deleteAll() does). This method dynamically tries to load the schema information, but it will ask the Java module for information about all classes it finds including "AppModule", but that doesn't exist since it isn't part of the module, so the method crashes with the exception you see. This kinda conflicts with 1.
I suspect this error can also show up in other ways, e.g. through the Realm.isEmpty() method which will return the wrong result.
I'll need to dig a little further, but it looks like we need to fix schema.findAll() method to only return the classes in the defined schema for typed Realms and everything when using DynamicRealm.
3 workarounds exist right now:
// Manually delete objects of a given type. You need to call this for all types in your module.
realm.delete(LibraryModule.class);
// Use different names for files used by app / libraries
RealmConfiguration libConfig = new RealmConfiguration.Builder()
.name("library.realm")
.build()
RealmConfiguration appConfig = new RealmConfiguration.Builder()
.name("app.realm")
.build()
// Use DynamicRealm to delete everything
DynamicRealm dynamicRealm = DynamicRealm.getInstance(config);
dynamicRealm.executeTransaction(r -> r.deleteAll());
Oh hey, it'd work with DynamicRealm? that's pretty cool, I didn't think of that.
Thanks for investigating this @cmelchior - I like the solution of using separate files for each module. I'm currently manually deleting each model type, which can be a bit error prone if I forget to update it. Glad I could help surface the issue with schema.findAll() and using multiple modules.
Would this issue be related to what I am seeing here? - https://stackoverflow.com/questions/49164180/need-help-understanding-how-to-remove-realm-models-from-schema?noredirect=1#comment85351896_49164180
Most helpful comment
I found the issue(s).
1) You are re-using the underlying file between the two Realm instances. This means that when you open the library instance, the class "AppModel" is already in there. Currently, we don't treat extra tables as an error as long as they don't conflict with the schema you define.
2) The problem arises when you call
schema.getAll()(whichdeleteAll()does). This method dynamically tries to load the schema information, but it will ask the Java module for information about all classes it finds including "AppModule", but that doesn't exist since it isn't part of the module, so the method crashes with the exception you see. This kinda conflicts with 1.I suspect this error can also show up in other ways, e.g. through the
Realm.isEmpty()method which will return the wrong result.I'll need to dig a little further, but it looks like we need to fix
schema.findAll()method to only return the classes in the defined schema for typed Realms and everything when usingDynamicRealm.3 workarounds exist right now: