20211202のAndroidに関する記事は9件です。

Android開発チュートリアル翻訳1(フランクに)

※少し、いや、かなり意訳が含まれている部分もあります。 1.紹介 このコードラボではエミュレータとか実機上で動くHello World!って表示するアプリを作って実行するよ! 前提知識はこうだ! Android StudioみたいなIDE(統合開発環境)を使ったオブジェクト指向アプリの一般的なソフトウェア開発の流れを知っている。 少なくとも一年以上のオブジェクト指向プログラミングの経験があって、さらにkotlinとjava(javascriptじゃないんだからね!!!)について理解している。 学習内容だ! Android Studioでの基本的なアンドロイドアプリのビルドの仕方。 テンプレートからのアンドロイドプロジェクトの作り方 アンドロイドのメジャーな部品のみっけかた エミュレータと実機でのビルドの仕方 やることだyo! helloworldってゆうデフォルトのアプリの作成 PC上で実行できるようにエミュレータの作成 エミュレータと実機でHelloWolrdのビルド プロジェクトのレイアウトを確認する。 AndroidManifest.xmlってゆうファイルの確認 2.アプリ概要 このアプリは画面にHello Worldって文字列を表示するだけのクソアプリだぜ! その画面がこちら 1、2、3.... 3.タスク:Hello Worldプロジェクトを作れ! このタスクでは、新しいアプリのプロジェクトを作成し、Android Studioが正しくインストールされていることを確認しよう! 1.まだ開いてなかったらAndroid Studioを開きんさい 2.ウェルカム画面でStart a new Android Studio projectをクリックしよう! 3.プロジェクト選択画面が表示されただろ?そしたら、Empty Activityを選択してNextボタンをクリックだ! どのアプリもエントリーポイントとして少なくても一つはアクティビティを持ってないといけないんだ。 エントリーポイントは他のプログラムでいうところのmain()関数だとでも思いたまえ。アクティビティは大体UIが画面上にどんな感じに表示されるか定義するレイアウトが結びつけられてるんだ! Android Studioは君たちがとっつきやすいように何個かのActivity templatesを用意してくれてるんだぜ! 4.プロジェクト設定画面でNameのところにHelloWorldって入れてみよう! 5.会社のドメインとしてデフォのandroid.example.comで行くか独自のドメインを設定しよう。ドメインにアプリ名を加えたやつがアプリのパッケージ名になるんだぜ!公開するつもりないならそのままで問題ないぜ!あとで変更できるけどめんどくさいから気をつけるんだよ! 6.アプリの保存場所を確認せぃ!あかん場所やったら好きなとこ選んでみぃ! 7.もちろん言語はkotlinだよなあ!!?? 8.Minimum API level はAPI 19: Android 4.4 (KitKat)を選んでね!このAPIレベルだったら公式のコードラボ作成した時点だったら95%くらいの端末で動くんだって!すごいね! 9.Use AndroidX artifactsにチャック入れてな 10.他はもう触らんとチェック外した状態でFinish押して!君らが選んだSDKのコンポーネントが必要な時はAndroidStudioが勝手にインストールしてくれるんやけど時間かかるときあるからごめんやで! Android Studioがプロジェクト作ってくれよるからだまって待っときや!エラーはでーへんと思うけど警告が出ても無視やで。 4.タスク: Android Studio確認 このタスクはAndroid StudioのHelloWorldプロジェクトを調査して、Android Studioでの開発の基本をおべんつよするんやで。(疲れてきた。) Step1: Project pane確認 プロジェクトタブが開いてなかったら開いてな。左側の縦のタブ欄にあるさかいに。 プロジェクトの階層を見るために上の方にあるドロップダウンメニューからAndroid押して。プロジェクトのファイルは、ファイルシステム階層の中でどんな感じに表示されるかとか、いろんな方法で見れるで。 Note: このコードラボでは、プロジェクトペインをAndroidに設定した場合、プロジェクト > Androidペインと表記しています。 Step 2: アプリフォルダの確認 アプリに関係あるコードとかリソースは全部appフォルダにあるで。 1.Project > Androidペインでappフォルダ開いて。そんなかにminifests java generatedJava resの4つのフォルダある? 2.javaフォルダ開いてcom.example.android.helloworldフォルダ開いたらMainActivity.ktってゆうファイルあるやろ。 javaフォルダには、Androidアプリの主要なKotlinコードが全部はいっとる。Kotlinのコードがjavaフォルダに格納されるのには、歴史的な理由があるんやとさ。これは、同じプロジェクトやアプリの中で、KotlinとJavaプログラミング言語で書かれたコードをシームレスに相互運用するための規則です。(deepl楽やわー、こっからDeepLでいこ。) アプリのクラスファイルは、上の図のように3つのサブフォルダに入っています。com.example.hello.helloworld(または指定したドメイン名)フォルダには、アプリのパッケージに必要なすべてのファイルが含まれています。特に MainActivity クラスは、アプリのメイン・エントリー・ポイントとなります。MainActivity については、次の codelab で詳しく説明します。javaフォルダ内の他の2つのフォルダは、ユニットテストなどのテスト関連のコードに使用されます。 注:ファイルシステムでは、Kotlinファイルの拡張子は.ktで、Kのアイコンが付いています。Projectビューでは、Android Studioは拡張子のないクラス名(MainActivity)を表示します。 3.generatedJavaフォルダに注目してください。このフォルダには、Android Studioがアプリをビルドするときに生成するファイルが入っています。アプリを再構築する際に変更が上書きされる可能性があるので、このフォルダ内のファイルは編集しないでください。しかし、デバッグ中にこれらのファイルを見る必要がある場合には、このフォルダーについて知っておくと便利です。 Step 3:resフォルダの確認 1.プロジェクト→Androidペインでresフォルダを展開します。 resフォルダには、リソースが格納されています。Androidにおけるリソースとは、アプリで使用する静的なコンテンツのことです。リソースには、画像、文字列、画面レイアウト、スタイル、16進数の色や規格の寸法などの値が含まれます。 Androidアプリでは、Kotlinのコードとリソースを可能な限り分離しています。そのため、アプリのUIに使われている文字列やアイコンなどを探しやすくなっています。また、これらのリソースファイルを変更すると、そのファイルがアプリ内で使用されているすべての場所で変更が反映されます。 2.resフォルダ内のlayoutフォルダを展開すると、activity_main.xmlファイルがあります。 そのレイアウトファイルは、通常、そのアクティビティにちなんだ名前が付けられます。この場合、アクティビティ名はMainActivityなので、関連するレイアウトはactivity_mainとなります。 Step4: manifestsフォルダとAndroidManifest.xmlを確認 manifestsフォルダには、アプリに関する重要な情報をAndroidシステムに提供するファイルが含まれています。 1.manifestsフォルダを展開し、AndroidManifest.xmlをダブルクリックして開きます。AndroidManifest.xmlファイルには、どのアクティビティがアプリの一部であるかなど、アプリを実行するためにAndroidシステムが必要とする詳細情報が含まれています。 2.MainActivityは要素で参照されていることに注意してください。アプリ内のアクティビティは、マニフェストで宣言する必要があります。以下にMainActivityの例を示します。 <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> 3.<activity>の中にある<intent-filter>要素に注目してください。このインテントフィルターの<action>と<category>要素は、ユーザーがランチャーアイコンをクリックしたときに、どこでアプリを起動するかをAndroidに伝えます。インテント・フィルターについては、後のコードラボで詳しく説明します。 AndroidManifest.xmlファイルは、アプリが必要とするパーミッションを定義する場所でもあります。パーミッションには、アプリが電話の連絡先を読んだり、インターネットでデータを送信したり、デバイスのカメラなどのハードウェアにアクセスしたりする機能が含まれます。 一旦終了 ちょい疲れたんで続きはまた今度でお願いしやす。申し訳ねえ。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Android】PlayStore遷移時のActivityNotFoundException

