続 RecyclerView in FragmentContainerView in MotionLayoutでリストが描画されない問題
Androidバージョンの切り分け
問題点1, そもそもRecyclerViewのWidth/Heightが0dpである
- デフォルトのvisibilityがGONEの場合、VISIBLEに切り替えてもレイアウトのサイズが再計算されない
ワークアラウンド
- GONEではなく、INVISIBLEを使うことでレイアウトのサイズが計算される
問題点2, adapterをsetしてもinvalidateしてもコンテンツ数がいくつあっても再描画されない
- メインの問題
- notifyDatasetChangedとかinsertとかdeleteとか考えつく限りのイベント通知をしたけどダメ
- setLayoutParamsとかしなおしたけどダメ
- invalidate()とかしたけどダメ
ワークアラウンド
- RecyclerViewには
dispatchLayout()
という描画開始を行うためのpackage privateメソッドがある - それがコールされないらしい
- 描画効率のため、いろんな判定が入ってるけどどこかに不具合があるんかな?
- リフレクション使って、adapterをセットした直後とか、強制描画したいタイミングでコールしてあげると描画される
// こんなふうにするといい fun RecyclerView.forceDispatchLayout() { val method = RecyclerView::class.java.getDeclaredMethod("dispatchLayout") method.isAccessible = true method.invoke(this) }
謎
flutter buildで "Target kernel_snapshot failed" が出た場合の対処
エラー内容
flutter clean flutter build aot
Wrong full snapshot version, expected '20e5c4f7dc44368ac5a17643b93665f6' found '8343f188ada07642f47c56e518f1307c' Building AOT snapshot in release mode (android-arm-release)... 0.1s Target kernel_snapshot failed: Exception: Errors during snapshot creation: null Failed to build aot.
原因
- flutterのキャッシュが壊れている
- キャッシュを全部削除するか、flutterツール自体を再度インストールすれば直る
rm -rf path/to/flutter/bin/cache flutter pub cache repair flutter clean
遠因
- channelをstable/beta/masterあたりで入替えすぎたかな?
2019年に作ったAndroid用ライブラリと、作った割に役立たなかったライブラリ
- 2019年、自分用に色々作っては「まあええやん」「コレあかん」という感じで新しいものを試したり壊したりしてきた
- ライブラリを作るのは個人的な趣味であり、いろんな設計を試せる娯楽であり、勉強でもある
armyknife-*
- armyknifeシリーズ
- 自分用の十徳ナイフ、ちょっとしたことを楽に書くために作ってる
- だいたい拡張関数、ときどきObject
- 拡張関数なので、ライブラリをリンクしたくないときはコピペでもだいたい動くようになってる
- 作ったあとにKotlin標準/Jetpack標準で同等機能が生えたりして意味がなくなったりしてる
- その時は気づいたタイミングでDeprecatedにしたり。
armyknife-runtime
よく使う機能
// 32文字のa-z, A-Z, 0-9で構成される乱数を作成する, UnitTestとかに。 Random.string(32) // base64変換に。Java8, Java7(Android)両対応 ByteArray.encodeBase64() // Channelの受け手にエラーを投げる。非同期処理でキャンセル以外の例外を投げたいときに。 Channel<E>.cancelByError(e: Throwable) // Receiveするか、例外として処理する。非同期処理で例外以外のエラーを受け取りたいときに。 Channel<E>.receiveOrError()
armyknife-jetpack
- Android Jetpack/Google Play Serviceの機能拡張ライブラリ
- github
- ほぼ全部のjetpackを参照しているが、利用側が強制リンクされないように細工してる
- なので、依存関係は自分で解決することを前提にしている
- 強制で
CameraX
とかリンクされてもやだし、excludeも面倒だし、全部ライブラリ分けても面倒、という理由
よく使う機能
// 実行中のRuntimeをチェックする ApplicationRuntime.runIn(flags /*RUNTIME_ROBOLECTRIC とか*/) // Lifecycleが終わったらCoroutineをキャンセルする // LifecycleOwnerに同等のExtensionが公式で生えたりしたので、チクチクと切り替えたりしてる CoroutineContext.with(lifecycle: Lifecycle) // LiveDataの変形 // MediatorLiveDataをラップしたもので、最大6個のLiveDataを集約して別なLiveDataを作ったりできる // ViewModelでModelのLiveDataを受け取って表示用のLiveDataに変換したりするのに使う LiveDataFactory.transform() LiveDataFactory.transformNullable() LiveDataFactory.transformInto() LiveDataFactory.transformNullableInto() // Activity/FragmentをLiveData<Context>に変形する // onDestroyされたらnullを入れて殺す // LiveDataFactory.transform()と組み合わせて、ViewModelの表示データ構築に利用 LiveDataFactory.contextFrom() // ParcelableのオブジェクトをByteArray経由でインスタンスコピーする // UnitTestで正しくコピーできることの検証にも使ったり。 Parcelable.deepCopy() // Intentに強制的に独自Parcelableオブジェクトを挿入する // AndroidのIntentは独自Parcelableを突っ込んでSelectorが出ると(対応Intent-Filterが複数あると)Intentを一旦OSがハンドリングする // このとき、OS標準以外のparcelableが入っているとクラッシュするので、ソレを防止するために一旦ByteArrayに変換して突っ込む Intent.putMarshalParcelableExtra(name: String, value: Parcelable?) ntent.getMarshalParcelableExtra(name: String) // Google Play Service APIを読んだときのTaskを待ち合わせる // awaitだと名前が競合する // awaitだとblockingするが、こっちはsuspendで、CoroutineがキャンセルされたらTaskもキャンセルして抜ける Task<T>.awaitInCoroutines() PendingResult<T>.awaitInCoroutines() // ほかいろいろ
armyknife-gms
- Google Play Service拡張ライブラリ
- github
よく使う機能
// Classが見つからなかったらnullを返すアクセサ // RobolectricではClassNotFoundExceptionが発生するので、それを防いでUnitTestしやすくしたりする Firebase.app Firebase.remoteConfig // ライブラリモジュールのInstrumentation TestでFirebaseへの接続を実現する // google-services.jsonをandroidTest/assetsに入れてパスを与えると、JSONをパースして強制的にFirebaseを初期化する // これでUnitTestでもFirestoreとかにつないだりできる Firebase.provideFromAssets() Firebase.provideFromGoogleServiceJson() // Firebaseの状態をLiveDataとして管理する // RemoteConfigが更新されたり、 // FirebaseAuthの状態が変更されたり、 // トークンが変更されたりしたらLiveDataで通知される // 変化を見る場合はRxKotlinのObservableとかに変換してる // Debugモードの場合、Firebase Authのリフレッシュは5分、RemoteConfigは10分で自動リフレッシュする FirebaseContext.getInstance().observe()
armyknife-android-junit4
- Robolectric/Instrumentation TestのJUnit4動作を補助する
- github
- そろそろJUnit5に移行したいところ
よく使う機能
// src/test配下にあるけどInstrumentationにもUnitTestとしてとしてディレクトリが追加されてるとき、 // instrumentationだけ、robolectricだけ、両方という切り分けを簡単にする // なおかつ、blockの中はsuspend関数で実行したい // 自動的に ShadowLog.stream = System.out みたいな互換性チェックもしてる instrumentationBlockingTest {} localBlockingTest {} compatibleBlockingTest {} // トップレベルプロパティ // コイツらのアクセスを補助 // InstrumentationRegistry.getInstrumentation().context // InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as Application // InstrumentationRegistry.getInstrumentation().targetContext val targetApplication val targetContext val testContext // UnitTest用に正規ルートでViewModelインスタンスを作る // Fragmentに紐付いたり、Activityに紐付いたり。 makeActivityViewModel() makeFragmentViewModel() // ViewModelのLiveDataをすべてactive状態にする // MediatorLiveDataはactiveじゃないと変換処理が走らない仕様なので、UnitTest中はActiveであってほしい // 本来はDataBindingによってActiveなので、UnitTestだけ必要 ViewModel.activeAllLiveDataForTest() // ほかいろいろ
armyknife-android-bundle
- Bundleに便利機能を追加する
- github
- 作った割に、「あ、あんまり使わないわ」みたいになった
よく使うと思って作った割に使わなかった機能
// BundleへのアクセスをKotlinのDelegate機能で実現する // Bundleのラッパークラスを作ったときの補助にしようかとおもった // 実際のところBundleを直接ラップするクラスを結構作ったのだが、この機能はあまり使っていない // こんな感じで使う想定だったのだが、 // 「無理にdelegateするよりget()set()作ったほうが後々みやすいだろうか?」という悩みが垣間見える // val foo: Int by bundle.delegateIntExtra("EXTRA_FOO", 0) Bundle.delegateIntExtra() Bundle.delegateStringExtra()
armyknife-reactivex
- RxKotlinとChannel/Lifecycle管理用ブリッジを提供する
- github
- 結局の所、
firearm-event
との組み合わせでしか使ってない
よく使う機能と思ったけど、用途が局所的
- 省略
armyknife-jetpack-lifecycle
- Lifecycleのなかで、たまに面倒と思う処理を提供する
- github
まれによく使う機能
// LiveDataの中にLiveDataを抱えていて、 // LiveDataのなかのLiveDataをViewModelを使いたいとき // 特にLiveDataのなかのLiveDataがMediatorLiveDataだと、ちゃんとactivateしないと値が更新されないので、それを回避したい /** * class ExampleRepository { * val url: LiveData<String> = ... * } * * val exampleRepository: LiveData<ExampleRepository> * val url: LiveData<String> = InternalLinkLiveData(exampleRepository, { it.url }) */ InternalLinkLiveData<T>
armyknife-widgets
たまに役立つ
// DialogFragmentを使うほどのことじゃないけどDialogを出したくて // Coroutinesの中で呼び出して次の処理をハンドリングしたい // そんなときにDialogをsuspend関数として扱う // Dialog表示中に画面回転しても気にしなくてもいいタイミングとかで使う // Channelが返ってくるので、receive()するとユーザーの選択肢が取得できる buildAlertDialogChannel(): Channel<DialogResult>
armyknife-jetpack-camera
- CameraXをちょっと便利にする
- github
- CameraX自体がまだ不安定だったり、機能が増えていって不要になったりしてる
まれに使う
// Firebase ML Visionのanalyzerを使う ImageAnalysis.setVisionAnalyzer // BarcodeDetectorのanalyzerを使う ImageAnalysis.setVisionBarcodeDetectorAnalyzer
firearm-*
firearm-channel
- startActivityForResult/RuntimePermission/DialogをChannelとして利用する
- github
- annotation-processorが要らなかったり、ほかのAPT系と競合しない
- Channelを使うので、suspend関数の中で終了待ちとか戻り値が取得できる
- Channelの特性上、Activityが破棄されたりプロセスがシャットダウンされると死ぬ
- 画面回転、low memoryにとにかく弱い
- ご利用は計画的に
計画的に使う機能
val activityDispatcher = ActivityResultDispatcher.get(fragment) suspend fun example() { // これでstartActivityForResultして、onActivityResult()の結果を受け取るまで待ち合わせる val result = activityDispatcher.startActivityForResultWithResult(intent) result.result // 戻り値をRESULT_OKかどうか確認したり result.data // 戻り値のIntentを見たり // なんかしたりする }
firearm-event
- ViewModelで発生したEventをpublishする
- github
- 内部はRxKotlinでできている
- UnitTestするとき、Channelに変換して「この処理をしたらこのイベントが発生する」といったUnitTestを書ける
- ViewModelで発生したイベントと、イベントに対するUI動作をFragmentやActivity等の外部で行いたいときに使う
- ViewModel内でハンドリングするとしても、FragmentやActivityで発生イベントをハンドリングできたほうが便利な場合は多い
- Notificationを出したり、別なFragmentやViewModelにアクションを行ったり
よく使う機能
// 発生するEventは最初にValidatorを定義する // これは「なんのEventが来るか、後々仕様がわからなくなる」ことを防ぐために強制している val EVENT_CLICK_DONE_BUTTON = EventId("EVENT_CLICK_DONE_BUTTON ") val EVENT_CLICK_OK_BUTTON = EventId("EVENT_CLICK_OK_BUTTON") data class OnDataLoadError(cause: Int): Event val event = EventStream { event -> when(event) { EVENT_CLICK_DONE_BUTTON -> true is OnDataLoadError-> true else -> false } } // DataBindingで、ボタンクリックでコイツが呼ばれると、イベントを発生させる // event.subscribe()してるやつに画面遷移をまかせる fun onClickDone() { event.next(EVENT_CLICK_DONE_BUTTON) } // ロードボタンを押したら非同期処理する fun onClickLoad() { launch { // けど失敗してしまった // エラー表示はevent.subscribe()してるやつにまかせる event.next(OnDataLoadError(1)) } } // OKボタンが押された fun onClickOk() { // しかしValidatorで弾かれてアプリが落ちる // UnitTestでコレに気づくはずである event.next(EVENT_CLICK_OK_BUTTON) }
firearm-di
- Factory/BuilderパターンのDependency Injectionを行う
- github
- Kotlinは言語機能が強力だし、Dagger等のDIって実際のところ無理に使うほどでもないな、という個人的見解
- 不安定だったり、ASバージョンアップで死んだり、色々あったから
- UnitTestだけちょっと挙動を変更したりMockを返したい場合に使う
よく使う機能
object FooClassFactory { val provider = ProviderRegistry.newProvider<FooClass, Builder> { /* this = this@Builder */ // Builderが渡されるので、オブジェクトを作って返す return Foo(this.context) } // Builderは引数の集約だけを行う。 // 実際のオブジェクト生成はprovider()に行わせる class Builder(var context : Context) { fun build() : Foo = provider(this) } } // 通常はこうなる val builder = FooClassFactory.Bulider(context) val foo = builder.build()
// UnitTestでちょっとFooを変更したり // 固定オブジェクトを返したりしたい FooClassFactory.provider.overwrite { /* this = this@Builder */ FooForTest(this.context) } val builder = FooClassFactory.Bulider(context) val foo = builder.build() // FooForTestのインスタンスが返される // けどテスト中に条件によっては本来のオブジェクトに戻したい FooClassFactory.provider.reset() val builder = FooClassFactory.Bulider(context) val foo = builder.build() // Factoryがもとに戻ったので、Fooのインスタンスが返される
firearm-workflow
- firearm-channelがActivityの再生成やプロセス再起動に弱いため、それを克服するライブラリ
- github
- startActivityForResultやDialogFragmentやRuntime Permissionをプロセス再起動を前提に復旧可能にしている
- FragmentやActivityにonActivityResult()のハンドラを書かなくてもいい
- APTは使ってないので、他のAPT系ライブラリと共存できる
- startActivityとかDialogFragmentを起動する際に、一時的に値を保存する機能がある
- コレは一時的なイベントの途中のステートなので、メンバ変数に保存したくない
- けれどstartActivityForResultのタイミングでプロセス再起動されていたら困る
- 例えば。。。
- ユーザー一覧を開く
- ユーザーをクリックして別Activityでユーザー詳細が開いて、連絡先を選択させる
- 連絡先をIntentで受け取って、非同期でサーバーで処理させる
- DialogFragmentで「パターンA/Bどっちにする?」と聞く
- 処理前にRuntime Permissionで必要なパーミッションも取得する
- サーバーの結果をUIに反映する(ここまで一連の処理)
- 画面を開いたり、一時的なステートがいっぱいある
- firearm-channelとかでcoroutineに閉じ込められるけど、プロセス再起動とか画面回転とかしたら死ぬ
- 一時的なイベント用ステートがいっぱいある
- こんなとき、一時的なステートを保存する機能を提供したり、プロセス再起動してもちゃんとフローを継続できるようにする
面倒だけど使う機能
- 詳細は省くが、色々機能はあるけど面倒だ
- 結局の所Androidでは「プロセスやActivityが死ぬことを前提に一連の処理を作っていくのは面倒だ」という点に集約された
- 詳細はUnitTestをみてくれ
firearm-experimental
割といい感じだった機能 / ViewModelSession
// ViewModelの所有者と現在のライフサイクルを明確にする // 所有者がonDestroyされるとViewModleSessionがnullになる // ViewModelに画面用のLifecycleが必要だったり、Contextが必要なときや // 所有者が確定・不定になるタイミングで処理したいときに使う // ViewModel自体のclear(再生成されないことが確定する)のとは違うライフサイクルをチェックできる class ExampleViewModel : ViewModel() { val session = ViewModelSession<Fragment>() val context: LiveData<Context> get() = session.context private val observeSessionToken = Observer<ViewModelSession.Token<Fragment>> { val token = it ?: return token.launch(Dispatchers.IO) { // do initialize. } } } class ExampleFragment : Fragment() { val viewModel: ExampleViewModel by lazy { val viewModel: ExampleViewModel = // ViewModelProvidersあたりから取得 viewModel.session.refresh(this) // 所有者を確定させる, onDestroyで自動的に開放される } }
冷静になってDeprecatedした機能 / SingleTask
// SingleTask.run {} でタスクを実行する // SingleTask.run {}実行中に別な処理を SingleTask.run{} すると、後勝ちになるため、先の処理していたCoroutineがまるっとキャンセルされる // 作った割には、ふつうにKotlinのSemaphore使ったほうがいいという結論に至る SingleTask.run { // 排他処理したい機能 }
転生した機能 / LiveTaskCounter
// LiveDataでタスク実行数を管理するようにした機能 // 非同期処理が複数箇所あり、どこか1箇所でも処理していたらProgress UIを出したい、とかの場合に使う // Kotlin標準にあるやつだとLiveDataではないので、LiveDataとして扱うとUIにリンクしやすい val counter = LiveTaskCounter() // DataBindingで、Progress UIの表示可否を紐付ける val progressVisibility: LiveData<Int> = LiveDataFactory.transform(counter) { snapshot-> if(snapshot.count > 0) { // 何かしら非同期タスクを実行中なので、UIを表示 View.VISIBLE } else { // なにも処理してないので、UIを非表示 View.GONE } } suspend fun asyncWrite() { counter.withCount { // ブロックの中ではカウンタがインクリメントされる, 抜けたらデクリメント } } suspend fun asyncRead() { counter.withCount { // ブロックの中ではカウンタがインクリメントされる, 抜けたらデクリメント } }
こいつら使うとき
- build.gradleにrepositoryを追加
allprojects { repositories { maven(url = "https://dl.bintray.com/eaglesakura/maven/") }
- ライブラリをimplementation
"implementation"("com.eaglesakura.armyknife.{リポジトリ名}:{リポジトリ名}}:{バージョン名}") // armyknife-jetpackの場合 // 全部 `v1.4.2` とかタグ打ってるので、そのmajor.minor.build を入れる // bintrayとかも参照で // https://bintray.com/eaglesakura/maven/armyknife-jetpack "implementation"("com.eaglesakura.armyknife.armyknife-jetpack:armyknife-jetpack:1.4.2")
色々つくったな
- 来年は何を作ろうか
外出先での作業にモバイルモニターを導入した感想
動機
- ほしかった
- やってみたかった
- 便利そうだった
購入したもの
- 4Kにしなかったのは、使用PCとdpiがズレすぎると文字が小さすぎたり大きすぎたり問題が起きることが多い、という経験則
- 4Kノート+フルHDモニターを併用したことあると、結構面倒ごとが多かった
- 高dpi制御に対応してないアプリとか、表示崩れがひどい
良かった点
- 広々と使える
- Thunderbolt3対応ならケーブル1本で電力供給とディスプレイ接続が行える
思ってたんと違う点
- 商品画像と比べて、実物はクッソ分厚く見える
- まあ、それ自体はあんまり責めてはいない
実際に使ってみた問題点
GPU負荷が大きい
- 意外な問題点だった
- i7Uの組み込みGPUだとWin+Tabでディスプレイ一覧を見るときとかめっちゃディレイが発生する
消費電力デカイ
- Thinkpad X1 Carbon だと、稼働時間が2~3割減る
- Android Studioを起動してると、2時間程度でバッテリーが枯渇する
- 頑張って3時間弱だろうか
腰にくる
- 500gくらいの重量がある
- 数値としてみるとそうでもないが、リュックで背負うと腰へのダメージがガンガンくる
利用シーン
- 確実に充電ができる箇所
- ちょっと気分転換で外作業したいとき
- 歩き回ることが確定していたり、電源確保が不定のときに使うもんじゃない
全体として
- 腰は大事
突然Google App Engineの特定APIが2回に1回しか繋がらなくなった問題への対処
状況
- GAE/Go 1.12を使っている
- 複数のServiceをデプロイしていて、今回問題になったのは
認証用APIを提供しているService
- 環境セットアップのため、APIを連続で叩いてているとき、それはおこった
- 50回くらい同じAPIを叩く
動かなくなる直前
- ナニもしていない。
- 僕はナニもしていない
最後に動いていた時刻
- 18:30頃まで、APIは叩けていた
動かなくなった時刻
- コードを変更せず(リトライの検証のため)再実行すると、2回に1回しかAPIが呼び出せない
- 成功 -> 即座にレスポンスが返る
- 失敗 ->connect timeout.
- 必ず交互に発生する
- 失敗する場合、ログを見る限りサーバーまでリクエストが届いていないようだ
改善した方法
理由の推測
Windows版Android Studioのターミナルでbashする
Windows版のTerminal
- IntelliJやAndroid Studioの標準Terminalはプラットフォームに合わせて起動する
- Windowsはcmd.exeが起動
- bashのほうがなれてて使いやすい
Terminalを変更する
- Settings > Tools > Terminal > Shell Path
- 標準だとcmdになってる
- こんなバッチを書いた(
terminal.cmd
とかファイル名で適当な場所に保存)
C:\Users\eaglesakura\tools\git-sdk-installer-1.0.7-64.7z\usr\bin\bash.exe --login -i
Ubuntu環境使えばいいやん?
Mac環境使えばいいやん?
- 16コア32スレッド環境 -> 6コア12スレッド環境へのダウングレードは、通常開発環境として受け入れがたい
- 大型水冷筐体 -> 小型空冷筐体による、騒音問題も受け入れがたい
- iOS/Flutterするときだけ使う
Windows版Android Studioの動作を軽くする
Windows版Android Studioは遅い
- どういうわけかデフォルト設定だと遅い
- CPUスペックとかに関係なく遅そう
- Code Completionが遅い
- LayoutEditorのプレビューが遅い
- そもそも起動が遅い
- どうにか高速化したい
- LinuxとかMacとかでは軽い
原因
- Android Studio 3.5くらいだろうか、いつからだろうか?
- ポップアップでAnti Virusの例外指定しろという通知が出た
- Windows Defencerに例外を追加した
- このへん参考
- その他、ChromeとかShiftとか常駐しているアプリの一部をホワイトリストに加えた
その他
- JVMに食わせるメモリ量を控えた
- RAM容量に対してキャッシュがデカすぎたらしい
org.gradle.jvmargs=-Xmx2g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options="-Xmx2g"
- Android Emulatorをシャットダウンした
- 外部リソース最高!
- 実機最高!
結果
- 軽い