オブジェクトのやり取りにもの思う
Androidの話になりますが、例えばAPIからのレスポンスをJavaのentityとして保持していて、 Intentを飛ばして受け取る先でもそのentityを使いたい、という場面があるとします。
その際、考えられるのは
- Intentを受け取る先で再びAPIを叩く
- entityをIntentのpuExtra系メソッドでなんとか受け渡しする
- メモリキャッシュなど、キャッシュしておいてIntent受け取り先でそのキャッシュからentity復元
あたりかなと思います。(他にあったらごめんなさい)
ナンセンスなのは1です。値が必要なたびにAPIを叩いていたらロードでユーザを待たせることになりますし、APIサーバへのアクセスを頻繁にさせることで電池の消耗も激しくします。(Ream Academyにそんな話があった気が... Becoming a Better Battery Citizen)
2のIntentで受け渡しするのはいくつか方法があります。
- Parcelableとしてentityを実装
- Gsonを使ってjson変換したものをStringとして受け渡し
ちなみに、うちのプロダクトコードではParcelableとして実装されたentityが受け渡されています。
うちのプロダクトのコードでよく見る例
public class Profile implements Parcelable { private int id; private String name; protected Profile(Parcel in) { id = in.readInt(); name = in.readString(); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(id); dest.writeString(name); } @SuppressWarnings("unused") public static final Parcelable.Creator<Profile> CREATOR = new Parcelable.Creator<Profile>() { @Override public CustomerProfile createFromParcel(Parcel in) { return new Profile(in); } @Override public Profile[] newArray(int size) { return new Profile[size]; } } }
entityとなるオブジェクトをParcelableとして実装しています。 Parcelableインタフェースを実装することで、そのオブジェクトをParcelとして扱うことができます。 (Parcelは世の中色々ありますが、Androidにおいてはこちらを参照 https://developer.android.com/reference/android/os/Parcel.html)
Intentには、putExtra系のメソッドがあり、Intentにデータを渡すことができます。 その中に、putExtra(String name, Parcelable value) があり、オブジェクトを直接渡すことはできませんが、Parcelableとして実装したオブジェクトを渡すことができます。
しかし、これには難点があり、なんといっても実装が面倒臭い。(コードジェネレータ・プラグインやアノテーションライブラリでなんとかする方法もあるが)
事実、この形のコードは今置き換えられているところです。
次に、Gson変換してStringとして受け渡しする方法もあります。
Gsonは、Googleの開発、保守しているライブラリで、シンプルなインタフェースでPOJOからJsonへのシリアライズ、またでシリアライズを行うことができます。 シリアライズされたJsonは、Stringとして扱うことができます。
public class Profile { private static final Gson GSON = new GsonBuilder().create(); @SerializedName("user_id") private int id; @SerializedName("user_name") private String name; public static Profile fromJson(String json) { if (json == null) return null; return GSON.fromJson(json, Profile.class); } public String toJson() { return GSON.toJson(this); } }
先ほどと比べてだいぶすっきりしました。 Intentを投げる側ではputExtra(String key, String value)で投げて、受け取り側は受け取った後でGson.fromJsonででシリアライズしてあげればいいだけです。
Intentのデータとして受け渡しする難点
しかし、Parcelableとして実装するにしろ、Gsonでシリアライズするにしろ、難点がいくつかあります。
- アクティビティの結合が密になる。
- オブジェクトのデータがでかくなると、クラッシュの危険が高くなる。
まず、必要なIntentのパラメータが増えるとアクティビティ感の結合が密になります。 例えば、MainActivityからProfileActivityに遷移する際、ProfileActivityが前のアクティビティからのProfileを期待している場合、 Profileを持っていないアクティビティからの遷移はできなくなります。 ProfileActivityの振る舞いは、遷移元のActivityに依存します。
また、オブジェクトがListで、アルバムなどの大量のデータを保持する場合、Intentはそんなに大きなデータを持つよう想定されていないため、クラッシュします。(実体験)
Intentのパラメータはuser_idなどのインデックスに留めておきたいものです。
キャッシュをしよう
そこで、 3. メモリキャッシュなど、キャッシュしておいてIntent受け取り先でそのキャッシュからentity復元 が今の所望ましい形なのかなと考えています。
アーキテクチャとしてrepositoryを用意し、Activityは何も考えずにrepositoryからデータを取ってくるメソッドを叩く。 repositoryはキャッシュの有無に応じて、RemotoDataSourceから取ってくるか、LocalDataSourceからとってくるか選択する。 repositoryのクライアントはデータの出どころは知る必要なく、データを取得することができる。
この方法がアクティビティ間の結合を疎にできて、ユーザがロード時間にストレスを感じなくて済み、開発者は実装に時間を取られずに済む方法かな?と最近は思っています。
実際、プロダクトにまだrepositoryを完全に導入できていなくて、ParcelableになっているところをGsonで簡素化してごまかしている節があるので、早いところ整理したいものです。