20200101のAndroidに関する記事は6件です。

Dagger/MissingBindingエラー

以下のようなエラーが出た

エラー: [Dagger/MissingBinding] [dagger.android.AndroidInjector.inject(T)] jp.co.xxxxx.yyyy.data.repository.ZzzRepository cannot be provided without an @Provides-annotated method.
public abstract interface AppComponent extends dagger.android.AndroidInjector<jp.co.xxxxx.yyyy.App>
 ・
 ・
 ・

結論

対象repositryのBindsができていなかった。。。

@Module(includes = [DataSourceModule::class])
interface RepositoryModule {
    @Binds
    fun bindZzzRepository(repository: ZzzDataRepository): ZzzRepository
}

新たな画面を追加する際にrepositoryの追加も行う場合に、moduleに対しても色々と設定追加してやらないといけないのが漏れていた。Daggerの概念をちゃんと理解しつつ設定周りをちゃんと確認しないとですね。。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Androidアプリのインストール数をGCPに自動収集させる

はじめに

Androidアプリのインストール数をGCPのBigQueryに自動収集するようにしました。

これまで、Androidアプリのインストール数を確認するときは、Google Play Consoleを開き、アプリを選択し、レポートをcsv形式でダウンロードし、、、といった作業が必要でそれが面倒でした。:tired_face:

この面倒な作業をなくすために、Google Play から取得できるアプリのインストール数をGCPのBigQueryに毎日自動転送するよう設定した流れが以下の通りです。

GCPのBigQueryにデータを格納する

  1. Google Cloud PlatformのメニューからBigQueryを開く
    スクリーンショット 2019-12-20 11.08.38.png

  2. 転送を選択して、+転送を作成ボタンを選択する
    ソースタイプを選ぶところがあるので、「Google Play」 を選択します。
    スクリーンショット 2019-12-20 11.11.22.png

  3. 設定画面が表示されるので、項目を埋めていく

    • 「転送項目名」と「スケジュールオプション」は、任意でokです。今回は、毎日繰り返すように設定しました。
    • 「転送データの宛先」は、転送したいデータセットのIDを選択します。新しいデータセットに転送したい場合は、GCPのBigQueryのトップ画面に戻って、データセットの作成ボタンをタップして、作成してください。
    • 「Cloud Storage bucket」 は、IDに基づく Google Cloud Storage のバケットパスを入れる必要があります。 このIDは、Google Play Console に接続して、「レポートをダウンロード」から対象アプリを選択するとレポートの最下部に "gs://pubsite_prod_rev_[ID]/reviews/" の記載があるのでこれを利用します。 スクリーンショット 2019-12-20 11.23.46.png

以上で指定したデータセットにレポートのデータが格納されます!簡単だー!:smile:

格納されたデータを確認する

BigQueyの転送を選択した画面をみると、先ほど作成した転送項目名が表示されています。
スクリーンショット 2019-12-20 11.29.32.png

転送項目名を選択すると、転送の詳細画面に遷移して、転送がスケジュール通りに実行されていることを確認できます。
スクリーンショット 2019-12-20 11.28.10.png

BigQueryのトップ画面に戻り、先ほど選択した転送データの宛先を選択します。

すると、Installs_app_versionやInstalls_carrierなどたくさんデータが取得できていることが見てわかると思います。実は、今回転送設定したGoogle Play のレポートには、インストール数だけでなく、アンインストール数、クラッシュ、ANR、レビュー、などなど色々なデータも含まれているんです。今回のやり方で一緒に取得できているものは、こちらにまとまっています。
:point_right:Google Play report transformation
スクリーンショット 2019-12-20 11.32.55.png

それでは、クエリエディタで、今日のキャリアとアクティブデバイスインストールの数字を取得する以下のようなクエリを試しにたたいてみます。

SELECT Carrier, Active_Device_Installs, DATE ('2019-12-16') AS _LATEST_DATE, DATE (_PARTITIONTIME) AS _DATA_DATE FROM `449658023734.firebase_download.p_Installs_carrier_MT`

