Android
MVP
Realm

日記アプリのMVP化工程記録・6

躓きが、解決しました。

前回の記事
ItemTouchHelper.SimpleCallbackで登録したonSwipedを起因として
RealmDBの更新をMVPで実装させようとして、前回は躓きました。
notifyItemRemovedでリストアイテムの削除を通知してもDividerItemDecorationで実装した罫線が残ってしまっています。今回は解決までの道のりを公開しようと思います。

解決する一番の近道は、仮定を一つずつ試していくこと

今回はここのコミットでスワイプ削除のMVP化を実現させました。

予告じゃないですが、前回に記事の最後で
匿名クラスでスワイプ削除イベントを拾っているというのに、チグハグな実装になっています。
みたいなフラグっぽい事書いてました。そんな事すると大体検討違いだったりするのですが、今回は珍しく直感が当たっていました。

そもそもスワイプ削除の通知では
notifyItemRemoved()を使用しており、Android Developersによると
アイテムがデータセットから削除されたことを、登録されたオブザーバーに通知する。
とあるので、こいつを呼べば万事解決だと思ったところから躓きは始まっていたようです。

ここで、罫線はスワイプ削除されたアイテムとは別なのでは?という過程に思い当たりました。

なればこそ表示されているRecyclerView全体に通知をかける
notifyDataSetChanged()
を試したのですが、結果は同じ。うむ、いい線いってると思ったがこれはますます
ItemTouchHelper.SimpleCallbackとRealmDBの更新がうまく連携していないところが怪しく思えてきました。

次の仮定を検証していきましょう。

またしてもスレッドの一貫性に欠けていた件

では、怪しいItemTouchHelper内のonSwiped()コールバックMethodを覗いてみましょう。

ItemTouchHelper
void postDispatchSwipe(final RecoverAnimation anim, final int swipeDir) {
        // wait until animations are complete.
        mRecyclerView.post(new Runnable() {
            @Override
            public void run() {
                if (mRecyclerView != null && mRecyclerView.isAttachedToWindow() &&
                        !anim.mOverridden &&
                        anim.mViewHolder.getAdapterPosition() != RecyclerView.NO_POSITION) {
                    final RecyclerView.ItemAnimator animator = mRecyclerView.getItemAnimator();
                    if ((animator == null || !animator.isRunning(null))
                            && !hasRunningRecoverAnim()) {
                        mCallback.onSwiped(anim.mViewHolder, swipeDir);
                    } else {
                        mRecyclerView.post(this);
                    }
                }
            }
        });
    }

ここで匿名クラスでスワイプ削除イベントを拾っているというのに、という直感が当たっていた事が分かります。
mCallback.onSwiped別スレッドから投げている
つまりItemTouchHelper.SimpleCallbackのonSwiped()契機でDBを操作しようとすると、
端的にいえば別スレッドで作成されたRealmResultsは渡せない
という4回目の記事のRealmの制約にぶつかります。

(Realmって速い、速いともてはやされがちだけど、この制約は結構使い辛いな。。)

なんて思ってしまいましたが、気を取り直して解決していきましょう。

Handler.handleMessage()でUIスレッドに噛ませる

Realmの制約にぶつかろうとも、RecyclerViewのスワイプ削除イベントを拾おうと思ったら
ItemTouchHelper.SimpleCallbackは捨てたくないので
Handler.sendMessage(mHandler.obtainMessage(MSG_REMOV_LIST,index))
でメッセージを投げてUIスレッドで受け取ってもらう事にしました。

あとは

PersonFragment
if (mHandler == null) {
            mHandler = new Handler(Looper.getMainLooper()) {
                @Override
                public void handleMessage(Message msg) {

                    switch (msg.what) {
                        case MSG_REMOV_LIST:
                            int index = (int) msg.obj;
                            // リストのスワイプ削除イベント
                            mPresenter.onSwiped(index);
                            Log.d(TAG, "removedList");
                            break;
                    }
                }
            };
        }

でメッセージを受け取れば、UIスレッドでRealmDBを操作する事ができます。

ここまでくれば後は
Presenter → Repository → RealmDB更新 → Presenterに通知 → Viewに通知
の手順を踏んで

PersonFragment
@Override
    public void notifyItemRemoved() {
        // Adapterにリストデータ変更を通知する
        mAdapter.notifyDataSetChanged();
    }

からリストの更新を通知します。

結果、無事に罫線も削除されてスッキリスワイプ削除をMVP化する事ができました!

残りのMVP化機能

今回の対応でリストのスワイプ削除までMVP化が完了しました。
残るはリストの入れ替え機能のMVP化です。

ついに終わりが見えて来ましたね!