Understanding Crashes Due to Realm Schema Changes

Asked 2 years ago, Updated 2 years ago, 122 views

There has been a crash around Realm in the development of Android apps.

Realm files were generated and modified in the following time series.

  • Alpha version
    • Use only the default Realm
  • beta edition
    • Use RealmModule to split into two, default Realm and separate Realm
    • Change some properties from "Date" to "String" in a model class on the default Realm side
  • Use only the default Realm
  • Use RealmModule to split into two, default Realm and separate Realm
  • Change some properties from "Date" to "String" in a model class on the default Realm side

When testing the beta version, we confirmed that there were no crashes, so we released the beta version as a product version.
(The α version was used only for internal testing, and has not been released.)

  • Detected a large number of crashes (crash rate is approximately 16% of total downloads) with the following error logs immediately after release
  • Verify that the production version from Google Play is installed on the test terminal, but does not crash
  • When I updated the production version to a terminal with the alpha version installed, I got the same error log as below and confirmed that the crash occurred
Fatal Exception: java.lang.RuntimeException: Unable to start activity ComponentInfo {xxx}:io.realm.exception.RealmMigrationNeededException: Invalid type 'String' for field 'notifyAt' in existing Realm file.
   at android.app.ActivityThread.performLaunchActivity (ActivityThread.java:2482)
   at android.app.ActivityThread.handleLaunchActivity (ActivityThread.java:2545)
   at android.app.ActivityThread.access$900 (ActivityThread.java:186)
   at android.app.ActivityThread$H.handleMessage (ActivityThread.java:1410)
   at android.os.Handler.dispatchMessage (Handler.java:102)
   at android.os.Looper.loop (Looper.java:148)
   at android.app.ActivityThread.main (ActivityThread.java:5636)
   at java.lang.reflect.Method.invoke (Method.java)
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run (ZygoteInit.java:730)
   at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:620)
Caused byio.realm.exception.RealmMigrationNeededException: Invalid type 'String' for field 'notifyAt' in existing Realm file.
   atio.realm.ChildNotificationEntityRealmProxy.validateTable (ChildNotificationEntityRealmProxy.java:270)
   atio.realm.DefaultModuleMediator.validateTable (DefaultModuleMediator.java:83)
   atio.realm.Realm.initializeRealm(Realm.java:342)
   atio.realm.Realm.createAndValidate (Realm.java:299)
   atio.realm.Realm.createInstance(Realm.java:278)
   atio.realm.RealmCache.createRealmOrGetFromCache (RealmCache.java:143)
   atio.realm.Realm.getDefaultInstance(Realm.java:209)
   at(appname).models.ChildModel.findById(ChildModel.java:56)
   at(appname).models.UserModel.getCurrentChild(UserModel.java:30)
   at(appname).activities.LaunchActivity.sendLaunchScreen(LaunchActivity.java:70)
   at(appname).activities.LaunchActivity.onCreate(LaunchActivity.java:39)
   at android.app.Activity.performCreate (Activity.java:6315)
   at android.app.instrumentation.callActivityOnCreate (instrumentation.java:1111)
   at android.app.ActivityThread.performLaunchActivity (ActivityThread.java:2435)
   at android.app.ActivityThread.handleLaunchActivity (ActivityThread.java:2545)
   at android.app.ActivityThread.access$900 (ActivityThread.java:186)
   at android.app.ActivityThread$H.handleMessage (ActivityThread.java:1410)
   at android.os.Handler.dispatchMessage (Handler.java:102)
   at android.os.Looper.loop (Looper.java:148)
   at android.app.ActivityThread.main (ActivityThread.java:5636)
   at java.lang.reflect.Method.invoke (Method.java)
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run (ZygoteInit.java:730)
   at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:620)
  • Actual equipment terminal for testing
    • Xperia Z5, Android 6.0
    • Nexus 5, Android 6.0.1
    • Galaxy S3α, Android 4.3
  • Crashing terminal, OS information (information from Crashlytics)
    • Sony 60% and others 40%
    • OS Information
      • 6.0 is 50%
      • 4.0, 5.0 20% each
      • 7.0 is 10%
  • Xperia Z5, Android 6.0
  • Nexus 5, Android 6.0.1
  • Galaxy S3α, Android 4.3
  • Sony 60% and others 40%
  • OS Information
    • 6.0 is 50%
    • 4.0, 5.0 20% each
    • 7.0 is 10%
  • 6.0 is 50%
  • 4.0, 5.0 20% each
  • 7.0 is 10%

