bowyer-appです。
今回新しいアプリ弓道のアプリ 採点簿 for チームの中で、スコアや選手の検索を実装しました。
僕のアプリは、タイトル、人数、日付などいろんな条件で検索する必要がありました。
より操作を簡単にするために極力画面遷移を挟まず、その場で検索したかったので何かいい方法は無いかなと探してたら、
AAkiraさんの作ったExpandableLayoutがちょうど良かったので入れてみました。
UI
通常時 | 詳細検索時 |
---|---|
![]() |
![]() |
僕のアプリから一部コードの切り出してこちらに公開してますので、よかったら御覧ください
https://github.com/bowyer-app/PlayerManage
必要なときにカスタムな検索機能が出てくるのでとても使いやすくなりました。
気づいたらどの画面でもExpandableLayoutを使っていました。
DB
DBにはsquareさんのsqlbriteを使いました。
細かいところはsql書きたいけど、それ以外のところを書くのはめんどくさかったので、sqliteをラップしてくれているこちらを選定しました。
今回工夫した点
Daoの実装を工夫してみました。
PlayerDao.java
sqlbriteのサンプルでは、検索条件を書くときにSQLを定義し
private static final String LIST_QUERY = "SELECT * FROM "
+ TodoItem.TABLE
+ " WHERE "
+ TodoItem.LIST_ID
+ " = ? ORDER BY "
+ TodoItem.COMPLETE
+ " ASC";
@Inject BriteDatabase db;
上記で定義したsqlを元にdbに対してqueryを実行しています。
String listId = String.valueOf(getListId());
subscriptions.add(db.createQuery(TodoItem.TABLE, LIST_QUERY, listId)
.mapToList(TodoItem.MAPPER)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(adapter));
BriteDatabase
のcreateQuery
を見てみると
public QueryObservable createQuery(@NonNull final String table, @NonNull String sql,
@NonNull String... args) {
引数にargs
を渡しているので、こことsqlの= ?
の部分がズレると検索条件があわなくなり、引数足りないよってエラー出たりします。
僕が作ったPlayerDao.javaでは、sql
とargs
を関連付けたPlayerQuery
というオブジェクトを生成できるようにしました。
public static final class SQLBuilder {
private String sql = "SELECT * FROM " + Player.TABLE + " WHERE ";
private final List<String> values = new ArrayList<>();
private String getAndSql() {
if (!values.isEmpty()) {
sql = sql + " and ";
}
return sql;
}
private String getOrSql() {
if (!values.isEmpty()) {
sql = sql + " or ";
}
return sql;
}
public SQLBuilder name(String name) {
if (TextUtils.isEmpty(name)) {
return this;
}
//全部マッチさせるため
sql = getAndSql() + Player.LAST_NAME + " like ? ";
values.add("%" + name + "%");
sql = getOrSql() + Player.FIRST_NAME + " like ? ";
values.add("%" + name + "%");
sql = getOrSql() + Player.LAST_NAME_PHONETIC + " like ? ";
values.add("%" + name + "%");
sql = getOrSql() + Player.FIRST_NAME_PHONETIC + " like ? ";
values.add("%" + name + "%");
return this;
}
public SQLBuilder sex(Sex sex) {
if (sex == Sex.ALL) {
return this;
}
sql = getAndSql() + Player.SEX + " = ? ";
values.add(String.valueOf(sex.getSex()));
return this;
}
public SQLBuilder rank(Rank rank) {
if (rank == Rank.ALL) {
return this;
}
sql = getAndSql() + Player.RANK + " = ? ";
values.add(rank.getRank());
return this;
}
public SQLBuilder id(long playerId) {
sql = getAndSql() + Player.ID + " = ? ";
values.add(String.valueOf(playerId));
return this;
}
public PlayerQuery build() {
if (values.isEmpty()) {
sql = sql.replace("WHERE", "");
}
return new PlayerQuery(sql, values.toArray(new String[values.size()]));
}
}
public static class PlayerQuery {
public final String sql;
public final String[] values;
public PlayerQuery(String sql, String[] values) {
this.sql = sql + " ORDER BY " + Player.ID + " DESC, " + Player.LAST_NAME + " ASC";
this.values = values;
}
}
Builder
パターンを使ってsql
のカラムとargs
の値を関連付けて組み立てているので、
足りないとかズレるということが発生しません。
使い方はこちら、MainActivity.java#L182-L192
検索条件が変わる度にPlayerDao.PlayerQuery
を生成してPlayerDao
に食わせるだけ。
private void doSearch() {
String name = mSearchQueryText.getText().toString();
Sex sex = getCheckedSex(mSexGroup.getCheckedRadioButtonId());
Rank rank = Rank.of(mRankText.getText().toString());
PlayerDao.PlayerQuery playerQuery =
new PlayerDao.SQLBuilder().name(name).sex(sex).rank(rank).build();
mSubscription = (PlayerDao.getPlayerByQuery(mDb, playerQuery)
.mapToList(Player.MAPPER)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(mAdapter));
}
sql書くのすごくめんどくさいし、条件が複雑になっていくと検索句のカラム名と値がズレてよくわからなくなるので、こういった工夫が役に立つのではと思います。
https://github.com/bowyer-app/PlayerManage
こちらにソースコードを公開してあるのでよかったら見てください。
もっとこうしたほうが良いよってのがあればPR頂けると嬉しいです。