20210727のAndroidに関する記事は6件です。

Android Studio と IBM Security Verify SDKをはじめて触ってみる③

はじめに 前回記事では、GitHubに公開されているサンプルアプリケーションを利用する際に、Pythonのコードを利用してTOTPを生成しました。 Android Studio と IBM Security Verify SDKをはじめて触ってみる② 今回は、IBM Security VerifyのSDKを使って、TOTPを生成してみたいと思います。 1.QR Code Scan Demoアプリをカスタマイズする QR Code Scan DemoアプリのMainActivity.javaには、Android StudioのLogcatにHOTPのコードを生成するサンプルが記載されています。 このサンプルを活用して、Totpを生成していきます。 MainActivity.java import com.ibm.security.verifysdk.HotpGeneratorContext; ~割愛~ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ContextHelper.sharedInstance().setContext(getApplicationContext()); HotpGeneratorContext HotpGeneratorContext = new HotpGeneratorContext("secret", 6, HmacAlgorithm.SHA1, 0); Log.i("SDK Demo", "Hotp: " + HotpGeneratorContext.create()); 1.1 MainActivity.java - Hotp→Totpの文字列置換 QR Code Scan DemoアプリのMainActivity.java内のHotpをTotpに一括置換します。 MainActivity.java import com.ibm.security.verifysdk.TotpGeneratorContext; ~割愛~ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ContextHelper.sharedInstance().setContext(getApplicationContext()); TotpGeneratorContext TotpGeneratorContext = new TotpGeneratorContext("secret", 6, HmacAlgorithm.SHA1, 30); Log.i("SDK Demo", "Totp: " + TotpGeneratorContext.create()); ~割愛~ 1.2 MainActivity.java - onCreateのコード修正 TotpGeneratorContextの第4引数であるPeriodを0→30に変更します。 MainActivity.java protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ContextHelper.sharedInstance().setContext(getApplicationContext()); TotpGeneratorContext TotpGeneratorContext = new TotpGeneratorContext("secret", 6, HmacAlgorithm.SHA1, 30); Log.i("SDK Demo", "Totp: " + TotpGeneratorContext.create()); 1.3 dialog_layout_otp.xmlの修正 生成したTotpを表示させるため、dialog_layout_otp.xmlを修正します。 修正した内容は以下の通りです。 TableRowの追加 User_nameなど他の行を参考に、totpの行を追加 生成したtotpを表示させるTextViewを追加し、idにtotpを指定 XMLでみると、以下の値を追加した形となります。 dialog_layout_otp.xml <TableRow android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingStart="8dp" android:paddingEnd="8dp" android:padding="3dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="8dp" android:text="totp:" android:textSize="12sp" /> <TextView android:id="@+id/totp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:textAppearance="?android:attr/textAppearanceLarge" android:textSize="12sp" /> </TableRow> 1.4 MainActivity.java - onActivityResultの修正 totpを生成するため、MainActivity.javaのonActivityResultに3行追加します。 MainActivity.java ~割愛~ TextView tvUsername = alertDialog.findViewById(R.id.user_name); TextView tvIssuer = alertDialog.findViewById(R.id.issuer); TextView tvSecret = alertDialog.findViewById(R.id.secret); TextView tvType = alertDialog.findViewById(R.id.type); TextView tvAlgorithm = alertDialog.findViewById(R.id.algorithm); TextView tvDigits = alertDialog.findViewById(R.id.digits); TextView tvCounter = alertDialog.findViewById(R.id.counter); TextView tvPeriod = alertDialog.findViewById(R.id.period); TextView tvTotp = alertDialog.findViewById(R.id.totp); //追加した行 tvUsername.setText(otpQRScan.getUsername()); tvIssuer.setText(otpQRScan.getIssuer()); tvSecret.setText(otpQRScan.getSecret()); tvType.setText(otpQRScan.getType()); tvAlgorithm.setText(otpQRScan.getAlgorithm().name()); tvDigits.setText(String.valueOf(otpQRScan.getDigits())); tvCounter.setText(String.valueOf(otpQRScan.getCounter())); tvPeriod.setText(String.valueOf(otpQRScan.getPeriod())); TotpGeneratorContext TotpGeneratorContext = new TotpGeneratorContext( otpQRScan.getSecret(), 6, HmacAlgorithm.SHA1, 30); //追加した行 tvTotp.setText(String.valueOf(TotpGeneratorContext.create())); //追加した行 ~割愛~ 2.TOTPの生成確認 IBM Security Verifyでセキュリティタブを開き、「新しい方式の追加」をクリックします。 オーセンティケーター・アプリのセットアップメニューをクリックします。 「次:オーセンティケーターの接続」をクリックします。 QRCodeが表示されることを確認します。 Androidで起動したQR Code Scan DemoアプリでQRCodeを読み取ると、TOTPが表示されることがわかります。なお、TOTPは30秒ごとにかわるため、画面表示されたらなるべく早く検証に進みます。 生成されたTOTPが正しいことを確認するため、「次:オーセンティケーターのテスト」をクリックします。 アクセス・コードに表示されたTOTPを入力して、「次:検証」をクリックします。 無事にオーセンティケーターが登録が完了しました。 最後に IBM Security Verify SDKを使ってTOTPを生成することができました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Android Studio と IBM Security Verify SDKをはじめて触ってみる②

