[JAVA] Generics Metamorphosis Grammar-RealmObject backup-I thought it was a pain to write RealmMigration during development but I wanted to take over the data-

The beginning of things

I'm currently making an Android app using Realm, but I often want to mess with the DB configuration. ↓ However, even though it is an application in the debugging stage, it is troublesome to lose data because I have already started using the application practically. ↓ The only thing to do is to increase the number of columns, so it is troublesome to check and write Realm Migration one by one. ↓ I think it's better to jsonize the list of RealmObject, save it in Preferences, restore it to RealmObject and upsert it. ↓ let's try it

way

For the time being

ItemRealmObject

This is an ordinary object class

Access class Create a class called RealmHelper to access

public class ItemRealmHelper extends AbstractRealmHelper {

    public static void insertOneShot(ItemRealmObject itemRealmObject) {
        executeTransactionOneShot(insertTransaction(itemRealmObject));
    }

    private static Realm.Transaction insertTransaction(final ItemRealmObject itemRealmObject) {
        return new Realm.Transaction() {
            @Override
            public void execute(Realm realm) {
                realm.insertOrUpdate(itemRealmObject);
            }
        };
    }

    @Override
    public void upsert(final ItemRealmObject item) {
        item.updateDate = new Date();
        executeTransaction(new Realm.Transaction() {
            @Override
            public void execute(Realm realm) {
                realm.copyToRealmOrUpdate(item);
            }
        });
    }

    @Override
    public RealmResults<ItemRealmObject> findAll() {
        return mRealm.where(ItemRealmObject.class).findAll();
    }
}
public abstract class AbstractRealmHelper<T extends RealmObject> {

    protected final Realm mRealm;

    public AbstractRealmHelper() {
        mRealm = getRealm();
    }

    static Realm getRealm() {
        return Realm.getInstance(new RealmConfiguration.Builder().deleteRealmIfMigrationNeeded().build());
    }

    protected static void executeTransactionOneShot(Realm.Transaction transaction) {
        Realm realm = getRealm();
        realm.executeTransaction(transaction);
        realm.close();
    }

    public abstract void upsert(T t);

    public abstract RealmResults<T> findAll();

    protected void executeTransaction(Realm.Transaction transaction) {
        mRealm.executeTransaction(transaction);
    }

    public void destroy() {
        mRealm.close();
    }
}

"At first, I just put it back in json ~", I felt light.

Read the Realm documentation https://realm.io/jp/docs/java/latest/#gson

GSON is a Google library that serializes / deserializes JSON. GSON does not require any special settings and can be used with Realm as it is.

I see. You can go.

First of all, I want to save it in Preferences, so I will serialize it immediately.

new Gson().toJson(RealmObject);

Then

StackOverFlowError

It's no good ... Take a look at the documentation again.

Regarding serialization, Realm objects cannot be serialized with the GSON defaults. This is because GSON does not use getters / setters and directly refers to instance variables. In order for JSON serialization of Realm objects using GSON to work properly, you need to write a custom JsonSerializer and TypeAdapter for each model. The code in this Gist shows how to write them.

Do I have to write something ... I don't know. Can deserialization be done for RealmObject? If···.

So, I decided to create a model class that is not the same RealmObject and convert it to Json. Create a model class with the same field name and set it so that you can enter a value in each field if you pass RealmObject to the constructor. Create a list of model classes by turning RealmResults fetched by findAll of RealmHelper with for statement, convert it to Json and save it in Preferences.

    void save() {
        writeData(PrefKey.itemRealm, new ItemRealmHelper());
        writeData(PrefKey.priceRealm, new PriceRealmHelper());
    }

    private <T extends RealmObject> void writeData(Enum key, AbstractRealmHelper<T> helper) {
        RealmResults<T> results = helper.findAll();
        List<Object> list = new ArrayList<>();
        for (T t : results) {
            list.add(getModel(t));
        }
        PreferenceUtils.writeValue(getContext(), key, JsonUtils.toJson(list));
        helper.destroy();
    }

    //Add description here when it increases
    private Object getModel(Object o) {
        if (o instanceof ItemRealmObject) {
            return new ItemModel((ItemRealmObject) o);
        } else if (o instanceof PriceRealmObject){
            return new PriceModel((PriceRealmObject) o);
        }
        throw new InternalError();
    }

(This code was a bit more complicated originally because I refactored it after creating the load method below)

... Well, the save went well!

Well, all you have to do is put it back

Let's write the code to bring it back

ItemRealmHelper itemHelper = new ItemRealmHelper();
String json = PreferenceUtils.readValue(getContext(), PrefKey.itemRealm, "");
List<ItemRealmObject> itemModelList = new Gson().fromJson(json, new TypeToken<List<ItemRealmObject>>() {}.getType());
for (ItemRealmObject itemModel : itemModelList) {
    itemHelper.upsert(itemModel);
}

Oh, I was able to go.

However, I think about it here. "I'm making some RealmObjects, so if I write it honestly, it's not easy for the code to increase as it is, and it's troublesome to copy and change the type every time ..."

//If you write it honestly, it will increase ...
void load() {
    ItemRealmHelper itemHelper = new ItemRealmHelper();
    String json = PreferenceUtils.readValue(getContext(), PrefKey.itemRealm, "");
    List<ItemRealmObject> itemModelList = new Gson().fromJson(json, new TypeToken<List<ItemRealmObject>>() {}.getType());
    for (ItemRealmObject itemModel : itemModelList) {
        itemHelper.upsert(itemModel);
    }
    itemHelper.destroy();
    
    Item2RealmHelper item2Helper = new Item2RealmHelper();
    String json2 = PreferenceUtils.readValue(getContext(), PrefKey.item2Realm, "");
    List<Item2RealmObject> item2ModelList = new Gson().fromJson(json, new TypeToken<List<Item2RealmObject>>() {}.getType());
    for (Item2RealmObject item2Model : item2ModelList) {
        item2Helper.upsert(item2Model);
    }
    item2Helper.destroy();
}
// ...It increases every time it increases

