private-yusuke / interscheckin Goto Github PK
View Code? Open in Web Editor NEW交差点でのチェックインを補助するために作成された Swarm 利用者のための Android アプリケーション
交差点でのチェックインを補助するために作成された Swarm 利用者のための Android アプリケーション
日常生活の中で特定の Venueに立ち入るときがあるとき、そのチェックインをするために画面操作をすることすら面倒に感じることがある。そこで、deep link で Venue の ID を受け取りつつアプリを立ち上げ、そのリンクを辿るだけで自動的にチェックインできるようにしたい。
想定するユースケースは、NFC タグに deep link が発火するような URI を書き込んでおいて、頻繁に訪れる場所にそれを貼り付けておき、そこに自分が来たらスマホをかざすだけでチェックインできるようにする、というものが挙げられる。
AndroidManifest.xml
内で deep link を使用するための定義を追加する
MainActivity
に対応するタグの中に <intent-filter>
を追加するinterscheckin://
で始まる URI を対象にしたいInterscheckinNavigations.kt
3 秒以下の設定を使うことが本当に無い(高頻度すぎる)ので 10 秒を「高」にしてもっと「中」と「低」での頻度を落とすことにする
Originally posted by @private-yusuke in #115 (comment)
現在の実装では Venue のリストの更新を待ってからチェックインをすることになっている。Venue リストの更新で最も時間を必要とするのは最大で数秒程度かかる位置情報の取得であり、良好なインターネットへの接続があるときには API レスポンスの取得は 1 秒もかからず終了している。これによってチェックインの機会を逃すことが無視できない程度に発生している。
そこで、位置情報の取得については一定時間ごとに実施できるような実装を追加する。これは、定期的な位置情報の取得をすると、一回限りの位置情報取得よりも安定して短時間に新しい位置情報を得られる特性があるためである。
また、その間隔をユーザーが設定できるようにする。
定期的に位置情報を見る必要があり、間隔はユーザーが設定できるようにしたい。
プリセットとして以下のような値が指定されているとよさそう。
間隔のカスタマイズ機能は必要だと思ったときに開発すればよい。
#114 が実現できたら「位置情報」の欄から定期的に位置情報を取得するのを切り替えるためのスイッチと、それが有効なときに取得頻度の設定ができるようにする。
MainScreen
の実装定期的な位置情報の取得が有効になっている場合にはそのプリセット設定に従って定期的に位置情報を取得するようにする。
位置情報の取得のためには昔に実装されていた以下の関数を復活させるとよさそう。
高頻度な位置情報の更新であっても Foursquare の API rate limit には引っ掛からないし1、普段遣いしていても毎月貰える $ 200 分のクレジットのうち $ 2 も使えていないので問題はないと考える。困ったらそのときに対応策を練る(定期的な位置情報の更新時には API を利用せずにローカルのキャッシュのみを用いるようにする、など)。
今後 build.gradle.kts に移行したい。
Originally posted by @private-yusuke in #66 (comment)
#66 で行ったようなマニュアルな差分を以下のようにまとめられるようになる(かも)。
kotlin {
jvmToolchain {
(this as JavaToolchainSpec).languageVersion.set(JavaLanguageVersion.of(17))
}
}
また、IDE の補完が効きやすくなりそう。
plugins
の箇所で org.jetbrains.kotlin
から始まるグループ ID を指定している箇所を kotlin("...")
で書き換える
Navigation Compose 関連のコードが MainActivity に集約されており、若干汚くなっているので分離する。
InterscheckinScreens
を定義する。このクラスを継承する object はコンストラクタで name: string
と navArguments: List<NamedNavArgument>
を受け取る@Composable
関数 InterscheckinNavigations
を作成し、現状 NavHost に渡している箇所を切り出す@Composable
関数 InterscheckinNavHost
を作成し、その中に NavHost を書いている箇所を切り出す@Composable
関数 InterscheckinMain
を作成し、その中に InterscheckinNavHost
を置く
MainActivity
の setContent
内で呼ぶ詳細はあとでかく
プロジェクト全体の annotation processor として kapt ではなく KSP を使うように変更すると、コンパイルにかかる時間が削減されることが期待できるため取り組みたい。
Dagger Hilt と Room について手を加える必要がありそう。
#34 で追加した HistoriesScreen
のページネーションがおかしくなっている。具体的には、すでにリストに入っているチェックイン履歴が後の append 処理で追加されているように見える。
FoursquareClientImpl
から CheckinApiService
を利用する際に limit
の値が伝播していなかった。また、CheckinApiService.getCheckinHistories
にて limit
引数を用意していなかった。
interscheckin/.github/workflows/ci.yml
Line 34 in 4cfdc73
この部分の job が確率的に落ちることがある。手動でリトライすると通ることがあるが、煩雑である。
具体的な処理としては x86-64 な Android のエミュレータが QEMU で走っており、instrumented test 自体が開始されるまでに 15 分程度の時間がかかる。確率的に落ちるのは、このテストの実行中に発生するものであり、特段理由を吐かずに死ぬケースもあったと記憶している。
例
https://circleci.com/ja/product/features/resource-classes/ を見ていると無料で 4 CPU, 16 GB RAM な Arm VM を使わせてくれるらしい。これを使ったら安定稼動かつテスト時間が半分ぐらいになったりしないかなと気になっている。
あるいは、github.com が arm64 な runner を提供しはじめてくれると非常に助かる(他力本願)。
Material You(Material 3)に対応したい。
androidx.compose.material3
以下のクラスを使うようにするとよさそう。
依存関係は androidx.compose.material3:material3
などを追加する。
https://material.io/blog/migrating-material-3 が参考になりそう。
https://engawapg.net/jetpack-compose/1616/migrate-to-material-3/ に具体的な以降手段がまとまっていそう。
TopAppBar
の種類が増えているらしい。
Venues list の更新をしている最中に、Venue を長押しすることによるチェックインを行うとアプリが異常終了する。
E/AndroidRuntime: FATAL EXCEPTION: main
Process: pub.yusuke.interscheckin, PID: 26525
java.lang.ClassCastException: pub.yusuke.interscheckin.ui.main.MainContract$LocationState$Loading cannot be cast to pub.yusuke.interscheckin.ui.main.MainContract$LocationState$Loaded
at pub.yusuke.interscheckin.ui.main.MainViewModel.checkIn(MainViewModel.kt:109)
at pub.yusuke.interscheckin.ui.main.MainScreenKt$MainScreen$1$1$1$2$1.invokeSuspend(MainScreen.kt:98)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at androidx.compose.ui.platform.AndroidUiDispatcher.performTrampolineDispatch(AndroidUiDispatcher.android.kt:81)
at androidx.compose.ui.platform.AndroidUiDispatcher.access$performTrampolineDispatch(AndroidUiDispatcher.android.kt:41)
at androidx.compose.ui.platform.AndroidUiDispatcher$dispatchCallback$1.run(AndroidUiDispatcher.android.kt:57)
at android.os.Handler.handleCallback(Handler.java:942)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7898)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [androidx.compose.ui.platform.MotionDurationScaleImpl@f3ac0ce, androidx.compose.runtime.BroadcastFrameClock@170bef, StandaloneCoroutine{Cancelling}@94d16fc, AndroidUiDispatcher@5bbc185]
全然安全じゃなかった件について
LocationState.Loading
のときには lastLocation
が格納されていることが期待されるので、それを取り出すようにすればよいと考える。この State は lastLocation
が null のときは起動直後で位置情報を受け取ったことがない扱いとしているようだが、素直に InitialLoading
のような状態を新たに用意してここの null 許容をやめるようにしても良いと思う。
root issue: #183
Foursquare の API を利用して友達一覧を取得し、それを表示する画面を実装する。
「API を叩いて画面を表示する」という点では #34 でも同じことをしているので、参考になるかもしれません。
:library:foursquareclient
の pub.yusuke.foursquareclient.network
以下に FriendApiService
を作成し、友達一覧を取得する API を呼び出すためのメソッドを持つ interface を定義する。
ページネーションの処理も入れてください。
TODO: 具体的な API エンドポイントやページネーション用のパラメータを書く
以下のものを表示してください。
TopAppBar
があり、左上に「戻る」ボタンがあること@Preview
を用いて適当なユーザーを一行だけ表示したときの見た目が確認できるようになっているとベターまた、MainScreen
からこの画面に遷移するためのボタンを配置してほしいです。
v3 の Venue 検索 API で交差点が出てこない件について Foursquare のサポートチケットを切って尋ねてみたところ location
属性の中にある "Cross Street" という属性を指定することで検索結果に出現させられるのではないかという提案を得た。これを(ローカル DB でのキャッシュを抜きにして)試してみて数多の交差点が表示されるようであるならば PR にしたい。
related: #32
GitHub Actions が提供する x86_64 な仮想マシン上で x86_64 なヘッドレス Android を動作させ、そこで instrumented test を走らせると flaky な動作になってしまう(成功するはずのテストが不安定な実行環境のために失敗することがある)。
そこで、どうやら実機を用いてテストを走らせてくれるらしい Firebase Test Lab を CI から利用するようにしてみる。
これらを参考にして実装してみる
このアプリを Google Play で公開したい。
現在周辺 Venue を取得するために利用している Foursquare の v3 API は、公式 Swarm アプリで得られる Venue を返してくれないことが多々ある。そのため、妥協案として、過去にチェックインした Venue の履歴を取得するための API を利用し、そこから再度チェックインできるようにしたい。
#12 と連携することで、「過去にチェックインした Venue をお気に入りに登録」することができるようにしたい。これによって、Foursquare の v3 API の弱さをカバーできるのではないかと考える。
Swarm アプリには、友達を指定して同時にチェックインするための機能がある。これと同等の機能を実装できると、友達を巻き込んで交差点にチェックインできるようになり、より便利である。特に運転手がチェックインできないときに助手席で交差点にチェックインしやすくなる。
以下の実装が必要である。
具体的な API エンドポイントは後で調べる。
:library:foursquareclient
の pub.yusuke.foursquareclient.network
以下に FriendApiService
を作成する。
以下の箇所に変更を加え、HTTP リクエスト内で友達を指定する。
友達一覧を取得するための API を CheckinApiService
から呼び出せるようにする。
この画面に遷移するとき、引数として友達のリストを渡せるようにすること。また、この画面から返す結果は、同じ型の友達のリストとすること。
なお、画面間で共有する友達の型は navigation パッケージ以下に定義すること。
余裕があれば、instrumentation test で以下のことを検証する。
MainScreen では、友達選択画面で選択した友達の状態を保持し、またその状態と共に友達選択画面への遷移ができるようにする。
また、チェックインを作成する際には、その状態を見て一緒にチェックインする友達を指定してチェックインできるようにする。
なお、チェックイン作成前後で友達のリストは変化しない。
余裕があれば、instrumentation test で以下のことを検証する。
checkIn
に渡されていることCompose Compiler は各バージョンにおいて動作のためにある Kotlin のバージョンを要求するようになっており、Renovate で自動的に片方をアップデートしようとしても必ずテストが失敗し、人の手による手動アップデートが必要になってしまっている。
これを解決するため、依存パッケージをまとめて 1 つの PR でアップデートさせる Renovate の機能を使うようにする。
https://medium.com/androiddevelopers/automating-dependency-updates-in-a-compose-project-168ef5e89ac5 が参考になるはず(これを読んでいてやりたくなった)。
周囲には API レベル 27 までしかバージョンが上げられなかった端末がいくつかあり、それらで instrumented test を走らせることができたら面白いので minSdk
を 27 まで落としたい。
このためには、少なくともまずは VibratorManager
に直接依存しないようにするべきである。せっかく DI フレームワークを利用しているので、Interactor から端末を振動させるための関数を呼び出して利用する際、そのために Interactor の constructor で VibratorManager
に近い interface を実装したものを注入することにし、その実装は API レベルが 31 以上とそうでない場合でそれぞれ違うものを利用するようにしたい。
shout(チェックインのコメント)を入力してチェックインしても、そのチェックインに shout が付いていない。
この 2 行で、MainContract.ViewModel.checkIn
に shout を渡せておらず、checkIn
の方はデフォルト引数として null
が使われてしまっているのが原因である。
#68 を解消することができたが、その手法 #134 では Renovate による自動アップデートができなくなってしまっている。
https://docs.renovatebot.com/modules/manager/gradle/#file-matching より、Renovate は libs.versions.toml
を見てくれるようである。そのため、Gradle version catalogs を導入し、https://github.com/private-yusuke/interscheckin/blob/fa7996438f8dd9846d2e76ce16953942e552d2cf/buildSrc/src/main/kotlin/LibraryVersions.kt を削除したい。
SettingsScreen
に新規機能用の設定用の部品を追加すると煩雑になるため、各設定項目について画面を分けるようにし、それらにアクセスするための「設定一覧画面」を新しく作成したい。
以下のような形式で導線を作成する。
詳細はあとでかく
現状、Interscheckin にはロゴが無く、ホーム画面に追加してもアプリのアイコンは Android アプリのデフォルトのものとなっている。
他のアプリたちと見分けることが難しくなっており、見栄えも良いわけではないのでアイコンを追加したい。
"Update Venue list" ボタンを押下して位置情報の更新をかける際、移動前の場所の位置情報に似た値が 1, 2 回帰ってきて現在位置が素早く得られないことがある。
期待する動作は、ボタンの 1 回のみの押下ですぐに現在位置が帰ってくるようになることである。
@SuppressLint("MissingPermission")
suspend fun FusedLocationProviderClient.currentLocation(): Location = suspendCoroutine { cont ->
this.getCurrentLocation(CurrentLocationRequest.Builder().build(), null)
.addOnSuccessListener { location ->
cont.resume(location)
}
}
以上のような関数を定義し、MainInteractor.fetchVenues
関数の中で呼び出して返り値をそのまま返す。
後でドライブしながら動作の検証を行い、これで期待した動作が得られたならば PR を作成して出すことにする。
This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.
These updates have all been created already. Click a checkbox below to force a retry/rebase of any.
com.google.devtools.ksp
, org.jetbrains.kotlin.plugin.serialization
, org.jetbrains.kotlin.jvm
, org.jetbrains.kotlin.android
).github/workflows/build_apk.yml
actions/checkout v4
actions/setup-java v4
actions/upload-artifact v4
ubuntu 22.04
.github/workflows/ci.yml
actions/checkout v4
actions/setup-java v4
actions/checkout v4
actions/setup-java v4
actions/checkout v4
actions/setup-java v4
actions/checkout v4
ubuntu 22.04
ubuntu 22.04
ubuntu 22.04
gradle.properties
settings.gradle.kts
build.gradle.kts
app/build.gradle.kts
composeOptions 1.5.10
gradle/libs.versions.toml
com.google.accompanist:accompanist-permissions 0.34.0
androidx.activity:activity-compose 1.8.2
androidx.compose:compose-bom 2024.02.02
androidx.core:core-ktx 1.12.0
androidx.core:core-splashscreen 1.0.1
androidx.fragment:fragment-ktx 1.6.2
androidx.test.ext:junit 1.1.5
androidx.lifecycle:lifecycle-runtime-ktx 2.7.0
androidx.lifecycle:lifecycle-viewmodel-ktx 2.7.0
io.coil-kt:coil-compose 2.6.0
androidx.datastore:datastore-preferences 1.0.0
io.gitlab.arturbosch.detekt:detekt-formatting 1.23.5
androidx.test.espresso:espresso-core 3.5.1
com.google.dagger:hilt-android-compiler 2.51
com.google.dagger:hilt-android 2.51
com.google.dagger:hilt-android-gradle-plugin 2.51
com.google.dagger:hilt-android-testing 2.51
com.google.dagger:hilt-compiler 2.51
androidx.hilt:hilt-navigation-compose 1.2.0
junit:junit 4.13.2
org.jetbrains.kotlinx:kotlinx-collections-immutable 0.3.7
org.jetbrains.kotlinx:kotlinx-serialization-protobuf 1.6.3
io.mockk:mockk 1.13.10
io.mockk:mockk-agent 1.13.10
io.mockk:mockk-android 1.13.10
androidx.navigation:navigation-compose 2.7.7
androidx.paging:paging-common 3.2.1
androidx.paging:paging-compose 3.2.1
com.google.android.gms:play-services-location 21.2.0
androidx.room:room-compiler 2.6.1
androidx.room:room-ktx 2.6.1
androidx.room:room-paging 2.6.1
androidx.room:room-runtime 2.6.1
com.github.anboralabs:spatia-room 0.2.9
com.google.crypto.tink:tink-android 1.12.0
com.twitter.compose.rules:detekt 0.0.26
androidx.appcompat:appcompat 1.6.1
com.squareup.retrofit2:retrofit 2.9.0
com.squareup.retrofit2:converter-moshi 2.9.0
com.squareup.moshi:moshi-kotlin 1.15.1
com.squareup.okhttp3:logging-interceptor 4.12.0
org.jetbrains.kotlinx:kotlinx-coroutines-core 1.8.0
org.jetbrains.kotlinx:kotlinx-coroutines-android 1.8.0
androidx.wear.compose:compose-material 1.3.0
com.android.application 8.3.0
com.android.library 8.3.0
org.jetbrains.kotlin.android 1.9.22
org.jetbrains.kotlin.jvm 1.9.22
org.jetbrains.kotlin.plugin.serialization 1.9.22
io.gitlab.arturbosch.detekt 1.23.5
com.google.devtools.ksp 1.9.22-1.0.18
com.autonomousapps.dependency-analysis 1.30.0
library/foursquare-client/build.gradle.kts
library/fusedlocationktx/build.gradle.kts
library/repositories/build.gradle.kts
watchapp/build.gradle.kts
composeOptions 1.5.10
gradle/wrapper/gradle-wrapper.properties
gradle 8.6
手元で開発しているときは FusedLocationProviderClient.requestLocationUpdates
を呼んで定期的に位置情報を取得するようにしていたが、現在は FusedLocationProviderClient.getLastLocation
で現在端末から得られる最新の位置情報を得るような実装に変更した。
その結果、すぐに現在の位置情報が欲しくなったときに得られず、基本的には数秒前の位置情報を元にチェックインしたり Venue の検索をしたりするようになってしまった。
FusedLocationProviderClient.requestLocationUpdates
を呼び出して最新の位置情報を端末に格納してもらうようリクエストするような実装方法に変えたい。
FusedLocationProviderClient.locationFlow
をすでに実装していたので、MainInteractor.fetchLocation
を、この Flow の first だけを取ってくる実装に変えたら解決できないだろうか。
共通して設定できる項目が多いので、このあたりは別のファイルを作成してまとめると良いかもしれない。
Originally posted by @private-yusuke in #49 (comment)
:library:repositories
は複数形だが、これは統一性に欠けるので :library:repository
に変更したい。
- ef2c4ea で
$compose_ui_version
からバージョンを取得するようにしたが、これは不適切なバージョン管理であると思う。- https://developer.android.com/jetpack/compose/setup#using-the-bom を参考にして BOM(Bill of Materials)を使ったほうがよさそう。
The Compose Bill of Materials (BOM) lets you manage all of your Compose library versions by specifying only the BOM’s version. The BOM itself has links to the stable versions of the different Compose libraries, in such a way that they work well together.
Originally posted by @private-yusuke in #23 (comment)
現在の実装では初回起動時に位置情報の取得を許可しないでいるといつまでたってもローディング中のような表示になってしまうのみであり非常に不親切な設計になっている。また、初回の位置情報取得許可を取った後であっても、それを取る前から位置情報の取得を実施しようとする動作をするのでどちらにせよ好ましくない動作をしている。
これを解決するために、初回起動時(または位置情報取得許可が取れていないとき)にはそこへの導線を置くための画面を作成して表示したい。
また、位置情報取得の許可がなければチェックイン履歴を見ることのみができるようにする。
位置情報を取得できない状態の場合は Snackbar でその旨を伝え、Snackbar にあるボタンから位置情報取得導線画面へ遷移するためのボタンを置く。
位置情報を取得できない状態の場合は位置情報取得導線画面へ遷移するためのボタンが押せるようにし、そうでない場合は許可済みであることを示しつつボタンを押せないようにする。
もしアプリ側で precise な位置情報の取得ができていないことを検知できるのであれば、かわりにそれを要求するための処理を実施させるようにする。
アプリの初回起動時などの、位置情報を利用する旨をユーザーが一切決めていない状況のときは起動時にこの画面を表示するようにする。他の画面からこの画面に遷移することもできるが、それは曖昧な位置情報のみの取得許可または位置情報取得許可が出ていないときのみ許される。
related: #223
Wear OS 向けのアプリケーションを作成するにあたって、Android 向けに今まで記述してきた ViewModel をそのまま流用したい。
MVVM アーキテクチャにおいては、ViewModel を流用して View(当アプリケーションでは Screen 関連の関数)をそれぞれの OS 向けに記述するという方針をとることに問題がないだろうと考えている。
そのため、まずは ViewModel を別モジュールに切り出し、それを Android アプリから参照するようにして、後に Wear OS 向けのアプリケーションからも参照するようにしたい。
Foursquare API の無料枠の credit がなくなると API レスポンスで 429 が帰るようになるが、現状ではその背景の説明をする表示がない。これを表示するための新しい例外や画面表示を追加する。
ユーザーが Venue をお気に入りに追加できる機能があるとうれしそう。
交差点に限らず、よくチェックインする Venue では、毎回公式 Swarm アプリからその Venue を選んでチェックインをする手順が煩雑であると感じることがある。
これを解決するため、「お気に入り」に入れた Venue を表示する画面を作成し、そこから選ぶことでもチェックインできるようにしたい。
そのとき、不正なチェックイン(遠隔地からチェックインする行為)を防ぐため、現在位置から 1 km 以内でないとチェックインできないような制限も設けておきたい。
メイン画面や履歴一覧画面で Venue に関する詳細な情報を表示するための画面を実装したい。
:library:foursquare-client
にも手を加える必要がある@Preview
を適宜書いてみると良いかもしれないHistoriesScreen が参考になりそう:#34
アーキテクチャの部分は VIPER から Presenter を消したようなものに近いです(どちらかというと MVVM なのかも)。https://techlife.cookpad.com/entry/2020/11/17/110000 が参考になるかもしれません
$ ./gradlew detekt
を実行し、自動フォーマッタを動かしてください$ ./gradlew cAT
が成功することを確認してください
@private-yusuke が趣味で Wear OS を搭載した Pixel Watch を取得し、せっかくなのでアプリを書きたいと考えている。
今は大学の前の交差点などで毎回スマホをポケットから取り出してチェックインしているが、これを腕時計で完結できたら非常に便利になると思われる。そこで、Wear OS 向けの Interscheckin 実装を作成したい。
related: #179
デザインの変更によって README.md
に掲載している画像が現状のアプリの画面と異なっているとき、人間がわざわざスクリーンショットの撮影をして更新する必要があり、面倒である。
そこで、機械的にスクリーンショットを撮影できるようにしたい。
https://docs.fastlane.tools/getting-started/android/screenshots/ を参考にして fastlane の導入と自動テスト中の撮影ができるかもしれない。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.