Androidはワンツーパンチ 三歩進んで二歩下がる

プログラミングやどうでもいい話

iOSのNsPredicateを使ってスペース区切りの文字列の検索条件を指定する&RealmでDBから読み込むメモ

こんにちは。自分の勉強メモです。
間違っていたらお声がけ下さると嬉しいです(●´ω`●)

前提

環境はXcode9.2、Swift4.0を使用しています。
Realmを使ったDBの処理を書いている最中です。

やりたいこと

よくあるキーワード検索がやりたいです。
・スペース区切りの文字列を入力された時に、スペースで文字列を分割する。
・対象のフィールドに分割した文字列がそれぞれ含まれているものを抽出する。

このように検索条件を入力したとすると
f:id:sakura_bird1:20180123224904p:plain


SQL文は次のようになります。

SELECT  "foods".* FROM "foods" WHERE (("foods"."name" LIKE '%たま%' OR "foods"."search_word" LIKE '%たま%') AND ("foods"."name" LIKE '%豆%' OR "foods"."search_word" LIKE '%豆%'))

この例では"foods"というテーブルから"name"または"search_word"というフィールドに
入力した文字列があった時にデータが抽出されます。

検索条件を指定するには

NsPredicateクラスを使っての検索条件を指定するのが定石のようです。
RealmでもNsPredicateがサポートされておりNSPredicate Cheatsheetという資料が用意されています。
NSPredicate Cheatsheet

前処理

検索する文字列をスペースで分割して配列に入れておきます。
有効な文字列が入っている前提です。

    let words = " たま 豆"
    // 全角スペースを半角スペースに置き換え余計なスペースはトリムする
    let newWords = words.replacingOccurrences(of: " ", with: " ").trimmingCharacters(in: .whitespacesAndNewlines)
    // componentsメソッドで指定の区切り文字で配列に分割する
    let wordsArray = newWords.components(separatedBy: " ")
    print(wordsArray) // ["たま", "豆"]

↑2018/03/18追記 トリム以外にも大文字小文字の区別も無くしたいという要望が多いと思うので、lowercased()などの関数を利用してもいいですね。

NsPredicateに条件を指定する

Swiftでは「let predicate = NSPredicate(format: "search_word CONTAINS %@", "たま")」
のようにformatに条件文を入れていきます。
NSPredicate - Foundation | Apple Developer Documentation

私の例ですと、SQLのLIKE、つまり文字列の部分一致に対応するオペレーターは
「CONTAINS」を使います。

また、比較したい文字列が複数あるかもしれない前提なので
NSPredicateオブジェクトを配列に格納してから「NSCompoundPredicate」クラスを利用してそれぞれの条件を結合します。
AND条件なので「andPredicateWithSubpredicates」を指定します。
比較したい文字列が1つしかなくてもこのコードで問題ありません。

    var predicates: [NSPredicate] = []

    for word in wordsArray {
      predicates.append(NSPredicate(format: "search_word CONTAINS %@ OR name CONTAINS %@", word, word))
    }

    print(predicates)// [search_word CONTAINS "たま" OR name CONTAINS "たま", search_word CONTAINS "豆" OR name CONTAINS "豆"]

    let compoundedPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicates)

    print(compoundedPredicate)//(search_word CONTAINS "たま" OR name CONTAINS "たま") AND (search_word CONTAINS "豆" OR name CONTAINS "豆")

Realmで検索する

filterメソッドに先程のNSCompoundPredicateオブジェクトをセットすればOKです。
公式ドキュメントはこちらです。
https://realm.io/docs/swift/latest/#queries

let result = realm.objects(Foods.self).filter(compoundedPredicate)

以上です。

Swift4のCodableプロトコルをRealmのモデルクラスに適用してJsonをパースした後にDBに投入するサンプル

RealmのモデルクラスにSwift4のCodableプロトコルを実装したいと思いましたのでサンプルを作りました。
環境はXcode9.2、Swift4.0を使用しています。
作成にあたってこちらのスライドを参考にさせていただきました。
ありがとうございます。
speakerdeck.com

サンプルの仕様

ローカルの「Kinds.json」を読み込み、モデルのオブジェクトに変換し
DBに書き込む。

実行結果

このようにコンソールに表示されます。

f:id:sakura_bird1:20180123005648p:plain:w300
f:id:sakura_bird1:20180123010744p:plain:w300


何かおかしな部分がございましたらお声がけくださると大変ありがたいです。

関連記事
sakura-bird1.hatenablog.com

Android Studioで作成したAPKファイルをGoogle Play Consoleにリリースしようとすると「アップロードできませんでした テスト専用の APK はアップロードできません。」というエラーになる状況について

前提

Android Studio 3.0.1(2018/01/22現在)

対象のアプリは本番用アプリへの署名はbuild.gradleに設定済みで
variantを切り替えた後に、Runボタンか
Android Studio メニュー > Build > Build APK(s)を
選択するとapkがビルド出来る状態です。

状況

Android StudioでRunボタンを押して作成したAPKファイルを
Google Play Consoleにリリースしようとすると
「アップロードできませんでした テスト専用の APK はアップロードできません。」
というエラーが出てしまいました。
f:id:sakura_bird1:20180122004903p:plain:w400

このAPKファイルをadbコマンドで端末にインストールしようとすると
このようなエラーメッセージが表示されます。

adb: failed to install アプリのパス.apk:
 Failure [INSTALL_FAILED_TEST_ONLY: installPackageLI]

原因

Android Studio のバージョンが3以上ですと、
Runボタンでビルドすると「testOnly="true"」になってしまうようです。

以前はRunボタンでビルドしたapkでもリリース出来ていたのですが、
出来なくなった人がちらほら書き込んでいます↓。
android - ADB Install Fails With INSTALL_FAILED_TEST_ONLY - Stack Overflow
f:id:sakura_bird1:20180122005651p:plain

対策

メニュー > Build > Build APK(s)を
選択してAPKファイルを作成します。

又はコマンドラインから「./gradlew build」でAPKファイルを作成します。



エンジニアによるエンジニアのためのサイト始まる!!【teratail】