そうすると、クエリ結果欄で以下のように結果が出ました。
無事にデータがBigQueryに転送されていることが確認できました:ok_hand:
(SoftBankユーザー多いな~。とかわかりますね)
スクリーンショット 2019-12-20 11.39.02.png

設定はたったこれだけで完了です。
これで、アプリのバージョンやOSによるインストール数、クラッシュ数など非常に色々な数字を毎日BigQueryに取得できるようになりました!:grinning:

おわりに

以上で、Androidアプリのインストール数の収集を、GCPのBigQueryに自動収集できるようになりました。

収集したデータの中から必要なインストール数とアンインストール数のみをスプレッドシートに出力したり、視覚的にもわかりやすいデータポータルにグラフとして出力したりすれば、常に自動更新された形いつでも見ることができるようになります。

スプレッドシートに出力するのも、データポータルに出力するのもとても簡単だったので、ぜひ試してみてください。:ok_hand:

読んでいただきありがとうございました:bow_tone2:

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

10年ぶりのAndroidアプリ開発 (1)開発環境準備編

はじめに

およそ10年ぶりにAndroidアプリを作ってみることにしたので、準備から(たどり着ければ)公開までを記事にしていきたいと思います。
私はAndroidのバージョンがまだ2の頃、少しだけアプリを作って遊んだことはありますが、その後はまったく触っていません。なので大まかなアプリの仕組みや開発の流れは分かっているつもりですが、Android Studioも触ったことがないし、Kotlinもまったく知りません。その他にも10年前と変わっていることはたくさんあると思いますので、そういった変化で戸惑ったところなど、紹介していければと思っています。

環境

  • ホストPC: Ubuntu 18.04.3 LTS
  • Android Studio: 3.5.3 for Linux 64-bit
  • デバッグに使うデバイス: 主にSHARP SH-M08 (Android 9)

Android Studioのインストール

Android Studioのインストールに従ってインストールします。ちゃんとLinux用の手順も書いてくれているのでありがたいです。Android Studioのバージョンは3.5.3でした。

まずはダウンロードページからダウンロードし、解凍した android-studio フォルダを /usr/local/ に置きます。

以下のコマンドで32bit用のライブラリをインストールします

$ sudo apt-get install libc6:i386 libncurses5:i386 libstdc++6:i386 lib32z1 libbz2-1.0:i386

android-sudio/bin/ フォルダで ./studio.sh を実行します。
ここで私の環境では下記のエラーが出ました。

$ ./studio.sh
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x00007f301898f3e6, pid=8378, tid=0x00007f2ffc2cc700
#
# JRE version: OpenJDK Runtime Environment (8.0_202) (build 1.8.0_202-release-1483-b49-5587405)
# Java VM: OpenJDK 64-Bit Server VM (25.202-b49-5587405 mixed mode linux-amd64 compressed oops)
# Problematic frame:
# V  [libjvm.so+0x4b13e6]  MarkFromRootsClosure::scanOopsInOop(HeapWord*)+0x1c6
#
# Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
#
# An error report file with more information is saved as:
# /home/atsushi/java_error_in_STUDIO_8378.log
#
# If you would like to submit a bug report, please visit:
#   http://bugreport.java.com/bugreport/crash.jsp
#
Aborted (core dumped)

とりあえずapt upgradeapt updateして再チャレンジしたらインストールできました。ただ、一度失敗した影響か、ウィザードのステップが随分と省略されてしまっているような気がしました。たぶん、必要なら後からでも設定変更できると思うのでそのまま続行することにします。

[Welcome to Android Studio] の画面右下の [Configure] の中の [Create Desktop Entry] を選択することにより、Ubuntuのアプリケーション一覧の中にAndroid Studioが表示されるようになります。

これでAndroid Studioのインストールは完了したっぽいです。

Hello World

