20191204のAndroidに関する記事は19件です。

Androidでの位置情報取得(Android10対応)

動機

バックグラウンドで常に位置情報取得を行う必要が出てきて、探してみるもQiitaの記事は古いものばかり。
ほぼ公式のガイドを参考にしましたが、散らばっていて分かりづらい部分もあったので、ユースケース別にまとめてみます。

Case1. マップに表示するだけ

位置情報を使うアプリといえば大抵はこのユースケースでしょう。

その場合はMaps SDK for Androidを使うのがいいでしょう。現在地表示はSDK側で用意されており、SDK側が位置情報を取得、更新してくれるため、自身で現在地を取得することはないかと思います。

ただし、現在地の位置情報データ(緯度経度情報)を利用するためには、次のように自身で位置情報を取得する必要があります。1
Maps SDKにも現在地を取得する方法はありましたが、depricatedになっており、後述する方法に誘導されています。

Case2. 現在の位置情報を利用

Google Play services location APIを利用します。
Android frameworkのLocationManagerを利用する方法もありましたが、現在は非推奨になっています。

FusedLocationProviderClientlastLocation で最新の位置情報を取ることができますが、古いことがあるので、自分で更新をかけて取得するべきでしょう。(端末が再起動した場合などはnullが返ってきてしまいます。)

ほぼ公式ガイド通りですが、以下簡単に現在地を更新し、取得する方法をみていきます。

1. 位置情報更新のリクエストを行う

更新頻度や、精度の設定を行います。電池消費にも関わるので、ここはアプリによってチューニングするところかと思います。

val locationRequest = LocationRequest.create()
        .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
        .setInterval(LOCATION_REQUEST_INTERVAL)

2. 位置情報更新のコールバック作成

このコールバックを使って、現在地の Location を取得します。

val locationCallback = object : LocationCallback() {
        override fun onLocationResult(locationResult: LocationResult?) {
            locationResult ?: return
            location = locationResult.lastLocation
        }
    }

3. 現在地の更新情報をリクエストする

FusedLocationProviderClient に上記で定義したリクエストと、コールバックを渡し、位置情報更新をリクエストします。

fun startLocationUpdates() {
    // 定期的な位置情報の更新をリクエストする.
    fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, null)
}

Case3. バックグラウンドでの位置情報取得

アプリの利用中にユーザがホームボタンを押したり、アプリが表にいないときがあります。
注意しないといけないのは、android8以上であれば、バックグラウンドの位置情報の制限を受けます。

ユーザがアプリを操作してる間だけ位置情報があれば良い、もしくは、位置情報の取得自体が遅延してもいいタイプのアプリを作るのであれば、あまり気にしなくても良いですが、ユーザがアプリを操作していない場合にも位置情報を定期的に取得する必要があるアプリもあるかと思います。
ナビアプリや、トラッキング系のアプリがこれにあたるでしょう。

この場合は フォアグラウンドサービス で位置情報の更新を行います。2

[重要]android10だからといって、ACCESS_BACKGROUND_LOCATIONを求めなくてよい

android10からはより厳しくなっており、バックグラウンドで位置情報を取得する場合、 ACCESS_BACKGROUND_LOCATION が必要と言われます。このパーミッションをユーザが許可しておらず、バックグラウンドで位置情報を取ってしまうと、システムから通知が表示されてしまいます。3

image

ここで ACCESS_BACKGROUND_LOCATION をとりたくなるかと思いますが、推奨されていません。
常時位置情報を取得する必要があれば、 フォアグラウンドサービス を利用します。その際、マニフェストに android:foregroundServiceType="location" を指定します。→リファレンス

まとめ

自身がどのように位置情報を使うか、ユースケースを考えることが重要です。

  • マップに現在地を表示するだけ→map sdkだけで事足りるはず
  • 位置情報(lat, long)に合わせたデータをapiなどから取得する→fusedLocation
  • トラッキングなど、アプリが裏に行った時も常時位置情報が必要→フォアグラウンドサービス

参考


  1. 例えばユーザの現在地を元にサーバに問い合わせたり。 

  2. 公式によるとフォアグラウンドサービスとは、アプリが表にいないといけないというわけではなく、ユーザが認識できるということで、フォアグラウンドという意味みたいです。 

  3. android10がリリースされて、バックグラウンドで位置情報を取っていたアプリが、軒並みこの通知で晒されてました。 

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

Android × TDD = ?

TL; DR

  • テスト駆動開発(Test-Driven Development: TDD)
  • 純粋なロジック(モデル)を頭の中で切り出し、まずは空っぽのclass定義する
  • JUnitテスト作成(androidTestじゃないよ)
  • 「テスト実装→モデル側の中身を実装」を繰り返す
  • テストコードに悩んだら、直感で!簡単に!とりあえず書く!
  • テストは網羅性より仕様、設計の確認
  • 慣れるとテスト無しでは生きられない

テストコード書いてますか?

「書きたいと思っているが、なかなか難しい」が正直な現状でしょうか。
筆者も何度もトライし、何度も挫折しています。
「この記事(Daggerでテストは楽になるか?)」も結構頑張って考えたのですが、UIの凄まじい変化のスピードに耐えられませんでした。

さて、どうやったら無理なく習慣化出来るのか。

答え : TDD × Checking

弊社内のモバイルチームでは、日々、振り返りと議論と励まし合い(?)ながら開発に取り組んでいます。
とある日、上記の悩みを話していると「Checking」というキーワードが出てきました。
(僕以外のメンバーは比較的テストコードに明るい...)

今までは「テストコードは品質重視だ!網羅性だ!」と息巻いていたのですが、それは「Testing」という考え方らしく、開発コストが重い。

それに対し、「Checking」は「仕様、設計を確認する事を重視する(ざっくり)」なのだとか。
テストコード検討を軽くできるし、テストコードと同時に設計も行う事ができる。

本当だとしたら、すごい。

補足:TDDとは?

以下のサイクルを高速で回すことだそう。

  1. まずテストコードを書く(実装側より先に。当然、失敗する)
  2. 成功する最低限の実装を書く
  3. テストが通ったら、リファクタリングして綺麗にする(メンテナビリティ確保)

テストがあるので安全にリファクタリングできる事が大きなメリットですね。

方針 : UIのテストは諦め、純粋なロジックのテストを行う

では「TDD × Checking」をどうやって進めていくか。
結論は「まずビジネスロジック(モデル、ドメイン)のテストをやってみよう」でした。

UIが絡まなければ陳腐化も遅いだろうという点と、UI系のテストの環境構築やテストコード作成が重いという理由から。

具体的には、RobolectricもEspressoも使わず、JUnitのみ、つかいます。
境界値や網羅性にはこだわらず、仕様、設計の確認を主目的に据えてスタートです。

TDDの為の8つのstep

では、ここから具体的な環境と方法をケーススタディでご紹介します。

モデルの分離→テストコード実装→モデル実装→テストコード実装...のループを実演していきます。

少々長くて大変ですが、慣れてしまえば無意識に出来るようになります!

環境

  • JUnit4
  • Assertj

step 1 / 8 : ビジネスロジック(モデル)を分離する

新規実装する機能をターゲットにし、純粋なロジックを抽出します。
もし可能ならばビジネスロジック専用のモジュールプロジェクトを追加して、そこにクラスを配置しましょう。
そうすると、テスト時のビルド範囲が極小になり、一瞬でビルド&テストが出来て恩恵を最大限に享受できます。

マルチモジュール化については「こちら(マルチモジュール化したりビルド時間を短くするコツ)」をご参考にどうぞ。
リンク先の記事の「domain_core」だけをプロジェクトに追加すると良いです。

ケーススタディ : どうやってモデル分離するの?

例として、通信中のローディング表示機能からモデルを分離してみます。

基本機能
ローディング表示は、通信の開始と同時に行い、完了したら非表示にする

これだけならばシンプルですね。
しかし、企画チームから追加要求がきます。

追加要求
アプリのパフォーマンス向上のため、複数の通信を同時並行で行う

こうなると、通信開始は同時ですが、終了はバラバラ&どの通信が先に終わるのか分からない。
どれが実行中でどれが完了かを制御しなければならないですね。
どうします?

モデル分離
通信開始時にカウントアップ(add)、完了時にカウントダウン(remove)する。
カウントが0になったら完了通知(complete)を出す。

もっと良い設計はあると思います!あくまで例です!
このモデル(以後、TaskCounter)があれば、通信の制御とUIが分離できます。
各々の通信機能の開始時にadd&ローディング表示し、各々の完了時にremoveし、completeが来たらローディング非表示にする。

準備はできました。次のステップ行きましょう。

step 2 / 8 : 空っぽのクラスを定義する

モデル
class TaskCounter {
}

なんと、これでおしまい。
コツは「モデルの実装はテストコードより後に!」を意識すること。
この時点でテストコードがないので、ロジック実装できないという理屈です。

step 3 / 8 : 空っぽのテストクラスを定義する

テストコード
class TaskCounterTest {
}

おしまい。ここまでは、簡単ですよね?
※テストクラスの作成方法が分からない方は、すみません、ご自身でお調べ頂ければと思います。

step 4 / 8 : TaskCounterの初期値を考える

なにはともあれ、まずは初期値ですよね。
きっと「0かな?」と思うでしょう。
そう、それをそのままテストに書いてください。

テストコード
class TaskCounterTest {
    @Test
    fun initialCount() {
        ProgressTaskCounter().run {
            assertThat(count).isEqualTo(0)
        }
    }
}

これも簡単ですね。
次にテストを実行します。
まぁ失敗(ビルドエラー)しますよね。なにせ、モデルは空っぽなのですから。
ここでいよいよ、モデル側を実装します。

モデル
class TaskCounter {
    var count: Int = 0
}

おしまい。1ケース完成です。

step 5 / 8 : カウントアップ(add)を考える

はい、モデル側を実装したくなりますが、グッとこらえてテストコードを書きます。

テストコード
class TaskCounterTest {

    (...中略...)

    @Test
    fun add() {
        ProgressTaskCounter().run {
            add()
            assertThat(count).isEqualTo(1)
        }
    }
}

これでおしまい。
なぜなら、「初期値0」のテストが成功しているので、「addしたらcount=1」となればインクリメントが正常に機能することが立証されます。
これ以上のテストは要りません。

さて、実装側も書きます。

モデル
class TaskCounter {

    (...中略...)

    fun add() {
        count++
    }
}