By saying ...

Generics come into play

I made it by trying to make it a method with upsert.

    private <T extends RealmObject> void upsert(Enum key, TypeToken<T> typeToken, final AbstractRealmHelper<T> helper) {
        List<T> list = new Gson().fromJson(PreferenceUtils.readValue(getContext(), key, ""), new TypeToken<List<T>>() {}.getType());
        for (T t : list) {
            helper.upsert(t);
        }
        helper.destory();
    }

Alright, I wrote it well. So execute.

ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast

What is this ...

When I looked it up, an article came out.

I'm addicted to converting JSON strings to generics objects with Gson http://osa030.hatenablog.com/entry/2015/10/20/182439

As you read on,

The result of Gson fromJson can be decoded up to Hoge class, but the value is "com.google.gson.internal.LinkedTreeMap" instead of Fuga class.

In other words, it is a List type, but it seems that the destination is LinkedTreeMap.

Once your .java source file is compiled, the type parameter is obviously thrown away. Since it is not known at compile time, it cannot be stored in bytecode, so it's erased and Gson can't read it.

In short "" disappears at build time, so it doesn't look dynamic. That's why I kindly decided to use Linked Tree Map.

There was a StackOverFlow link, so take a look.

Java Type Generic as Argument for GSON https://stackoverflow.com/questions/14139437/java-type-generic-as-argument-for-gson/14139700#14139700

This is an example that I want to convert to List, so it matches me.

Copy ListOfSomething from StackOverFlow and use it.

    private <T extends RealmObject> void upsert(Enum key, Class<T> clazz, final AbstractRealmHelper<T> helper) {
        List<T> list = new Gson().fromJson(PreferenceUtils.readValue(getContext(), key, ""), new ListOfSomething<>(clazz));
        for (T t : list) {
            helper.upsert(t);
        }
        helper.destory();
    }

O. did it. It's amazing.

Looking at the contents, getRawType returns List.class fixedly. So I understand that the person in the first article created the GenericOf class. Since it's a big deal, I'll change it (?) Considering versatility.

    private <T extends RealmObject> void upsert(Enum key, Class<T> clazz, final AbstractRealmHelper<T> helper) {
        List<T> list = new Gson().fromJson(PreferenceUtils.readValue(getContext(), key, ""), new GenericOf<>(List.class, clazz));
        for (T t : list) {
            Footprint.fields(t);
            helper.upsert(t);
        }
        helper.destory();
    }

It was the most refreshing.

Now, in the load method, you only need to call upsert, so it goes from 7 lines to 1 line. For example, even if there are 2 RealmObjects, just add them line by line!

void load() {
    upsert(PrefKey.itemRealm, ItemRealmObject.class, new ItemRealmHelper());
    upsert(PrefKey.item2Realm, Item2RealmObject.class, new Item2RealmHelper());
}

It was refreshing and easy, and I was happy.

Summary

    void save() {
        writeData(PrefKey.itemRealm, new ItemRealmHelper());
        writeData(PrefKey.priceRealm, new PriceRealmHelper());
    }

    private <T extends RealmObject> void writeData(Enum key, AbstractRealmHelper<T> helper) {
        RealmResults<T> results = helper.findAll();
        List<Object> list = new ArrayList<>();
        for (T t : results) {
            list.add(getModel(t));
        }
        PreferenceUtils.writeValue(getContext(), key, JsonUtils.toJson(list));
        helper.destroy();
    }

    //Add description here when it increases
    private Object getModel(Object o) {
        if (o instanceof ItemRealmObject) {
            return new ItemModel((ItemRealmObject) o);
        } else if (o instanceof PriceRealmObject){
            return new PriceModel((PriceRealmObject) o);
        }
        throw new InternalError();
    }
    void load() {
        upsert(PrefKey.itemRealm, ItemRealmObject.class, new ItemRealmHelper());
        upsert(PrefKey.item2Realm, Item2RealmObject.class, new Item2RealmHelper());
    }

    private <T extends RealmObject> void upsert(Enum key, Class<T> clazz, final AbstractRealmHelper<T> helper) {
        List<T> list = new Gson().fromJson(PreferenceUtils.readValue(getContext(), key, ""), new GenericOf<>(List.class, clazz));
        for (T t : list) {
            helper.upsert(t);
        }
        helper.destory();
    }

Finally

I was happy to be able to use TypeToken to specify a class that includes a list in Gson, but when I involved generics there, I found that I needed a ParameterizedType. Both Gson and Generics are still deep. What if you want to take the List type of List with generics? I thought, but it seems dangerous so I stopped thinking. (Is it okay to set both arguments of GenericsOf to TypeToken ...?)

If this happens, why not write JsonSerializer and TypeAdapter so that RealmObject can be serialized? I thought it was a secret. Furthermore, wasn't it faster to write Migration in the first place? That's more secret. It's good because I learned ...

Recommended Posts

Generics Metamorphosis Grammar-RealmObject backup-I thought it was a pain to write RealmMigration during development but I wanted to take over the data-
It was a life I wanted to reset the thread-safe associative counter
I was charged a lot by AWS during development on rails, but ...
When I wanted to create a method for Premium Friday, it was already in the Java 8 standard API