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

Android卵プログラマーの記録ブログ

iOSでRealmのデータファイルの場所の見つけ方

初期データのあらかじめ入ったRealmのファイルを用意しておき、
アプリに組み込んで使いたいと思っています。
それにはデータが入ったDBのファイルが必要です。
Realmの保存場所を特定する方法をメモしておきます。

前提

環境はXcode9.2、Swift4.0を使用しています。
初期データ作成用のXcodeプロジェクトを作っておきます。
実機かシミュレーターにRunしておきます。

シミュレーターにインストールした場合の場所

    // DBのファイルの場所
    print(Realm.Configuration.defaultConfiguration.fileURL!)

このコードでファイルパスを表示してくれます。
f:id:sakura_bird1:20180124152523p:plain

これをコピーします。

finderを開いてCommandキー+Shiftキー+Gを押します。

先ほどのパスを貼り付けます。
f:id:sakura_bird1:20180124153944p:plain:w200

先頭の「file://」は削除して移動します。

実機にインストールした場合

Xcodeのメニューから Window > Devices and Simulators を選択します。

実機にインストールされているアプリが一覧で表示されます。

アプリを選択します。

左下の設定アイコンをクリック > “Download Container…“を選択します。
f:id:sakura_bird1:20180124153205p:plain:w300


保存ダイアログが開きます。プロジェクトルートがデフォルトの保存場所になっています。
◯◯.xcappdataという名前がセットされているのでそのまま保存します。

Finderで保存した場所を開きます。
◯◯.xcappdataを右クリックして「パッケージの内容を表示」(英語表示の場合「“Show Package Contents”」)をクリックします。
f:id:sakura_bird1:20180124153450p:plain:w300

ここにあります。
AppData / Documents / default.realm
f:id:sakura_bird1:20180124153600p:plain:w300

以上です。

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)

以上です。

オンラインブートキャンプ iPhoneアプリコース



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




【未経験歓迎】アプリ開発、ゲーム開発、web開発、短期集中型の無料プログラミング&就活スクール

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】

Gradleのバージョンを3以上に上げた時に「Cannot set the value of read-only property 'outputFile' for ApkVariantOutputImpl_Decorated」というエラーが発生した時の対処方法

Android StudioでGradleプラグインを2系から3.0.1にアップデートしたところ、
次のようなエラーが発生しました。

Error:(160, 0) Cannot set the value of read-only property 'outputFile' for ApkVariantOutputImpl_Decorated{apkData=Main{type=MAIN, fullName=debug, filters=[]}} of type com.android.build.gradle.internal.api.ApkVariantOutputImpl.

エラーメッセージに出てくる'outputFile'というのがread-onlyプロパティなのに
書き換えようとして怒られていますね。

'outputFile'はビルドしたapkファイルの名前を書き換える処理に使っている方が多いのではないでしょうか。

Gradleは3.0.0のメジャーアップデートで色々変わったようです。
この時に仕様変更した影響によるエラーでした。

Migrate to Android Plugin for Gradle 3.0.0 | Android Studio

f:id:sakura_bird1:20180119020157p:plain

これによると、Variant APIを使用してバリアントの出力をいじるのは
apkの名前変更みたいな簡単なもの以外出来なくなったようです。
outputFileのアクセスも出来なくなったので、
公式ドキュメントのようにファイルの名前を変更する箇所を書き換える必要があります。


私のアプリの例ですが、次のように書き換えました。

変更前

def toApkFile(originalFile) {
    def namePrefix = "PocketCarbo"
    def name = originalFile.name.replace(project.name,
            namePrefix + "-v" + android.defaultConfig.versionName)
    return new File(originalFile.parentFile, name)
}

android.applicationVariants.all { variant ->
    variant.outputs.each { output ->
        output.outputFile = toApkFile(output.outputFile)
        if (output.packageApplication.outputFile != output.outputFile) {
            output.packageApplication.outputFile = toApkFile(output.packageApplication.outputFile)
        }
    }
}

変更後

android.applicationVariants.all { variant ->
    variant.outputs.all {
        outputFileName = "PocketCarbo_v${android.defaultConfig.versionName}_${variant.name}.apk"
    }
}

書き方も簡略化してスッキリしましたが、
変更前、変更後のどちらもビルドすると
デバッグビルド時「PocketCarbo_v1.1_debug.apk」
リリースビルド時「PocketCarbo-v1.1-release.apk」
というような名前でapkファイルが作成されます。



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