はじめに 前回記事では、GitHubに公開されているサンプルアプリケーションがAndroidデバイスで起動するところまで確認できました。 Android Studio と IBM Security Verify SDKをはじめて触ってみる① 今回は、QR Code Scan Demoアプリ/Authenticator Demoアプリでなにができるか動作確認を行っていきます。 動作確認した環境は、Windows10+Android Studio 4.2.1 +Android10デバイス(Xperia XZ2)となります。 1.Authenticator Demoアプリを試す 7/29 更新:このAuthenticator DemoアプリはTOTPを想定した作りになっていました。記事のように、IBM Security Verify アプリとして登録すると、登録処理はできますが、多要素認証として利用できる状態まではなりませんでした。IBM Security Verifyアプリの登録処理部分のサンプルコードとしてご利用頂くのがよいと思います。 前回セットアップしたAuthenticator Demoアプリの動作確認から実施しました。 まずは、IBM Security Verifyにログイン/プロフィールからセキュリティタブを表示します。 多要素認証をを求められるため、利用できるオプションを用いて行います。 「新しい方式の追加」をクリックします。 IBM Security Verify アプリの「デバイスの追加」メニューをクリックします。 「次:アカウントの接続」をクリックします。 QRCodeが表示されることを確認します。 Androidで起動したAuthenticator DemoアプリでQRCodeを読み取るとHellow Worldと表示されます。 デバイスの検証メニューは動作しなかったため、キャンセルし、IBM Security Verify側に多要素認証要素が登録されたか確認しました。 前の画面に戻ると、IBM Security Verifyの多要素認証が追加されていることが確認できます。 このサンプルを利用することで、IBM Security Verify上にアプリを登録することができました。 また、デバイスの検証やテストはこのサンプルアプリでは動作させることができませんでした。 2.QR Code Scan Demoアプリを試す 次に、QR Code Scan Demoアプリの動作確認を行います。 Android Studioからのアプリケーション起動や、SDKファイルの配置、アプリケーションの実行手順は、Authenticator Demoアプリと同様の手順です。 IBM Security Verifyでセキュリティタブを開き、「新しい方式の追加」をクリックします。 オーセンティケーター・アプリのセットアップメニューをクリックします。 「次:オーセンティケーターの接続」をクリックします。 QRCodeが表示されることを確認します。 Androidで起動したQR Code Scan DemoアプリでQRCodeを読み取ると、TOTPのSecretが表示されます。 このSecretを用いて、6桁のTOTPを生成します。 サンプルアプリでTOTP生成機能があるかわからなかったため、Pythonで実装したアプリを利用しました。 コードの中身は、こちらの記事を参考にさせて頂きました。 参考: PythonでTOTP(Time-based One-Time Password)を実行する 「次:オーセンティケーターのテスト」をクリックします。 アクセス・コードに生成したTOTPを入力して、「次:検証」をクリックします。 無事にオーセンティケーターが登録が完了しました。 セキュリティタブでもオーセンティケーター・アプリの登録が確認できました。 このアプリは、TOTP生成に必要なSecretなどの情報を取得する機能を持っていることが確認できました。 TOTPの生成機能が見つからなかったため、Pythonで代替して動作確認を行いました。 3.アプリケーションのコードを理解するためには? サンプルのコードの概要を理解するためには、以下の記事が参考になります。 Create your own Authenticator この記事の中で、Authenticator Demoアプリのコードの解説があります。 各クラスの中身を理解するためには、SDKファイルに含まれるjavadocが参考になります。 HTMLファイルが含まれていて、クラスの説明など確認できます。 最後に IBM Security Verify SDKを使ってGithubに公開されているアプリの動作確認を行いました。 Android Studio利用が初めてのため、お作法など抜けている箇所があると思いますが、ご容赦ください。 7/29追記: TOTPをSDKを使って生成する方法を試しました。 Android Studio と IBM Security Verify SDKをはじめて触ってみる③
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Android Studio と IBM Security Verify SDKをはじめて触ってみる①