クラッシュリティクスにこんな報告が上がってきました。 Fatal Exception: android.content.ActivityNotFoundException No Activity found to handle Intent { act=android.intent.action.VIEW dat=https://play.google.com/... pkg=com.android.vending } PlayStoreへのIntentが失敗している? 中華スマホとか、Kindleならわかるが対象端末は普通にキャリアで売られているものばかり。。 問題はこれかも Disabledにして、強制アップデートダイアログを叩いてストア遷移しようとすると落ちました。 省電力アプリとかでアプリを停止させられてしまうこともあるんだろうか・・・?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Android】公開タイミングの管理ボタンがない

このボタンがなくて困った… PlayStoreにアプリを"初めて"リリースしようとすると「管理対象の公開のステータス」ボタンが表示されません。 審査が通ったらいきなり公開されてしまうんでは・・?という不安な気持ちになります。 こんなときにどうするか、書いておきます。 結論 クローズドテストで公開する クローズドテストなら予めベータテスタとして登録した人だけに公開されるので安心です。 審査に出すタイミングで「完全公開を開始しました」という恐ろしい文言が出ますが、大丈夫です。 審査中のステータスになると、「管理対象の公開のステータス」ボタンが表示されるので、 画像のようにチェックを入れておけば勝手に公開されることはなくなります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

暗黙的な内部の Intentを解決したい

Android Studioにて開発を行っております。 課金部分のテストを行うために内部テストとしてリリースを行おうとしておりますが、 セキュリティと信頼性の項目にて「暗黙的な内部の Intent」としてエラーが返ってきております。 こちら該当コード箇所が以下になります。 Intent intent = new Intent(); intent.setAction("hoge.hoge.hoge"); intent.putExtra("status", 1); sendBroadcast(intent); AndroidManifest.xmlにて受信側の宣言として <receiver android:name="com.hoge.app.activity.HogeActivity"> <intent-filter> <action android:name="hoge.hoge.hoge" /> </intent-filter> </receiver> というようにブロードキャストレシーバーを宣言しました。 しかし、同じエラー内容ではじかれてしまっております。 こちらどうすれば解決するのでしょうか。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

`Run flutter doctor --android-licenses to accept the SDK licenses.` が上手く行かなった時のtips

flutter doctorした時にこのようなエラーが出るケースがある ❯ flutter doctor Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel stable, 2.5.2, on macOS 11.6 20G165 darwin-arm, locale ja-JP) [!] Android toolchain - develop for Android devices (Android SDK version 31.0.0) ✗ Android license status unknown. Run `flutter doctor --android-licenses` to accept the SDK licenses. See https://flutter.dev/docs/get-started/install/macos#android-setup for more details. [✓] Xcode - develop for iOS and macOS [✓] Chrome - develop for the web [✓] Android Studio (version 2020.3) [✓] Android Studio (version 2020.3) [✓] IntelliJ IDEA Ultimate Edition (version 2021.2.2) [✓] IntelliJ IDEA Ultimate Edition (version 2021.2.2) [✓] VS Code (version 1.60.2) [✓] Connected device (3 available) ! Doctor found issues in 1 category. 言われたとおりにflutter doctor --android-licensesを実行するも上手く行かない ❯ flutter doctor --android-licenses ERROR: JAVA_HOME is set to an invalid directory: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home Please set the JAVA_HOME variable in your environment to match the location of your Java installation. javaを入れてJAVA_HOMEを有効にする ここで指定するjavaのバージョンは恐らくAndroid SDKのversionと関係しているのかと予想。 /android/app/build.gradleに記載があるので対応するjavaのバージョンが何かを調べてみても良さそうです。 $ brew tap homebrew/cask-versions $ brew install adoptopenjdk8 --cask $ echo JAVA_HOME=/Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home >> ~/.zshrc
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AndroidでUIへのイベント通知としてSharedFlowとChannelを上手に使い分ける

