20210508のAndroidに関する記事は7件です。

Jetpack Composeの仕組みを使ってAndroid FrameworkのViewを作ってみて仕組みを知る

Jetpack ComposeはTextViewやLinearLayoutなどといったAndroid FrameworkのViewを使ってコンポーネントを描画するわけではなく、新しく作ったComponentをCanvasにdrawしています。 Jetpack ComposeはAndroidのUIの部分と木構造を扱う部分(Tree diff toolとして利用できる)としてのComposeがあり、その木構造を扱う部分の仕組みを使うことで、別のUIも作れてしまいます。 (詳しくは https://github.com/JakeWharton/mosaic/#i-thought-jetpack-compose-was-a-ui-toolkit-for-android ) 例えばJetpack Compose for Web、JakeWharton/mosaicなどです。 そのため、Android FrameworkのViewを置けるようにもできます。 理解を深めるために、こんな感じのLinearLayoutとTextViewだけ置けるサンプルを作ってみました。 コード @Composable private fun AndroidViewApp() { var count by remember { mutableStateOf(1) } LinearLayout { TextView( text = "This is the Android TextView!!", ) repeat(count) { TextView( text = "Android View!!TextView:$it $count", onClick = { count++ } ) } } } 作って満足していたんですが、Composeでなにかしたくなったときに、定期的にこの仕組みを思い出したくなるので、書いておきます。 もう少しシンプルにして以下のようにします。 @Composable private fun AndroidViewApp() { LinearLayout { TextView( text = "This is the Android TextView!!", ) } } LinearLayout()やTextView()は独自に定義したComposable関数です。 それを元の関数に展開したり、ちょっと関連が薄い部分を外すと以下になります。 以下のようにその要素を作る factory = {}と、要素をアップデートできるupdate = {}があります。 @Composable private fun AndroidViewApp(context: Context) { ComposeNode<LinearLayout, ViewApplier>( factory = { LinearLayout(context) }, update = {}, content = { val text = "This is the Android TextView!!" ComposeNode<TextView, ViewApplier>( factory = { TextView(context) }, update = { set(text) { this.text = text } }, ) } ) } ここに書いてあることで、それぞれのViewの作り方とアップデートの仕方はわかりました。 しかしどのように親Viewと子Viewを結びつけるのでしょうか? それは以下のViewApplierにて実装しています。 このViewApplierが使われることによって親子関係が設定されることで利用できます。 class ViewApplier(val view: FrameLayout) : AbstractApplier<View>(view) { override fun onClear() { (view as? ViewGroup)?.removeAllViews() } override fun insertBottomUp(index: Int, instance: View) { (current as? ViewGroup)?.addView(instance, index) } override fun insertTopDown(index: Int, instance: View) { } override fun move(from: Int, to: Int, count: Int) { // NOT Supported } override fun remove(index: Int, count: Int) { (view as? ViewGroup)?.removeViews(index, count) } } このViewApplierは最初にCompositionというインスタンスを作るときに必要になり、これで親子関係が作られるようです。 Recomposerはいろいろ処理するところのようで、スタックトレースを見るとここからApplierやComposeNodeが呼ばれるようです。 val composer = Recomposer(Dispatchers.Main) // composerの初期化処理 val rootView = FrameLayout(context) Composition(ViewApplier(rootView), composer).setContent { AndroidViewApp(context) } まとめ ComposeはUIの部分と木構造を扱う部分からできている。 Applierが木構造を操作する。 ComposeNodeがそれぞれのノードの作成とアップデートを行う。 まとめると以下のようになります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

掌田 津耶乃著『Android Jetpack プログラミング』の歩き方(Section4-2) Kotlin-Android-Extensionsの設定

#1. はじめに  掌田 津耶乃著『Android Jetpack プログラミング』(以下、本書と言う)で勉強しています。この本は、著者が記述しているように入門書ではないようです。それゆえ、本書をしっかりと読み込んでいないと、記載されているソースコード通りに写経しても、エラーで弾かれることがあります。本レポートでは、「『Android Jetpack プログラミング』の歩き方」と題して、掌田 津耶乃著『Android Jetpack プログラミング』の歩き方(Section3-1)に引き続き、ソースコードどうりに写経してアプリが正常に動くようにするための注意点をレポートします。記述や考え方などに誤りがあれば、ぜひご教示いただきたいと思います。 2. 動作環境  2021年5月6日ごろ、当方のAndroidStuidoが4.2にアップグレードしました。以下の動作条件で本記事の内容を確認しました。 Android Studio 4.2 Build #AI-202.7660.26.42.7322048, built on April 29, 2021 Runtime version: 11.0.8+10-b944.6842174 amd64 VM: OpenJDK 64-Bit Server VM by N/A Windows 10 10.0 GC: G1 Young Generation, G1 Old Generation Memory: 1280M Cores: 8 Registry: documentation.show.toolbar=true, external.system.auto.import.disabled=true Non-Bundled Plugins: org.jetbrains.kotlin  なお、AndroidStuidoが4.2にアップグレードに伴って、Kotlinコンパイラも1.5にアップデートしたようで少し不具合が出ているようです。  そのため、build.gradleのprojectの方(buildscriptのext.kotlin_version)を以下のとおり変更してました(ググった結果、このように変更すればいいとあったのでこのように変更しました)。本不具合が早く解消されることを願います。 build.gradle#project // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { ext.kotlin_version = "1.5.0" // <-- 変更 repositories { google() mavenCentral() } // 以下省略 3. Section4-2以降を写経する上での注意点  先にレポートしたSection3-1同様、Section4-2以降でも、Kotlin-Android-Extensionsを使用しているため、build.gradleのModule:appに対して、以下のとおりプラグインの追加の設定が必要です。Kotlin-Android-Extensionsについては本書p61を参照して下さい。 build.gradle#app plugins { id 'com.android.application' id 'kotlin-android' id 'kotlin-android-extensions' // 追記 } // ・・・ 以上
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

M1搭載MacでReact Nativeの環境構築してみた

はじめに M1 Macの環境構築で苦労したので、うまくいった方法をまとめたいと思います。 これから始める方にも分かりやすくしようと思うので、分かるところは飛ばしながら読んでください。 細かい説明は省いているので、随時気になるところは調べてください。 参考にさせていただいた記事 Homebrewのインストール 2021/2/5にAppleSilicon(M1)対応のHomebrew3.0.0がリリースされました。 今回はこちらをインストールしていきたいと思います。 以下サイトからインストールできます https://brew.sh/ Install Homebrewと書かれているところの下のコードをコピーしてターミナルに貼り付けます。 パスワードを入力するとインストールが始まります。 Press RETURN to continue or any other key to abort と表示されたら[Enter]を押します。 インストールが完了したらHomebrewのパスを追加する必要があるので、以下のコードを実行します。 ユーザー名と書かれているところは自身のユーザー名に変更して下さい。 echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> /Users/ユーザ名/.zprofile 以下のコマンドを実行し、正常に出力されれば成功です。 brew help Nodeenvのインストール brew install nodenv touch .zshrc インストール出来たらパスを通します eval "$(nodenv init -)" Nodeのインストール ターミナルをRosetta2に切り替えてインストールしてください。 ファインダーのアプリケーションからターミナルを探し、右クリックで「情報を見る」を選択。 Rosettaを使用して開くにチェックを入れて閉じます。 一度ターミナルを終了し再度起動します。 以下コマンドを実行すると現在の状態が確認できます。 ・arm64: M1 ・x86_64: Rosetta2 uname -a # Darwin Kernel Version 20.3.0... x86_64 以下コマンドでNodeをインストールしていきます nodenv install 15.9.0 インストール出来たらバージョンを確認しましょう nodenv -v yarnのインストール brew install yarn インストール出来たらバージョンを確認しましょう yarn -v Watchmanのインストール brew install watchman JDKのインストール brew install --cask adoptopenjdk/openjdk/adoptopenjdk8 Cocoapodsのインストール brew install cocoapods React Native CLIのinstall npm install -g react-native-cli npmはnodeと共にinstallされるので、すでに使用できる状態になっていると思います。 CLI(Command Line Interfaceの略) Xcodeのインストール App Storeからインストールして起動 (結構時間がかかります) Preferences > Locations > Command Line Tools を設定します Android Studioのインストール 以下サイトからインストール出来ます https://developer.android.com/studio?hl=ja&gclid=Cj0KCQjwytOEBhD5ARIsANnRjVj-zR8PvuV40cHP5tnPfqo4sCn9VZigpAvyiMCFWqHrmlxFAZyg0_YaAhhCEALw_wcB&gclsrc=aw.ds 初回起動時は「Custom」を選択してください ・ Android SDK ・ Android SDK Platform ・ Android Virtual Device の3つにチェックを入れ「Next」を押します。 SDK Manager ① ・Configure > SDK Manager を選択 ・"SDK Platforms" > "Show Package Details"にチェックを入れる ・以下にチェックを入れインストールする Android SDK Platform 29 Intel x86 Atom_64 System Image ② "SDK Tools" > "Show Package Details"にチェックを入れる 29.0.2 にチェックを入れインストールする AVD Manager 以下サイトの android-emulator-m1-preview.dmg をダウンロードする https://github.com/741g/android-emulator-m1-preview/releases/tag/0.1 「アプリケーション」フォルダで右クリックして「開く」 これでAndroidエミュレータが起動できるようになりますので、起動した状態でAndroid Studioを再起動すればエミュレータが認識されます。 プロジェクトの作成 以下公式サイトを参考にプロジェクトを作成していきます。 https://reactnative.dev/docs/environment-setup npmを使用してExpoCLIコマンドラインユーティリティをインストールします npm install -g expo-cli 次に「AwesomeProject」という新しいReactNativeプロジェクトを作成します。 expo init AwesomeProject cd AwesomeProject npm start これで開発サーバーが起動します。 動作確認 先ほど作った「AwesomeProject」の中に「App.js」というファイルがあるのでエディターで開いてみましょう。 export default function App() と書かれている中のTextの文字の中身を自由に変更し、ファイルを保存。 開発サーバーの中央左側にある Run on Android device/emulator Run on iOS simulator をクリックしてください。 デバイスが起動し、「AwesomeProject」が実行されて変更した文字列が表記されれば成功です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Android] お寿司屋で例えるAndroid Architecture Compoment 第三貫:LiveData

この記事は お寿司屋で例えるAndroid Architecture Compoment 第二貫:DataBindingの続きです。 前回は、Android Architecture CompomentのData Bindingについて解説しました。 これまでのシリーズを見てない人へ 一応共有しておきたいことで言うとこちら。 View(Activity/Fragment)はお客さん ViewModelが板前さん Repositoryが、漁師 Modelが、海 MVVMを海からお魚を取ってきてお客さんに提供するまでの役割を分割したものと捉えています。 詳しくは、第一貫をご覧ください。(5分位で読める内容ですよ!) 本シリーズで作成するアプリ 今回このシリーズを通して実装する事は、お寿司屋で例えるAndroid Architecture Compoment ということで、アプリ自体も先ほど共有したお寿司屋さんの流れを踏襲します。 こんな感じのアプリ作りまーす! マグロしか食べてなかったり、小学生女子が下校途中にお寿司屋に寄っていたりとツッコミどころがありますが、スルーしてください。 また、見た目はくそアプリですが、設計はMVVMで作ります。(この時点では、View-ViewModel間しか実装してませんが…) 先程のMVVMの例えと、アプリの例えが少し違いがありますが、こちらもスルーで。 View(お客さん) 注文画面を表示 ViewModel(板前さん) 注文画面のデータや処理を保持 Model 注文履歴を保持 あ、ここ違くね???ってところがありましたら、コメントか筆者のTwitterにて教えていただけると助かります。 本題 今回は、LiveDataについて解説していきます。 LiveDataとは? ドキュメントによると LiveData は監視可能なデータホルダー クラスです。 LiveDataの役割は、「通知センサ」です。 お寿司屋で例えると、某回転寿司チェーンではお馴染みの「タッチパネル」では、お客さんが注文したお寿司が来ると通知によって教えてくれますよね! それと同じ感じ!それをLiveData、及びObserverが担います。 ちなみに、LiveDataが通知センサの内の「送信」、Observerが「受信」を担います。 もう少し、ドキュメントを読んでみましょう。 通常の監視とは異なり、LiveData はライフサイクルに応じた監視が可能です。つまり、アクティビティ、フラグメント、サービスなどの他のアプリ コンポーネントのライフサイクルが考慮されます。このため、LiveData はライフサイクルの状態がアクティブなアプリ コンポーネント オブザーバーのみを更新します。 訳)お客さん(=アクティビティ、フラグメント、サービスなどの他のアプリ コンポーネント)が、注文し始めた時に通知センサーを起動させた方が無駄がなくて良い。 こんな感じのことを言ってますw LiveData を使用するメリット LiveData を使用するメリットには次のようなものがあります。 UI をデータの状態と一致させることができる LiveData はオブザーバーのパターンに従います。LiveData は、基盤となるデータが変更されると Observer オブジェクトに通知します。Observer オブジェクト内の UI を更新するコードは統合できます。これにより、オブザーバーが自動的に更新を行うので、アプリデータが変更されるたびに UI を更新する必要がなくなります。 訳)板前さんがお寿司を握ると(=基盤となるデータが変更されると)、自動でお客さんに通知がいきます(=Observer オブジェクトに通知します)。 メモリリークが発生しない オブザーバーは Lifecycle オブジェクトにバインドしていて、関連付けられているライフサイクルが破棄されたときに自身のクリーンアップを行います。 停止されたアクティビティに起因するクラッシュが発生しない。 オブザーバーのライフサイクルがアクティブでない場合(アクティビティがバックスタック内にある場合など)、オブザーバーは LiveData イベントを受け取りません。 メモリリークとは、メモリの解放忘れで空き容量が減ることです。 通知センサをずっと起動したままだと、電力が無駄になります(≒解放わすれ)。 その為、通知センサはお客さんが来たら起動し、お客さんが帰ると停止する方が効率がいいよねって感じのことを言ってます。 手動によるライフサイクル処理が不要 UI コンポーネントは関連データを監視するだけで、監視の停止や再開は行いませんが、 LiveData はそのすべてを自動的に管理します。これは、LiveData が監視を行いながら、関連するライフサイクルの状態の変化を認識するためです。 先程の似たようなことですが、お寿司語で訳すと、通知センサは機械で自動でオンオフをしてくれるので、手動で電源を操作する必要ないよねってことを言ってます。 大体LiveDataがどんなものか分かってきたでしょうか? 早速、簡単な実装に移ってみます。 導入 以下の依存関係を追加します。 まずは、ライフサイクルのバージョン設定。 build.gradle(project) buildscript { ext { lifecycle_version = '2.1.0' } } そして、ViewModel及びLiveDataの追加。 build.gradle( dependencies { implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" } 作ってみる LiveData オブジェクトを操作する手順は次のとおりです。 1.特定のタイプのデータを保持する LiveData のインスタンスを作成します。これは通常、ViewModel クラス内で行います。 訳)板前さんが、通知センサ(ViewModel)の主導権を握ります。 まずは、ViewModelを継承させた板前さん(OrderViewModel)を作成します。 2.onChanged() メソッドを定義する Observer オブジェクトを作成します。このオブジェクトは、LiveData オブジェクトが保持するデータが変更されたときの処理を管理します。通常は、UI コントローラ(アクティビティやフラグメントなど)内に Observer オブジェクトを作成します。 3.observe() メソッドを使用して、Observer オブジェクトを LiveData オブジェクトにアタッチします。observe() メソッドは LifecycleOwner オブジェクトを取得します。これにより、Observer オブジェクトが LiveData オブジェクトに登録され、変更が通知されるようになります。通常は、UI コントローラ(アクティビティやフラグメントなど)内の Observer オブジェクトをアタッチします。 特定のタイプのデータを保持する LiveData のインスタンスを作成します。これは通常、ViewModel クラス内で行います。 いつもの通りドキュメントは、かなりお堅い日本語を使われているので、しつかりとお寿司屋に例えて解説していきたいと思います! 板前さん側 OrderViewModel.kt class OrderViewModel : ViewModel() { // 画像のidを保持 private val _orderImage = MutableLiveData<Int>() val orderImage: MutableLiveData<Int> get() = _orderImage // 注文ボタンのテキストの状態を保持 private val _orderText = MutableLiveData<String>() val orderText: LiveData<String> get() = _orderText // 会計ボタンのテキストの状態を保持 private val _billText = MutableLiveData<String>() val billText: LiveData<String> get() = _billText private val _customerType = MutableLiveData(CustomerType.ENTER) // 注文ボタンのテキストの状態だけ初期値を設定 init { _orderText.value = "入店" _orderImage.value = R.drawable.sushi_syokunin_man_mask } // お客さんの行動 fun orderTuna() { when (_customerType.value) { CustomerType.ENTER, CustomerType.RE_ENTER -> { _orderImage.value = R.drawable.sushi_syokunin_man_mask _orderText.value = "マグロ" _billText.value = "お会計" _customerType.value = CustomerType.EAT_TUNA } CustomerType.EAT_TUNA -> { _orderImage.value = R.drawable.sushi_akami _orderText.value = "完食" _customerType.value = CustomerType.COMPLETED_EAT } CustomerType.COMPLETED_EAT -> { _orderImage.value = R.drawable.sushi_syokunin_man_mask _orderText.value = "マグロ" _customerType.value = CustomerType.EAT_TUNA } CustomerType.GO_HOME -> { viewModelScope.launch { _orderImage.value = R.drawable.tsugaku _orderText.value = "" delay(1000) _orderImage.value = R.drawable.tsugaku _orderText.value = "再入店" _customerType.value = CustomerType.RE_ENTER } } } } // お会計ボタンを押したときの処理 fun pay() { when (_billText.value) { "お会計" -> { _orderImage.value = R.drawable.message_okaikei_ohitori _orderText.value = "帰る" _billText.value = "" _customerType.value = CustomerType.GO_HOME } } } enum class CustomerType { ENTER, RE_ENTER, EAT_TUNA, COMPLETED_EAT, GO_HOME } } 前々回の記事:どんなもんか知るにて、依存関係はView->ViewModel->Modelの方向にしかないと説明しました。 つまり、お客さん(Fragment)は板前さん(ViewModel)に依存しています。 その為、お客さん(Fragment)の注文やお会計に関するデータや処理は、全て板前さん(ViewModel)が「握っています」。 板前だけにな 失礼しました。 そのようなイメージが分かると良いです。 ちなみに、MutableLiveDataとLiveData二つありますが、違いは変更できるか否かです。 Mutableは、日本語で可変という意味なのでそこから推測できた人もいるかもしれません。 では、何故この二つが必要なのか? オブジェクト指向の考え方で、カプセル化というものがあります。 これは、簡単に言うと外部から変更できないように「隠す」ことです。 この場合、OrderViewModelが保持している注文をOrderFragmentで変更出来てしまったらまずいんです。 お客さんが、注文メニューを変更できちゃうくらいやばいことです。 可愛そうなことにお店は、マグロしか取り扱っていないようなので、サーモンに書き換えられたら店を閉めるしかありません… 前半の書き方の意味はこんな感じです。 では、次。 init { }。 これは、データを初期化しているだけです。 ん-、fun以降も関数を宣言しているだけなので、OrderViewModelは、これ以上特に説明は要らないかなと思います。 ちなみに、MutableLiveDataにデータをセットしたり、また取得したりするときは、valueというメソッドを使います。 お客さん側 OrderFragment.kt class OrderFragment : Fragment() { // 客席にタッチパネル設置 private val viewModel: OrderViewModel by viewModels() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? = FragmentOrderBinding.inflate(inflater, container, false).let { it.lifecycleOwner = viewLifecycleOwner it.orderButton.setOnClickListener { viewModel.orderTuna() } it.billButton.setOnClickListener { viewModel.pay() } // 監視センサ作成 val imageObserver = Observer<Int> { newImageId -> // ImageViewの更新処理 it.orderImageView.setImageResource(newImageId) } // 監視センサ作成 val textObserver = Observer<String> { newText -> // ボタンテキストの更新処理 it.orderButton.text = newText } val billObserver = Observer<String> { newText -> it.billButton.text = newText } // viewLifecycleOwnerを第一引数を渡すことで、お客さんに合わせて監視する設定 viewModel.orderImage.observe(viewLifecycleOwner, imageObserver) viewModel.orderText.observe(viewLifecycleOwner, textObserver) viewModel.billText.observe(viewLifecycleOwner, billObserver) it.root } } fragment_order.xml <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/orderImageView" android:layout_width="240dp" android:layout_height="240dp" android:layout_marginTop="80dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/orderButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="80dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/orderImageView" /> <Button android:id="@+id/billButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="80dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/orderButton" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout> まずは、先頭のviewModelの宣言。 by viewModels()という委譲を付けることでライフライクルを同期することが出来ます。 setOnClickeListnerに関しては、viewModelで宣言した関数を使っています。 これによって、xml側で宣言することなく使えます。 また、依存関係の方向がしっかりとお客さん->板前さんになっています。 監視センサを作成している部分に触れます。 お客さんは、通知を受信する側なのでObeserverを実装します。 3つのViewは、セットする値が違いますが、同じことを書いています。 例えば、ImageViewに関して、it.orderImageView.setImageResource(newImageId)とすることで、板前さん側で画像のリソースIDが変更された時、自動的にこちらに通知がやってきてUIを変更してくれます。 そして、最後のobserveメソッドの部分。 引数に、viewLifecycleOwnerと上で作成したobserverを持ちます。 第一引数のviewLifecycleOwnerは、ライフサイクルの同期を実現します。 お客さんが注文してから、会計する間だけ受信センサを起動させることで無駄がなくなります。 ソフトウェア的には、メモリリークを回避できます。 解説としては、以上になります。 第四貫へ続く
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AndroidでItemDecorationを使ってみた

目的 ItemDecorationを使ってみる。 題材:カレンダー(Grid形式)    下記で作成したカレンダーを改造し、月毎のヘッダをItemDecorationで実現する。 ポイント RecyclerView.ItemDecorationでできることは、大きく2つ。 - getItemOffsets  itemの位置を移動する - onDraw、onDrawOver  itemの下または上に描画を行う 実装内容 getItemOffsetsで、「月の一週目」の表示位置を100下方に移動します。 onDrawOverで、1で移動した部分にヘッダーの背景とテキスト(月、西暦)を描画します。 DateItemDecoration.kt class DateItemDecoration: RecyclerView.ItemDecoration() { companion object { private const val HEADER_HEIGHT = 100 private val headerBackGroundPaint = Paint().apply { color = Color.GRAY alpha = 200 } private val headerTextPaint = Paint().apply { textSize = 65f color = Color.WHITE } } override fun getItemOffsets( outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State ) { super.getItemOffsets(outRect, view, parent, state) if ((parent.getChildViewHolder(view) as DateAdapter.DateAdapterHolder).isFirstWeek) { outRect.top = HEADER_HEIGHT } else { outRect.top = 0 } } override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { super.onDrawOver(c, parent, state) val size = state.itemCount for (i in 0 until size step 7) { drawHeader(parent, c, i, i + 7) } } private fun drawHeader(parent: RecyclerView, c: Canvas, prev: Int, now: Int) { val prevView = parent.getChildAt(prev) val prevHolder = if (prevView == null) { return } else { parent.getChildViewHolder(prevView) as DateAdapter.DateAdapterHolder } if (prevHolder.isFirstWeek) { c.drawRect( 0f, prevView.y - HEADER_HEIGHT, parent.width.toFloat(), prevView.y, headerBackGroundPaint ) c.drawText( "${prevHolder.month} ${prevHolder.year}", 0f, prevView.y, headerTextPaint ) } } } コードの詳細は下記参照: https://github.com/yoshihiro-kato/android-samples/tree/develop/KotlinSampleApplication
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Android】ViewModel + LiveData のUNITテストを書く

はじめに Android では MVVM のアーキテクチャでの実装が一般的になっています。 ViewModel と LiveData を組み合わせた実装が公式サイトでも紹介されているのでリンクを貼っておきます。 (公式)アプリ アーキテクチャ ガイド 今回は ViewModel + LiveData の実装における UNITテストを紹介していこうと思います。 ViewModelの実装 まずはテスト対象のクラスを用意したいと思います。 APIにリクエストしてレスポンスを返すサンプルアプリを想定して実装することにします。 せっかくなので上記の公式サイトの例を参考にして ViewModel、Respository、DataSource のクラスをそれぞれ用意することにしました。 まず、テスト対象の ViewModel クラスを実装します。 リクエスト用のメソッドと LiveData だのシンプルな実装にしました。 リクエストした結果の SampleRequest オブジェクトを View に返す実装です。 SampleViewModel.kt class SampleViewModel( private val repository: SampleRequestRespository ): ViewModel() { private val _sampleRequest = MutableLiveData<SampleRequest>() val sampleRequest: LiveData<SampleRequest> = _sampleRequest // Activity や Fragment からリクエストするメソッド fun loadSampleRequest() { viewModelScope.launch { _sampleRequest.value = repository.getSampleRequest() } } } その他必要なクラスも用意します。 APIへ本当にリクエストする処理は今回は省略しています。 SampleRequestRespository.kt class SampleRequestRespository( private val remoteSource: SampleRemoteDataSource ) { suspend fun getSampleRequest(): SampleRequest = remoteSource.getSampleRequest() } SampleRemoteDataSource.kt class SampleRemoteDataSource { fun getSampleRequest(): SampleRequest = SampleRequest() } SampleRequest.kt // テスト用なので適当に定義 class SampleRequest { val sampleRequestItem1: String? = null val sampleRequestItem2: Int? = null } これでテスト対象のクラス実装は完了です。 ViewModelのテスト mockk を利用したテストを書きます。 gradle ファイルに以下の定義を追加します。 build.gradle dependencies { ・ ・ ・ // ↓を追加(最新のバージョンにしてください) testImplementation 'io.mockk:mockk:1.10.5' testImplementation 'androidx.arch.core:core-testing:2.1.0' testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.1.0' } mockk を利用するので mockk はわかると思うのですが、その他はテスト実行時の Exception で失敗にならないようにする為の定義です。 androidx.arch.core:core-testing は java.lang.RuntimeException: Method getMainLooper in android.os.Looper not mocked. See http://g.co/androidstudio/not-mocked for details. 対策で org.jetbrains.kotlinx:kotlinx-coroutines-test は Exception in thread "main" java.lang.IllegalStateException: Module with the Main dispatcher had failed to initialize. For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used 対策です。 gradle の定義が追記できたのでテストを書いていきます。 SampleViewModelTest.kt // Exception in thread "main" java.lang.IllegalStateException 対策で必要 @ExperimentalCoroutinesApi class SampleViewModelTest { // java.lang.RuntimeException: Method getMainLooper in android.os.Looper not mocked. 対策で必要 @get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule() @Before fun setUp() { // Exception in thread "main" java.lang.IllegalStateException 対策で必要 Dispatchers.setMain(Dispatchers.Unconfined) } @After fun tearDown() { // Exception in thread "main" java.lang.IllegalStateException 対策で必要 Dispatchers.resetMain() } @Test fun loadSampleRequest_正常系テスト() { val mockRepository = mockk<SampleRequestRespository>() val target = SampleViewModel(mockRepository) val result = SampleRequest() // coEvery を使用することで suspend fun の mockRepository.getSampleRequest() の戻り値を明示的に返せる coEvery { mockRepository.getSampleRequest() } returns result // ViewModel の sampleRequest が変更されたことを確認する為 observer を mock する val mockObserver = spyk<Observer<SampleRequest>>() target.sampleRequest.observeForever(mockObserver) // テスト対象のメソッドを呼び出す target.loadSampleRequest() // mockObserver が mockRepository.getSampleRequest() の結果で呼ばれたことを確認する verify(exactly = 1) { mockObserver.onChanged(result) } } } これでテストを実行すると無事成功しました。 今回は正常系のテストだけでしたが、推奨されるアーキテクチャを使うことでテストも書きやすい設計にもなるかと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Teachable Machine の機械学習モデル出力に関するメモ 〜音声・ポーズプロジェクト編〜【2021年5月版】

Teachable Machine について以下の記事を書いたのですが、それと似た話の記事です。 以下の記事では画像プロジェクトの話を扱いましたが、この記事では音声プロジェクトとポーズプロジェクトの話を扱います。 ●Teachable Machine の機械学習モデル出力に関するメモ 〜画像プロジェクト編〜【2021年5月版】 - Qiita  https://qiita.com/youtoy/items/685d97e0acc5cfc184c0 音声プロジェクトの機械学習モデル 出力の方法と形式 音声プロジェクトの機械学習モデルを出力する際の画面は以下のとおりです。 画像プロジェクトの場合の出力形式は「TensorFlow.js、TensorFlow、TensorFlow Lite」の 3つがありますが、音声プロジェクトの場合は 2つになります。 上記の画像では、出力形式で TensorFlow.js が選択されていますが、その横に TensorFlow・TensorFlow Lite の 2つもあります。 ここで、出力できる方法・形式をまとめてみます。 クラウドにアップロード TensorFlow.js ダウンロード TensorFlow.js TensorFlow Lite ダウンロードして得られるデータについて 以下の内容は、バックグラウンドノイズと 1つのクラスを学習させて、機械学習モデルを各種形式でダウンロードした場合に得られたファイルの情報です。 TensorFlow.js metadata.json model.json weights.bin TensorFlow Lite soundclassifier.tflite labels.txt 出力したモデルの利用について 機械学習モデルの出力形式は複数の形式を選択できますが、それぞれをプログラムで利用する際に用いる言語等は、提示されるコードスニペットをベースにすると以下のようになります。 TensorFlow.js JavaScript(TensorFlow.js か ml5.js と組み合わせ) TensorFlow Lite Kotlin 機械学習モデルを用いる場合のコードスニペット・手順について、上記の中の一部はコード・手順だけでなく GitHub へのリンクも表示されます。 TensorFlow.js を利用する場合のコードの関連リンク ml5.js を利用する場合のコードの関連リンク TensorFlow Lite の機械学習モデルを Android で利用する場合 ポーズプロジェクトの機械学習モデル 出力の方法と形式 上記の音声プロジェクトを見ていった時と同様に、出力方法・形式を確認します。 クラウドにアップロード TensorFlow.js ダウンロード TensorFlow.js なお、出力を行う際の画面は以下のようになっています。 ダウンロードして得られるデータについて 以下の内容は、2つのクラスを学習させて、機械学習モデルをダウンロードした場合に得られたファイルの情報です。 TensorFlow.js metadata.json model.json weights.bin 出力したモデルの利用について 機械学習モデルをプログラムで利用する際に用いる言語等は、提示されるコードスニペットをベースにすると以下のようになります。 TensorFlow.js JavaScript 機械学習モデルを用いる場合のコードスニペット・手順について、上記はコード・手順だけでなく GitHub へのリンクも表示されます。 TensorFlow.js を利用する場合のコードの関連リンク
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む