はじめに IBM Security Verify には、iOS/Android向けのSDKが提供されています。 AndroidStudioを初めてインストールし、試行錯誤しながら、GitHubに公開されているサンプルアプリケーションでなにができるのか確認しました。 動作確認した環境は、Windows10環境+Android10デバイス(Xperia XZ2)となります。 対象は以下の2つのアプリにしました。 1.QR Code Scan Demoアプリ 2.Authenticator Demoアプリ 1.QR Code Scan Demoアプリ 2.Authenticator Demoアプリ アプリ概要 QRCodeを読み取るサンプルアプリケーション IBMVerifyアプリを登録するサンプルアプリケーション アプリ起動画面 ) 1.公開情報について 最初に、IBM Security より公開されている参考情報をまとめておきます。 SDK IBM Security App Exchangeからダウンロードします。IBMidが必要となります。 1.IBM Security Verify SDK (Android) 2.IBM Security Verify SDK (iOS) SDKの使い方  - 1.(SDKのインポート/簡易動作確認)Getting started with the IBM Verify SDK  - 2.(デモアプリのソース解説) Create your own Authenticator  - 3.(開発者向けポータルサイト) Developer Portal サンプルアプリ(IBM Security の Github) 1. (Android向けサンプルアプリ)IBM Security Verify SDK for Android 2. (iOS向けサンプルアプリ)IBM Security Verify SDK for iOS 2.Android Studioのインストール Android Studioのモジュールは、以下のURLから入手できます。 インストール方法はいろいろな公開記事があるので割愛させて頂きます。 動作検証では、4.2.1 for Windows 64bit版をインストールしました。 https://developer.android.com/studio/ 3.SDKのダウンロード IBM Security App ExchangeからAndroid向けのSDKをダウンロードします。 ダウンロードの際には、IBMidが必要になります。お持ちでない方は以下のリンクを参考にIBMidの登録してください。 サポート・コミュニティーを使おう:ステップ1 IBMid の作成 なお、検証で利用したSDKのバージョンは2.1.2でした。 現在は、2021/6/30 に公開された2.1.4が最新版です。 4.Githubからのサンプルアプリケーションダウンロード Githubからサンプルアプリケーションをダウンロードします。 ダウンロードしたファイルを解凍して、C:\Users<UserName>\AndroidStudioProjectsAndroid Studioのディレクトリに展開します。 Android Studioを起動して、「Open an Existing Project」を選択します。 samplesフォルダにサンプルアプリから、「AuthenticatorDemo」を選択し、「OK」ボタンをクリックします。 各アプリケーションには、Verify SDKが配置されていないため、SDKファイル(VerifySdk.aar)を配置します。 Show in Explorerメニューから、VerifySdkフォルダを開きます。 各フォルダにダウンロードしたSDKファイル(VerifySdk.aar)をコピーします。 5.AndroidデバイスをUSB接続する AndroidデバイスをWindowsPCに接続して利用するためには、事前に2つの設定を行います。 1.Androidデバイスをネットワークに接続しておく。 2.Androidを開発者モードにする 設定メニュー -> デバイス情報 -> ビルド番号を7回タップする。 3.開発者向けオプションで、USBデバッグを有効にする。 検証で利用したデバイスでは、設定メニュー -> システム -> 詳細設定 -> 開発者向けオプション -> USBデバッグ の項目を有効化する。 2.開発者モード 3.USBデバッグ有効化 6.Android Studioからアプリを起動する Android Studioで接続したデバイスが認識されています。(Sony SOV37と表示されている箇所です。) Runボタンをクリックして、アプリを実行します。 Androidにアプリがインストール/起動できました。 QRCodeを読み取るアプリのため、カメラが起動した形でアプリが起動しました。 最後に Android Studio と IBM Security Verify SDKを使って、サンプルアプリ(Authenticator Demo)が起動するところまでご紹介しました。 次回は、実際にアプリの動作を確認していきます。 Android Studio と IBM Security Verify SDKをはじめて触ってみる②
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CheckedTextViewを知り、ListViewを知る

