- 投稿日:2019-12-04T23:57:23+09:00
Androidでの位置情報取得(Android10対応)
動機
バックグラウンドで常に位置情報取得を行う必要が出てきて、探してみるもQiitaの記事は古いものばかり。
ほぼ公式のガイドを参考にしましたが、散らばっていて分かりづらい部分もあったので、ユースケース別にまとめてみます。Case1. マップに表示するだけ
位置情報を使うアプリといえば大抵はこのユースケースでしょう。
その場合はMaps SDK for Androidを使うのがいいでしょう。現在地表示はSDK側で用意されており、SDK側が位置情報を取得、更新してくれるため、自身で現在地を取得することはないかと思います。
ただし、現在地の位置情報データ(緯度経度情報)を利用するためには、次のように自身で位置情報を取得する必要があります。1
Maps SDKにも現在地を取得する方法はありましたが、depricatedになっており、後述する方法に誘導されています。Case2. 現在の位置情報を利用
Google Play services location APIを利用します。
Android frameworkのLocationManagerを利用する方法もありましたが、現在は非推奨になっています。
FusedLocationProviderClient
のlastLocation
で最新の位置情報を取ることができますが、古いことがあるので、自分で更新をかけて取得するべきでしょう。(端末が再起動した場合などは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ここで
ACCESS_BACKGROUND_LOCATION
をとりたくなるかと思いますが、推奨されていません。
常時位置情報を取得する必要があれば、フォアグラウンドサービス
を利用します。その際、マニフェストにandroid:foregroundServiceType="location"
を指定します。→リファレンスまとめ
自身がどのように位置情報を使うか、ユースケースを考えることが重要です。
- マップに現在地を表示するだけ→map sdkだけで事足りるはず
- 位置情報(lat, long)に合わせたデータをapiなどから取得する→fusedLocation
- トラッキングなど、アプリが裏に行った時も常時位置情報が必要→フォアグラウンドサービス
参考
- 投稿日:2019-12-04T23:04:49+09:00
Android × TDD = ?
TL; DR
- テスト駆動開発(Test-Driven Development: TDD)
- 純粋なロジック(モデル)を頭の中で切り出し、まずは空っぽのclass定義する
- JUnitテスト作成(androidTestじゃないよ)
- 「テスト実装→モデル側の中身を実装」を繰り返す
- テストコードに悩んだら、直感で!簡単に!とりあえず書く!
- テストは網羅性より仕様、設計の確認
- 慣れるとテスト無しでは生きられない
テストコード書いてますか?
「書きたいと思っているが、なかなか難しい」が正直な現状でしょうか。
筆者も何度もトライし、何度も挫折しています。
「この記事(Daggerでテストは楽になるか?)」も結構頑張って考えたのですが、UIの凄まじい変化のスピードに耐えられませんでした。さて、どうやったら無理なく習慣化出来るのか。
答え : TDD × Checking
弊社内のモバイルチームでは、日々、振り返りと議論と励まし合い(?)ながら開発に取り組んでいます。
とある日、上記の悩みを話していると「Checking」というキーワードが出てきました。
(僕以外のメンバーは比較的テストコードに明るい...)今までは「テストコードは品質重視だ!網羅性だ!」と息巻いていたのですが、それは「Testing」という考え方らしく、開発コストが重い。
それに対し、「Checking」は「仕様、設計を確認する事を重視する(ざっくり)」なのだとか。
テストコード検討を軽くできるし、テストコードと同時に設計も行う事ができる。本当だとしたら、すごい。
補足:TDDとは?
以下のサイクルを高速で回すことだそう。
- まずテストコードを書く(実装側より先に。当然、失敗する)
- 成功する最低限の実装を書く
- テストが通ったら、リファクタリングして綺麗にする(メンテナビリティ確保)
テストがあるので安全にリファクタリングできる事が大きなメリットですね。
方針 : 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 = あなたには何が見えましたか?
- 投稿日:2019-12-04T23:04:49+09:00
Android × TDD
TL; DR
- テスト駆動開発(Test-Driven Development: TDD)
- 純粋なロジック(モデル)を頭の中で切り出し、まずは空っぽのclass定義する
- JUnitテスト作成(androidTestじゃないよ)
- 「テスト実装→モデル側の中身を実装」を繰り返す
- テストコードに悩んだら、直感で!簡単に!とりあえず書く!
- テストは網羅性より仕様、設計の確認
- 慣れるとテスト無しでは生きられない
テストコード書いてますか?
「書きたいと思っているが、なかなか難しい」が正直な現状でしょうか。
筆者も何度もトライし、何度も挫折しています。
「この記事(Dagger2を使ったらUnitTestが楽になるか考えてみた)」も結構頑張って考えたのですが、UIの凄まじい変化のスピードに耐えられませんでした。さて、どうやったら無理なく習慣化出来るのか。
答え : TDD × Checking
弊社内のモバイルチームでは、日々、振り返りと議論と励まし合い(?)ながら開発に取り組んでいます。
とある日、上記の悩みを話していると「Checking」というキーワードが出てきました。
(僕以外のメンバーは比較的テストコードに明るい...)今までは「テストコードは品質重視だ!網羅性だ!」と息巻いていたのですが、それは「Testing」という考え方らしく、開発コストが重い。
それに対し、「Checking」は「仕様、設計を確認する事を重視する(ざっくり)」なのだとか。
テストコード検討を軽くできるし、テストコードと同時に設計も行う事ができる。本当だとしたら、すごい。
補足:TDDとは?
以下のサイクルを高速で回すことだそう。
- まずテストコードを書く(実装側より先に。当然、失敗する)
- 成功する最低限の実装を書く
- テストが通ったら、リファクタリングして綺麗にする(メンテナビリティ確保)
テストがあるので安全にリファクタリングできる事が大きなメリットですね。
方針 : 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に書き換える様にしています。
機会があれば、その辺りもご紹介できればと思います。
- 投稿日:2019-12-04T21:11:40+09:00
Androidでリン○フィットアドベンチャー
おはようございます. はじめまして.
NTTドコモ 先進技術研究所 1年目社員 實成です.
僕は社内でも自分の時間が取りやすい部署[要出典]で働いています.
なので,普段の業務では取り扱っていないテーマの記事にしました.バラエティ重視!!普段の業務はこんなことしてます.
(ちょうど3日前にまとまった進捗を報道発表する機会がありました.)
僕は上記の解析担当をメインでやっています.背景
入社してから
大学生のときは週2でテニスをしていましたが,社会人になってからは一切運動をしなくなりました.
せめて家でできる運動はしたい と考えて入社早々に腹筋ローラーを買うもこれだけだとすぐ飽きます.
世間では運動不足解消が流行っていて,こんなアニメも流行りました.
運動したい欲に対して満足のいく回答がないまま生きていた僕はリングフィットアドベンチャーに出会いました.リングフィットアドベンチャーとは??
公式動画
「Nintendo Switchで冒険しながらフィットネス」というキャッチフレーズのゲームです.
身体に1つセンサーを,コントローラに1つセンサーを装着して全身がコントローラーとなる新感覚ゲーム.
2つのセンサーが腕や脚やお腹などの動きを認識し,ゲームの中のキャラクターを自在に操ることが可能.
ファンタジー世界を己の身体1つで冒険しながら,スリムで引き締まったボディを目指すゲームです.任天堂は過去にもWiiFitシリーズを製作し,見事ゲームと運動を結びつけています.
僕も過去には大人から「ゲームばかりして」という言葉を頂いていましたが,今となってはそれは無縁.
加えて,最近のゲームは操作が難しく,遊ぶ人を選んでしまっている要素が増えてきてもいると感じています.
その点,直感的な操作が可能なこのゲームは,誰とでも一緒に遊ぶことが可能です.
これによって,家族や友達との仲を繋ぐ,素晴らしい側面も併せ持っていると考えています.
ぜひこれからもこのようなシリーズが続いていって欲しいです.勘違い
- リングフィットアドベンチャーをやりたい
でもSwitch買うまでのモチベはない
加速度センサーあれば作れそうじゃね?
- 手元にはAndroid端末が都合よく転がっていました
よくある勘違いでした.
リングフィットアドベンチャーは直感的な操作であるため,
3日ぐらいで実装できて良い小ネタになると考え,このタイトルでAdCを投稿すると宣言しました.準備
最初はTwitterでバズった動画しか見てませんでした.
これが盛大な勘違いの主因であり,実装中に上記の公式動画を見たときに
クオリティの高さ(選べる運動の数の多さ)に驚きました. 流石任天堂.実装
Android版リングフィットアドベンチャーの製作方法概要を書きます.
用いるセンサーは加速度センサのみです.
本来,正確な自己位置推定には地磁気センサーも必要なのですが,そこまで正確に求めなくても
動作検知は可能だと感じたので今回は取り扱いませんでした.
センサー情報を取得するViewとスタート画面や操作説明などViewの2種類を行き来しています.
センサー情報の取得はOnSensorChangedで逐一取得する簡単な実装です.現状,スクワットと腿上げと腰回しの3種類のトレーニングを実装しています.
各トレーニングの1回行ったという判定処理がif文のオンパレードになってしまっています.
もっと効率よく書けたら良かったなと感じています.demo
プレイ中の自分も撮影したのですが,自分の顔が写ってしまったので,アップできませんでした.
Qiitaは動画を上げられないので,無理矢理.gifに変換しました.
コード
例外,効率一切度外視のパワー系実装です.すみません.
1億と2千年ぶりにJava書いたら全然分からなかったです.
コードはこちら今後の予定
- OpenHouse2020に出します.
- もしよかったら遊びに来てください
- OpenPoseを使用した実装に切り替えるかもしれません.
結論
- 任天堂しゅごい.(見た目簡単そう≠実装簡単.)
- デザイン系のお仕事の人しゅごい
- 画面遷移やボタン配置などのゲーム設定.レイアウト設定に一番時間がかかった.
余談
- 投稿日:2019-12-04T21:10:36+09:00
アプリのコンポーネント(サービス, ブロードキャスト レシーバ)について
概要
androidの開発を始めた頃につまずいた箇所をAdevent Calenderを機に振りかえりたいと思い記事にしました。
サービス
サービスの概要
サービスはバックグラウンドで動作するクラスで、バックグラウンドでのデータダウンロードや音楽の再生などに使われます。
過去のアプリではバックグラウンドで歩数を取得しごにょごにょするなんてこともサービスでしてました。サービスには以下の3種類があります。
- フォアグラウンドサービス
- バックグラウンドサービス
- バインドされたサービス
例:サービス起動中に通知を表示する
AndroidManifest.xml<manifest> ... // 追加 <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <application ... > ... // 追加 <service android:name=".service.ForegroundService" /> </application> </manifest>ForegroundService.ktclass 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.ktclass 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)
- 投稿日:2019-12-04T21:00:05+09:00
【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 まとめ
・すっきりできた
・情報が多いので迷ってしまった・DBや追記が出来ていないのでやろうと思う
- 投稿日:2019-12-04T19:19:14+09:00
プロフィール画面で見るアレやってみたい(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に載せてますので見てください。
- 投稿日:2019-12-04T18:52:19+09:00
Androidを開発する上で参考にしているもの
この記事は第二のドワンゴ Advent Calendar 2019の5日目の記事です。
ドワンゴではN予備校Androidアプリを開発しているのですが、その中で実装で困ったときに参照している資料や、新しい技術についていくための情報収集などをまとめたいと思います。
Androidを開発する上で参考にしているもの
公式アプリのソースコード
Androidの有名なカンファレンスとしてAndroid Dev SummitやDroidKaigiなどがありますが、そのイベントで作成されているAndroidアプリのコードがとても参考になります。
Android Dev Summitアプリのソースコード
Github google/ioschedDroidKaigiアプリのソースコード
Github DroidKaigi/conference-app-2019Android Dev Summitは世界で最も大きなAndroid開発者のイベントですし、DroidKaigiも国内最大級のイベントなので、そこで使用されるAndroidアプリはとても素晴らしいものであるはず!と信じています。
(実際にコミット数も多く、コミッターにも著名な方が多いですし。)使用しているライブラリやその使い方などを参考にしたり、実装で困ったらコード検索をして拝借したりしています。
ちなみに最近だと、xmlのImageViewのcontentDescriptionで何を指定するか悩んで調べたら、
@null
で逃げられることがわかったの良かったです。<ImageView android:id="@+id/reserved_icon" android:contentDescription="@null"Google Codelabs
全くの新しい技術を触るときには、Google Codelabにあるコースを実際にやってみて、その使い方や利用背景を学んでいます。
Androidに関連するあらゆる要素についてのコースが用意されていて、内容は英語ですが説明が丁寧で情報も最新に即しているので、教材としてはとても素晴らしいです。
具体的には、以下のようなコースが用意されています。
KotlinでAndroidアプリを作る入門コース
Android StudioでAndroidのアプリを立ち上げるところから、Layoutの組み方、Navigationによる画面遷移、ViewModelによるデータの取り扱い、Room DBにデータを格納する方法など、その内容はAndroidの基本を網羅しています。
Android Kotlin Fundamentals CourseAndroidの応用的な内容を扱うコース
Androidアプリにおける通知やアニメーションの実装、地図の扱い、テストを書く、ログインを実装するなど、応用的な内容を学習します。
ここまで完璧にできていれば、Android開発者として就職には困らなさそう。
Advanced Android in KotlinJetpack 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 KotlinRxMarbles
N予備校Androidアプリでは、非同期処理にRxJavaを使用しており、そのイメージのためにRxMarblesをよく参照しています。
ReactiveXのドキュメントにも説明は色々と書かれているのですが、RxMarblesのほうが自分でMarbleを動かせて直感的にわかりやすいので、困ったときには参照しています。
Androidテスト全書
Androidの技術も変化が激しいのでなかなか書籍での学習は難しく感じるのですが、Androidテスト全書は断片的に散らばっているテストの知見を体系的にまとめてくれていたので、とても参考になりました。
単体テスト、統合テスト、UIテストそれぞれの実行のやり方や、テストライブラリの選定基準、CIへの組み込みと運用まで書かれていて、開発の参考にさせていただきました。
また、Andrid全書が書かれたPEAKSには、他にもクラウドファウンディングで需要が認められた書籍が何冊かあるので、Androidに関連が高いものについては参照していこうかなと思います。
Android Dagashi
本来は、色々なメディアの記事を見るべきかと思いますが、重要な情報はAndroid Dagashiに流れてくるので、そこでリリース情報や技術トレンドなどを見ています。
Kotlinのリリース内容や、Android Jetpackまわりのアップデート内容、Deprecatedになるライブラリ情報や今後のAndroidに関する記事などが紹介されているので、とりあえずここを追えていれば問題ないと思います。
また新しいものが増えたら、随時更新していきます。
- 投稿日:2019-12-04T18:26:24+09:00
[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を組み合わせた以下のものを作成していきます。
手順その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.ktimport 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を作成してみました。
このコードはGitHubに載せておりますので気になった方はご覧ください。
- 投稿日:2019-12-04T17:54:28+09:00
GitHub ActionsでAndroidビルドとユニットテストとDeployGateに公開する
1.以下のDeployGateのAPIキーとユーザはGitHubのリポジトリページにあるSettings→Secretsから追加してください
DEPLOYGATE_API_KEY ・・・DeployGateのプロフィールページに記載されてます
DEPLOYGATE_USER ・・・DeployGateのユーザ名1.[Settings]をクリック
2.[Secrets]をクリック
3.以下の要領で[DEPLOYGATE_API_KEY]と[DEPLOYGATE_USER]を追加
4.最終的にこのような感じ
2.GitHubのリポジトリページにあるActionsからymlファイルを追加します。
1.[Actions]をクリック
2.[Set up a workflow yourself]をクリック
3.プロジェクトルートに/.github/workflows/main.ymlがができるので以下の内容をコピペ
ymlファイルのファイル名はなんでも良い
main.ymlname: 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アップロードまで行います。
- 投稿日:2019-12-04T17:48:47+09:00
CameraXとMLKitを組み合わせてQRコードを読み取る
CameraXはまだベータですが、その精度の高さには驚かされるばかりです。
本記事はAndroid Advent Calendar 2019の2019/12/04分です。
とてもQRコードリーダーを作れちゃうので、その方法を紹介します。今回は、Firebase MLKitと組み合わせて「QRコードリーダー」を作ってみます。
※Firebaseプロジェクトの準備は別途ご用意ください下準備
Gradleに必要なものを追加
CameraXとMLKitを追加しましょう
build.gradleimplementation "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.ktprivate 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.ktprivate 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/
- 投稿日:2019-12-04T17:19:24+09:00
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
を追加してみました.
ちーんΣ(・□・;)
これでもアプリ自体は実行できますが,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用の関数に引数を与えるように変更しました.
結局だめでした.
しかし,公式サンプルアプリを眺めていたところ,Class内では使っていないとこが判明しました(そもそも公式サンプルアプリではUI用の関数はClassの中に書いていないんですね).なんとか表示できました(Dark仕様にしていることもあり,かなーーり見辛いですが一応表示されています).
なぜクラスの中で表示されないのか原因はよくわからないです.
公式サイトには記述されていないような感じでした(知っている方いらっしゃいましたらコメント欄にでも...).結論としては,Live Previewを使用する場合には,UI用のファイルを用意して利用するのが現状のベストっぽいです.
まとめ
この記事では,Jetpack Compose用の新機能であるLive Previewを導入した際に自分がつまづいたことを書きました.
Live Previewと言う割には,レイアウト変更後Build & Refresh
を押さないと更新されないので少々使い勝手が悪い印象を受けました.
また,画像のように背景が透過になっていて,このままだと見辛いのもどうにかにして欲しいと感じました.
アプリを実行した際のデフォルトの背景は白なので,この部分も背景のデフォルトは白にして欲しいところです.
今後,どのような変更が加わってくるのかによって,かなり使い勝手が変わってくるように感じます.参考サイト
- Jetpack Compose : https://developer.android.com/jetpack/compose
- Tutorial Jetpack Compose Basics : https://developer.android.com/jetpack/compose/tutorial
- compose-samples : https://github.com/android/compose-samples
- Use Android Studio with Jetpack Compose : https://developer.android.com/jetpack/compose/setup
- 投稿日:2019-12-04T17:02:36+09:00
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.0
とv5.0
で差分がありました。
結構苦労したので備忘録として記事化しました。
- 投稿日:2019-12-04T16:47:51+09:00
[ 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
- 投稿日:2019-12-04T15:22:38+09:00
BottomNavigationViewについてと要素を動的に減らしたり
- この記事はAndroid 初心者向け Advent Calendar 2019 の6日目の内容です
![]()
はじめに
- 意外とよく見る系画面の代表格、下に配置されるタブUIのBottomNavigationViewを取り上げて記事にしてみようと思います。
BottomNavigationViewって
![]()
- MaterialDesignComponents に含まれるUIコンポーネント
- アプリの主要画面の切り替えを行えるようにするもの
- 主要画面(メインとなるもの)でない画面を配置しては
![]()
- 何を配置すべきかという事を意識して使用するように
![]()
ね、このUIよくみませんか?
BottomNavigationを使用したAndroidアプリをつくる
- AndroidStudio に含まれる、ProjectTemplateを利用してサクッと
![]()
- [File] -> [New] -> [New Project]を選択
- ProjectTempleteでBottomNavigationActivityを選択
生成されたプロジェクト一式をみると、大まかな実装が見えてくるのおすすめ
![]()
MainActivity
- メインという名の通り、元となるActivityクラス
- この中でBottomNavigationViewを管理
- 表示項目は
menu/bottom_nav_menu
に記載- 画面遷移処理は、
navigation/mobile_navigation
で記載HomeFragment / HomeViewModel
- Homeタブ選択時に表示される
Fragment
とViewModel
部分- タブに対応したFragmentという役割
- Dashboard も Notification も同じ役割となる
menu
のIDとnavigation
のIDを同じものにすると連動してくれる![]()
要素を動的に減らしたり
- アプリの状態で一部の主要機能を使用させたくないという場合の対応
注意
![]()
- BottomNavigation は最小3個、最大5個の範囲内で使うのが推奨されています。
- 今回の話では 3個 <-> 2個 に減らしているので、例としては良くなかったり
![]()
消し方&戻し方
- BottomNavigationViewの表示項目は
menu
が管理しているので、子要素を探す(findViewById()
)するのではなく、menu
からfindItem()
で取得したMenuitem
に対して設定の変更を実施MainActivity.ktprivate 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というコンポーネントで、初期からあった
- 最近に使用できるようになったコンポーネントで、それまでは各アプリで実装していた代表格
![]()
- ただ、iOSのように画面の管理といった機能は無いので、そのあたりは自分で実装する必要がある
![]()
- バッジの表示といった追加機能もあるが
alphaバージョン
orbetaバージョン
を使う必要があったり- ただ、自分で作るよりかははるかに楽だよねというコンポーネントでした
![]()
これからも楽しいアプリケーション開発を
- 投稿日:2019-12-04T14:53:58+09:00
【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-04T14:12:35+09:00
Android Studio 4.0でJetpack Composeと戯れながらQiitaビューアを作ってみた
この記事はNTTテクノクロス Advent Calendar 2019の9日目です。
こんにちは、NTTテクノクロスの戸部@etctaroと申します。
普段は、社内でモバイル関連開発の技術支援や社内向けのノウハウ記事執筆、社内研修講師活動などを行っています。はじめに
- Jetpack ComposeはGoogle 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
(ハマり)ポイント
- 宣言的に実装するのは割とわかりやすい。ただし、現時点では参考ドキュメントが不足。
- リストをStateとして保持したい場合にはModelListを使う必要がある。
- スクロールするためには、
VerticalScroller
とFlexColumn
を組み合わせる。- 通信の処理はこれまでと同様にできる。
- Jetpack Composeでインテント・・・どうやって投げるの?
- UIテストは現時点では無理。
Step1: アプリ用のプロジェクトを作成する
- Android Studio 4.0(Canary)から新規にアプリのプロジェクトを作成しようとすると
Empty Compose Activity
というテンプレートが選べます。
- このテンプレートを選び、適宜プロジェクト名などを埋めプロジェクトを作成します。
Step2: アプリのUI部分を実装する
早速ですが、Jetpack ComposeでUI部分を実装します。
記事の一覧っぽいものを実装する
- Jetpack Composeでは
@Composable
というアノテーションをつけた関数の組みあわせでUIを表現します。- 今回作ろうとしている例では、記事の一覧を
Text
、Row
、Column
というComposable
を組み合わせて作成することにします。- 公式サンプルの
Greeting
の例に則り、Qiita記事の一行を表すComposable
を作ってみました。- 一覧には
タイトル
と記事のURL
を表示します。データクラスの作成
- 今回は必要となるQiitaの記事のtitleとurlだけを保持するdata classを作成します。
data class Qiita(val title: String, val url: String)一つの記事を表すComposable
- 画面には
Column
を使い、各記事のタイトルとURLを縦並びで表示させることにします。Row
はComposable
を横並びで表示する際に使います。この後に何かボタンを入れようと思ってとりあえず追加してみました。(が、この記事では最後まで何も使っていません。)@Composable fun QiitaItem(title: String, url: String) { Row() { Column() { Text(text = title) Text(text = url } } }複数の記事の一覧を表すComposable
QiitaItem
を複数組み合わせて記事の一覧とします。
- データクラス
Qiita
のオブジェクトのリストを通信で取得する前提ですので、これを引数としました。- 区切り線(
Divider
Composable)も追加してみました。- 現時点の公式の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") ) ) }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>() }
- ModelListというクラスが使われていました。
詳細な情報が若干不足していますが、かなり怪しいのでこのクラスを使ってみることにします。
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のシリアライズ)というライブラリを利用して実装しました。詳しくは下記を参照してください。
- fuel: https://github.com/kittinunf/Fuel/tree/master/fuel#installation
- moshi: https://github.com/square/moshi#download
- fuelとmoshiの連携: https://qiita.com/naoi/items/144950ea422cea86274d
仮に、通信結果をシリアライズし、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)でまとめられている状態になっています。このため、ボタンを探せずエラーとなっているようです。参考
おわりに・雑感
そこそこ動きそうなアプリを作ってみつつ、最後にオチをつけたところで、まとめです。
Jetpack Composeについては、まだまだ公式情報が不足しているところではありますが、現時点でもそこそこ動くアプリを作ることができました。
既存のAPIの知識を活かせばもっと複雑なアプリも作れそうです。正式版になる頃には参考ドキュメントなど、もう少し改善されていることを期待しますが、公式のサンプルは現時点でもかなり参考になりそうな感触です。
ただ、画面遷移やIntentなどこれまでも大いに使われているAPIの扱いについてはもう少し明確になってほしいと思います。
といったところで、本日の記事は以上となります。
それでは、NTTテクノクロス Advent Calendar 2019 の10日目の記事も引き続きお楽しみください。
- 投稿日:2019-12-04T10:08:29+09:00
Android StudioでAndroidの画面をキャプチャ/録画しよう
はじめに
アプリ開発の際、端末のキャプチャや録画したい時、
Android Studioで簡単にできる方法があったので紹介します。キャプチャの方法
まず、USBデバックを有効にし、
端末のキャプチャしたい画面を表示しておきます。
そして、下記に画像に表示されいている、
Android StudioのLogcatにあるカメラのアイコンをクリックします。
はい、キャプチャできました!
めっちゃ簡単ですね!
録画方法
次に、録画の方法を紹介します。
録画はMP4形式で最大3分間録画できます。
先ほどの画像のカメラアイコンの下にある、動画アイコンをクリックします。
クリックすると、録画オプションが設定できます。
・Bit Rate:ビットレートを設定できます。
・Resolution:幅と高さをピクセル単位の16の倍数で設定します。
デフォルトだとデバイスの解像度です。
・Show Taps:画面内のどこをタップしたか視覚的に表示してくれます。
音声は録音できないため、Show Tapsをオンにするとわかりやすいかも!Start Recordingをクリックすると録画が始まり、
Stop Recordingをクリックすると停止します。最後に
Android Studioには便利な機能がたくさんありますね!
- 投稿日:2019-12-04T07:19:11+09:00
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.ktclass 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!
を表示することができます.List表示
現時点で
Jetpack Compose
には,ListView
やRecyclerView
に相当するコンポーネントはないそうです.そのため今回は,VerticalSclloer
とColumn
によるListView
の実装をしたいと思います.MainActivity.ktclass 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
では各要素のスペースを指定しています.
このようにVerticalScroller
とColumn
を組み合わせることで,ListView
を作成することができます.最後に
今年発表された
Jetpack Compose
.「名前は聞いたことあったけど,どんな感じなんやろ?」と気になったので,軽くですが触ってみました.
今回はJetpack Compose
でHello 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