前置き AndroidでUIへイベントを通知する方法としては最近LiveDataよりもよくSharedFlowを使う方法が紹介されていると思います。 Kotlin標準の言語機能で実現できるので今後もデファクトスタンダードになっていくと感じています。 シンプルな例ですが、下記のようなコードを書くことになると思います。 Activity class CounterActivity : AppCompatActivity() { private val counterViewModel = // getViewModel() override fun onCreate(savedInstanceState: Bundle?) { lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { counterViewModel.countEvent.collect { count -> ... } } } } } ViewModel class CounterViewModel(private val counterRepository: CounterRepository) : ViewModel() { private val _countEvent = MutableSharedFlow<Int>() val countEvent = _countEvent.asSharedFlow() init { viewModelScope.launch { val count = counterRepository.fetchCount() _countEvent.emit(count) } } } しかしながら上記のコードには場合によってはうまく動作しないケースが存在します。 問題点 SharedFlowは購読者の有無に関わらずイベントを即時発火させて内部には状態を保持しない特性があります。(保持するのはStateFlow) それがゆえに一度だけ発火させる振る舞いには向いているのですが、そのせいでイベントが握りつぶされるケースが少なからずあります。 具体的には、上記コードではViewModelのinit {}内でデータ取得処理を記述していますが、当然ながらこのinit {}はonCreate()内のcollect {}を呼ぶより先に実行されます。 上記コードのfetchCount()がAPI通信などを行ってしばらく時間がかかるのであれば特に問題にはならないとは思います。 しかしながら下記のように処理を書き換えたらどうなるでしょうか? ViewModel class CounterViewModel() : ViewModel() { private val _countEvent = MutableSharedFlow<Int>() val countEvent = _countEvent.asSharedFlow() init { viewModelScope.launch { _countEvent.emit(1) } } } init {}内のデータ取得処理が即時完了する上記のようなケースの場合は、collect {}されるより先に_countEvent.emit(1)が実行されてしまいます。 その場合はSharedFlowの購読者はまだ存在してないなか発火してしまい、イベントがそのまま立ち消えるのでその後にcollectされてもイベントをキャッチすることが出来ません。 Channelを使う このような場合に代わりに使えるのがChannelクラスです。 Channelクラスは特徴として、購読者が存在しない場合は状態を保持して一人目の購読者が現れるまで発火を待ってくれます。 なのでデータ代入よりcollect {}のほうがあとになってもイベントをキャッチすることが出来ます。 使い勝手もreceiveAsFlow()というFlow<T>へ変換できる関数が生えており、下記のようにSharedFlowとほとんど同じように使うことが可能です。 ViewModel class CounterViewModel() : ViewModel() { private val _countEvent = Channel<Int>() val countEvent = _countEvent.receiveAsFlow() init { viewModelScope.launch { _countEvent.send(1) } } } LiveData時代のSingleLiveEventと同等の機能というと通じる人も多いかと思います。 余談ですがreceiveAsFlow()とは別にconsumeAsFlow()という関数も生えておりこちらもFlow<T>へ変換できますが、メソッド名の通り一度きりのイベントとなり2回目のイベントを送ろうとするとExceptionが吐かれるのでご注意ください(一度間違えてひどい目に会いました。。) SharedFlowとChannelの使い分け ここまでSharedFlowではうまく行かずChannelだと問題ないケースを紹介しました。 ではSharedFlowは使わずに常にChannelを使えばいいのか?とお思いになる方もいるかも知れませんが、必ずしもそうとは限りません。 上記でも紹介したとおり、Channelは一人目の購読者が現れるまで待ってから発火するのが特徴です。 これは逆に言えば、2人以上購読者がいるときに期待通りの動作にならない(=2人目の購読者に通知されない)ことがあります。 このような場合はSharedFlowを使うことで2人ともにそれぞれイベントが適切に通知されます。 一般的にはViewModelはView側を意識せず、Viewからどう使われようとも問題ないように実装するのがセオリーです。 しかしながらView側の購読タイミングや購読者数に応じてSharedFlowやChannelをうまく使い分ける必要があります。 その上で個人的なおすすめとしては、基本的にはSharedFlowで実装しつつタイミング的に通知されない場合のみChannelを利用することを検討するのが良いかと思います。 あるいはそもそもですが、一度きりのイベントではなくなるべく状態として扱いStateFlowを利用できないか積極的に検討していく、というのがよりベターな解法だとも思います。 まとめ とりあえずSharedFlowとChannelをイベント通知として利用する場合は下記のルールを覚えておいてください。 クラス 複数の購読者に対する挙動 購読タイミングによる影響 SharedFlow 購読者が何人いても全員に通知される collect前に代入されたイベントは消えてしまう Channel 購読者が複数人いても一人にしか通知されない collect前に代入したイベントは保持されて一人目の購読者に通知される まずはSharedFlowを使いつつ、動作しないときにChannelを検討する、というのがおすすめ。 あわせて、一度きりのイベントではなく状態として扱ってStateFlowで代用できないかも検討する。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RE: AndroidでFlow<T>をcollectするのをちょっとだけ楽にする

前置き 以前 AndroidでFlowをcollectするのをちょっとだけ楽にするという記事を書いたのですが、 Flow<T> をcollectする際には launchWhenStarted ではなく代わりに repeatOnLifecycle を使いましょうというお達しが出ているのでそれに合わせて記事の内容も書き直してみます。 課題 公式の案内でもあるとおり、AndroidのActivity/FragmentでFlow<T>を collect する場合には下記のいずれかの記述をする必要があります。 Activity override fun onCreate(savedInstanceState: Bundle?) { lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.uiState.collect { uiState -> ... } } } } Activity override fun onCreate(savedInstanceState: Bundle?) { lifecycleScope.launch { viewModel.uiState .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED) .collect { uiState -> ... } } } } なんというかネストが深くて記述が冗長ですね。 repeatOnLifecycleよりはflowWithLifecycleを使うことでそこはかとなく読みやすくなっているような気もしますが、それでもいくつもcollectが増えると可読性が下がりそうです。 Flow<T>の拡張関数を作って対処する そこで下記のような拡張関数を用意して、launch関数でネストさせなくてもcollect出来るようにしてみます。 FlowCollector inline fun <T> Flow<T>.collectOnLifecycle(lifecycleOwner: LifecycleOwner, state: Lifecycle.State, crossinline action: suspend (value: T) -> Unit): Job { return lifecycleOwner.lifecycleScope.launch { lifecycleOwner.repeatOnLifecycle(state) { collect(action) } } } inline fun <T> Flow<T>.collectOnCreated(lifecycleOwner: LifecycleOwner, crossinline action: suspend (value: T) -> Unit): Job { return collectOnLifecycle(lifecycleOwner, Lifecycle.State.CREATED, action) } inline fun <T> Flow<T>.collectOnStarted(lifecycleOwner: LifecycleOwner, crossinline action: suspend (value: T) -> Unit): Job { return collectOnLifecycle(lifecycleOwner, Lifecycle.State.STARTED, action) } inline fun <T> Flow<T>.collectOnResumed(lifecycleOwner: LifecycleOwner, crossinline action: suspend (value: T) -> Unit): Job { return collectOnLifecycle(lifecycleOwner, Lifecycle.State.RESUMED, action) } repeatOnLifecycle+collectを置き換える 先程の拡張関数を使うと下記のように表すことができます。 LiveData並に読みやすくなったと思います。 Activity override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewModel.flow.collectOnStarted(this) { binding.textView.text = it } } Fragment override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewModel.flow.collectOnStarted(viewLifecycleOwner) { binding.textView.text = it } } 余談: FragmentにおけるlifecycleScopeのlifecycleOwnerについて 上記でさらりとFragmentにおけるパターンも紹介しましたが、lifecycleOwnerに指定には注意点があります。 ActivityではActivity自身がlifecycleOwnerなので、とりあえずthisを指定しておけば基本的には問題ありませんが、FragmentではlifecycleOwnerはFragment自身とviewLifecycleOwnerの2種類があります。 Fragment自身のほうはFragmentクラス自体のライフサイクルに準拠しており、Viewのライフサイクルとは異なります。 なので、あくまでFragment内のViewを操作する場合にはviewLifecycleOwner側のlifecycleScopeを使うようにしてください。 ではFragment自身のlifecycleOwnerはいつ指定するのかというと、私がこれまで必要だったシーンとしては、UIを持たない(=Activityにattachしない)Fragmentとして利用する場合です。 その場合は逆に誤ってviewLifecycleOwnerを指定してしまうとViewが存在しないのでSTARTEDまで到達せず、結果としてcollectが通知されなくなってしまうのでご注意ください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RE: AndroidでFlow<T>をrepeatOnLifecycleでcollectするのをちょっとだけ楽にする

