20210916のAndroidに関する記事は4件です。

Jetpack Composeで、Mutable系統をStateに登録したらあかん

Mutable系統をStateに入れていて、数日ドツボにはまったのでメモしておきます。 問題 Jetpack Composeの勉強のために、以下のようなアプリを作成しました。 ソースコードは以下の通りです。 List.kt @Composable fun List(){ //表示するStateを作成 val list by remember { mutableStateOf((0..3).toMutableList()) } LazyColumn(Modifier.fillMaxSize()){ //カラムを表示する部分 item{ for(i in list){ LazyCard(text = i.toString()) } } //ボタンを表示する部分 item { AddButton(onClick = { list.add(list.size) }) } } } これでボタンを押せば、カラムが増えていくはず・・・ですがなぜか増えません。 解決策? 原因ですが、おそらく List.kt //ボタンを表示する部分 item { AddButton(onClick = { list.add(list.size) }) } ここでadd関数を使っていますが、これでは差分を検知してくれないからではないかと思います。 そこでこの部分をとりあえず以下のように書き直してみました。 List.kt //表示するStateを作成 val list by remember { mutableStateOf((0..3).toMutableList()) } ・・・ //ボタンを表示する部分 item { AddButton(onClick = { val tempList = list tempList.add(list.size) list = tempList }) } これで、listに再代入しているので差分を検知してくれるはず・・・がまたしてもピクリともしない。 原因 なぜ検知してくれないのかと思い、tempListに要素を追加したタイミングでそれぞれの値を見てみました。 List.kt //ボタンを表示する部分 item { AddButton(onClick = { val tempList = list tempList.add(list.size) Log.d("debug", "list: $list, tempList: $tempList") list = tempList }) } すると驚くことに、以下の結果が出力されました。 D/debug: list: [0, 1, 2, 3, 4], tempList: [0, 1, 2, 3, 4] これはつまり tempList.add(list.size) が実行されたタイミングでlistの方にも反映されているということです。 ということは val tempList = list ここでの代入は値渡しではなく、参照渡しになっている?ということでしょう。 確かにそう考えると、add関数では差分を検知しないし、最後の代入でも差分がない。 UIの再描写が走らないはずです。 解決策 GoogleのCodelabを見て回っていたら、このような感じで記述していました。 List.kt var list by remember { mutableStateOf((0..3).toList()) } ・・・ //ボタンを表示する部分 item { AddButton(onClick = { list = list + listOf(list.size) }) } なるほど・・・完全に新しく作り直していますね。 確認したところ、これだと正しく動作しました。 となってくると、StateでMutable系統を使うのはやめた方がよさそうですね。 add関数が使えない時点で通常の型の代わりに使うメリットがないし、むしろバグの温床になりそうです。 補足 参照渡しになるのはKotlin(正確にはJava)の仕様のようですね。 プリニティブ型では値渡し、クラス型では参照渡しをするようで、Mutable系統はもちろんクラス型なので参照渡しになるということのようです。 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JUnit5の@ParameterizedTestがAndroidStudioで認識されない

複数の引数を入れて期待値が得られるかをテストするには、 JUnit5のParameterizedTestが便利らしいので導入しようとしました。 gradleに導入 testImplementation 'org.junit.jupiter:junit-jupiter:5.5.2' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.2' testImplementation 'org.junit.jupiter:junit-jupiter-params:5.5.2' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.5.2' Testは認識されるがParameterizedTestが認識されない unresolvedになってしまう option + Enterも試しましたが候補が出ず、導入に問題があるのかと思い悩んでました。。 結論、importに手動で書く 自動補完になれすぎた弊害ですね。。これで動きました。 import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.MethodSource
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Flipper の導入

Flipper Androidで、デバッグする際、logcatを確認することは多いと思いますが、DBの中身だったりレイアウトのチェックなどは出来ません。 そこで、プラグインを導入することで、デバッグすることが可能な Flipper を紹介したいと思います。 Flipperは、Facebook製のデバッグツールです。 Androidからの通知をPCのデスクトップアプリにGUIツールです。 Flipper Github Flipper の導入 Gradleに定義 build.gradle.kts debugImplementation("com.facebook.flipper:flipper:0.104.0") debugImplementation("com.facebook.soloader:soloader:0.10.1") releaseImplementation("com.facebook.flipper:flipper-noop:0.104.0") // plugin debugImplementation("com.facebook.flipper:flipper-network-plugin:0.104.0") Applicationクラスに実装 MyApplication.kt override fun onCreate() { super.onCreate() SoLoader.init(context, false) if (BuildConfig.DEBUG) { if (FlipperUtils.shouldEnableFlipper(context)) { AndroidFlipperClient.getInstance(context).apply { addPlugin(InspectorFlipperPlugin(context, DescriptorMapping.withDefaults())) addPlugin(DatabasesFlipperPlugin(context)) addPlugin(SharedPreferencesFlipperPlugin(context)) addPlugin(LeakCanary2FlipperPlugin()) start() } } } } これだけで、デバッグ情報がPCのFlipperアプリで確認することが可能です。 ただし、プラグインを導入する場合、少し複雑になります。 Gradleの定義で、debugImplementationとなっているものは、デバッグ時だけ読み込まれます。 releaseImplementationは、リリース時に読み込まれれますが、noopとなっているため処理が実装されていないインターフェイスが呼び出されます。 flipper-network-pluginは、debugImplementationのみとなるので、リリース時はインターフェイスが呼び出されないため、ビルドエラーとなります。 そのため、デバッグとリリースで、処理を分ける必要があり、リリース時はプラグインを使わないようにしてあげる必要があります。 プラグイン使用時 例として、Network Pluginを記載します。 まず、/app/src/debugと/app/src/releaseのフォルダーを用意します。 /app/src/debug debugImplementationで定義されているプラグインが使用できる。 FlipperNetwork.kt object FlipperNetwork { private val networkFlipperPlugin = NetworkFlipperPlugin() fun createFlipper(context: Context) { SoLoader.init(context, false) if (BuildConfig.DEBUG) { if (FlipperUtils.shouldEnableFlipper(context)) { AndroidFlipperClient.getInstance(context).apply { addPlugin(InspectorFlipperPlugin(context, DescriptorMapping.withDefaults())) addPlugin(DatabasesFlipperPlugin(context)) addPlugin(networkFlipperPlugin) addPlugin(SharedPreferencesFlipperPlugin(context)) addPlugin(LeakCanary2FlipperPlugin()) start() } } } } fun getOkHttpClient(): OkHttpClient { return OkHttpClient.Builder() .addInterceptor(FlipperOkhttpInterceptor(networkFlipperPlugin)) .addInterceptor(HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY }) .build() } /app/src/release プラグインは、呼び出せないため、空の呼び出しを作成する。 FlipperNetwork.kt object FlipperNetwork { fun createFlipper(context: Context) { } fun getOkHttpClient(): OkHttpClient { return OkHttpClient.Builder() .build() } Applicationクラスに実装 これを実装することで、デバック時はFlipperを使用して、リリース時はFlipperを使用しない実装が可能になります。 MyApplication.kt override fun onCreate() { super.onCreate() FlipperNetwork.createFlipper(this) }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pepper SDK入門(36) LocalizeAndMapでPepper専用地図を得る

はじめに PepperはLocalizeAndMapアクションとLocalizeアクションによって周辺環境の地図を作成し、移動しても自分の相対位置を保持します。 //アクションをビルドする LocalizeAndMap localizeAndMap = LocalizeAndMapBuilder.with(qiContext).build(); //非同期にアクションを実行 localizeAndMap.async().run(); //LocalizeAndMapの実行を保存 private Future<Void> localizationAndMapping; つまりLocalizeが実行されていれば、Pepperを特定の位置に配置して周辺をネズミ花火のように動き回ってもらってもPepperは自分の立ち位置を見失いません? LocalizeAndMapの使い方は以下の通りです↓ //アクションをビルド LocalizeAndMap localizeAndMap = LocalizeAndMapBuilder.with(qiContext).build(); //Localize状況を取得するためのリスナーを追加 localizeAndMap.addOnStatusChangedListener(localizationStatus -> { if (localizationStatus == LocalizationStatus.LOCALIZED) { //アクションを停止 localizingAndMapping.requestCancellation(); //Localizeアクションが使う地図情報を出力する ExplorationMap explorationMap = localizeAndMap.dumpMap(); } }); //アクションを実行 localizingAndMapping = localizeAndMap.async().run(); 既存の地図をパン生地のように拡張する? 周辺環境の地図が既に作成できていれば、新しいLocalizeAndMapによってその地図の拡張が可能になります。ドリルなどは使いません。 Pepperが地図を読み込むのに成功したら、ExplorationMapにデータを追加します。 この機能性はステレオカメラを装備したPepperで、APIレベル7のSDKでビルドされたExplorationMap上でのみ実行されます。 Pepperがステレオカメラを装備していない場合などは、LocalizeAndMapの呼び出しは失敗します。 //アクションをビルド LocalizeAndMap localizeAndMap = LocalizeAndMapBuilder.with(qiContext) .withMap(explorationMap) .build(); センサーは超常的な何かを感知しない 位置を特定するには、センサーの情報を用いて環境内のPepperの位置を計算します。 主としてレーザーセンサーを用いるため、見えるのは数メートル先までです。鳥取砂丘などの開かれた環境ではPepperが得られる情報は少なく、一貫性のないものになります。 人間とか幻獣とかポルターガイストとか全ての動くオブジェクト 周辺環境の地図を作成している間にPepperが検出した障害物は、人間も含みすべて地図上に保存されます。極小規模なストリートビューですね。 人間が多ければ多いほどLocalizeアクションのパフォーマンスが落ちて、地図の結果に影響します。 障害物の検出は、LocalizeAndMapアクションの実行中以外にも継続的に実行されます。 Pepperの障害物検出の能力は、センサー値によって時間と共に上がります。その結果、LocalizeAndMapがスタートする数秒前にPepperの周りにいた人間は障害物などとして地図上に保存されます。 よって周辺環境の地図を作成する前には、Pepperの近くに人間や動くオブジェクトがいない方が望ましいです。15秒ほど待ってからLocalizeAndMapアクションを実行してください。 見えない何かがいそうな場合は、除霊などしないまでも窓を開けて外へ逃すなどしてから実行しましょう! アニメーションを読み込む(熟読はしない) LocalizeAndMapアクションがスタートした時、Pepperはセンサーから情報を集めるために周辺を見回します。 LocalizeAndMapがこのアニメーションを実行できない時、例えば既に実行中の他のアクションがある場合には、LocalizeAndMapはアニメーション失敗のエラーを投げます。椅子取りゲーム方式ですね? あとがき 今回もPepperSDKforAndroidを参考に書かせていただきました。 さらに詳しい情報は、LocalizeAndMapのAPIドキュメントを参照してください。 次回のPepperSDK入門はExplorationMapです。Pepperによる探検は始まりません?⛏それでは〜
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む