CheckedTextViewをご存知でしょうか。 私は先日、Android Studioの予測機能で存在を知りました。 ということで、この機会にCheckedTextViewを使えるようになりたいと思います。 実行環境 Android Studio:4.2.2 Kotlin:1.3.72 CheckedTextViewとは TextViewのサブクラスに位置する、チェック判定機能がついたTextViewのことです。 ListViewと共に使われるもので、ListViewの設定においてsetChoiceModeがCHOICE_MODE_NONEでない場合、つまりListViewの各項目が選択可能な場合に使用することができます。 ちなみにですが、ListViewを実装せずに個別で使うこともできました。 特段思いつく用途はありませんが、何かの際に役立つかも知れませんので覚えておこうと思います。 また、CheckedTextViewではチェック判定を可視化するためにsetCheckMarkDrawableという機能を用います。 よく見かけるCheckBoxクラスでは左側にチェックボックス、その右側にテキストという並びです。一方、CheckedTextViewでCheckMarkを設定した場合は左側にテキスト、右側にチェックマークが配置されます。 CheckMarkはdrawable階層から静止画を選択して設定するため、事前にアイコンなどを読み込んでおくことをおすすめします。 手軽に行う場合には、Android SDKやmaterialデザインのライブラリに用意されているアイコンをVectorDrawableとして設定するのが良いと思います。 また、CheckMarkを設定しない場合はTextViewと同じように使用することができます。 今回はListViewも初めての実装となるため、簡単にListViewについてもまとめました。 ListViewの実装 ListViewとは その名の通り、リストを表示させるためのViewクラスです。 似たようなクラスとしてrecyclerViewがありますが、 「とにかく手軽に、垂直方向に要素を並べて表示したい!」という方はListviewを、 「機能や表示をカスタマイズしてより柔軟に実装したい!」という方はrecyclerViewを使用するのがおすすめです。 ListViewの使い方 手順は以下の通りです。 ①xmlファイルでListViewを定義する ②リスト項目を設定する ③activity_main.xmlに設定したListViewを読み込む ④adapterを用意し、リスト項目とListViewを対応付ける activity_main.xml <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content"> <!-- ①xmlファイルでListViewを定義 --> <ListView android:id="@+id/list_view" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> listItem.xml <resources> <string-array name="nutrients"> <item>タンパク質</item> <item>脂質</item> <item>飽和脂肪酸</item> ... </string-array> </resources> MainActivity.kt class MainActivity : AppCompatActivity(){ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // ②リスト項目を設定 val itemList = resources.getStringArray(R.array.nutrients) // ③activity_main.xmlに定義したListViewを読み込む val listView = findViewById<ListView>(R.id.list_view) // ④ArrayAdapterを用意し、リスト項目とListViewを対応付ける // ArrayAdapterではcontext、1項目分のレイアウトファイル、項目を定義した配列を指定する val adapter: ArrayAdapter<Any> = ArrayAdapter(this, R.layout.item, itemList) listView.adapter = adapter } } 今回はリスト項目を設定するためにresourcesファイルを作成し、string-arrayとして設定しました。 そのほかにも様々な実装方法がありますので忘れないように記載しておきます。 //arrayを設定する方法 val itemList = arrayOf( "aaa", "bbb", "ccc", ...) //for文を使って設定する方法 val itemList = arrayListOf<Any>() for(i in 0..10) {itemList.add("${i}番")} また、Android SDKには1項目分のレイアウトファイルとして、simple_list_item_1.xmlが用意されています。 そのため、自分で用意せずにそちらを使用することも可能です。 以上で基本的なListViewが実装できました。 CheckedTextViewの実装 ここからは、本題のCheckedTextViewの実装と、それに合わせたListViewの修正を行います。 現在、1項目分のレイアウトファイルとして指定したitem.xmlは以下のようになっています。 item.xml <?xml version="1.0" encoding="utf-8"?> <CheckedTextView android:id="@+id/checked_text_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="10dp" android:textSize="20sp" android:checked="false" //checkステータスを設定 android:checkMarkTint="@color/teal_700" //checkMarkの色を指定 android:checkMark="@drawable/ic_android_black_24dp" //checkMarkを指定 app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android" /> このCheckedTextViewを使用して、以下のような機能を実装したいと思います。 各項目をクリックすることでその項目のAndroidマークとチェックマークが入れ替わる機能です。 手順は以下の通りです。 ①カスタムAdapterを作成し、getView内にクリックリスナーを設置する BaseAdapterを元にして作成するため、BaseAdapterに必須の以下の4つの要素を含める必要があります。 getCount:リストの要素数を数える getItem:position位置の要素を取得する getItemId:position位置の要素IDを取得する getView:Viewを生成する ②先ほどArrayAdapterを使用して実装した部分をカスタムAdapterに変更する MyAdapter.kt class MyAdapter( private val context: Context, private var itemList: Array<String> ) : BaseAdapter() { override fun getCount(): Int { return itemList.size } override fun getItem(position: Int): Any { return itemList.get(position) } override fun getItemId(position: Int): Long { return position.toLong() } @SuppressLint("ViewHolder") override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { val inflater = LayoutInflater.from(context) val convertView = inflater.inflate(R.layout.item, parent, false) val checkedTextView = convertView.findViewById<CheckedTextView>(R.id.checked_text_view) //viewに要素を設定 checkedTextView.setText(itemList[position]) // ①viewにクリックリスナーを設定 checkedTextView.setOnClickListener { if (checkedTextView.isChecked) { //押し直した時にAndroidのマークになるように設定 checkedTextView.setCheckMarkDrawable(R.drawable.ic_android_black_24dp) checkedTextView.isChecked = false } else { //1回押した時にチェックマークが出るように設定 checkedTextView.setCheckMarkDrawable(R.drawable.ic_baseline_check_24) checkedTextView.isChecked = true } } return checkedTextView } } MainActivity.kt val adapter: BaseAdapter = MyAdapter(this, itemList)// ②カスタムAdapterを読み込むように修正 以上で各要素をクリックした際にチェックマークが動的に変更される機能が実装できました。 まとめ:値の受け渡し方に工夫が必要 今回は結構な時間をかけてカスタムAdapterの実装を行いましたが、その後の調査でArrayAdapterを使用しても、今回のような動的にレイアウトを変更するクリックリスナーの実装ができそうだと分かりました。再度調べて実装してみようと思います。 また、今回の実装では、スクロールによる画面外への移動でクリック状態が初期値に戻ってしまいます。この機能をアプリなどに組み込む場合には、画面外に移動した場合にも値が保存される必要があります。この点については次回まとめられたらと思います。 ArrayAdapterを使用した場合のクリックリスナーの実装やAdapterの使い分けについて知見やおすすめ記事情報をお持ちの方は、ぜひコメントをお願いいたします。 参考 Developers:Android ListView 【Android】ListViewを使うための基礎知識(1) - Tumbling Dice Developers:CheckedTextView 9 thoughts on “CheckedTextView Tutorial With Example In Android Studio” [Android] BaseAdapterで画像とテキストをListView表示 Android-ListView(BaseAdapter) Android Open Source Project
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Jetpack Compose】Githubリポジトリを一覧表示するサンプルアプリ作った