前置き 以前 AndroidでFlowをcollectするのをちょっとだけ楽にするという記事を書いたのですが、 Flow<T> をcollectする際には launchWhenStarted ではなく代わりに repeatOnLifecycle を使いましょうというお達しが出ているのでそれに合わせて記事の内容も書き直してみます。 課題 公式の案内でもあるとおり、AndroidのActivity/FragmentでFlow<T>を collect する場合には下記のいずれかの記述をする必要があります。 Activity override fun onCreate(savedInstanceState: Bundle?) { lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.uiState.collect { uiState -> ... } } } } Activity override fun onCreate(savedInstanceState: Bundle?) { lifecycleScope.launch { viewModel.uiState .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED) .collect { uiState -> ... } } } } なんというかネストが深くて記述が冗長ですね。 repeatOnLifecycleよりはflowWithLifecycleを使うことでそこはかとなく読みやすくなっているような気もしますが、それでもいくつもcollectが増えると可読性が下がりそうです。 Flow<T>の拡張関数を作って対処する そこで下記のような拡張関数を用意して、launch関数でネストさせなくてもcollect出来るようにしてみます。 FlowCollector inline fun <T> Flow<T>.collectOnLifecycle(lifecycleOwner: LifecycleOwner, state: Lifecycle.State, crossinline action: suspend (value: T) -> Unit): Job { return lifecycleOwner.lifecycleScope.launch { lifecycleOwner.repeatOnLifecycle(state) { collect(action) } } } inline fun <T> Flow<T>.collectOnCreated(lifecycleOwner: LifecycleOwner, crossinline action: suspend (value: T) -> Unit): Job { return collectOnLifecycle(lifecycleOwner, Lifecycle.State.CREATED, action) } inline fun <T> Flow<T>.collectOnStarted(lifecycleOwner: LifecycleOwner, crossinline action: suspend (value: T) -> Unit): Job { return collectOnLifecycle(lifecycleOwner, Lifecycle.State.STARTED, action) } inline fun <T> Flow<T>.collectOnResumed(lifecycleOwner: LifecycleOwner, crossinline action: suspend (value: T) -> Unit): Job { return collectOnLifecycle(lifecycleOwner, Lifecycle.State.RESUMED, action) } repeatOnLifecycle+collectを置き換える 先程の拡張関数を使うと下記のように表すことができます。 LiveData並に読みやすくなったと思います。 Activity override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewModel.flow.collectOnStarted(this) { binding.textView.text = it } } Fragment override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewModel.flow.collectOnStarted(viewLifecycleOwner) { binding.textView.text = it } } 余談: FragmentにおけるlifecycleScopeのlifecycleOwnerについて 上記でさらりとFragmentにおけるパターンも紹介しましたが、lifecycleOwnerに指定には注意点があります。 ActivityではActivity自身がlifecycleOwnerなので、とりあえずthisを指定しておけば基本的には問題ありませんが、FragmentではlifecycleOwnerはFragment自身とviewLifecycleOwnerの2種類があります。 Fragment自身のほうはFragmentクラス自体のライフサイクルに準拠しており、Viewのライフサイクルとは異なります。 なので、あくまでFragment内のViewを操作する場合にはviewLifecycleOwner側のlifecycleScopeを使うようにしてください。 ではFragment自身のlifecycleOwnerはいつ指定するのかというと、私がこれまで必要だったシーンとしては、UIを持たない(=Activityにattachしない)Fragmentとして利用する場合です。 その場合は逆に誤ってviewLifecycleOwnerを指定してしまうとViewが存在しないのでSTARTEDまで到達せず、結果としてcollectが通知されなくなってしまうのでご注意ください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JetpackComposeのEffect系関数一覧