step 6 / 8 : カウントダウン(remove)を考える

まずはテストコードから。

テストコード
class TaskCounterTest {

    (...中略...)

    @Test
    fun remove_when_count_is_1() {
        ProgressTaskCounter().run {
            add()
            remove()
            assertThat(count).isEqualTo(0)
        }
    }

    @Test
    fun remove_when_count_is_0() {
        ProgressTaskCounter().run {
            remove()
            assertThat(count).isEqualTo(0)
        }
    }
}

addされたカウンタが、removeでデクリメントされている事が確認できました。
また、カウンタが負の値にならない事も確認できました。
続いて、モデル側です。

モデル
class TaskCounter {

    (...中略...)

    fun remove() {
        count = (count--).coerceAtLeast(0)
    }
}

着々と完成してきましたね。
お気づきでしょうか。

設計と実装と動作確認が同時にできています。

しかも、ビルド時間も普通にアプリ起動して所定の操作をするより何十倍も早いはず。

ここまで、さほど難しい事はしていないと思います。
そう、TDDは頑張る必要は全くないのです。むしろ、楽をする為なのです。

step 7 / 8 : 完了通知(complete)を考える

例によって、まずはテストコード。
そろそろ慣れて来ました?

テストコード
class TaskCounterTest {

    (...中略...)

    @Test
    fun complete() {
        var testSuccess = false
        ProgressTaskCounter().run {
            complete = {
                testSuccess = true
            }
            add()
            remove()
            assertThat(testSuccess).isTrue()
        }
    }
}

カウンタが0になるとcompleteが呼ばれる事が確認できました。
続いてモデル側。

モデル
class TaskCounter {

    (...中略...)

    var complete: () -> Unit = {}

    (...中略...)

    fun remove() {
        count = (count--).coerceAtLeast(0)
        if (count == 0) {
            complete()
        }
    }
}

これでadd、remove、complete、全て揃いましたね。

普通に実装&試験をするより、だいぶ時間が節約出来たのではないでしょうか。
次で最後です。

step 8 / 8 : 残業せず帰宅し、たっぷり栄養と睡眠を取る

半分冗談、半分本気です!
前述で示せたかと思いますが、開発は気合いで何とかなるものではなく、かつ、かかった時間と成果が比例するものでもありません。

TDDで仕事が早く終わったから、別の仕事をするのも結構ですが、得てしてエンジニアは多忙であるもの。
たっぷり栄養と睡眠を取ってパフォーマンスを最大限に発揮する。
私も上司からよく言われますが、休む事も重要な仕事です

いかがでしたでしょうか

今回はシンプルな例を選びました。
実際は、マルチスレッドだったり非同期コールバックだったりと、様々なモデルがあると思います。
弊社において、その際はテストにcoroutineを用いて待ち合わせを行ない、モデル側もcoroutineに書き換える様にしています。
機会があれば、その辺りもご紹介できればと思います。

Android × TDD = あなたには何が見えましたか?

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

Android × TDD

TL; DR

  • テスト駆動開発(Test-Driven Development: TDD)
  • 純粋なロジック(モデル)を頭の中で切り出し、まずは空っぽのclass定義する
  • JUnitテスト作成(androidTestじゃないよ)
  • 「テスト実装→モデル側の中身を実装」を繰り返す
  • テストコードに悩んだら、直感で!簡単に!とりあえず書く!
  • テストは網羅性より仕様、設計の確認
  • 慣れるとテスト無しでは生きられない

テストコード書いてますか?

「書きたいと思っているが、なかなか難しい」が正直な現状でしょうか。
筆者も何度もトライし、何度も挫折しています。
「この記事(Dagger2を使ったらUnitTestが楽になるか考えてみた)」も結構頑張って考えたのですが、UIの凄まじい変化のスピードに耐えられませんでした。

さて、どうやったら無理なく習慣化出来るのか。

答え : TDD × Checking

弊社内のモバイルチームでは、日々、振り返りと議論と励まし合い(?)ながら開発に取り組んでいます。
とある日、上記の悩みを話していると「Checking」というキーワードが出てきました。
(僕以外のメンバーは比較的テストコードに明るい...)

今までは「テストコードは品質重視だ!網羅性だ!」と息巻いていたのですが、それは「Testing」という考え方らしく、開発コストが重い。

それに対し、「Checking」は「仕様、設計を確認する事を重視する(ざっくり)」なのだとか。
テストコード検討を軽くできるし、テストコードと同時に設計も行う事ができる。

本当だとしたら、すごい。

補足:TDDとは?

以下のサイクルを高速で回すことだそう。

  1. まずテストコードを書く(実装側より先に。当然、失敗する)
  2. 成功する最低限の実装を書く
  3. テストが通ったら、リファクタリングして綺麗にする(メンテナビリティ確保)

テストがあるので安全にリファクタリングできる事が大きなメリットですね。

方針 : UIのテストは諦め、純粋なロジックのテストを行う

では「TDD × Checking」をどうやって進めていくか。
結論は「まずビジネスロジック(モデル、ドメイン)のテストをやってみよう」でした。

UIが絡まなければ陳腐化も遅いだろうという点と、UI系のテストの環境構築やテストコード作成が重いという理由から。

具体的には、RobolectricもEspressoも使わず、JUnitのみ、つかいます。
境界値や網羅性にはこだわらず、仕様、設計の確認を主目的に据えてスタートです。

TDDの為の8つのstep

では、ここから具体的な環境と方法をケーススタディでご紹介します。

モデルの分離→テストコード実装→モデル実装→テストコード実装...のループを実演していきます。

少々長くて大変ですが、慣れてしまえば無意識に出来るようになります!

環境

  • JUnit4
  • Assertj

step 1 / 8 : ビジネスロジック(モデル)を分離する

新規実装する機能をターゲットにし、純粋なロジックを抽出します。
もし可能ならばビジネスロジック専用のモジュールプロジェクトを追加して、そこにクラスを配置しましょう。
そうすると、テスト時のビルド範囲が極小になり、一瞬でビルド&テストが出来て恩恵を最大限に享受できます。

マルチモジュール化については「こちら(マルチモジュール化したりビルド時間を短くするコツ)」をご参考にどうぞ。
リンク先の記事の「domain_core」だけをプロジェクトに追加すると良いです。

ケーススタディ : どうやってモデル分離するの?

例として、通信中のローディング表示機能からモデルを分離してみます。

基本機能
ローディング表示は、通信の開始と同時に行い、完了したら非表示にする

これだけならばシンプルですね。
しかし、企画チームから追加要求がきます。

追加要求
アプリのパフォーマンス向上のため、複数の通信を同時並行で行う

こうなると、通信開始は同時ですが、終了はバラバラ&どの通信が先に終わるのか分からない。
どれが実行中でどれが完了かを制御しなければならないですね。
どうします?

モデル分離
通信開始時にカウントアップ(add)、完了時にカウントダウン(remove)する。
カウントが0になったら完了通知(complete)を出す。

もっと良い設計はあると思います!あくまで例です!
このモデル(以後、TaskCounter)があれば、通信の制御とUIが分離できます。
各々の通信機能の開始時にadd&ローディング表示し、各々の完了時にremoveし、completeが来たらローディング非表示にする。

準備はできました。次のステップ行きましょう。

step 2 / 8 : 空っぽのクラスを定義する

モデル
class TaskCounter {
}

なんと、これでおしまい。
コツは「モデルの実装はテストコードより後に!」を意識すること。
この時点でテストコードがないので、ロジック実装できないという理屈です。

step 3 / 8 : 空っぽのテストクラスを定義する

テストコード
class TaskCounterTest {
}

おしまい。ここまでは、簡単ですよね?
※テストクラスの作成方法が分からない方は、すみません、ご自身でお調べ頂ければと思います。

step 4 / 8 : TaskCounterの初期値を考える

なにはともあれ、まずは初期値ですよね。
きっと「0かな?」と思うでしょう。
そう、それをそのままテストに書いてください。

テストコード
class TaskCounterTest {
    @Test
    fun initialCount() {
        ProgressTaskCounter().run {
            assertThat(count).isEqualTo(0)
        }
    }
}

これも簡単ですね。
次にテストを実行します。
まぁ失敗(ビルドエラー)しますよね。なにせ、モデルは空っぽなのですから。
ここでいよいよ、モデル側を実装します。

モデル
class TaskCounter {
    var count: Int = 0
}

おしまい。1ケース完成です。

step 5 / 8 : カウントアップ(add)を考える

はい、モデル側を実装したくなりますが、グッとこらえてテストコードを書きます。

テストコード
class TaskCounterTest {

    (...中略...)

    @Test
    fun add() {
        ProgressTaskCounter().run {
            add()
            assertThat(count).isEqualTo(1)
        }
    }
}

これでおしまい。
なぜなら、「初期値0」のテストが成功しているので、「addしたらcount=1」となればインクリメントが正常に機能することが立証されます。
これ以上のテストは要りません。

さて、実装側も書きます。

モデル
class TaskCounter {

    (...中略...)

    fun add() {
        count++
    }
}

step 6 / 8 : カウントダウン(remove)を考える

まずはテストコードから。

テストコード
class TaskCounterTest {

    (...中略...)

    @Test
    fun remove_when_count_is_1() {
        ProgressTaskCounter().run {
            add()
            remove()
            assertThat(count).isEqualTo(0)
        }
    }

    @Test
    fun remove_when_count_is_0() {
        ProgressTaskCounter().run {
            remove()
            assertThat(count).isEqualTo(0)
        }
    }
}

addされたカウンタが、removeでデクリメントされている事が確認できました。
また、カウンタが負の値にならない事も確認できました。
続いて、モデル側です。

モデル
class TaskCounter {

    (...中略...)

    fun remove() {
        count = (count--).coerceAtLeast(0)
    }
}

着々と完成してきましたね。
お気づきでしょうか。

設計と実装と動作確認が同時にできています。

しかも、ビルド時間も普通にアプリ起動して所定の操作をするより何十倍も早いはず。

ここまで、さほど難しい事はしていないと思います。
そう、TDDは頑張る必要は全くないのです。むしろ、楽をする為なのです。

step 7 / 8 : 完了通知(complete)を考える

例によって、まずはテストコード。
そろそろ慣れて来ました?

テストコード
class TaskCounterTest {

    (...中略...)