About もうすぐRelease版が出るJetpack Composeでサンプルアプリを作ったので、色々知見を書いていこうと思います! 本当はちゃんと整理された記事を書こうと思ったのですが、色々浅く広く書きたかったので思い出したままに見出し付けて書いていきます。目次を参照して興味のあるところだけ見ていただいても良いかもしれないです? 画面としては超シンプルな感じです。 自分のGithubリポジトリを一覧表示する リポジトリの詳細画面をWebViewで表示する UIはこんな感じ。 かなりシンプルなアプリになっているので、読みやすいコードになっているかなと思っています。 もし凝ったUIを見てみたいなという場合は、 @KKusumi さんのこの記事とコードが参考になると思います? アーキテクチャ シンプルなMVVM構成になっています。 ここでのポイントは、ViewModelからDataまでのコードはJetpack Composeだからといって特別何も変えていないことです。 今まで通りの書き方で大丈夫で、既存のLayoutをJetpack Composeにリプレースするときも、ViewModelは多少変更すると思いますが、Repository層以降は変更の必要がありません。 ViewModelはAndroid JetpackのViewModelです。Jetpack ComposeはViewModelとの連携がサポートされているので、基本的にはViewModelを採用するのが良いと思います。 Viewの構成 シングルActivity、No Fragment構成になっています。Jetpack ComposeのNavigationを使っていて、いわゆるルーティングで画面遷移を定義しています。FlutterやReact、Vue.jsを触っている方なら馴染みのある形式です。 @Composable fun App() { val navController = rememberNavController() NavHost(navController = navController, startDestination = "home") { composable("home") { HomeScreen(navController, hiltViewModel()) } composable( "web-view/?url={url}", arguments = listOf(navArgument("url") { type = NavType.StringType }), ) { backStackEntry -> WebViewScreen(url = backStackEntry.arguments?.getString("url") ?: "") } } } 詳細は公式を参照ですが、この手のルーティング遷移を経験したことのある人なら難しくないと思います。 Webアプリのように、URL定義をして、そのURLを指定することで画面遷移することができます。 Scaffold( topBar = { TopAppBar() }, ) { HomeScreenBody( user = user, repositories = repositories, onClickUserCard = { navController.navigate("web-view/?url=${homeViewModel.userPageUrl}") }, onClickRepository = { navController.navigate("web-view/?url=${homeViewModel.getRepositoryPageUrl(it)}") } ) } navigateする側はとってもシンプルでいい感じです。遷移に制限もなさそうなので快適に使うことができそうだなと思いました(XMLのJetpack NavigationのほうはGraphの制限などが厳しかった。。) 反面、定義側はちょっと冗長な感じがしています。もう少しかんたんに書けるようになるといいですね? Jetpack Compose Navigationを使ってみて思ったところ このSingle Activity、No Fragment構成ですが、今までと書き方が違いすぎるので、既存のプロジェクトからリプレースするのはかなり大変です。また、以下のようなデメリットもありました。 画面遷移のアニメーションがいまのところサポートされていない すべてActivty上なので、いままでのライフサイクルを使った処理が使えない 画面単位のContextを使った処理などをどうするべきかの知見が少ない やはり一番大きな違いはFragmentやActivityが画面とセットで存在しないので、それらが必要になったときにどうするべきかがイマイチわからなかった点です。 例えば動画リワードライブラリや広告系のSDKなどを使っている場合は、色々苦戦しそうですね。 もちろん、今まで通りのActivity、Fragment構成から、シンプルにXMLをJetpack Composeに置き換えるような使い方もできるので、僕はしばらくそっちの書き方にしようかなと思いました。 Fragmentから呼び出す方法 今回のサンプルコードでは使っていはいませんが、FragmentからXML LayoutではなくJetpack Composeを使う場合はこんな感じで使うことができます。これなら、今まで通りの処理を存分に活用することができるのでしばらくはおすすめかなと思いました。 class HomeFragment : Fragment() { private val viewModel: HomeViewModel by viewModels() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View = ComposeView(requireContext()).apply { setContent { MyApplicationTheme { Scaffold { Body() } } } } DI DIライブラリはHiltを使っています。 Jetpack Composeをただ使うだけなら、特別な対応は必要なく、今まで通りHiltを使って大丈夫です。 ただ、今回Jetpack ComposeのNavigationを使っているので、それの連携処理と、専用の依存を追加してあげないとビルドが通らないので注意です。 こちらを参照です。 値の自動検知 今回のサンプルコードではViewModelからComposeに状態を通知するのはFlowを使っています。LiveDataもサポートされているのでそっちでも大丈夫です。 LiveDataのように状態を管理するために、StateFlowを使っています。 class HomeViewModel : ViewModel() { private val _user: MutableStateFlow<User?> = MutableStateFlow(null) val user: StateFlow<User?> get() = _user } Compose側でObserveするのはこう。 @Composable fun HomeScreen( navController: NavController, homeViewModel: HomeViewModel = viewModel(), ) { val user = homeViewModel.user.collectAsState().value val repositories = homeViewModel.repositories.collectAsState().value これ、値が変更されたときはどうなるかというと、このComposeが自動で再生成されます。普通Viewが再生性されるとなると描画のコストとかを懸念しますが、Composeは低コストなので全然大丈夫みたいですね?? 同じ理由で、Layout XMLのときはネストしないように作ったり、RecyclerViewではViewの使い回しがされたりなどしてましたが、Jetpack Composeの場合は基本的にガンガンネストしていく書き方ですし、List表示もそのまま新しいComposeが生成されるみたいです。1000件のリスト表示も使いまわしとかしないようですがパフォーマンスになんら影響はないみたいですね? MutableState Jetpack Compose専用のLiveDataもあって、 MutableState というのがあります。 LiveDataを使う場合はobserveする処理がView側で必要なのですが、そこらへんが不要になってコードが少なくなるLiveDataです。欠点としてはCompose以外では使えないので、Layout XMLなども使う想定がある場合は使わないほうがいいです。 Jetpack Composeだけからしか参照されないViewModelで、LiveDataを使うなら MutableState を使って書くほうがかなりスッキリしてかんたんに書けるようになるのでおすすめです? remember〇〇 Jetpack Composeを使っていて頻出するのがこの remember〇〇 系の関数。Navigationでも、 rememberNavController() とか出てきました。これは何かというと、Composeのメモリに保存させている処理です。 Androidの今までのViewは、View側で値を保持していました。例えばEditTextViewとか、View自身も値を保持していましたし、ViewModelでも値を保持していて、連動させるのに苦労したことがあると思います。 Composeでは完全にStatelessになっていて、値を何も保持していません。テキストが入力されてもCompose自身は何も保持しないので参照とかできません。 そういうときに、Composeに値を保持しておいてほしい場合に使うのがこの remember〇〇 系の関数になります。 rememberかViewModelか Composeは値を保持していないので、かつて問題が合ったViewModelとViewの値の二重管理やズレが解消されています。ViewModelの値をComposeがobserveしているなら、ViewModelの値とComposeがずれることはありませんし、更新処理で苦戦することもありません。 とは言え、ロジックで使わないような状態値であればComposeで値管理しておいてほしい場合もあります。そういう場合はrememberが便利です。 ロジックでComposeの値を使うのであれば、rememberではなくViewModelの値をObserveするほうがよくて、ロジックで使わないって場合はrememberするのがいいのかなと思いました。(Navigationの rememberNavController() とかはちょっと別な気がしますが) ○○Screen 画面単位のトップレベルのComposeの名前は特別決まっているわけではありませんが、○○Screenという名前で定義されていることが多いような気がします。Flutterだと○○Pageが多いですかね? このサンプルでもHome画面とWebViewの詳細画面がありますが、それぞれ HomeScreen と WebViewScreen という名前にしています。 Stateless Compose 基本的にComposeはStatelessにするのが望ましいとされています。例えば、ViewModelの値をObserveしているComposeは外からの値変更をそのCompose内で処理しているのでStatefullと言えそうです。 ViewModelをそのまま子要素のComposeに渡している場合、とても便利ですが影響範囲が大きく、またテストもしづらくなってしまうため基本的には必要な分だけ引数にしてあげるのが良さそうです。 @Composable private fun UserCard( user: User, onClick: () -> Unit, ) { Card( modifier = Modifier .padding(top = 16.dp) .padding(horizontal = 8.dp) .fillMaxWidth() .wrapContentHeight() .clickable(onClick = onClick), elevation = 4.dp, ) { // 中のコードは割愛 } } 例えばこのUserカードのComposeですが、この引数に取っている user: User や onClick: () -> Unit は HomeViewModel をそのまま渡してあげれば個別で受け取る必要はありません。ですが、ViewModelをそのまま受け取ってしまうと、このUserCardが直接値を変更したり、参照したり、操作できてしまうため、このようにStatelessに宣言しておいたほうがいいです。これをState hoistingと言うみたいです。 LazyColumn やっぱり、Jetpack Composeを使っていて最高だなと思うのがリスト表示、LazyColumnを使っているときです。モバイルアプリといえばスクロール可能なUIが必須と言えるのにAndroidネイティブの開発ではこのリスト表示が難解で扱いづらいところでした。RecyclerViewをそのまま使って 複雑なUIを作るのは難しいのでEpoxyやGroupieなどのライブラリを使って開発してました(それでもだいぶつらい) Jetpack Composeでは単純にfor分で回してあげるだけのような感じで書くことができてしまいます。学習コストと実装コストの高かったAdapterやViewHolderともおさらばです。 ↑のToolbar以下のUserのカード表示と、リポジトリのリスト表示の部分がLazyColumnになっています。 LazyColumn( Modifier.fillMaxSize() ) { item { UserCard(user = user, onClick = onClickUserCard) } item { Spacer(modifier = Modifier.size(16.dp)) } if (repositories.isEmpty()) { item { Center(modifier = Modifier.padding(top = 16.dp)) { CircularProgressIndicator() } } } else { repositories.forEach { repository -> item { RepositoryItem(repository = repository, onClick = onClickRepository) } item { Divider(Modifier.padding(start = 16.dp)) } } } } item {} の中のComposeが画面にリスト表示されます。LazyColumnはリスト表示というよりかは、スクロール可能なColumn表示のComposeです。なのでLazyColumnの中に書いたComposeが繰り返して表示されるというわけではありません。Githubリポジトリをリスト表示している部分は自分でforを回して表示しています。 repositories.forEach { repository -> item { RepositoryItem(repository = repository, onClick = onClickRepository) } item { Divider(Modifier.padding(start = 16.dp)) } } このUIでは上部にUser情報のカード表示があり、スペースがちょっとあってリポジトリのリスト表示があります。これをRecyclerViewで実装する場合は、ViewTypeを使って頑張って実装していました。LaxyColumnの場合は上記のコードのように簡単に書くことができます。実装したいことをそのまま書けばいいというイメージでしょうか。宣言的UIって素晴らしいなと思いますね? Surface SurfaceというComposeがあるのですが、これがとても便利です。例えば、Imageを丸く表示している部分で使っています。 Surface( modifier = Modifier .padding(start = 16.dp) .padding(end = 8.dp) .size(80.dp), shape = CircleShape, elevation = 4.dp, ) { Image( painter = rememberGlidePainter(request = user.avatarUrl), contentDescription = "avatar url" ) } Imageはそのまま画像を表示するComponentなのですが、これをWrapしてSurfaceを使っています。SurfaceはMaterialデザインのフレーム的なCompsoseで、Elevationをかけたり、Shapeをかけたりなどすることができます。 今までのAndroid Viewのほうでは、ViewにElevationをかけたりするときは僕はCardViewをよく使っていました。CardViewは簡単にElevationをかけることができたので多用していたのですが、正直それ目的で使うのは誤用感があって嫌でした。また、AndroidはMaterialデザインが推奨されている割には、Viewが特別対応しているわけでもなかったのでいろいろ不便だったのでこのSurfaceはとても便利です。 Elevationを簡単にかけることができるのも素晴らしいのですが、Shapeを指定できるのも素晴らしいです。ここでは CircleShape を指定してImageを丸く切り取っています。今まではこんな簡単に実装できませんでした。しかも、Elevationもかけることができるので、丸く切り取ったImageのViewに4dp elevationをかけています。この2つを実装しようと思ったら今までは結構大変です。これ、感動ですよね? Divier 地味ですがこれも便利ですね。文字通り罫線を引くComposeですが、標準で用意されています。こういうのも、今まではxmlでdrawable用意して~ってやってましたし、RecyclerViewで表示するのも面倒でしたよね。 Center ローディング表示を画面のど真ん中に表示したいことってあると思います。このサンプルプロジェクトでもたびたびローディング表示を画面の真ん中に表示しています。残念ながら(?)Center的なComposeでは標準で用意されていません。 ですが、Composeは簡単に自分で定義できるので、たったこれだけのコードで自作できます。 @Composable fun Center( modifier: Modifier = Modifier, content: @Composable () -> Unit, ) { Column( modifier = modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, ) { content() } } Center(modifier = Modifier.padding(top = 16.dp)) { CircularProgressIndicator() } 詳しいことはこちらのnoteにて書いてるのでぜひに。 WebView WebViewは残念ながらCompose対応されていませんでした。ですが、Jetpack Composeでは既存のViewを使うことができます。また、逆にComposeをAndroid View(XML Layout)から使うこともできます。 Githubの画面をWebView表示しているComposableのコードです。 @Composable fun WebViewScreen( url: String, webViewModel: WebViewModel = viewModel(), ) { val isLoading = webViewModel.isLoading.collectAsState().value AndroidView({ context -> WebView(context).apply { layoutParams = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) webViewClient = object : WebViewClient() { override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean { return false } override fun onPageFinished(view: WebView?, url: String?) { super.onPageFinished(view, url) webViewModel.onPageFinished() } } loadUrl(url) } }) if (isLoading) { Center(modifier = Modifier.padding(top = 16.dp)) { CircularProgressIndicator() } } } AndroidView のところが、Android Viewを呼び出すComposeです。webViewClientのところが幅を取っていますが、そこを除けば簡単に呼び出せますね。 こう見るとローディング表示も簡単ですね。 おわり 色々雑多にまとめてみました。Navigationまわりが個人的に不安感があり、どう導入していこうかなと悩ましいところではありますが、Layoutの部分だけみれば本当に書きやすくなっていてリリースが待ちどおしいですね?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

今年の夏の暑さを測るためにmicro:bitとweather:bitで遊んでみた時に苦労したことをメモする ※問題解決後更新予定

初めに 去年micro:bitとweather:bit(スイッチサイエンスに終売のお知らせがあったのが残念)でとりあえず温度測れるようにしたんですけど、単体での計測だったのでBLEのお勉強も兼て温度を定期的に送信することを目的に遊んでみました。 micro:bitとweather:bitは大して苦労はしなかったのですが、AndroidでBLEを扱うプログラムをちゃんと組んだのが初めてで知らないことだらけで苦労したので、その辺を載せておこうと思います。 micro:bitとAndroidの通信 UARTを使って通信します。 基本的に、micro:bitからAndroidへの通信です。 なので、TX Characteristicを利用します。 この時に、BluetoothGatt#setCharacteristicNotification()メソッドで、Notifyを有効にします。 そのあとに、Descriptorに値を設定するのですが、割とNotify関連を調べると、BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUEを設定する記事が多いです。 (さらにmicro:bitのことを調べると、Web Bluetooth APIの記事が多くて・・・) micro:bitのTX Characteristicの仕様を確認すると、Indicateが必須となっています。(無知とは罪ですね・・・) なので、micro:bitでTX Characteristicを使う場合、BluetoothGattDescriptor.ENABLE_INDICATION_VALUEを設定します。 私はこんな感じのメソッドを作って対応しました。 kotlinにまだ慣れていないので書き方として良くないところがあるかもしれませんが、見逃してくださいw private fun setNotify(ch : BluetoothGattCharacteristic) : BluetoothGattDescriptor?{ var isNotify : Boolean = ch.properties.and(BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY var isIndicate : Boolean = ch.properties.and(BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE return null } if(false == bleGatt?.setCharacteristicNotification(ch, true)){ return null } var descriptor : BluetoothGattDescriptor? = null ch?.descriptors.forEach{ desc -> if(NOTIFY_DESCRIPTOR == desc.uuid) { if(isNotify){ desc.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE }else{ desc.value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE } if (true == bleGatt?.writeDescriptor(desc)) { descriptor = desc } } } return descriptor } BluetoothGattCharacteristicのプロパティを調べることで、BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUEを設定すべきか、BluetoothGattDescriptor.ENABLE_INDICATION_VALUEをすべきかがわかるようです。 なので、プロパティを見て設定するようにしています。 (無駄に汎用的にしたかったんです) こんな感じにすることで、無事にmicro:bitから送信された文字列を受信することができました。 なぜかうまくいかないdiscoverServices()メソッド connectGatt()メソッドを呼び出すと、BluetoothGattCallbackのonConnectionStateChange()メソッドが呼び出されます。その中でBluetoothGatt#discoverServices()を呼び出しているんですが、なぜか、onServicesDiscovered()メソッドが呼び出されません。 3回くらい接続しなおすと、onServicesDiscovered()メソッドが呼び出されます。 コチラは現在調査中・・・ 解決したら更新します。 もし何かアドバイスがあるようでしたらコメントに書いていただけると助かります。 ちなみに、以下のようにしたら、少し良くなったような気がするが、まだおかしい。 方向性が間違ってる(ログ見て遅延させたらよくなるんじゃね?とお気軽に試しただけ)のと、なにか使い方が良くないと思われる。 private val gattCallback = object : BluetoothGattCallback(){ // ・・・中略・・・ bleGatt = gatt handler2.postDelayed({ if(true == bleGatt?.discoverServices()){ Log.d(LOG_TAG,"discoverServices success") }else{ Log.d(LOG_TAG,"discoverServices failed") } }, 200) // ・・・中略・・・ } 参考 Bluetooth Developer Studio Level 3 Profile Report スイッチサイエンス SparkFun weather:bit--販売終了
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む