Android Studioを起動し、[Start a new Android Studio project] を選択します。
Welcome to Android Studio_005.png

最初なので、[Empty Activity] を選んでみます。
Create New Project_006.png

[Name] を "Hello" として、[Language] が "Kotlin" になっていることを確認して [Finish] します。
Create New Project_008.png

今回はHello World程度なのでどうでもよいですが、API levelのHelpを開くと、API LevelとAndroid versionとユーザー比率が表示されるの、よいですね。古いAndroid versionを切り捨てるかどうかの判断がやりやすそうです。
Android Platform-API Version Distribution_009.png

さて、Hello Worldアプリなので、適当にラベルを配置して"Hello World"とでも表示しようと思ってactivity_main.xmlを開いたらなんと!!すでにHello World!と書かれたラベルが配置されている!!!考えが読まれている(笑)。コーディング0行でアプリができてしまいました。

エミュレータで実行

せっかくなのでこのまま進めましょう。エミュレータで実行してみます。ツールバーの[No devices]をクリックし、Open AVD Managerを開きます。
範囲を選択_010.png

[Create Virtual Device]をクリック。
Android Virtual Device Manager_011.png

個人的に欲しいと思っているPixel 3aを選んでみます。
Virtual Device Configuration_012.png

"Q"の横の"Download"をクリックし、System Imageを入手します。ライセンスを読んで同意しましょう。ダウンロードとインストールにしばらくかかります。インストールが完了したら、改めて"Q"を選択して"Next"をクリックします。
Virtual Device Configuration_013.png

あとからAVDを複数作ったときに見分けがつきやすいように、AVD Nameを適切につけておきます。もともと"Pixel 3a API 29"と入力されていたので、Android versionとの関係がわかりやすいように"(Q)"と付け加えました。
Virtual Device Configuration_014.png

"Finish"をクリックすると"Android Virtual Device Manager"の画面に戻るので、先程追加したデバイスの三角ボタン(Launch)を押すと・・
Android Virtual Device Manager_015.png

"KVM is required to run this AVD"と怒られました。そういえばさっきからずっと、"/dev/kvm is not found"ってメッセージが出てましたね。
Troubleshoot_016.png

KVMの準備

KVMって何?って感じですが、Kernel-based Virtual Machine、つまりLinux Kernel自身が仮想OSを管理するハイパーバイザとして動作するものらしいです。詳しく調べていくとこれだけで記事がいくつも書けそうですが、今回はとりあえず動かすことを優先します。ここここが参考になりました。試行錯誤しながらやったので、不要な手順も含んでしまっているかもしれません。

まず、必要なパッケージをインストールし、ユーザーグループに追加します。

$ sudo apt install -y qemu-kvm libvirt0 libvirt-bin virt-manager libguestfs-tools
$ sudo gpasswd libvirt -a [username]
$ sudo reboot

再起動のついでにBIOSに入り、Intel(R) Virtualization TechnologyとIntel(R) VT-d FeatureをEnableにします。

これで改めてAVDを起動してみると・・・

一歩進んだ感じですが、まだダメ。
Troubleshoot_017.png

KVMに対してもユーザーの追加が必要なようです。ということで以下を実行。

$ sudo adduser [username] kvm
$ sudo reboot

三度目の正直!今度こそエミュレータが起動しました。10年前はエミュレータは遅すぎて実用にならなかったですけど、今は十分使えそうです。
Android Emulator - Pixel_3a_API_29_Q_:5554_019.png

エミュレータ起動したので、意気揚々とRunボタンをクリック! Hello Worldアプリが起動しました!
Android Emulator - Pixel_3a_API_29_Q_:5554_018.png

実機で実行

続いて実機でも動作を確認します。

Androidデバイス側で 設定 > システム > 端末情報 > ビルド番号 を連打すると、開発者向けオプションが有効になるので、設定 > システム > 開発者向けオプション > USBデバッグ をONします。このあたりは昔から変わりませんね。