    @Test
    fun complete() {
        var testSuccess = false
        ProgressTaskCounter().run {
            complete = {
                testSuccess = true
            }
            add()
            remove()
            assertThat(testSuccess).isTrue()
        }
    }
}

カウンタが0になるとcompleteが呼ばれる事が確認できました。
続いてモデル側。

モデル
class TaskCounter {

    (...中略...)

    var complete: () -> Unit = {}

    (...中略...)

    fun remove() {
        count = (count--).coerceAtLeast(0)
        if (count == 0) {
            complete()
        }
    }
}

これでadd、remove、complete、全て揃いましたね。

普通に実装&試験をするより、だいぶ時間が節約出来たのではないでしょうか。
次で最後です。

step 8 / 8 : 残業せず帰宅し、たっぷり栄養と睡眠を取る

半分冗談、半分本気です!
前述で示せたかと思いますが、開発は気合いで何とかなるものではなく、かつ、かかった時間と成果が比例するものでもありません。

TDDで仕事が早く終わったから、別の仕事をするのも結構ですが、得てしてエンジニアは多忙であるもの。
たっぷり栄養と睡眠を取ってパフォーマンスを最大限に発揮する。
私も上司からよく言われますが、休む事も重要な仕事です

いかがでしたでしょうか

今回はシンプルな例を選びました。
実際は、マルチスレッドだったり非同期コールバックだったりと、様々なモデルがあると思います。
弊社において、その際はテストにcoroutineを用いて待ち合わせを行ない、モデル側もcoroutineに書き換える様にしています。
機会があれば、その辺りもご紹介できればと思います。

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

Androidでリン○フィットアドベンチャー

おはようございます. はじめまして.
NTTドコモ 先進技術研究所 1年目社員 實成です.
僕は社内でも自分の時間が取りやすい部署[要出典]で働いています.
なので,普段の業務では取り扱っていないテーマの記事にしました.バラエティ重視!!

普段の業務はこんなことしてます.
(ちょうど3日前にまとまった進捗を報道発表する機会がありました.)
僕は上記の解析担当をメインでやっています.

背景

入社してから

大学生のときは週2でテニスをしていましたが,社会人になってからは一切運動をしなくなりました.
せめて家でできる運動はしたい と考えて入社早々に腹筋ローラーを買うもこれだけだとすぐ飽きます.
世間では運動不足解消が流行っていて,こんなアニメも流行りました.
運動したい欲に対して満足のいく回答がないまま生きていた僕はリングフィットアドベンチャーに出会いました.

リングフィットアドベンチャーとは??

公式動画
「Nintendo Switchで冒険しながらフィットネス」というキャッチフレーズのゲームです.
身体に1つセンサーを,コントローラに1つセンサーを装着して全身がコントローラーとなる新感覚ゲーム.
2つのセンサーが腕や脚やお腹などの動きを認識し,ゲームの中のキャラクターを自在に操ることが可能.
ファンタジー世界を己の身体1つで冒険しながら,スリムで引き締まったボディを目指すゲームです.

任天堂は過去にもWiiFitシリーズを製作し,見事ゲームと運動を結びつけています.
僕も過去には大人から「ゲームばかりして」という言葉を頂いていましたが,今となってはそれは無縁.
加えて,最近のゲームは操作が難しく,遊ぶ人を選んでしまっている要素が増えてきてもいると感じています.
その点,直感的な操作が可能なこのゲームは,誰とでも一緒に遊ぶことが可能です.
これによって,家族や友達との仲を繋ぐ,素晴らしい側面も併せ持っていると考えています.
ぜひこれからもこのようなシリーズが続いていって欲しいです.

勘違い

  • リングフィットアドベンチャーをやりたい
  • でもSwitch買うまでのモチベはない

    • 品薄らしく,中古で13000円する(友人談)
    • 調べたら予想以上だった.... スクリーンショット 2019-12-08 2.18.31.png
  • 加速度センサーあれば作れそうじゃね?

    • 手元にはAndroid端末が都合よく転がっていました

よくある勘違いでした.
リングフィットアドベンチャーは直感的な操作であるため,
3日ぐらいで実装できて良い小ネタになると考え,このタイトルでAdCを投稿すると宣言しました.

準備

最初はTwitterでバズった動画しか見てませんでした.
これが盛大な勘違いの主因であり,実装中に上記の公式動画を見たときに
クオリティの高さ(選べる運動の数の多さ)に驚きました. 流石任天堂.

実装

Android版リングフィットアドベンチャーの製作方法概要を書きます.
用いるセンサーは加速度センサのみです.
本来,正確な自己位置推定には地磁気センサーも必要なのですが,そこまで正確に求めなくても
動作検知は可能だと感じたので今回は取り扱いませんでした.
センサー情報を取得するViewとスタート画面や操作説明などViewの2種類を行き来しています.
センサー情報の取得はOnSensorChangedで逐一取得する簡単な実装です.

現状,スクワットと腿上げと腰回しの3種類のトレーニングを実装しています.
各トレーニングの1回行ったという判定処理がif文のオンパレードになってしまっています.
もっと効率よく書けたら良かったなと感じています.

demo

プレイ中の自分も撮影したのですが,自分の顔が写ってしまったので,アップできませんでした.
Qiitaは動画を上げられないので,無理矢理.gifに変換しました.
output.gif

コード

例外,効率一切度外視のパワー系実装です.すみません.
1億と2千年ぶりにJava書いたら全然分からなかったです.
コードはこちら

今後の予定

  • OpenHouse2020に出します.
    • もしよかったら遊びに来てください
  • OpenPoseを使用した実装に切り替えるかもしれません.

結論

  • 任天堂しゅごい.(見た目簡単そう≠実装簡単.)
  • デザイン系のお仕事の人しゅごい
    • 画面遷移やボタン配置などのゲーム設定.レイアウト設定に一番時間がかかった.

余談

  • 締め切り駆動型RTAは他人に迷惑をかける.
  • 本当にすみませんでした!!! スクリーンショット 2019-12-08 2.08.53.png
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

アプリのコンポーネント(サービス, ブロードキャスト レシーバ)について

概要

androidの開発を始めた頃につまずいた箇所をAdevent Calenderを機に振りかえりたいと思い記事にしました。

サービス
ブロードキャストレシーバ

サービス

サービスの概要
サービスはバックグラウンドで動作するクラスで、バックグラウンドでのデータダウンロードや音楽の再生などに使われます。
過去のアプリではバックグラウンドで歩数を取得しごにょごにょするなんてこともサービスでしてました。

サービスには以下の3種類があります。

  • フォアグラウンドサービス
  • バックグラウンドサービス
  • バインドされたサービス

例:サービス起動中に通知を表示する

AndroidManifest.xml
<manifest>
...
    // 追加
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

    <application
    ...
    >
      ...
      // 追加
      <service android:name=".service.ForegroundService" />
    </application>
</manifest>
ForegroundService.kt
class ForegroundService : Service() {
    // `bindService`で呼び出した場合呼ばれる
    override fun onBind(intent: Intent?): IBinder? {
        return null
    }
    // サービスで実行する処理
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {

        /* 通知の作成など ... */
        // `startForeground`をサービス起動から5秒以内に呼び出さないといけない
        startForeground(notificationId, builder.build())
        return START_NOT_STICKY
    }
}
MainActivity.kt
// 起動
val intent = Intent(this, ForegroundService::class.java)
if (Build.VERSION.SDK_INT >= 26) {
    startForegroundService(intent)
} else {
    startService(intent)
}
MainActivity.kt
// 停止
val intent = Intent(this, ForegroundService::class.java)
stopService(intent)

ブロードキャストレシーバ

ブロードキャストの概要
参考にした記事

システムイベントの受信や位置情報の変更や機内モードへの変更を検知したりできます。

例: 機内モードのon/offを検知

AndroidManifest.xml
<manifest>
...
    <application
    ...
    >
        ...
        // 追加
        <receiver android:name=".AirplaneModeChangedReceiver">
            <intent-filter>
                <action android:name="android.intent.action.AIRPLANE_MODE" />
            </intent-filter>
        </receiver>
    </application>
</manifest>
AirplaneModeChangedReceiver.kt
class AirplaneModeChangedReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        if (Intent.ACTION_AIRPLANE_MODE_CHANGED == intent?.action) {
            val toast = Toast.makeText(context, "AirplaneMode", Toast.LENGTH_SHORT)
            toast.show()
        }
    }
}
MainActivity.kt
// 登録
val receiver = AirplaneModeChangedReceiver()
val filter = IntentFilter().apply {
    addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED)
}
registerReceiver(receiver, filter)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Androidアプリ入門】内部ストレージに保存・読込する♪文字化け解消

【Androidアプリ入門】内部ストレージに保存・読込する♪ハマった編で日本語等が文字化けしていましたが、以下のコードで文字化け解消しましたm(__)m

R.id.btClick1 -> {
                    var temp=""
                    var pathUtf8 =  getFilesDir().getAbsolutePath(); 
                    temp = File(pathUtf8+"/testfile.txt").readText(Charsets.UTF_8)

                    output.text = df.format(date) + "\n"  + temp
                    input.setText("")
                }
1 2 3
honjitu2.jpg ihaveanapple.jpg kaochai2.jpg

まとめ

・すっきりできた
・情報が多いので迷ってしまった

・DBや追記が出来ていないのでやろうと思う

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

プロフィール画面で見るアレやってみたい(CoordinatorLayout)

おはようございます!
CA Tech Dojo/Challenge/JOB Advent Calendar 2019(長ぇ)
の4日目を担当をさせていただきます、zoothezooです!!

夏休みにDojo,Challengeに参加し、
12月から(厳密には明日)TechJOBに挑戦します。
では、参りましょう。

概要

プロフィール画面でよく見るような上にスクロールすると、
ヘッダー画像がいい感じに消えていく??やつを作りたいと思います。

理解し難い実装たくさんあると思いますが、
お手柔らかにお願いしますね。

これです。

