注意! この記事はQiitaにて公開されていた内容をimportしたものです。
これらの内容は場合によっては陳腐化していて役に立たなくなっていたり、有害であったり、現在の著者の主張と異なることがあります。
皆様の判断の上でご利用いただけますと幸いです(度を超してヤバいものは著者に連絡して頂ければ対応します m(_ _)m)


はじめに

皆さん、onSaveInstanceStateしてますか?(挨拶)

Androidアプリ開発において、Parcelableの実装はしばしば行われることと思われます。 AndroidStudioにプラグインを入れて対応される方もいますね。 最近知った話ではpublic class Hoge implements Parcelable とクラスにParcelableインターフェースを定義したときにメソッド未定義の赤線エラーが出ると思うのですが、このとき赤線上でOption+Enter (macOS)を押すと項目内にParcelableを自動実装したり実装を削除する項目がでてきます(知らなかった・・・)。

ただ、個人的に嫌なのが、クラス内にParcelable関連のメソッド類がポンと置かれることです。 本来このクラスにおいてParcelable関連の項目は本質的に関係がないので、そこに居てほしくないのですね。。

そんなわけで、アノテーションで記述してスタティックメソッドを間に介入させることで、クラス内にParcelable関連ロジックを排除できるParcelerは非常に便利最高と言えるわけです。

ところで、そもそもどうしてParcelableの実装をするのかと言うと、色々理由はあるかと思いますが、ActivityやカスタムViewの再生成時にデータを破棄されないようにすることもあるかと思います。

愚直にやるならば、例えばActivity#onSaveInstanceState(Bundle)のBundleに対してキーと値を型にあわせてセットしていったり、Activity#onRestoreInstanceState(Bundle)でキーから値を取り出したりするのですが、これも本質的に関係がないので書きたくないわけです。

そんなわけで、Activityなどのメンバ変数にアノテーションを記述し、onSaveInstanceStateonRestoreInstanceStateなどでスタティックメソッドを間に介入させることでBundleへの出し入れ処理を排除できるIcepickは非常に便利最高と言えるわけです。

では、ParcelerとIcepickを併用したい場合はどうすればいいでしょうか? 僕なりのやり方を解説したいと思います! (しかしIcepickのREADMEにほぼ答え書いてあるんですけどね。。)

モデル側の記述(Custom Bundlerの作成とParcelerとの連携)

いきなりコードを示すと、以下のように書きます。

@Parcel
public class Article {
    public String title;
    public Author author; // ← Authorにも @Parcel を忘れずに!

    public static class Bundler implements icepick.Bundler<Article> {
        @Override
        public void put(String key, Article item, Bundle bundle) {
            bundle.putParcelable(key, Parcels.wrap(item));
        }

        @Override
        public Article get(String key, Bundle bundle) {
            return Parcels.unwrap(bundle.getParcelable(key));
        }
    }
}

ポイントはArticle.Bundlerインナークラスですが、僕はだいたいここに書いてます(さっき本質的なコードは嫌だって言ってたのにそこに書くんかいというツッコミは甘んじて受け入れよう)。 見ての通りstatic classなので、別ファイルに切り出しても良いでしょう。

icepick.Bundlerの記述はややめんどくさいので、僕はLive Templateにしていつでも呼び出せるようにしてあります。以下に置いておくのでよければお使いください。 (Abbreviationはbundler、ApplicableはJava: declarationにしてあります)

public static class Bundler implements icepick.Bundler<$CLASSNAME$> {
    @Override
    public void put(String key, $CLASSNAME$ item, Bundle bundle) {
        bundle.putParcelable(key, Parcels.wrap(item));
    }
    
    @Override
    public $CLASSNAME$ get(String key, Bundle bundle) {
        return Parcels.unwrap(bundle.getParcelable(key));
    }
}

メンバ変数(View)側の記述(アノテーションの定義)

public class HogeFragment extends Fragment {

    @State(Article.Bundler.class)
    Article article; // ← Icepickが外からアクセスできる設定にする(privateつけない)

    @Override
    public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
        super.onViewStateRestored(savedInstanceState);
        Icepick.restoreInstanceState(this, savedInstanceState);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Icepick.saveInstanceState(this, outState);
    }
}

ポイントは、@State(value)に先程のArticle.Bundlerクラスを指定することです。 これで、Icepickが自動的にArticle.Bundlerのputやgetを使ってArticleの変換・復元を行ってくれるようになります。

まとめ

一見するとカスタムクラスをIcepickで扱うのは難しそうに見えますが、分かってみれば意外と簡単でした! そんなわけで便利最高ですが、IcepickはともかくParcelerは用法用量を守ってお使い下さい。 (さいきんはAlarmManagerでParcelableが取れないとかそういう話もあるらしいので…)

おまけ

Realmと組み合わせるとき、RealmListをParcelerが受け付けてくれない場合は、 以下のGistをありがたく参考にし、@ParcelPropertyConverter(RealmListParcelConverter.class)だけ付けてあげるとよいです。 (@Parcelあたりがごちゃごちゃしてますが、付けなくても問題ないです。指定したい人はどうぞ)

https://gist.github.com/Rexee/3ec92b759b3a944d4ad4b28666b2479c