USBケーブルでデバイスとPCを接続したところ、Android Studioのデバイス欄に "Unknown Device" と出ました。機種名が表示されないのが気になりますが、とりあえずRunしてみます。
範囲を選択_020.png

案の定、怒られました。

01/01 19:23:46: Launching 'app' on Unknown Device.
Installation did not succeed.
The application could not be installed.
Installation failed due to: 'insufficient permissions for device: user in plugdev group; are your udev rules wrong?
See [http://developer.android.com/tools/device.html] for more information'

メッセージに従い、http://developer.android.com/tools/device.html を確認すると、adbを入れろとのこと。そりゃそうか。

$ sudo apt install adb

下記で、自分がplugdevグループに所属しているかどうかを確認。私の環境ではすでにグループに参加しているようでした。

$ groups [ユーザー名]

デバイス側で「USBデバッグを許可しますか?」のメッセージが表示されるので、OKを押します。
すると、Android Studioのデバイス欄に "SHARP SH-M08" が表示されました。
範囲を選択_021.png

改めて、Runしたらデバイスで実行できました!
68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3437303434362f39656236663563352d613562322d336562642d613064382d6132316331353636376535382e706e67.png

まとめ

KVMを導入するところで若干苦労しましたが、それ以外は比較的スムーズに進めることができました。公式のドキュメントもしっかりしているので、この先の開発もやりやすそうな予感です。
2020年の元旦から引き籠もってしまいましたが、今年はたくさんソースコード書いてたくさんアプリ作って楽しめるといいなあと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Realmのデータを確認するツール・ライブラリまとめ

Androidアプリの開発中、Realmに書き込んだデータを確認しようとしたのですが、
色々あってどれを使うかめちゃくちゃ迷ったので、調べたことをまとめておきます。
(間違いなどあればご指摘お願いします:pray:

Realm Studio

概要 :pencil:

スクリーンショット 2020-01-01 17.45.26.png

使い方 :pick:

adbコマンドを使って.realmファイルを取り出し、アプリケーション上で開いてデータを確認する。

所感 :information_desk_person:

Realmが2017年以降力を入れて開発しているサービスっぽい。
できることの幅も広く、Realm Browserの昇華版と言えそう。

Realm Browser

概要 :pencil:

スクリーンショット 2020-01-01 17.45.33.png

使い方 :pick:

adbコマンドを使って.realmファイルを取り出し、アプリケーション上で開いてデータを確認する。

所感 :information_desk_person:

2017年でアップデートが終わっているので、今後もうメンテされることはなさそう?
今から使うのならRealm Studioを選びたい気がする。

stetho-realm

概要 :pencil:

  • Stethoは、Chrome Developer Toolsを利用して、Chromeからデバッグを行うことができるAndroidのデバッグブリッジ
  • stetho-realmは、StethoをRealmに対応したライブラリ
  • GitHub: https://github.com/uPhyca/stetho-realm
  • 2017年を最後に更新されていないが、その後もコミュニティによって対応がされているらしい
  • 個人が作っている

スクリーンショット 2020-01-03 19.48.08.png

使い方 :pick:

build.gradleに依存関係を追加し、Application内で初期化、
アプリを起動後、Chrome上でデータを確認する。

所感 :information_desk_person:

個人的には一番簡単で、使いやすいと感じた(新たにアプリケーションをインストールしたり、adbコマンドを叩いたりする必要がないため)。
一点注意点としては、Realm3.7.1以上のバージョンはサポートされておらず、Realmの最新版を使ったりするとアプリがクラッシュするので、下記記事を参考にコミュニティが対応しているリポジトリを使う必要がある。
https://blog.local-c.com/archives/2496
https://stackoverflow.com/a/51381971/11934352

realm-browser

概要 :pencil:

スクリーンショット 2020-01-01 18.05.02.png

使い方 :pick:

build.gradleに依存関係を追加し、初期化、
閲覧用のActivityを呼び出して、デバイス上でデータを確認する。

所感 :information_desk_person:

デバイス上で見れるのは楽そうでいいなと思うが、参考になる記事も少ないので、ハマったときにつらそう。

結論

  • さくっと確認するなら stetho-realm が一番使いやすいと感じた(→結果これを使った)
  • これからRealm公式ツールを使うなら、Realmが推している Realm Studio を使いたい
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【ぶっちゃけAndroidアプリ開発】SQLiteデータベースにアクセスして画面に表示するサンプル

sqliteで作成したテーブルにアクセスして、Activityに表示する簡単なサンプルを作成しました。
動くサンプルコードが欲しい人のために、簡単な解説とともに載せておきます。

Android StudioでEmpty Activityで新規プロジェクト作成し、コピペすれば動くはずです。

<前提>
作成日:2020年1月1日
AndroidStudio 3.5.3
targetSdkVersion 26
CompileSdkVersion 29

・Empty Activityの雛形を元に作成。

1.データベースファイルの作成

test.csv
1,東京都庁,東京都新宿区西新宿2-8-1,03-5321-1111,metro.tokyo.jp
2,神奈川県庁,神奈川県横浜市中区日本大通1,045-210-1111,pref.kanagawa.jp
3,埼玉県庁,埼玉県さいたま市浦和区高砂3-15-1,048-824-2111,pref.saitama.lg.jp
4,千葉県庁,千葉県千葉市中央市場町1-1,043-223-2110,pref.chiba.lg.jp
5,静岡県庁,静岡県静岡市葵区追手町9-6,054-221-2455,pref.shizuoka.jp

・住所録ちっくなcsvファイルをインポートし、sqliteを使用してデータベースファイル「test.db」を作成する。
・テーブル名は「addressbook」、項目は seq(integer),name(text),address(text),phone(text),mail(text) の5つ。
・データベースファイルの作成方法は、「【ぶっちゃけAndroidアプリ開発】sqliteでデータベースを作成する」を参照。

2.データベースファイルをプロジェクトに追加する

(1)Assetsフォルダの作成

・ツールウィンドウのモジュールを右クリックして、Assetsフォルダを追加する。
スクリーンショット 2020-01-01 12.56.05.png

(2)データベースファイルの追加

・作成した「test.db」ファイルをAssetsフォルダにコピペする。
スクリーンショット 2020-01-01 17.56.20.png

3.SQLiteOpenHelperクラスの実装

・SQLiteOpenHelperを継承したクラスを実装する。
・実装例は「Y.A.M.さんのページ」を参照させていただきました。

DatabaseHelper.java
public class DatabaseHelper extends SQLiteOpenHelper {

    private static String DB_NAME = "test";
    private static String DB_NAME_ASSET = "test.db";
    private static final int DATABASE_VERSION = 1;

    private final Context mContext;
    private final File mDatabasePath;

    public DatabaseHelper(Context context) {
        super(context, DB_NAME, null, DATABASE_VERSION);
        mContext = context;
        mDatabasePath = mContext.getDatabasePath(DB_NAME);
    }

    /**
     * asset に格納したデータベースをコピーするための空のデータベースを作成する
     */
    public void createDatabase() throws IOException {
        boolean dbExist = checkDatabaseExists();

        if (dbExist) {
            // すでにデータベースは作成されている
        } else {
            // このメソッドを呼ぶことで、空のデータベースがアプリのデフォルトシステムパスに作られる
            SQLiteDatabase db = getReadableDatabase();
            db.close();

            try {
                // asset に格納したデータベースをコピーする
                copyDatabaseFromAsset();

                String dbPath = mDatabasePath.getAbsolutePath();
                SQLiteDatabase checkDb = null;
                try {
                    checkDb = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READWRITE);
                } catch (SQLiteException e) {
                }

                if (checkDb != null) {
                    checkDb.setVersion(DATABASE_VERSION);
                    checkDb.close();
                }

            } catch (IOException e) {
                throw new Error("Error copying database");
            }
        }
    }

    /**
     * 再コピーを防止するために、すでにデータベースがあるかどうか判定する
     *
     * @return 存在している場合 {@code true}
     */
    private boolean checkDatabaseExists() {
        String dbPath = mDatabasePath.getAbsolutePath();

        SQLiteDatabase checkDb = null;
        try {
            checkDb = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READONLY);
        } catch (SQLiteException e) {
            // データベースはまだ存在していない
        }

        if (checkDb == null) {
            // データベースはまだ存在していない
            return false;
        }

        int oldVersion = checkDb.getVersion();
        int newVersion = DATABASE_VERSION;

        if (oldVersion == newVersion) {
            // データベースは存在していて最新
            checkDb.close();
            return true;
        }

        // データベースが存在していて最新ではないので削除
        File f = new File(dbPath);
        f.delete();
        return false;
    }

    /**
     * asset に格納したデーだベースをデフォルトのデータベースパスに作成したからのデータベースにコピーする
     */
    private void copyDatabaseFromAsset() throws IOException{

        // asset 内のデータベースファイルにアクセス
        InputStream mInput = mContext.getAssets().open(DB_NAME_ASSET);

        // デフォルトのデータベースパスに作成した空のDB
        OutputStream mOutput = new FileOutputStream(mDatabasePath);

        // コピー
        byte[] buffer = new byte[1024];
        int size;
        while ((size = mInput.read(buffer)) > 0) {
            mOutput.write(buffer, 0, size);
        }

        // Close the streams
        mOutput.flush();
        mOutput.close();
        mInput.close();
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }
}