実装

  • activity_main.xml (かなり省略しています)
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
    android:fitsSystemWindows="true">

    <com.google.android.material.appbar.AppBarLayout
        android:fitsSystemWindows="true"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <com.google.android.material.appbar.CollapsingToolbarLayout
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            app:layout_anchor="@+id/appbar"
            app:layout_anchorGravity="center"
            app:title="Profile">

            <ImageView
                android:minHeight="100dp"
                android:scaleType="centerCrop"
                app:layout_collapseMode="parallax"
                android:src="@drawable/curry"
                />

            <androidx.appcompat.widget.Toolbar
                app:layout_collapseMode="parallax"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
                />

        </com.google.android.material.appbar.CollapsingToolbarLayout>
    </com.google.android.material.appbar.AppBarLayout>

    <include
        layout="@layout/basic_profile"
        app:layout_anchor="@id/appbar"
        app:layout_anchorGravity="bottom|center"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

  • basic_profile.xml (中身は自由に書いてください)
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout>
    <androidx.core.widget.NestedScrollView
        android:fitsSystemWindows="true">
    </androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

各要素について

以下の要素が重要になります。

Coordinatorlayout

  • スーパークラス : FrameLayout
    • Constraintlayoutばっかり使ってたから慣れなかった
  • 複数の子ビューとの相互作用のコンテナ

AppBarLayout

  • CoordinatorLayoutの直下に配置
  • 垂直方向のスクロールができる
  • nestedscrollviewやrecyclerview

CollapasingToolbarLayout

  • AppBarLayoutの直下に配置
  • ↑をCoordinatorLayout内に配置しないと、 CollapsingToolbarLayoutのスクロールなどの機能は使えなさそう
  • collapseModeについて
    • parallax,pin,(なし)がある
    • parallaxは、視差方式でスクロールするらしい(英語難しかった)

まとめ

僕はかなりこれを実装できた時感動したんですけど...(めっちゃ調べたし、バグるし)

ユーザとして使う分にはすげぇ程度に終わってしまいますが、
実装してみるとやっぱりエンジニアの人ってすげぇ(!!!!!!!)ってなりますよね

よかったらgithubに載せてますので見てください。

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

Androidを開発する上で参考にしているもの

この記事は第二のドワンゴ Advent Calendar 2019の5日目の記事です。

ドワンゴではN予備校Androidアプリを開発しているのですが、その中で実装で困ったときに参照している資料や、新しい技術についていくための情報収集などをまとめたいと思います。

Androidを開発する上で参考にしているもの

公式アプリのソースコード

Androidの有名なカンファレンスとしてAndroid Dev SummitDroidKaigiなどがありますが、そのイベントで作成されているAndroidアプリのコードがとても参考になります。

Android Dev Summitアプリのソースコード
Github google/iosched

DroidKaigiアプリのソースコード
Github DroidKaigi/conference-app-2019

Android Dev Summitは世界で最も大きなAndroid開発者のイベントですし、DroidKaigiも国内最大級のイベントなので、そこで使用されるAndroidアプリはとても素晴らしいものであるはず!と信じています。
(実際にコミット数も多く、コミッターにも著名な方が多いですし。)

使用しているライブラリやその使い方などを参考にしたり、実装で困ったらコード検索をして拝借したりしています。

ちなみに最近だと、xmlのImageViewのcontentDescriptionで何を指定するか悩んで調べたら、@nullで逃げられることがわかったの良かったです。

<ImageView
    android:id="@+id/reserved_icon"
    android:contentDescription="@null"

参照:https://github.com/google/iosched/blob/89df01ebc19d9a46495baac4690c2ebfa74946dc/mobile/src/main/res/layout/item_feed_session.xml#L58

Google Codelabs

全くの新しい技術を触るときには、Google Codelabにあるコースを実際にやってみて、その使い方や利用背景を学んでいます。

Androidに関連するあらゆる要素についてのコースが用意されていて、内容は英語ですが説明が丁寧で情報も最新に即しているので、教材としてはとても素晴らしいです。

具体的には、以下のようなコースが用意されています。

KotlinでAndroidアプリを作る入門コース

Android StudioでAndroidのアプリを立ち上げるところから、Layoutの組み方、Navigationによる画面遷移、ViewModelによるデータの取り扱い、Room DBにデータを格納する方法など、その内容はAndroidの基本を網羅しています。
Android Kotlin Fundamentals Course

Androidの応用的な内容を扱うコース

Androidアプリにおける通知やアニメーションの実装、地図の扱い、テストを書く、ログインを実装するなど、応用的な内容を学習します。
ここまで完璧にできていれば、Android開発者として就職には困らなさそう。
Advanced Android in Kotlin

Jetpack Composeを一足先に体験するコース

おそらく来年にはリリースされるであろうJetpack Composeの基本を学ぶことができます。
AndroidにおけるViewの作成が一気に変わっていきそうです。
Jetpack Compose Basics

その他多数

Using Dagger in your Android app
Android Data Binding
Jetpack Navigation
Using Kotlin Coroutines in your Android App
Taking Advantage of Kotlin

RxMarbles

N予備校Androidアプリでは、非同期処理にRxJavaを使用しており、そのイメージのためにRxMarblesをよく参照しています。

スクリーンショット 2019-12-04 18.19.15.png

ReactiveXのドキュメントにも説明は色々と書かれているのですが、RxMarblesのほうが自分でMarbleを動かせて直感的にわかりやすいので、困ったときには参照しています。

Androidテスト全書

Androidの技術も変化が激しいのでなかなか書籍での学習は難しく感じるのですが、Androidテスト全書は断片的に散らばっているテストの知見を体系的にまとめてくれていたので、とても参考になりました。

単体テスト、統合テスト、UIテストそれぞれの実行のやり方や、テストライブラリの選定基準、CIへの組み込みと運用まで書かれていて、開発の参考にさせていただきました。

project005_ogimage.jpg

また、Andrid全書が書かれたPEAKSには、他にもクラウドファウンディングで需要が認められた書籍が何冊かあるので、Androidに関連が高いものについては参照していこうかなと思います。

Android Dagashi

本来は、色々なメディアの記事を見るべきかと思いますが、重要な情報はAndroid Dagashiに流れてくるので、そこでリリース情報や技術トレンドなどを見ています。

Kotlinのリリース内容や、Android Jetpackまわりのアップデート内容、Deprecatedになるライブラリ情報や今後のAndroidに関する記事などが紹介されているので、とりあえずここを追えていれば問題ないと思います。


また新しいものが増えたら、随時更新していきます。

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

[Android]簡単な自作Viewを作った

はじめに

簡単な自作Viewや独自Viewの作り方を初心者向けに説明していきたいと思います。
この記事で紹介する内容は下記に該当する方向けになっています。

主なターゲット層

・Androidアプリ初心者の方

・自作Viewを作ったことがない方

・難しい言い回しをすっ飛ばして、自作Viewを簡単に作ってみたい方

自作Viewを作る利点

今回の自作Viewを作る利点は以下のとおりです。

・独自にアレンジされた仕様のViewを使える点

・複数のViewで処理をする仕様をひとかたまりにして使える点

たとえば本来はできない、ButtonなどにClickListenerを複数追加することで自分なりにアレンジできる点など、その他もろもろの利点があると思います。自作Viewに取り組んだことがない方は是非取り組んでみてください。

自作View作成の手順

自作Viewの作り方は様々で色々な作り方があるっぽいのですが、今回はできるだけシンプルで楽に作れる方法で紹介していきたいと思います。

まずは自作View作成の大まかな流れを紹介します。

流れとしては、

自作のViewのレイアウトをxmlで作成(フォルダーは/res/layout)
     ↓
作成したxmlに機能を加えるJava or Kotlinクラスの作成
     ↓
MainActivityで使用

上記の手順で元々あるViewたちを組み合わせたorアレンジしたオリジナルViewを作成していきます。

今回作成していく自作ViewはTextViewとButtonを組み合わせた以下のものを作成していきます。
2FK9Cm1egITPNd7lZMXQ1575436894-1575437078.gif

手順その1 自作Viewのレイアウトをxmlで作成

まずは/res/layout/フォルダーで自作Viewのレイアウトを作成していきます。
今回使うのはTextViewとButtonです。これらをLinearLayoutで囲んでいきます。

my_original_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/my_original_view_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/green_message"
        android:textColor="@android:color/black"
        android:layout_marginBottom="20dp"/>

    <Button
        android:id="@+id/my_original_view_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimary"
        android:text="@string/button_text"
        android:textColor="@android:color/white"/>

</LinearLayout>

これで手順1は以上です。このxmlが一つのViewとして成り立つことになります。

手順1は普段使っている感じに作っていきました。そこまで特殊ではありませんね。

手順その2 作成したxmlに機能を加えるJava or Kotlinクラスの作成

ここから普段しないような書き方をしていきます。
手順1を終えた後はJava or Kotlinの新たなクラスを作成していきます。今回はMyOriginalView.ktを作成します。
そして、作成したクラスに先ほど手順1の親ViewのLinearLayoutを継承させます。

MyOriginalView.kt
// 自作Viewクラス
class MyOriginalView(context: Context,
                     attributeSet: AttributeSet): LinearLayout(context, attributeSet) {

}

引数の部分ですが、基本的にContextとAttributeSetでなんとかなると思いますが、他のサイトを色々みているとそれ以外のコンストラクタも必要だったりする場合があるそうです。これで動かなかった時は他のサイトに飛んでみてください。

そして初期化時に、手順1のレイアウトファイルを割り当てます。

MyOriginalView.kt
// 自作Viewクラス
class MyOriginalView(context: Context,
                     attributeSet: AttributeSet): LinearLayout(context, attributeSet) {
    init{
        // このクラスに対応するViewを設定する : my_original_view.xml
        LayoutInflater.from(context).inflate(
            R.layout.my_original_view, this, true
        )
    }
}

これでMainAcitvityで使えるようになりました。これにTextViewとButtonの挙動を書いていきます。

最終的にこのような感じにしました。

MyOriginalView.kt
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.LinearLayout
import androidx.core.content.ContextCompat
import kotlinx.android.synthetic.main.my_original_view.view.*

// 自作Viewクラス
class MyOriginalView(context: Context,
                     attributeSet: AttributeSet) : LinearLayout(context, attributeSet) {

    private var buttonChecker = false

    init{

        // このクラスに対応するViewを設定する : my_original_view.xml
        LayoutInflater.from(context).inflate(
            R.layout.my_original_view, this, true
        )
        setOnButtonClickListener()
    }

    private fun setOnButtonClickListener(){

        // my_original_view.xml内にあるViewはそのViewのidで取得する
        my_original_view_button.setOnClickListener {
            if(!buttonChecker) {
                it.setBackgroundColor(
                    ContextCompat.getColor(
                        context, R.color.colorAccent
                    )
                )

                // my_original_view.xml内にあるViewはそのViewのidで取得する
                my_original_view_text.text = context.getString(R.string.red_message)
                buttonChecker = true
            }
            else {
                it.setBackgroundColor(
                    ContextCompat.getColor(
                        context, R.color.colorPrimary
                    )
                )

                // my_original_view.xml内にあるViewはそのViewのidで取得する
                my_original_view_text.text = context.getString(R.string.green_message)
                buttonChecker = false
            }
        }
    }

}