はじめに Google公式ドキュメントで説明されているComposeのEffect系関数について、端的にまとめました。 Effect系関数の役割・目的とは? Composable関数の実行したときの副作用(Composable関数を実行したことによる、Composable関数外への影響)を定義するための関数。 (Google公式ドキュメント上では「Side-Effect APIs」と表現されている) LaunchedEffect 「非同期で実行される副作用」を定義する関数。 LaunchedEffect関数にkeyを設定することで、Recomposeに伴うCoroutineの再起動を抑止することができる。 以下の処理の場合、remember関数でsnackbarHostStateが生成されているため、snackbarHostStateはRecomposeを跨いで生存する。つまり、このsnackbarHostStateをkeyとしているLaunchedEffect関数は、Recomposeを跨いで非同期処理を続行する。 LaunchedEffect単体 @Composable private fun VideoScreen( state: PlaybackState, snackbarHostState: SnackbarHostState = remember { SnackbarHostState() } ) { if (state is PlaybackState.ERROR) { LaunchedEffect(key1 = snackbarHostState) { // showSnackbarはsuspend関数 snackbarHostState.showSnackbar( message = "再生に失敗しました", actionLabel = "Retry message" ) } } /* 以下、UI構築処理 */ } LaunchedEffectとrememberUpdatedStateの組み合わせ Recomposeに伴うLaunchedEffectの再起動は避けたいが、「LaunchedEffectの中で参照しているオブジェクト」は常に最新の値に保ちたいときは、rememberUpdatedState()を利用する。 @Composable fun LandingScreen(onTimeout: () -> Unit) { // Recomposeの度に最新のラムダ式が代入される val currentOnTimeout by rememberUpdatedState(onTimeout) // key1は固定値のため、Recompseで再起動することはない。 LaunchedEffect(key1 = true) { delay(splashWaitTimeMillis) // recomposeで得られた最新のonTimeout関数を実行する currentOnTimeout() } /* 以下その他処理 */ } rememberUpdatedStateを利用しなくても、引数として定義されている「onTimeout」をそのままLaunchedEffect内で実行すればよいのでは? No。その場合、LandingScreenのRecomposeが何度実行されても、初回Composeで渡したonTimeoutが実行されてしまう。 DisposableEffect 「同期的に実行される副作用」を定義する関数。 DisposbleEffectでは、onDispose関数を用いることでComposition終了(leaves the Composition)時の挙動も定義できる。 また、LaunchedEffectと同様にkeyを設定することで、Recomposeに伴うDisposableEffectの再起動を抑止することができる。 DisposableEffectとrememberUpdatedStateの組み合わせ DisposableEffectの再起動は避けたいが、「DisposableEffectの中で参照しているオブジェクト (今回の場合は、onStartとonStop関数オブジェクト)」を常に最新の値に保ちたいときは、rememberUpdatedState()を利用する。 @Composable fun Screen( lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current, onStart: () -> Unit, onStop: () -> Unit ) { val currentOnStart by rememberUpdatedState(onStart) val currentOnStop by rememberUpdatedState(onStop) DisposableEffect(key1 = lifecycleOwner) { val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_START) { currentOnStart() } else if (event == Lifecycle.Event.ON_STOP) { currentOnStop() } } lifecycleOwner.lifecycle.addObserver(observer) onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } } /* 以下その他処理 */ } rememberUpdatedStateを使わずに、onStartとonStopをそのままobserverの中で実行しても同じ結果になるのでは? No。その場合、onStartもしくはonStopが更新されてHomeScreenがRecomposeしても、「observerの引数に与えたラムダ式」は永遠に、初回のcomposition時に与えたonStartと onStopを実行し続けてしまう。 lifecycleOwner(Effectのkey)は変更されていないが、onStartもしくはonStopが変更されて、 HomeScreenのReCompositionが起動した場合がこれにあたる。 rememberCoroutineScope Composable関数内で使用するCoroutineScopeを生成する時に使用。 このScopeはRecompositionを跨いで生存する。 @Composable fun Screen(scaffoldState: ScaffoldState = rememberScaffoldState()) { val scope = rememberCoroutineScope() Scaffold(scaffoldState = scaffoldState) { Column { Button( onClick = { scope.launch { scaffoldState.snackbarHostState .showSnackbar("") } } ) { Text("スナックバー表示") } } } } SideEffect Compositionの成功後(recomposition含む)に必ず実行したい副作用を定義する。 @Composable fun rememberAnalytics(user: User): FirebaseAnalytics { val analytics: FirebaseAnalytics = remember { MockFirebaseAnalytics() } // composition成功後に必ず実行される SideEffect { analytics.setUserProperty("userType", user.userType) } return analytics } produceState 非同期処理(Coroutine、Rxjava、コールバック関数等)の「実行~値の取得までの流れ」をStateクラスで表現できる。 Composable関数外の状態をCompose内のStateとして取り込む際に利用できる。 @Composable fun loadNetworkImage( url: String, imageRepository: ImageRepository ): State<Result<HiResImage>> { return produceState( initialValue = Result.Loading<HiResImage>() as Result<HiResImage>, url, imageRepository ) { // produceState内で実行するこの関数はCoroutineScope内で実行されるため、suspend関数の実行も可能 val hiResImage = imageRepository.loadAsync(url).first() this.value = if (hiResImage == null) { Result.Error("読み込み失敗") } else { Result.Success(hiResImage) } } } derivedStateOf あるStateに紐づいた新しいStateを作成するときに使う。 derivedStateOfで定義したラムダ式内で、何らかのStateを参照していた場合、 そのStateが更新される度にderivedStateの値を更新される。 recompositon時のパフォーマンス改善で利用することが多い。 (derivedStateOfを利用するとこで、recompositionのたびに式が実行される事態を防ぐことができる。) @Composable fun derivedExample() { val rootState: MutableState<String> = remember { mutableStateOf("A") } /** derivedStateOfに渡したラムダ式はrootStateが更新される度に評価され、 * rootStateの更新に紐づいてderivedStateの値が更新される。*/ val derivedState: State<Boolean> = remember() { derivedStateOf() { rootState.value == "A" } } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む