・createDatabaseメソッドでデータベースをプログラムで使用できるようにする。AssetsフォルダのデータベースをAndroid端末内にコピーする。(そうしないと使えないらしい)

4.MainActivityからデータベースアクセス

MainActivity.java
public class MainActivity extends AppCompatActivity {

    private DatabaseHelper helper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView textView = findViewById(R.id.textview);

        helper = new DatabaseHelper(this);
        try {
            helper.createDatabase();
        }
        catch (IOException e) {
            throw new Error("Unable to create database");
        }
        StringBuilder builder = new StringBuilder();

        SQLiteDatabase db = helper.getReadableDatabase();
        String sql = "select seq, name, address, phone, mail from addressbook";

        try {
            Cursor cursor = db.rawQuery(sql, null);
            while(cursor.moveToNext()) {
                builder.append(cursor.getInt(0) + " ");
                builder.append(cursor.getString(1) + " ");
                builder.append(cursor.getString(2) + " ");
                builder.append(cursor.getString(3) + " ");
                builder.append(cursor.getString(4) + "\n");
            }
        } finally {
            db.close();
        }
        textView.setText(builder.toString());
    }
}

・helperオブジェクトを作成する。
・createDatabaseメソッドでデータベースを使用できる状態にする。
・getReadableDatabaseメソッドで読み取り専用データベースをオープンする。
・sqlで全レコードをselectし、TextViewに表示する。
・このサンプルは読み取り専用だが、getWritableDatabaseで読み書きできるデータベースとしてオープンできる。

5.レイアウト

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

・TextViewのidに「textview」を指定してください。

6.実行結果

20200101_173927.jpg

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LIFFを更新してもAndroidでは反映されない問題に対処

概要

LIFF(LINE Front-end Framework)でとあるサービスを開発している際、ページ内容を更新しようとすると、Androidではキャッシュされしまい更新されなくなってしまった。

方法

LIFFのエンドポイントに適当なクエリパラメータを指定し、ページを更新するたびに値を変えれば良い。

スクリーンショット 2020-01-01 2.40.22.png
123を124とかに変更すると更新される。

余談

まともなLIFFのサービスってあるんですかね。知ってる人いたら教えてください。開発中って人も、

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む