手順その3 MainActivityで使用

作ったViewをMainActivityで使用と書いていますが、MainActivityでは書かなくても利用でき、main_activity.xmlに記述するだけで機能します。

main_activity.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">

    <com.iwacchi.myoriginalview.MyOriginalView
        android:id="@+id/myOriginalView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

以上で実行していただけると、貼ったgifのとおりに挙動すると思います。割とすぐに簡単な自作Viewを作ることができるのではないかと思います。

おわりに

わかりやすくするために簡単な自作Viewを紹介しましたが、これだと自作Viewにするまでもないと思いまして、これを少し応用してアニメーションをつけたいい感じのオリジナルButtonを作成してみました。

ezgif-5-989fba684c30.gif

このコードはGitHubに載せておりますので気になった方はご覧ください。

GitHub → https://github.com/iwacchi/MyOriginalView

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

GitHub ActionsでAndroidビルドとユニットテストとDeployGateに公開する

1.以下のDeployGateのAPIキーとユーザはGitHubのリポジトリページにあるSettings→Secretsから追加してください

DEPLOYGATE_API_KEY ・・・DeployGateのプロフィールページに記載されてます
DEPLOYGATE_USER ・・・DeployGateのユーザ名

1.[Settings]をクリック

スクリーンショット 2019-12-05 10.12.46.png

2.[Secrets]をクリック

スクリーンショット 2019-12-05 10.16.38.png

3.以下の要領で[DEPLOYGATE_API_KEY]と[DEPLOYGATE_USER]を追加

スクリーンショット 2019-12-05 10.17.53.png

4.最終的にこのような感じ

スクリーンショット 2019-12-05 10.18.31.png

2.GitHubのリポジトリページにあるActionsからymlファイルを追加します。

1.[Actions]をクリック

スクリーンショット 2019-12-05 10.25.01.png

2.[Set up a workflow yourself]をクリック

スクリーンショット 2019-12-05 10.25.55.png

3.プロジェクトルートに/.github/workflows/main.ymlがができるので以下の内容をコピペ

ymlファイルのファイル名はなんでも良い

main.yml
name: Android CI

on: [push]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v1
    - name: set up JDK 1.8
      uses: actions/setup-java@v1
      with:
        java-version: 1.8
    # The following generates a debug APK
    - name: Build with Gradle
      run: ./gradlew assembleDebug
    # The following is running unit tests
    - name: Unit Test
      run: ./gradlew test
    # The following is uploading debug apk to the deploygate
    - name: Distribute App
      run: |
       curl \
        -H "Authorization: token ${{secrets.DEPLOYGATE_API_KEY}}" \
        -F "file=@app/build/outputs/apk/debug/app-debug.apk" \
        -F "message=Any message" \
        "https://deploygate.com/api/users/${{secrets.DEPLOYGATE_USER}}/apps"

https://github.com/tokuyama-san/MyApplication/blob/master/.github/workflows/blank.yml

4.[Start commit]ボタンをクリックしてymlファイルをリポジトリに登録します
5.あとはリポジトリにプッシュするたびに、ビルド、ユニットテスト、DeployGateへdebugAPKアップロードまで行います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CameraXとMLKitを組み合わせてQRコードを読み取る

CameraXはまだベータですが、その精度の高さには驚かされるばかりです。

本記事はAndroid Advent Calendar 2019の2019/12/04分です。
とてもQRコードリーダーを作れちゃうので、その方法を紹介します。

今回は、Firebase MLKitと組み合わせて「QRコードリーダー」を作ってみます。
※Firebaseプロジェクトの準備は別途ご用意ください

下準備

Gradleに必要なものを追加

CameraXとMLKitを追加しましょう

build.gradle
implementation "androidx.camera:camera-core:1.0.0-alpha06" 
implementation "androidx.camera:camera-camera2:1.0.0-alpha06"

implementation "com.google.firebase:firebase-ml-vision:24.0.1"

描画する領域を追加

それではレウアウトのXMLに、描画するための領域(TextureView)を追加します

activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextureView
        android:id="@+id/textureView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

MLKitを組み込んだAnalyzerを作成する

カメラが起動された際に「QRコードを読み取って解析する」ためのAnalyzerを作成します。
クラス名は QrCodeAnalyzer とでもしましょう。

QrCodeAnalyzer.kt
private val TAG = QrCodeAnalyzer::class.java.simpleName

class QrCodeAnalyzer : ImageAnalysis.Analyzer {
    var barcodeStr = ObservableField<String>()

    override fun analyze(image: ImageProxy, rotationDegrees: Int) {
        val img = image.image ?: return
        runBarcodeScanner(img, degreesToFirebaseRotation(rotationDegrees))
    }

    private fun runBarcodeScanner(src: Image, rotationDegrees: Int) {
        try {
            val image = FirebaseVisionImage.fromMediaImage(src, rotationDegrees)

            // 読み取るバーコードのタイプを指定する
            val barcodeFormatQr = FirebaseVisionBarcode.FORMAT_QR_CODE
            val options = FirebaseVisionBarcodeDetectorOptions.Builder()
                .setBarcodeFormats(barcodeFormatQr)
                .build()

            // 画像の解析
            FirebaseVision.getInstance().getVisionBarcodeDetector(options).detectInImage(image)
                .addOnSuccessListener { firebaseVisionBarcodeList ->
                    if (firebaseVisionBarcodeList.isNotEmpty()) {
                        // QRを複数読み込んだ場合に1つだけを対象にする
                        val barcode = firebaseVisionBarcodeList[0]
                        when (barcode.valueType) {
                            FirebaseVisionBarcode.TYPE_URL -> {
                                barcode.displayValue?.let {
                                    if (barcodeStr.get() != it) {
                                        barcodeStr.set(it)
                                    }
                                    return@addOnSuccessListener
                                }
                            }
                        }
                    }
                }
                .addOnFailureListener {
                    Log.e(TAG, "something wrong...")
                }
        } catch (e: Exception) {
            Log.e(TAG, e.message)
        }
    }

    /**
     * 画像の回転を ML Kit の ROTATION_ 定数のいずれかに変換する
     */
    private fun degreesToFirebaseRotation(degrees: Int): Int = when (degrees) {
        0 -> FirebaseVisionImageMetadata.ROTATION_0
        90 -> FirebaseVisionImageMetadata.ROTATION_90
        180 -> FirebaseVisionImageMetadata.ROTATION_180
        270 -> FirebaseVisionImageMetadata.ROTATION_270
        else -> throw Exception("Rotation must be 0, 90, 180, or 270.")
    }
}

「QRコードを読み取るんだ!」という目的のためにやることは、さほど多くはありません。
画像を解析する際にオプションをセットできますが、その際に FirebaseVisionBarcode.FORMAT_QR_CODE を指定してあげるだけで終わりです。
もちろんこのタイプの指定はひとつではなく、複数セットが可能です。

ここではObservableFieldを使って読み取った(ObservableField内のデータが書き換わった)ことを通知するように工夫していますが、おそらくCoroutinesを使ったらもっとキレイに書けます。
(一度Coroutinesを使ったんですが、ワケあってやめたのです)

カメラをスタートして画像を読み取る

それでは、実際にカメラを起動し、上で作成したAnalyzerを使って画像を解析する部分を作っていきます。

少しQiitaに書くコードとしては大きくなってしまいますが、これが全てです。
これだけでQRコードリーダーがほぼ完成です。
(※細かなTODOはまだまだありますが)

MainActivity.kt
private val TAG = MainActivity::class.java.simpleName

@RuntimePermissions
class MainActivity : AppCompatActivity() {
    companion object {
        val analyzerThread = HandlerThread(
            "CameraXAnalysis"
        ).apply { start() }
    }

    val textureView by lazy {
        findViewById<TextureView>(R.id.textureView)
    }

    private var fitPreview: FitPreview? = null
    private var analyzerUseCase: ImageAnalysis? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        launchCameraWithPermissionCheck()

        textureView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
            updateTransform()
        }

    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
            Toast.makeText(this, "Permission has not granted", Toast.LENGTH_LONG).show()
        }
        onRequestPermissionsResult(requestCode, grantResults)
    }

    @NeedsPermission(Manifest.permission.CAMERA)
    fun launchCamera() {
        textureView.post { startCamera() }
    }

    private fun startCamera() {
        try {
            val textureViewWidth = textureView.width
            val textureViewHeight = textureView.height
            val targetResolution = Size(textureViewWidth, textureViewHeight)
            val previewConfig = PreviewConfig.Builder()
                .setTargetResolution(targetResolution)
                .setTargetAspectRatio(Rational(textureViewWidth, textureViewHeight))
                .setLensFacing(CameraX.LensFacing.BACK)
                .build()

            fitPreview = FitPreview(textureView, previewConfig)

            val analyzerConfig = ImageAnalysisConfig.Builder().apply {
                setCallbackHandler(Handler(analyzerThread.looper))
                setTargetResolution(targetResolution)
                setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)
            }.build()

            analyzerUseCase = ImageAnalysis(analyzerConfig).apply {
                val qrCodeAnalyzer = QrCodeAnalyzer()
                qrCodeAnalyzer.barcodeStr.addOnPropertyChangedCallback(object :
                    Observable.OnPropertyChangedCallback() {
                    override fun onPropertyChanged(sender: Observable, propertyId: Int) {
                        val observableField =
                            sender as ObservableField<String>
                        observableField.get()?.let { barcodeStr ->
                            Log.d(TAG, "barcodeStr = $barcodeStr")
                        }
                    }
                })

                analyzer = qrCodeAnalyzer
            }

            CameraX.bindToLifecycle(this, fitPreview, analyzerUseCase)

        } catch (e: Exception) {
            Log.e(TAG, e.message)
        }
    }

    private fun updateTransform() {
        val matrix = Matrix()

        // Compute the center of the view finder
        val centerX = textureView.width / 2f
        val centerY = textureView.height / 2f

        // Correct preview output to account for display rotation
        val rotationDegrees = when (textureView.display.rotation) {
            Surface.ROTATION_0 -> 0
            Surface.ROTATION_90 -> 90
            Surface.ROTATION_180 -> 180
            Surface.ROTATION_270 -> 270
            else -> return
        }
        matrix.postRotate(-rotationDegrees.toFloat(), centerX, centerY)

        // Finally, apply transformations to our TextureView
        textureView.setTransform(matrix)
    }
}