I apologize for the inconvenience, but please let me know.

February 23, 2017
Additional Information

import android.util.Log;
importio.realm.DynamicRealm;
importio.realm.RealmMigration;
importio.realm.RealmSchema;

public class migration implements RealmMigration {
    private static final String TAG = Migration.class.getName();

    @ Override
    public void migrate (final DynamicRealm, long oldVersion, long newVersion) {
        Log.d(TAG, "realm migration version:" + String.valueOf(oldVersion)));
        RealmSchema schema=realm.getSchema();
    }
}
private static RealmConfiguration mDefaultConfig;
private static RealmConfiguration mReadOnlyConfig;

@RealmModule(classes={
        ChildArticleEntity.class,
        ChildEntity.class,
        ChildNotificationEntity.class,
        FavoriteArticleEntity.class,
        ReadArticleEntity.class
})
public static class DefaultModule {
}

@RealmModule(classes={
        ArticleEntity.class,
        CategoryEntity.class,
        DateEventEntity.class,
        DaysOldEventEntity.class,
        DaysOldNotificationEntity.class,
        MonthsOldEventEntity.class,
        MonthsOldNotificationEntity.class,
        SuggestSearchWordEntity.class,
        TimelineEntity.class
})
public static class ReadOnlyModule{
}

public static void setupDefaultRealm(){
    if(mDefaultConfig==null){
        mDefaultConfig=newRealmConfiguration.Builder()
                .schemaVersion (CURRENT_SCHEME_VERSION)
                .migration(new Migration())
                .modules (new DefaultModule())
                .build();
    }
    Realm.setDefaultConfiguration(mDefaultConfig);
}

public static Realm getReadonlyInstance(){
    if(mReadOnlyConfig==null){
        mReadOnlyConfig = new RealmConfiguration.Builder()
                .name(READ_ONLY_FILE_NAME)
                .schemaVersion (CURRENT_SCHEME_VERSION)
                .migration(new Migration())
                .modules(new ReadOnlyModule())
                .build();
    }
    return Realm.getInstance(mReadOnlyConfig);
}
import java.util.Date;

importio.realm.RealmObject;

public class ChildNotificationEntity extensions RealmObject {
    private int childId;
    private String message;
    private String notifyAt;

    public int getChildId(){
        return childId;
    }

    public void setChildId(intchildId){
        This.childId=childId;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage (String message) {
        This.message=message;
    }

    public String getNotifyAt(){
        return notifyAt;
    }

    public void setNotifyAt(String notifyAt){
        this.notifyAt =notifyAt;
    }
}
public static void importReadonlyRealmIfNeeded(Context context){
    try{
        // Intra-terminal Retention Version
        SharedPreferences=context.getSharedPreferences (AppConst.PACKAGE_NAME, Context.MODE_PRIVATE);
        intversionCode=s.getInt(KEY_VERSION_CODE, 0);

        // Current build version
        PackageInfo=context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
        int currentVersionCode=pInfo.versionCode;

        // Import runs when version code is updated
        if(versionCode==currentVersionCode){
            Log.i("realm", "import skip");
            return;
        }

        Log.d(TAG, "Copy readonly.realm to this device");
        copyBundledRealmFile(context.getResources().openRawResource(R.raw.readonly), READ_ONLY_FILE_NAME, context);

        s.edit().putInt(KEY_VERSION_CODE, currentVersionCode).apply();
        Log.i("realm", "success import");

    } catch(PackageManager.NameNotFoundExceptione){
        e.printStackTrace();
    }
}

private static void copyBundledRealmFile(InputStream inputStream, String outFileName, Context context) {
    try{
        File file=new File(context.getFilesDir(), outFileName);
        FileOutputStream outputStream=newFileOutputStream(file);
        byte [ ] buf = new byte [1024];
        int bytesRead;
        while((bytesRead=inputStream.read(buf))>0){
            outputStream.write(buf,0, bytesRead);
        }
        outputStream.close();
    } catch(IOExceptione){
        e.printStackTrace();
    }
}

android realm

2022-09-30 19:56

1 Answers

The migration does not appear to contain any instructions.

https://github.com/realm/realm-java/blob/v2.3.1/examples/migrationExample/src/main/java/io/realm/examples/realmmigrationexample/model/Migration.java#L114

describes how notifyAt is migrated from Date to String because there is an example of changing the type of field from String to int.


2022-09-30 19:56

If you have any answers or tips


© 2024 OneMinuteCode. All rights reserved.