Cursorをいじるメソッドを作った場合、引数として渡されたCursorのPositionを意識してあげましょう。というお話。
※CursorのPositionを特定の位置まで移動するようなメソッドを実装する場合は別です!
引数として渡されたCursorの最後のPositionのレコードをLog出力するようなメソッドを作る場合の例で考えてみましょう。
とりあえずダメな実装例
まずは、何も考えずにちゃちゃっとダメな実装例をご紹介。
※私のloggingがイケてないというのは、また別の話で…
public static void dumpLastRecord(Cursor cursor) {
Log.d("dumpLastRecord", "=============");
if (cursor != null) {
cursor.moveToPosition(-1);
if (cursor.moveToLast()) {
String[] columnNames = cursor.getColumnNames();
int length = columnNames.length;
for (int i = 0; i < length; i++) {
String value;
try {
value = cursor.getString(i);
} catch (SQLiteException e) {
value = "<unprintable>";
}
Log.d("dumpLastRecord", columnNames[i] + " : " + value);
}
}
}
Log.d("dumpLastRecord", "=============");
}
ダメな理由
引数で渡されたCursorのPositionをmoveToLastメソッドで最後まで動かして、そのまま処理が終わっているからです。
このメソッドを使う人はメソッド実行後、引数で渡したCursorのPositionが最後になったままであることを想定していないかもしれません。
てか、そんなこと考えないといけないメソッドの方がイケてないです。
これは、moveToLastメソッドでCursorのPositionを最後まで動かした場合以外でも同様です。
メソッド内でCursorのPositionを動かしたのであれば、今回の話に含まれます。
ダメな実装例のメソッド実行後のCursorのPositionを確認
ということで、ダメな実装例のメソッド実行後のCursorのPositionを確認してみよう。
以下のコードのCursorには、レコードが10件存在すると仮定します。
Cursor c = db.query(....);
c.moveToFirst();
Log.d("Before Cursor Position","" + c.getPosition());
dumpLastRecord(c);
Log.d("After Cursor Position","" + c.getPosition());
Log.dの出力結果は下記にようになる。
※dumpLastRecordメソッドの出力は省略
※CursorのPositionは、0から始まる
D/Bfter Cursor Position﹕ 0
D/After Cursor Position﹕ 9
意図的にmoveToFirstでCursorのPositionを最初にしたにもかかわらず、dumpLastRecordメソッド実行後にCursorのPositionを確認したところ、最後のPositionまで移動してしまっています。
このように引数で渡されたCursorのPositionをいじくったままにしてしまう実装は、意図しないバグを生み出してしまう可能性が考えられます。
では、いい実装例をご紹介。
お待ちかね、いい実装例
public static void dumpLastRecord(Cursor cursor) {
Log.d("dumpLastRecord", "===========================");
if (cursor != null) {
int startPosition = cursor.getPosition();
cursor.moveToPosition(-1);
if (cursor.moveToLast()) {
String[] columnNames = cursor.getColumnNames();
int length = columnNames.length;
for (int i = 0; i < length; i++) {
String value;
try {
value = cursor.getString(i);
} catch (SQLiteException e) {
value = "<unprintable>";
}
Log.d("dumpLastRecord", columnNames[i] + " : " + value);
}
}
cursor.moveToPosition(startPosition);
}
Log.d("dumpLastRecord", "===========================");
}
いい実装例のポイントは、以下のところです。
※コメント見づれー
public static void dumpLastRecord(Cursor cursor) {
Log.d("dumpLastRecord", "===========================");
if (cursor != null) {
int startPosition = cursor.getPosition(); // Cursorをいじる前に現在のPositionを一旦変数に保持
// 省略
cursor.moveToPosition(startPosition); // Cursorをいじり終わったら元のPositionに戻してあげる
}
Log.d("dumpLastRecord", "===========================");
}
という感じで、Cursorをいじる前に現在のPositionを一旦変数に保持しておき、いじり終わったら元のPositionに戻してあげる。
上記のような実装にしてあげると、メソッドを使う人は一々**CursorのPositionが変更されてないだろうか?**ということを気にしなくてもよくなります。
いい実装例のメソッド実行後のCursorのPositionを確認
ということで、いい実装例のメソッド実行後のCursorのPositionを確認してみよう。
※確認に使用する条件とコードは、ダメな実装例のメソッドを使った後のCursorのPositionを確認の時と同じ。
Log.dの出力結果は下記にようになる。
※dumpLastRecordメソッドの出力は省略
※CursorのPositionは、0から始まる
D/Bfter Cursor Position﹕ 0
D/After Cursor Position﹕ 0
ダメな実装例では、dumpLastRecordメソッド実行後のCursorのPositionが最後になっていました。
いい実装例では、dumpLastRecordメソッド実行後でもCursorのPositionが変わっていないことがわかります。
まとめ
引数で渡されたCursorをいじるメソッドを実装する場合、実行後のPositionを気にしてあげないと、使う側はその後意図しない動作をする可能性が考えられます。
Cursorをいじるメソッドを実装する人は、Positionを意識した実装を心がけましょう!という話でした。
あ、私も意識しないと…。
このような実装例は、SDKの実装では当たり前のように行われています。
android.database.DatabaseUtilsクラスの実装がこの記事のベースとなっています。
気になる人は、SDKの実装も合わせて確認してみてください!