メインは startCameraメソッド です。
startCameraメソッド以外は、カメラ起動のためのPermissionなど必要な手続きばかりです。

ここでは触れてはいませんが、注意が必要なことがいくつかあります。

  • CameraXでは、カメラをズームする場合は CameraX.bindToLifecycle()の後 でやる
  • 富士通の端末の場合、電池残量が10%未満の場合は RuntimeException(camera low battery) となる
  • カメラを止めたり再開したり(エラーダイアログを出してる最中は画像解析を止めるとか)する場合は、CameraXのライフサイクルに気をつけないと簡単にクラッシュする

他にも色々とありますが、挙げ出したらキリがないかもしれません…

まとめ

ここまで「CameraXはええで!MLKitと組み合わせたらめっちゃええで!」とばかり述べてきましたが、やはりまだ過去のAndroidのカメラが通ってきた闇の歴史を乗り越えきれてはいないように感じます。

Xperiaでは非常に画像解析の精度が低いですし、カメラのズームをすると画像がガビガビになったりする端末もありました。
個人的にCameraXには非常に注目しており好んではいますが、Xzingなどといった歴史のあるライブラリのほうが、今はプロダクトとしては選択肢として強い気がします。

P.S.

ここで述べてきたことは、CameraXのCodelabsでかなり触れられているので、そちらを試してみたらよいかと思います。
https://codelabs.developers.google.com/codelabs/camerax-getting-started/

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

Jetpack Compose用の新機能Live Previewでつまづいた件

本記事について

FUN Advent Calendar 2019 Part2,6日目の記事です

急遽Part2に参加しました.
Part1用の記事を書いている中で,方向性がずれていってしまった一部をPart2の記事にすることにしました.
多少(かなり)の手抜き感はお許しください.

概要

もともとはPart1用にプレアルファ版で利用できるようになった,Jetpack Composeについての記事を書いていました.
その導入の中で,Live Previewという機能が追加されていることを知りました.
どんなもんなのか自分で試している中で,つまづいた部分をまとめていきます.

Live Preview

Live PreviewはJetpack Composeに伴って追加された新機能になっています.
そもそもJeapack Composeとは宣言的にUIを作成することのできるライブラリです.
Jeapack Composeを利用することによって,従来のAndroidアプリ開発大きく変わるのは,Layoutファイルを使わなくなることです.
その代わり,Kotlinファイルに直接UIを書いていくことができます.

ここで問題になってくるのか,UIの確認ができなくなることです.
以前は,Layoutファイルの中でUIの調整を行い,UIの確認をDesignで確認していました.
しかし,Jetpack Composeを利用すると,アプリを実行させるまでUIの確認ができなくなってしまいました.

そこで,Jetpack Composeのプレアルファ版の中でLive Previewが追加されました.
Live Previewを利用することで,Jetpack Composeで記述したUIがどのような表示になるか確認できます.
※Jetpack Composeの書き方についての説明はしないので,こちらをご参照ください.

実際にLive Previewを使ってみる

早速Live Previewがどんな感じなのか試してみましょう.
基本的は使い方は@Composableの前に@Previewを追加するだけのようです.
実際に簡単なUIに@Previewを追加してみました.
スクリーンショット 2019-12-04 14.04.23.png
ちーんΣ(・□・;)
これでもアプリ自体は実行できますが,Live Previewは機能していません.
なんでだろうと思いつつ,サイトを読み直してみると
To create a live preview, create a new composable function that takes no parameters and includes a @Preview annotation before @Composable, as shown below
との記述がありました.ようは@Previewを付けた関数には,引数をつけるなとのことです.

そこで,@Previewをつける関数には引数を与えず,UI用の関数に引数を与えるように変更しました.
スクリーンショット 2019-12-03 21.24.09.png
結局だめでした.
しかし,公式サンプルアプリを眺めていたところ,Class内では使っていないとこが判明しました(そもそも公式サンプルアプリではUI用の関数はClassの中に書いていないんですね).

スクリーンショット 2019-12-04 12.57.43.png

なんとか表示できました(Dark仕様にしていることもあり,かなーーり見辛いですが一応表示されています).
なぜクラスの中で表示されないのか原因はよくわからないです.
公式サイトには記述されていないような感じでした(知っている方いらっしゃいましたらコメント欄にでも...).

結論としては,Live Previewを使用する場合には,UI用のファイルを用意して利用するのが現状のベストっぽいです.

まとめ

この記事では,Jetpack Compose用の新機能であるLive Previewを導入した際に自分がつまづいたことを書きました.
Live Previewと言う割には,レイアウト変更後Build & Refreshを押さないと更新されないので少々使い勝手が悪い印象を受けました.
また,画像のように背景が透過になっていて,このままだと見辛いのもどうにかにして欲しいと感じました.
アプリを実行した際のデフォルトの背景は白なので,この部分も背景のデフォルトは白にして欲しいところです.
今後,どのような変更が加わってくるのかによって,かなり使い勝手が変わってくるように感じます.

参考サイト

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

Facebookログインで`クエリを実行できませんでした。`と出る対処法

環境など

  • Facebook SDK v5.9
  • FirebaseUI
  • Android

内容

Facebookログインをログイン画面へ行く前に下記エラーがでました。

Caused by: SERVER_ERROR: [code] 1675030 [message]: クエリを実行できませんでした。 [extra]:

結論から言うと下記サイトの通りだったのですが、自動翻訳っぽくてわかりにくかったので少し説明します。
https://codeday.me/jp/qa/20190218/288009.html

facebook for developersにて役割>役割テスターを登録しておかないと上記エラーが出るようです。
管理者はテスターになれない上に、友達登録しておかないと、追加対象として出てこないようなので、
新規アカウントを作成し自分で友達登録を行い、新規アカウントをテスター登録することで、ログインできるようになりました。

2019年10月頃にサンプル作成したときには必要なかったのですが、2019年11月に実装した時には必要になってました。

管理画面上の差分を探したら、APIバージョンがv4.0v5.0で差分がありました。
結構苦労したので備忘録として記事化しました。

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

[ Android ] ブロードキャストでIntentを送受信する

今回はAndroidでブロードキャストの送受信する方法について記す。

目的

私が目的とするプログラムはAsyncTaskを継承したクラスからブロードキャストを送信し、メインアクテビティでそれを受信するプログラムである。

必要なクラス

ブロードキャストはアクティビティやサービスクラスから送信できる。
一方受信するにはBroadcastReceiverを継承したクラスが必要である。

実装

登録

まずBroadcastReceiverを継承したクラス(MyReciever)をマニフェストに登録しなければならないので、以下を追記する。このクラスは一クラスとして定義してもよいが、インナークラスとして定義することが多いらしい。本記事では、受信クラス(メインアクティビティ)内で定義している。またアクション名も定義しなければならない。本記事では”testとしている。”

<application>
<receiver android:name=".MainActivity$MyReceiver">
    <intent-filter>
        <action android:name="test" />
    </intent-filter>
</receiver>
</application>

送信側

送信にはIntentを使う。アクション名を使ってIntentを生成し、そこに送信したい内容を詰め、送信メソッドで送る。

Intent intent = new Intent("test");
intent.putExtra("ブロードキャストテスト", "OK");
applicationCommon.sendBroadcast(intent);

受信側

「登録」で述べたように、MyRecieverをMainActivityのインナークラスとして定義する。
このインナークラスは、引数なしのコンストラクタを持てないので、static修飾する必要がある。気を付けて頂きたい。
ブロードキャストが送信されると、マニフェストに登録されたレシーバクラスのonReceive()が動く。
そのメソッド内に行いたい処理を書けばよい。

MainActivity {
    onCreate(){

    }

    public static MyReciever extends BroadcastReceiver {
        @Override
            public void onReceive(Context context, Intent intent) {
                    //Intentから値を取得する
        }
    }
}

参考URL

http://mitoroid.com/category/android/android_broadcast_receiver.php

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

BottomNavigationViewについてと要素を動的に減らしたり

  • この記事はAndroid 初心者向け Advent Calendar 2019 の6日目の内容です:snowman2:

はじめに

  • 意外とよく見る系画面の代表格、下に配置されるタブUIのBottomNavigationViewを取り上げて記事にしてみようと思います。

BottomNavigationViewって:thinking:

  • MaterialDesignComponents に含まれるUIコンポーネント
  • アプリの主要画面の切り替えを行えるようにするもの
    • 主要画面(メインとなるもの)でない画面を配置しては:x:
    • 何を配置すべきかという事を意識して使用するように:o:

  BottomNavigationについて

ね、このUIよくみませんか?:grinning:

BottomNavigationを使用したAndroidアプリをつくる

  • AndroidStudio に含まれる、ProjectTemplateを利用してサクッと:relaxed:
  • [File] -> [New] -> [New Project]を選択
  • ProjectTempleteでBottomNavigationActivityを選択

image01.png

生成されたプロジェクト一式をみると、大まかな実装が見えてくるのおすすめ:point_up:

MainActivity

  • メインという名の通り、元となるActivityクラス
  • この中でBottomNavigationViewを管理
    • 表示項目はmenu/bottom_nav_menuに記載
  • 画面遷移処理は、navigation/mobile_navigationで記載

HomeFragment / HomeViewModel

  • Homeタブ選択時に表示されるFragmentViewModel部分
  • タブに対応したFragmentという役割
  • Dashboard も Notification も同じ役割となる
  • menuのIDとnavigationのIDを同じものにすると連動してくれる:point_up:

device-2019-12-04-144954.png

要素を動的に減らしたり

  • アプリの状態で一部の主要機能を使用させたくないという場合の対応
  • :bangbang:注意:bangbang:
    • BottomNavigation は最小3個、最大5個の範囲内で使うのが推奨されています。
    • 今回の話では 3個 <-> 2個 に減らしているので、例としては良くなかったり:frowning2:

消し方&戻し方

  • BottomNavigationViewの表示項目はmenuが管理しているので、子要素を探す(findViewById())するのではなく、menuからfindItem()で取得したMenuitemに対して設定の変更を実施
