UndoBarを使った削除処理を考える

UndoBar – GitHub

削除処理を行って、それを取り消し可能なようにする実装を考えます。例えばGmailのアプリでメッセージをアーカイブしたときなどに表示されるあれです。

削除する際に「本当に消しますか?」みたいな確認ダイアログを表示するのはナンセンスっぽいです。

なぜならその確認ダイアログは、データが消えてアクセスできなくなるという責任をユーザーに転嫁してるだけだということです。そもそも削除したいだけなのにいちいち確認されるのもうっとおしいだけではないかという理由もあります。

それならば、もし消したくないデータを誤って削除してしまったとしても、それを復元できるようにするのがユーザー目線でいいだろって話です。

この辺りの話はSmashing Android UI レスポンシブUIとデザインパターンという本で知りました。

では実際に取消可能な削除機能を実装するためにはどうすればいいかというと、UndoBarを利用するのが簡単そうな気がします。Crouton使おうかと思ったけども、いざ使おうと思ったらレイアウトを実装したりするのが~~面倒~~大変だったので、シンプルなUndoBarを使いました。ソースコードはCroutonの方が読みやすかったけど。

UndoBarの表示

今回私がやりたかったのは、データベースに保存してあるデータを消すという例です。データベースに保存されているデータをListViewで表示している。そのListViewのうちの1つのアイテムを選んで削除をするというパターン。

実装方法としては削除の操作を行った時(UndoBarを表示させる時点)でどうするかによって、2パターンに分岐するかと思います。

  1. この時点で実際にDB上のデータを消してしまう
  2. この時点ではDBは触らず、見た目上のデータを消す
どちらのパターンにせよ、この時点でListViewに表示されてるデータを消すのは共通です。データベースのデータを消すタイミングがここかどうかです。

1.のパターンは見た目と実装がそのままリンクしています。Listから消えたらデータベースからも消える、シンプルです。

このパターンの場合は操作の取消を選んだら、消したデータを復元する必要があります。そのためどうやってデータをロールバックするのかに頭を悩ませることになります。データベースの構造が複雑だと元に戻すのが大変かもしれません。

2.の場合はUndoといいつつ、実際には削除処理を猶予しているだけです。このパターンの場合、取消操作はデータベースからデータを削除するのを取り消す処理(ややこしい)になります。

こちらは復元処理を考える必要がありません。一方でUndoBarが消える前にアプリを終了したり、画面が回転したときどうすんのっていう新たな問題が生じます。

UndoBarからのコールバック

UndoBarController.AdvancedUndoListenerを使うことで3つのタイミングによるコールバックを受け取ることができます。

この3つのコールバックをうまいこと利用して削除機能を実装していくことになります。

onUndo()

取消ボタンを押した際に呼ばれます。Undo時の処理をここで行うと良いです。

パターン1のときならここで復元処理を行うことになります。

onHide()

取消ボタンが押されず、UndoBarが消える際に呼ばれます。UndoBarに設定した表示時間が経過した後でコールされるわけです。UndoBar表示中に画面回転した場合には呼ばれません。

ちなみに画面回転に対応するには、自分でハンドリングしてやらないといけないそうです。今回私はそこまで踏み込みませんでした。

onClear()

UndoBar.clear()を明示的にコールした際に呼ばれます。

単に画面回転させたら呼ばれるのかというとそうではありません。画面回転時にコールバックを受け取りたいなら、Activity#onDestroy()(もしくはonStop(),onPause())でclear()してやる必要があります。

そのためにはUndoBarのインスタンスをフィールド変数として保持しておかなりといけません。サンプルのように、単にnew UndoBar()して表示させていると、表示したUndoBarを識別できないので、clear()することができません。

ただし単にUndoBarを消したいときとごっちゃになってややこしくなるので、画面回転時にclearするのはよくないかもしれません。回転時は自分でハンドリングして再表示してやるのが一番いいと思います。

削除機能を実装する

1.実際に削除するパターン

onUndo()で削除した対象のデータを元に戻します。このパターンだとここがややこしいです。

消すデータをメモリ上に確保しておいて、Undoされたら消したデータを再インサートする。データベースに削除フラグ持たせてそれを立てる。退避用テーブルにデータを移しておく。・・・どういう方法を取るのがベストなんでしょうかね?

ちなみに私はメモリ上にデータを持たせて、Undoされた際に再登録する方法を選びました。

onHide()は何もしないでいいです。UndoBar表示時点でデータは消えてますので。

onClear()はonHide()と同じ処理でいいでしょう。

2.Undoされなかったら消すパターン

onUndo()はListViewを元に戻すだけでいいです。データベースは変更されていないので、DBから読みなおすなり、消したデータだけAdapterに追加するでもいいと思います。

onHide()で実際にデータベースからデータを消します。

onClear()はonHide()と同様にデータベースからデータを消します。でないとUndoBar表示中に追加で削除処理が実行された際に困ります。

UndoBar表示中に追加の削除処理を行ったとき

UndoBar表示中に更に削除処理を行い新しいUndoBarを表示させたらどうなるか。表示されているUndoBarは以前のもののままです。新しいUndoBarは古いUndoBarの表示時間が過ぎた後で表示されます。

これでは削除対象と表示されてるUndoBarがリンクしないのでよろしくありません。今削除したものを取り消したつもりが、以前処理した奴が取り消されてしまったということになります。

そのため削除処理を行ったら、現在表示中のUndoBarはclear()で消すべきでしょう。

簡単そうでいざ実装してみると大変だった

UndoBarを使って取消可能な削除機能を実装してみましたが、やってみると考えることが多くて意外と大変でした。

それでも自力で実装しようと思うともっと大変でしょうから、ライブラリ作者様には頭が上がりません。

UndoBarはbuild.gradleに1行追加するだけで使えるようになるので、削除処理を実装するときには仕様を検討してみてください。

UndoBar – GitHub

Amazonのほしいものリストを公開しています。仕事で欲しいもの、単なる趣味としてほしいもの、リフレッシュのために欲しいものなどを登録しています。 寄贈いただけると泣いて喜びます。大したお礼はできませんが、よりよい情報発信へのモチベーションに繋がりますので、ご検討いただければ幸いです。