MainActivity.kt
    private fun changeVisibleStatusTabId(id : Int , isVisible : Boolean){
        val navView: BottomNavigationView = findViewById(R.id.nav_view)
        navView.menu.findItem(id)?.also {
            it.isVisible = isVisible
        }
    }

さいごに

  • BottomNavigationViewはよくみかけるUIコンポーネントですが、推奨されたのは、Android OS 8(26.1)あたり
    • iOS ではTab Barsというコンポーネントで、初期からあった
    • 最近に使用できるようになったコンポーネントで、それまでは各アプリで実装していた代表格:sweat:
    • ただ、iOSのように画面の管理といった機能は無いので、そのあたりは自分で実装する必要がある:cry:
    • バッジの表示といった追加機能もあるがalphaバージョン or betaバージョンを使う必要があったり
    • ただ、自分で作るよりかははるかに楽だよねというコンポーネントでした:relaxed:

これからも楽しいアプリケーション開発を:dancers:

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

【Android/Kotlin】kotlinでAndroid開発tips

多言語対応のstringsファイルのディレクトリ

  • valuesと同等の階層に置く。valuesのなかにおくと参照できないのでNG。

onCreateでfindViewByIdをする

  • メンバー変数でViewを定義せず、
    xmlのViewをonCreateの中でfindViewByIdしたいときは、
    「as ImageView」をつければOK。

adapterでsetTagをする意味

  • adapterの中でsetTagをすると、
    viewHolderにviewが紐付けられる。
  • viewHolderは他のActivityなどから参照できるので、
    viewを紐付けて格納しておきたい。
  • ただし、「5番目」のViewを取得する、というようなことはできないので、
    別途ifなどでてらしあわせる処理が必要。

switch(トグル)の活性/非活性

  • isEnable = false : グレーになる
  • isClickable = false : 指定した色になる

クリックまわりで確認すべき仕様

  • 同じボタンをクリックしてもよいかどうか
  • 一度クリックされてから他のボタンをクリックできるまでに、
    インターバルをおくかどうか
  • アニメーションのかかっている最中に他のボタンをクリックできるかどうか

canvas関係

  • paint.style = paint.style.fill : ぬりつぶし
  • paint.style = paint.style.stroke : ぬりつぶしなし
  • canvas.drawRect : 四角形を描画
  • canvas.drawArc : 円を描画

conttextに?がついてしまうとき

  • context(=getContext)をすると?がついてしまうので、
    requireContextを使用すると解消される。
    ただし、内部的には!!のような処理が行われているみたいなので微妙。。

scaleanimation関係

 2019-12-04 14.48.00.png
 2019-12-04 14.52.51.png

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

Android Studio 4.0でJetpack Composeと戯れながらQiitaビューアを作ってみた

この記事はNTTテクノクロス Advent Calendar 2019の9日目です。

こんにちは、NTTテクノクロスの戸部@etctaroと申します。
普段は、社内でモバイル関連開発の技術支援や社内向けのノウハウ記事執筆、社内研修講師活動などを行っています。

はじめに

  • Jetpack ComposeGoogle I/O 2019で発表されたAndroidアプリの宣言的UI実装のためのライブラリ群です。
  • これまでAndroidで使われていたレイアウトエディタ + XMLによるUIの実現ではなく、FlutterやSwiftUIのように、画面の各UI部品を@Composableというアノテーションをつけた関数、およびその複数の組み合わせでUIを実現できます。
  • Android Studio 4.0のCanary版にて利用できるようになりました。
  • Google I/O直後は専用のIDEをリポジトリから取得する必要があり、若干ハードルが高かったところではありますが、IDEとして公式にサポートされることにより利用しやすくなりました。
  • そのような経緯と、個人的な興味がありましたので、サンプル的なアプリ作成を通じて戯れて(と言う名目の人柱になって)みました。

注意点

  • Android Studio 4.0、Jetpack Composeについてはプレビュー版であり、正式版では状況が変わることが予想されますので、その点はご注意ください。

こんなアプリを作ることにした

新しい開発手法が出てきたということで、社内の伝統に則り(?)、とりあえずQiitaのAPIを叩いて画面に一覧表示するViewerを作ってみようと思います。

要件としては以下のようなイメージ。

  • アプリを起動すると、空の一覧画面とリロードボタンが表示される。
  • リロードボタンを押すと以下の処理を実施する。
    • Qiitaの記事取得のAPIを実行し記事の一覧(JSON)を取得する。
    • 記事の一覧(JSON)をオブジェクト化する。
    • 取得した記事の一覧を(一旦クリアしてから)画面に反映する。
  • 記事の一覧をスクロールして表示できる
  • 一覧をタップしたら、タップした記事のURLをブラウザで表示する

完成形イメージ

前提条件

以下の環境を前提とします。

  • Android Studio 4.0 (Canary 4)
  • Target Sdk Version 29

(ハマり)ポイント

Step1: アプリ用のプロジェクトを作成する

  • Android Studio 4.0(Canary)から新規にアプリのプロジェクトを作成しようとするとEmpty Compose Activityというテンプレートが選べます。

Jetpack Compose用のPJ

  • このテンプレートを選び、適宜プロジェクト名などを埋めプロジェクトを作成します。

Step2: アプリのUI部分を実装する

早速ですが、Jetpack ComposeでUI部分を実装します。

記事の一覧っぽいものを実装する

  • Jetpack Composeでは@Composableというアノテーションをつけた関数の組みあわせでUIを表現します。
  • 今回作ろうとしている例では、記事の一覧をTextRowColumnというComposableを組み合わせて作成することにします。
  • 公式サンプルのGreetingの例に則り、Qiita記事の一行を表すComposableを作ってみました。
  • 一覧にはタイトル記事のURLを表示します。

データクラスの作成

  • 今回は必要となるQiitaの記事のtitleとurlだけを保持するdata classを作成します。
data class Qiita(val title: String, val url: String)

一つの記事を表すComposable

  • 画面にはColumnを使い、各記事のタイトルとURLを縦並びで表示させることにします。
  • RowComposableを横並びで表示する際に使います。この後に何かボタンを入れようと思ってとりあえず追加してみました。(が、この記事では最後まで何も使っていません。)
@Composable
fun QiitaItem(title: String, url: String) {
    Row() {
        Column() {
            Text(text = title)
            Text(text = url
        }
    }
}

複数の記事の一覧を表すComposable

QiitaItemを複数組み合わせて記事の一覧とします。

  • データクラスQiitaのオブジェクトのリストを通信で取得する前提ですので、これを引数としました。
  • 区切り線(DividerComposable)も追加してみました。
  • 現時点の公式のAPIにはListViewのようなクラスが見当たらないので、forループでQiitaItemを呼び出すようにしました。
@Composable
fun QiitaItemList(items: List<Qiita>) {
    Column {
        for (item in items) {
            QiitaItem(title = "${item.title}", url = "${item.url}")
            Divider(color = Color.Black)
        }
    }
}

一画面分のComposableおよび、Activityの実装

ここまでの情報を組み合わせて、一つの画面を示すComposableは以下の通りになります。
QiitaItemListの引数は適当なリストにしています。実際には、通信で取得した結果がここに入ることになります。

@Composable
fun HomeScreen() {
    MaterialTheme {
        QiitaItemList(listOf(Qiita("title1", "http://url1")))
    }
}

また、このComposableを使った、Activityの実装は以下の通りです。setContentに指定してあげるだけです。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            HomeScreen()
        }
    }
}

プレビュー表示

@Composableにさらに@Previewというアノテーションを加えることで、Android Studio上で画面イメージを見ることもできます。

@Preview
@Composable
fun prevQiitaItem() {
    QiitaItem("title1", "http://sample.co.jp/")
}

@Preview
@Composable
fun prevQiitaItemList() {
    QiitaItemList(
        listOf(
            Qiita("title1", "http://sample.co.jp/1"),
            Qiita("title2", "http://sample.co.jp/2"),
            Qiita("title3", "http://sample.co.jp/3")
        )
    )
}

Composableのプレビュー表示

State(状態)の管理

アプリで保持している何かしらの値が変更されたのを検知し、画面に反映させる場合、FlutterのStateと同様にJetpack Composeの場合にもState(状態)の概念を導入することができます。
CodeLabでは以下で紹介されています。

自作classまたはobjectで状態を管理する場合は、@Modelというアノテーションをつけることで、状態管理ができます。

今回はQiitaの記事の一覧を更新したいので、以下のようにQiitaのリストを持つModelをトップレベルで用意してみました。

@Model
object QiitaItemsStatus {
    val dataList = ArrayList<Qiita>()

    fun addItem(item: Qiita) {
        dataList.add(item)
    }

    fun clearItem() {
        dataList.clear()
    }
}

では適当にボタンを用意して、addItemを呼んでみます。

@Composable
fun SampleAddClearButton() {
    Column() {
        Row() {
            Button(text = "addItem", onClick = {
                with(QiitaItemsStatus) {
                    addItem(Qiita("1", "aaa"))
                    println(dataList.toString())
                }
            })
            Button(text = "clearItem", onClick = {
                with(QiitaItemsStatus) {
                    clearItem()
                }
            })
        }
    }
}

→ 何も起こらない。正確には、ボタンを押すたびにdataListの中身は増えているようですが、画面に反映されません。

State(状態)の管理 r1

そこで、公式サンプルのJetNewsアプリを確認してみました。

@Model
object JetnewsStatus {
    var currentScreen: Screen = Screen.Home
    val favorites = ModelList<String>()
    val selectedTopics = ModelList<String>()
}

詳細な情報が若干不足していますが、かなり怪しいのでこのクラスを使ってみることにします。

object QiitaItemsStatus {
    val dataList = ModelList<Qiita>()
}

→ビンゴでした。dataListを更新すると、画面に反映できるようになりました。

  • ポイント:
    • Stateにリストを使う場合はModelListを使う必要がある(らしい)

縦スクロール

  • 今回作るようなアプリでは縦スクロールが必要となります。CodeLabでは特に触れられていませんでしたが、公式サンプルのJetNewsアプリを参照したところ、以下の通り対応することで垂直方向のスクロールが実装できます。
    FlexColumn() {
        flexible(flex = 1f) {
            VerticalScroller {
                FooComposable()
            }
        }
    }

ネーミング的にはVerticalScrollerがあれば良さそうですが、FlexColumnで囲む必要がありました。

FAB(FloatingActionButton)

FABはJetpack ComposeのAPIに用意されていますが、そのまま使うと画面の全体がボタンになります。以下のようにAlignで囲むことで、Basic Activityテンプレートのようなボタンになります。

        Align(Alignment.BottomRight) {
            Padding(padding = EdgeInsets(right = 16.dp, bottom = 16.dp)) {
                FloatingActionButton(text = "reload", onClick = {
                    // タップ時の処理
                })
            }
        }

ここまでで、UI周りでやりたいことの実装は概ねできました。

Step3: 通信の処理などを実装する

通信の処理

通信の処理については、既存の知識の踏襲で実装できますので、ここでは省略します。
ここでは、fuel(通信)およびmoshi(JSONのシリアライズ)というライブラリを利用して実装しました。詳しくは下記を参照してください。

仮に、通信結果をシリアライズし、dataListに詰めるところまでの処理をfetchQiitaData()という関数で実装したものとします。

この関数をFABのonClickで呼ぶようにします。

                FloatingActionButton(text = "reload", onClick = {
                    fetchQiitaData()
                })

記事クリック時の処理

記事の@Composableをクリックした時、ブラウザで記事を表示するような機能をつけようと思います。
Rowなどのように、クリック時の処理を記載するパラメータを持たない場合は、Clickableで囲むことによってonClickパラメータに処理を記述することができます。

    Clickable(onClick = {
        //ここに記述する
    }) {
        Row() {
            Column() {
                Text(text = title)
                Text(text = url)
            }
        }
    }

さて、クリックできるのは良いですが、URLをブラウザで開くような処理はどのようにすれば良いでしょうか。
既存であれば、startActivityにIntentを渡してあげるような実装でできますが。

ここは、Jetpack Composeの世界だけではどうにもならなかったため、
以下のようにしてみました。

  • これを
// トップレベルに記述
var callbackOnLinkClicked : (Any) -> Unit = { }
  • こうして
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        callbackOnLinkClicked = {
            startActivity(it as String)
        }

        setContent {
            HomeScreen()
        }
    }

    private fun startActivity(url: String) {
        val uri = Uri.parse(url)
        val intent = Intent(ACTION_VIEW,uri)
        startActivity(intent)
    }
}
  • こうじゃ
@Composable
fun QiitaItem(title: String, url: String) {
    Clickable(onClick = {
        callbackOnLinkClicked(url)
    }) {
// 略
}

ここまでの完成形

サンプル完成形

  • 記事執筆時のAPI( https://qiita.com/api/v2/items )実行結果です。(内容に他意はありません。)
  • 見た目が若干気になったので、少しだけ文字サイズを調整してみました。
    Row() {
        Column() {
            Text(text = title, style = +themeTextStyle { subtitle1 })
            Text(text = url, style = +themeTextStyle { caption })
        }
    }

文字サイズを調整

そのうちやりたいこと

  • 今回はとりあえず動かすことを優先させましたが、一つのファイル内で色々とやっているのでリファクタリングを行う必要があります。

    • JetNewsのサンプルを見ていると以下のようにしているように見えます。
    • 一つの画面(タブで切り替えられたりする場合もそれぞれ1画面として数える)を1パッケージとしてディレクトリを切る。
    • 一つの@Composable関数が肥大化しそうなら適宜関数を分ける。
    • 一つの画面の中で@Composableが多くなったら適宜ファイルを分割する。
    • Stateやスタイルについても別ファイルに抽出する。

    このあたりは一般的なコーディングと同様かと思います。

  • 見た目についてももう少しこだわりたいと思います。

おまけ: UIテストについて

おまけとしてEspressoなどのUIテストツールで現時点でテストが実施できるか試してみます。

reloadボタンがあるので、このボタンをクリックするようにEspressoのコードを書いてみます。

    @Test
    fun Test_reloadButton() {
        onView(
            withText("reload")
        ).perform(click())
    }

実施してみると、結果として、以下の通りボタンが見つからないというエラーになります。

androidx.test.espresso.NoMatchingViewException: No views in hierarchy found matching: with text: is "reload"

Espresso Test Recorderを使って構造を見てみると以下の通り、Jetpack Composeの部分が全てViewGroup(AndroidComposeView)でまとめられている状態になっています。このため、ボタンを探せずエラーとなっているようです。

Espresso Test Recorder

参考

おわりに・雑感

そこそこ動きそうなアプリを作ってみつつ、最後にオチをつけたところで、まとめです。

Jetpack Composeについては、まだまだ公式情報が不足しているところではありますが、現時点でもそこそこ動くアプリを作ることができました。
既存のAPIの知識を活かせばもっと複雑なアプリも作れそうです。

正式版になる頃には参考ドキュメントなど、もう少し改善されていることを期待しますが、公式のサンプルは現時点でもかなり参考になりそうな感触です。

ただ、画面遷移やIntentなどこれまでも大いに使われているAPIの扱いについてはもう少し明確になってほしいと思います。

といったところで、本日の記事は以上となります。

それでは、NTTテクノクロス Advent Calendar 2019 の10日目の記事も引き続きお楽しみください。

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

Android StudioでAndroidの画面をキャプチャ/録画しよう

はじめに

アプリ開発の際、端末のキャプチャや録画したい時、
Android Studioで簡単にできる方法があったので紹介します。

キャプチャの方法

まず、USBデバックを有効にし、
端末のキャプチャしたい画面を表示しておきます。
そして、下記に画像に表示されいている、
Android StudioのLogcatにあるカメラのアイコンをクリックします。
はい、キャプチャできました!
めっちゃ簡単ですね!
スクリーンショット 2019-11-28 17.56.44.png

録画方法

次に、録画の方法を紹介します。
録画はMP4形式で最大3分間録画できます。
先ほどの画像のカメラアイコンの下にある、動画アイコンをクリックします。
クリックすると、録画オプションが設定できます。
スクリーンショット 2019-12-03 18.09.54.png

・Bit Rate:ビットレートを設定できます。
・Resolution:幅と高さをピクセル単位の16の倍数で設定します。
       デフォルトだとデバイスの解像度です。
・Show Taps:画面内のどこをタップしたか視覚的に表示してくれます。
音声は録音できないため、Show Tapsをオンにするとわかりやすいかも!

Start Recordingをクリックすると録画が始まり、
Stop Recordingをクリックすると停止します。

最後に

Android Studioには便利な機能がたくさんありますね!

参考にしたサイト
キャプチャ
録画

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

Jetpack ComposeでListViewっぽいの作ってみた

※「Android Studio 4.0 Canary 4」にて開発

はじめに

シンデレラの時間がやってきました!
[CA Tech Dojo/Challenge/JOB Advent Calendar 2019]も始まって今日でいよいよ1週間です.そして1週間目の今日は僕が担当させていただきます.ちなみに「シンデレラ」はインターン中に呼ばれていたあだ名です(笑)

僕はCA Tech Dojo-Androidアプリ(Kotlin)編-に参加させていただきました.前の方々も書いている通り本当に本当に最高のインターンでした!動画と記事にまとめたので,どういうインターンだったのか気になる方はぜひご覧ください.

動画はコチラ
記事はコチラ

Jetpack Composeとは

Google I/O 2019で発表された,UIを構築するための宣言型のツールキットです.簡単に言ってしまえば,今まではxmlファイルでUIをいじって,Kotlinファイルで処理を書いて,とやっていましたが,Jetpack Composeを使えば,KotlinファイルでUIから処理まで全部できちゃうっていう感じになります.

Hello World!の表示

MainActivity.kt
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Greeting("World")
        }
    }
}

@Composable
fun Greeting(name: String) {
   Center {
       Text(text = "Hello $name!",
           style = TextStyle(
               color = androidx.ui.graphics.Color.Red,
               fontSize = 24.sp
           )
       )
   }
}

まずはじめに,@Composableアノテーションをつけた関数を作成し,その中でテキストの配置やスタイル(色やフォントサイズ)を決める処理を書いています.Centerの中にTextを配置することで,画面中央への表示を可能にし,TextStyleで色やフォントの指定をしています.
次にonCreate内にsetContentを呼び,その中で先ほど作成した関数を呼び出しています.
たったこれだけで,画面の中央にフォントサイズ24spで真っ赤っかのHello World!を表示することができます.

☟こんな感じ☟
hello.png

List表示

現時点でJetpack Composeには,ListViewRecyclerViewに相当するコンポーネントはないそうです.そのため今回は,VerticalSclloerColumnによるListViewの実装をしたいと思います.

MainActivity.kt
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            CreateList()
        }
    }
}

@Composable
fun CreateList() {
    MaterialTheme {
        VerticalScroller {
            Column {
                (0..20).map {
                    ListItem(
                       text = it.toString(), 
                       onClick = { //クリック時の処理 }
                    )
                    Divider(
                       color = androidx.ui.graphics.Color.Blue
                    )
                    HeightSpacer(
                       height = 6.dp
                    )
                }
            }
        }
    }
}

VerticalScrollerで画面のスクロールを可能にしています.
Columnで20個の要素を縦並びに表示し,リストを作成しています.ListItemでは各要素のテキストを指定したり,クリック時の処理を書くことができます.Dividerでは各要素の区切り線を表示しており,ここでは区切り線の色を青に指定しています.HeightSpacerでは各要素のスペースを指定しています.
このようにVerticalScrollerColumnを組み合わせることで,ListViewを作成することができます.

☟こんな感じ☟
jetpack.png

最後に

今年発表されたJetpack Compose.「名前は聞いたことあったけど,どんな感じなんやろ?」と気になったので,軽くですが触ってみました.
今回はJetpack ComposeHello World!の表示とListViewを作成しましたが,ちょうど最近参加したAndroid勉強会でこの話を聞き,それを参考にさせていただきました.他にも,パラメータの変更によるUIの再描画は@Modelアノテーションを付与しなければいけなかったりと,学ぶべきことがたくさんあります.まだまだ分からないことだらけなので,これからもどんどん新しい技術に挑戦しキャッチアップしていきたいです.

以上,シンデレラでした.

参考文献

[1]https://developer.android.com/jetpack/compose?hl=ja
[2]https://codelabs.developers.google.com/codelabs/jetpack-compose-basics/#0
[3]https://speakerdeck.com/charcojp/about-jetpack-compose
[4]https://qiita.com/ovama-koffee/items/3ab389f41285c077252e
[5]https://qiita.com/kota_2402/items/7bbdd87be8024785e25b

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