20210611のAndroidに関する記事は8件です。

Android StudioでRealmのクラスパスの依存関係が追加できない時の対応策

Androidアプリケーションで用いるデータベースとしてRealmを導入する時にハマった場所についての記事です。デフォルトで自動生成されたbuild.gradleのprojectの方を開き、dependenciesの3行目に classpath "io.realm:realm-gradle-plugin:10.0.1" を追加します。 build.gradle(project) // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { ext.kotlin_version = "1.4.31" repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:4.2.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "io.realm:realm-gradle-plugin:10.0.1" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { google() jcenter() } } task clean(type: Delete) { delete rootProject.buildDir } ここでSyncすると、以下のようなエラーメッセージが出ます。 A problem occurred configuring root project 'MyApplication'. > Could not resolve all artifacts for configuration ':classpath'. > Could not find io.realm:realm-gradle-plugin:10.0.1. Searched in the following locations: - https://dl.google.com/dl/android/maven2/io/realm/realm-gradle-plugin/10.0.1/realm-gradle-plugin-10.0.1.pom - https://repo.maven.apache.org/maven2/io/realm/realm-gradle-plugin/10.0.1/realm-gradle-plugin-10.0.1.pom Required by: project : Possible solution: - Declare repository providing the artifact, see the documentation at https://docs.gradle.org/current/userguide/declaring_repositories.html ここで先程のbuild.gradle内のrepositoriesに jcenter()を追加すればビルドエラーが消えます。 build.gradle(project) // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { ext.kotlin_version = "1.4.31" repositories { google() mavenCentral() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:4.2.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "io.realm:realm-gradle-plugin:10.0.1" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { google() jcenter() } } task clean(type: Delete) { delete rootProject.buildDir } 出力 BUILD SUCCESSFUL in 10s
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Speech Recognizerの謎のエラーとAPIレベル

はじめに 結論から言うと、AndroidManifest.xmlへの記述が足りませんでした。 「何だそんなことか」と思われるかもしれませんが(実際自分も思いましたが)、エラーを見るとすぐに「プログラム側がおかしいに違いない!」とその他の部分をよく見ない癖があり、それを直さないと大切なことを見落とすよという自戒を込めて書き記しておきます。 いやでもほんと、書くのすら恥ずかしい失敗なんです//// では以下、お付き合いください。 環境 Android Studio 4.2.1 〜動かないスマホ〜 docomo Galaxy S20+ 5G SC-52A Android 11 APIレベル:30 docomo Galaxy Note20 Ultra SC-53A Android 11 APIレベル:30 〜動くスマホ〜 docomo arrows NX F-01K Android 9 APIレベル:28 docomo Xperia ACE SO-02L Android 10 APIレベル:29 この時点で「あっ・・・」って気づくべきでした エラー内容 2021-06-11 21:39:11.127 30307-30307/{パッケージ名} E/SpeechRecognizer: bind to recognition service failed 直訳すると認識サービスへのバインドに失敗しましたという意味。 この時点でMediaPlayerを疑い始めるという無能っぷり・・・ 焦るとプログラム側しか見えなくなるのはマジで直した方が良いですね・・・ 状況 問題発生時に書き記したものがこちら↓ ほぼ同じコードを持つアプリの音声認識の起動は成功する ただし問題のあるアプリのコードや状態を成功するアプリと全く同じにしても音声認識は起動しない 新しく起動に成功するアプリの状態を丸々コピーしたアプリも音声認識の起動に失敗する 音声合成サービスを正しく起動できる機種・できない機種がある こうなって「当然」な間違いを犯してるんですよね・・・ やったこと こちらも問題発生時に書き記したものがこちら↓ Splash画面の排除 関係ないとは思いつつ、直近で追加したアプリ起動時のスプラッシュ画面を排除しました。 が、状況は変わらず Media Playerの排除 まさか音声系+音声系でどこかぶつかっているのかと思い、MediaPlayerを排除しましたが、状況は変わりませんでした。 ちなみにテストで入れたコードなので、どのみち今後排除する予定ではあります 作り直し 新たなプロジェクトで音声認識に成功する旧バージョンのアプリのコードをコピペ、実行しました。 が、結果は変わらず 当たり前なんだよなぁ・・・ 結論 AndroidManifest.xmlに以下の記述をしていなかった <queries> <intent> <action android:name="android.speech.RecognitionService" /> </intent> </queries> どういう意味? Speech Recognizerを利用する際、Android 11(APIレベル30)以降は上記の記述をしなければ利用できないようです。 記述すべきものを記述していないのだから使えないのは当然です。 滑稽な慌てようを振り返ろう arrows NX F-01K・Xperia ACEで使えたが、Galaxy S20+ 5GとGalaxy Note20 Ultraでは使えない 所有するGalaxyは最新のAndroid11、Xperia ACEはAndroid10、arrowsはAndroid9。 10以前はqueriesの記述が不要なので使えるのは当然 やったこと欄 全く関係無いです。 旧バージョンでは使える 旧バージョンのアプリのManifestにはしっかりとqueriesを記述していたから使えて当然 作り直した方には記述されていないから使えなかった まとめ プログラム以外にも目を向けよう 決めつけはよくない 常に最新の情報を入手しよう ちなみに問題解決までに約6時間かかりました。 このエラー自体が初歩的すぎてどこにも情報がなく、中国語や英語のサイトを見ても「カスタムROM搭載」「Speech Recognizerが使えない端末」といったような情報ばかりが出てきてなかなか同じ状況の人が居ないのも焦りに拍車をかけていた気がします・・・ 今回の事件でコード以外の部分に目を配らないと行けないことがよく分かったので、今後は同じミスを犯さないように気をつけたいと思います・・・ 以上、超恥ずかしいミスを6時間も掛けて解決した話でした。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Kotlin研修8日目】Web APIを利用した非同期処理の実装、JSON解析

同期処理と非同期処理 同期処理 同じスレッド上で行われる処理。 同期処理のメソッドを呼び出した場合、 呼び出したメソッドの処理が終わるまで呼び出し元メソッドは待機状態となる。 Kotlin/Javaでは、基本的にメイン処理が同期処理で行われる。 スレッド 処理の一連の流れの単位。 Androidアプリでは、アクティビティを処理するUIスレッドと非同期処理を行うワーカースレッドに分類される。 UIスレッド アクティビティが実行されるメインスレッド。 ワーカースレッド UIスレッドとは異なるスレッドで非同期処理を行うスレッド。 非同期処理 異なるスレッド上で行われるマルチスレッド処理。 非同期処理のメソッドを呼び出した場合、 呼び出したメソッドの処理の終了を待たずに呼び出し元メソッドは続きの処理を実行するため、 時間がかかる処理は非同期処理で行うのが一般的。 Executorを利用した非同期処理 参考1: バックグラウンドスレッドでのAndroidタスクの実行 参考2: ExecutorService 参考3: Thread, Looper, Handler Executorを用いた非同期処理を実装する手順は、以下の通り。 Executorsクラスメソッドを用いてワーカースレッドを生成し、スレッドプールに追加 ワーカースレッドでの処理を終えた後にUIスレッドで処理を続行する場合は、HandlerCompatクラスメソッドを用いてHandlerオブジェクトを生成 Runnableインタフェースを実装したクラスを作成し、run()メソッドをオーバーライドして自動的に実行される処理を記述 スレッドプールに、3.で作成したクラスのオブジェクト(=Runnableオブジェクト)を送信 Executorインタフェース ワーカースレッドの生成やワーカースレッドでの処理を実行するメソッドを抽象的に定義したインタフェース。 Executorsクラス ExecutorServiceのファクトリクラス。 ExecutorServiceインタフェース Executorインタフェースの抽象メソッドを拡張しており、 スレッドプールの作成やスレッドプールへのRunnableタスクの送信など、 スレッドプールを管理するメソッドを抽象的に定義したインタフェース。 スレッドプール 複数のスレッドを待機させ、処理するRunnableオブジェクト(=Runnableタスク)が到着すると、 待機状態のスレッドにRunnableタスクを割り当て、処理を自動的に開始させる仕組み。 ファクトリ(クラス) 「インスタンス(=オブジェクト)の生成」を目的に作られたクラス。 Runnableインタフェース 特定のスレッド内での処理を自動的に実行するrun()メソッドを抽象的に定義したインタフェース。 Handler Looperオブジェクトを保持し、Looperから命令を受けるとメッセージキュー内の処理を実行するクラス。 Looperオブジェクトを保持していることから、 実質的に特定のスレッド内での処理を管理・実行する。 UIスレッドで作成したHandlerオブジェクトをワーカースレッドのRunnableオブジェクトに渡し、 ワーカースレッド側でUIスレッドで動作させる処理(=Runnableオブジェクト)を、 HandlerがもつLooperオブジェクトに送信(=post())してもらうことで、 ワーカースレッドでの処理を終えた後、UIスレッドでの処理続行が可能になる。 このことから、Handlerは「スレッド間の通信を行うクラス」とも言われる。 Looper 特定のスレッド内での処理(=Runnableオブジェクト)を先入先出法で管理し、 監視しているメッセージキューに処理がある限り、Handlerに実行するよう命令するクラス。 Executorを利用した非同期処理の実装 スレッドプールへのスレッド追加 定義 // 1スレッドの追加 Executors.newSingleThreadExecutor(): ExecutorService // 指定したスレッド数のスレッドプールに変更 Executors.newFixedThreadPool(nThreads: Int): ExecutorService // パラメータ // nThreads: スレッド数 // 再利用可能なスレッドを必要数に応じて追加 Executors.newCachedThreadPool(): ExecutorService // 指定したスレッド数をもち指定時間おきに処理するスレッドプールを追加 Executors.newScheduledThreadPool(corePoolSize: Int): ScheduledExecutorService // パラメータ // corePoolSize: スレッド数 サンプルコード MainActivity.kt // 1スレッド追加したスレッドプールをもつExecutorServiceプロパティ val executeService = Executors.newSingleThreadExecutor() Handlerオブジェクトの生成 定義 HandlerCompat.createAsync(@NonNull looper: Looper): Handler // パラメータ // looper: HandlerにバインドするLooper サンプルコード MainActivity.kt // UIスレッドで最初に実行する処理 // -> UIスレッドで動作することを明示的に記述(@UiThread) @UiThread private fun receiveWeatherInfo(urlFull: String) { // UIスレッドで動作する処理を管理するHandlerオブジェクトの生成 // mainLooper: アクティビティ(=UIスレッド)がもつLooperオブジェクト val handler = HandlerCompat.createAsync(mainLooper) ... } Runnableオブジェクトの定義 Runnableインタフェースは「スレッドプールやHandlerによって自動実行される処理」を抽象的に定義したrun()メソッドをもつため、 Runnableインタフェースを実装するクラスではオーバーライドして処理を記述する。 その際、動作するスレッドをアノテーションで記述することで、指定したスレッドでの動作が保証される。 また、UIスレッドからHandlerオブジェクトを受け取る場合は、コンストラクタに組み込む必要がある。 サンプルコード // 非同期処理を行うRunnableオブジェクトを定義するクラス private inner class WeatherInfoBackgroundReceiver(handler: Handler, url: String): Runnable { // クラス内で扱うHandlerオブジェクト(=Handlerプロパティ) // -> クラス内での書き換えが発生しないよう、スレッドセーフ(読み込み専用のval)で定義 private val _handler = handler ... // スレッドプールによって自動的に実行する処理 // -> ワーカースレッドで動作することを明示的に記述(@WorkerThreadアノテーション) @WorkerThread override fun run() { ... // 処理内容 // 非同期処理の終了後にUIスレッドで行う処理(Runnableオブジェクト) val postExecutor = WeatherInfoPostExecutor(result) // UIスレッドでの続行処理をUIスレッドHandlerのLooperに送信 _handler.post(postExecutor) } } MainActivity.kt // ワーカースレッドでの処理後に、 // UIスレッドで動作するRunnableオブジェクトを定義するクラス private inner class WeatherInfoPostExecutor(result: String) : Runnable { ... // Handlerによって自動的に実行される処理 // -> UIスレッドで動作することを明示的に記述(@UiThreadアノテーション) @UiThread override fun run() { ... // 処理内容 } } スレッドプールに処理を送信 定義 ExecutorService.submit(task: Runnable): Future<?> // パラメータ // task: スレッドプールに送信するRunnableオブジェクト サンプルコード MainActivity.kt // UIスレッドで最初に実行する処理 // -> UIスレッドで動作することを明示的に記述(@UiThread) @UiThread private fun receiveWeatherInfo(urlFull: String) { // スレッド数2のスレッドプールを管理するExecutorServiceプロパティ val executeService = Executors.newSingleThreadExecutor() // UIスレッドで動作する処理を管理するHandlerオブジェクトの生成 // mainLooper: アクティビティ(=UIスレッド)がもつLooperオブジェクト val handler = HandlerCompat.createAsync(mainLooper) // ワーカースレッドで処理するRunnableオブジェクト val backgroundReceiver = WeatherInfoBackgroundReceiver(handler, urlFull) // スレッドプールにRunnableオブジェクトを送信 // -> 非同期処理の開始 executeService.submit(backgroundReceiver) } AndroidのWeb連携 Android端末からインターネット上のデータベースにアクセスする場合、 DBと直接データのやり取りを行うサーバーサイドWebアプリ(=Web API)を通じてDBにアクセスする。 Web APIを利用したデータ取得 Web APIにアクセスするためには、APIを利用するためのAPIキーが必要となる。 また、Web APIの利用にあたってHttpURLConnectionクラスを用いてHTTP接続を行う必要がある。 HTTP接続を行う手順は、以下の通り。 AndroidManifest.xmlに、HTTP接続を許可するパーミッションタグを記述 接続先URLを定義したURLオブジェクトをHttpURLConnectionオブジェクトに変換 HTTP接続に関する設定を記述し、HTTP接続を実行 InputStream型のレスポンスデータをString型に変換 JSONデータに変換したレスポンスデータの解析(=JSON解析) HttpURLConnectionオブジェクトを解放 マニフェストファイルでHTTP接続を許可 参考1: マニフェストファイルの概要 参考2: ネットワークへの接続 参考3: 研修1日目 アプリの基本情報を記述するマニフェストファイルに、HTTP接続を許可するパーミッションを記述する。 サンプルコード AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest ...> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> ... </manifest> URLオブジェクトをHttpURLConnectionオブジェクトに変換 接続先URLの文字列から生成したURLオブジェクトを、HttpURLConnectionオブジェクトに変換する。 HttpURLConnection HTTP接続に関する設定値(=プロパティ)をもち、HTTP接続を実行するメソッドを定義するクラス。 定義 URL.openConnection(): URLConnection サンプルコード MainActivity.kt // String型 → URL型への変換 val url = URL("<URL文字列>") // URL型 → URLConnection型 → HttpURLConnection型への変換 val con = url.openConnection() as? HttpURLConnection HTTP接続設定の定義・実行 HTTP接続に関する設定 (Http)URLConnectionの主なプロパティ プロパティ 内容 connectTimeout HTTP接続がタイムアウトするまでの時間[ms] readTimeout データ取得がタイムアウトするまでの時間[ms] requestMethod HTTPリクエストのメソッド responseCode HTTPステータスコード HTTP接続の実行 HTTP接続がうまくできなかった場合は例外が発生するため、例外処理を用いて記述する必要がある。 定義 URLConnection.connect() // Throws: // SocketTimeoutException: タイムアウトに関する例外 // IOException: 接続エラーに関する例外 サンプルコード MainActivity.kt // URLオブジェクト val url = URL("<URL文字列>") // HttpURLConnectionオブジェクト val con = url.openConnection() as? HttpURLConnection // HttpURLConnectionオブジェクトがnullでないことを保証 // -> let関数ブロック内では、nullチェック対象(=con)がitで置換 con?.let { // 例外が発生する可能性があるブロック try { // -- HTTP接続設定の定義開始 -- // 接続がタイムアウトするまでの時間[ミリ秒] // -> タイムアウトした場合はSocketTimeoutExceptionが発生 it.connectTimeout = 1000 // データ取得がタイムアウトするまでの時間[ミリ秒] // -> タイムアウトした場合はSocketTimeoutExceptionが発生 it.readTimeout = 1000 // HTTP接続メソッドの指定 it.requestMethod = "GET" // -- HTTP接続設定の定義終了 -- // HTTP接続の実行 // ->場合によってSocketTimeoutExceptionまたはIOExceptionが発生 // -> 接続時、HttpURLConnectionオブジェクトのinputStreamプロパティに // InputStream型のレスポンスデータが自動的に格納 it.connect() } // 例外が発生した場合のエラー処理 catch (ex: SocketTimeoutException) { ... // Logcatにログを出力 } } InputStream型→String型の変換 InputStream型のレスポンスデータは、Byteデータであるため、InputStreamReaderやBufferedReaderを用いて、 文字データとして読み込んでからString型に変換する必要がある。 また、String型に変換した後はInputStreamReaderをラップしたBufferedReaderオブジェクトや、InputStreamオブジェクトを解放することでリソース効率を向上させる。 InputStream 読み込み専用のByteデータを抽象的に定義するクラス。 InputStreamReader InputStream型のByteデータを読み込み、様々な文字コードに対応しながら文字データに変換し、 変換した文字データをオブジェクトとして保持できるクラス。 BufferedReader バッファリングを行うことで読み込みの高速化を実現しながら、 読み込んだデータをオブジェクトとして保持できるクラス。 定義 // InputStreamオブジェクトの解放 InputStream.close() サンプルコード(InputStream→Stringへの変換) MainActivity.kt // InputStream型 → String型 への変換処理 private fun is2String(stream: InputStream?): String { // 1行分の文字データを格納するStringBuilderオブジェクト // StringBuilder: 文字列を格納する(同一ポインタ内での)可変配列を定義するクラス val sb = StringBuilder() // Byteデータ → 文字データ への変換 val reader = BufferedReader(InputStreamReader(stream, "UTF-8")) // 1行分の文字データ // -> 1行目の読み込み var line = reader.readLine() // 最終行までループさせる処理 while (line != null) { // 読み込んだ1行分の文字データをStringBuilderオブジェクトに格納 sb.append(line) // 次の行を読み込む line = reader.readLine() } // BufferedReaderオブジェクトの解放 reader.close() // 読み込んだ文字データ(StringBuilder型)をString型に変換 return sb.toString() } サンプルコード(InputStreamオブジェクトの解放) MainActivity.kt // HttpURLConnectionオブジェクトによるレスポンスデータの取得 val stream = <HttpURLConnectionオブジェクト>.inputStream // InputStream型のレスポンスデータをString型に変換 result = is2String(stream) // InputStreamオブジェクトの解放 stream.close() JSON解析によるデータ取得 HTTP接続時にHttpURLConnectionオブジェクトに格納されたByteデータ(=InputStream)の中身は、 JSON(JavaScript Object Notation)で記述されたJSONデータであるため、 JSON解析によって取得するデータを含むマップを抽出し、 最終的に、get<T>()メソッドを用いてデータを取得する。 JSONデータ キー(=name)と値を保持するマップが、データ記述言語であるJSONによって記述された、階層構造をもつデータ。 String型に変換したJSON文字列 // JSONObject: {...}で囲まれたキー付きのマップ { "<name>":{ "<name>":<value>, "<name>":<value>, }, // JSONArray: [...]で囲まれたキー付きのリスト "<name>":[ { "<name>":<value>, "<name>":<value>, }, ], ... "<name>":<value>, "<name>":<value> } 定義 /* いずれのメソッドも、条件に一致するJSONデータが見つからない場合、 JSONExceptionが発生 */ // JSONObjectの生成 val rootJSON = JSONObject(json: String): JSONObject // パラメータ // json: JSONによって記述された文字列 // JSONArrayの取得 rootJSON.getJSONArray(name: String): JSONArray // name: JSONArrayのキー // JSONObjectに含まれるJSONObjectの取得 rootJSON.getJSONObject(name: String): JSONObject // name: JSONObjectのキー // データの取得 rootJSON.get<T>(name: String): <T> // name: マップのキー WebAPIを利用したデータ格納 Web APIを通じてDBのデータ更新処理を行う場合は、 HttpURLConnectionオブジェクトがもつOutputStreamオブジェクト(=outputStreamプロパティ)を通じて、ByteArray型に変換したリクエストパラメータを送信する。 サンプルコード MainActivity.kt // URLオブジェクト val url = URL("<URL文字列>") // HttpURLConnectionオブジェクト val con = url.openConnection() as? HttpURLConnection // 送信するリクエストパラメータ文字列 val postData = "name=${name}&comment=${comment}" // HttpURLConnectionオブジェクトがnullでないことを保証 // -> let関数ブロック内では、nullチェック対象(=con)がitで置換 con?.let { // 例外が発生する可能性があるブロック try { // -- HTTP接続設定の定義開始 -- // 接続がタイムアウトするまでの時間[ミリ秒] // -> タイムアウトした場合はSocketTimeoutExceptionが発生 it.connectTimeout = 1000 // データ取得がタイムアウトするまでの時間[ミリ秒] // -> タイムアウトした場合はSocketTimeoutExceptionが発生 it.readTimeout = 1000 // HTTP接続メソッドの指定 it.requestMethod = "POST" // リクエストパラメータの出力を可能にする it.doOutput = true // -- HTTP接続設定の定義終了 -- // HTTP接続の実行 // ->場合によってSocketTimeoutExceptionまたはIOExceptionが発生 // -> 接続時、HttpURLConnectionオブジェクトのinputStreamプロパティに // InputStream型のレスポンスデータが自動的に格納 it.connect() // OutputStreamオブジェクト val outStream = con.outputStream // String型のリクエストパラメータをByteArray型に変換し、 // OutputStreamオブジェクトに書き込む // -> 出力エラー時はIOExceptionが発生 outStream.write(postData.toByteArray()) // バッファリングされたリクエストパラメータを // OutputStreamオブジェクトに強制的に書き込む // -> 基本的にはwrite()で書き込みが完了しているが、 // 万が一に備えてflush()メソッドを記述 // -> 出力エラー時はIOExceptionが発生 outStream.flush() // OutputStreamオブジェクトの解放 // -> 実行エラー時はIOExceptionが発生 outStream.close() } catch { ... // エラー処理を記述 } // HttpURLConnectionオブジェクトの解放 it.disconnect() }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Kotlin研修8日目】Executorを利用した非同期処理の実装、Web APIを利用したデータ取得およびJSON解析

同期処理と非同期処理 同期処理 同じスレッド上で行われる処理。 同期処理のメソッドを呼び出した場合、 呼び出したメソッドの処理が終わるまで呼び出し元メソッドは待機状態となる。 Kotlin/Javaでは、基本的にメイン処理が同期処理で行われる。 スレッド 処理の一連の流れの単位。 Androidアプリでは、アクティビティを処理するUIスレッドと非同期処理を行うワーカースレッドに分類される。 UIスレッド アクティビティが実行されるメインスレッド。 ワーカースレッド UIスレッドとは異なるスレッドで非同期処理を行うスレッド。 非同期処理 異なるスレッド上で行われるマルチスレッド処理。 非同期処理のメソッドを呼び出した場合、 呼び出したメソッドの処理の終了を待たずに呼び出し元メソッドは続きの処理を実行するため、 時間がかかる処理は非同期処理で行うのが一般的。 Executorを利用した非同期処理 参考1: バックグラウンドスレッドでのAndroidタスクの実行 参考2: ExecutorService 参考3: Thread, Looper, Handler Executorを用いた非同期処理を実装する手順は、以下の通り。 Executorsクラスメソッドを用いてワーカースレッドを生成し、スレッドプールに追加 ワーカースレッドでの処理を終えた後にUIスレッドで処理を続行する場合は、HandlerCompatクラスメソッドを用いてHandlerオブジェクトを生成 Runnableインタフェースを実装したクラスを作成し、run()メソッドをオーバーライドして自動的に実行される処理を記述 スレッドプールに、3.で作成したクラスのオブジェクト(=Runnableオブジェクト)を送信 Executorインタフェース ワーカースレッドの生成やワーカースレッドでの処理を実行するメソッドを抽象的に定義したインタフェース。 Executorsクラス ExecutorServiceのファクトリクラス。 ExecutorServiceインタフェース Executorインタフェースの抽象メソッドを拡張しており、 スレッドプールの作成やスレッドプールへのRunnableタスクの送信など、 スレッドプールを管理するメソッドを抽象的に定義したインタフェース。 スレッドプール 複数のスレッドを待機させ、処理するRunnableオブジェクト(=Runnableタスク)が到着すると、 待機状態のスレッドにRunnableタスクを割り当て、処理を自動的に開始させる仕組み。 ファクトリ(クラス) 「インスタンス(=オブジェクト)の生成」を目的に作られたクラス。 Runnableインタフェース 特定のスレッド内での処理を自動的に実行するrun()メソッドを抽象的に定義したインタフェース。 Handler Looperオブジェクトを保持し、Looperから命令を受けるとメッセージキュー内の処理を実行するクラス。 Looperオブジェクトを保持していることから、 実質的に特定のスレッド内での処理を管理・実行する。 UIスレッドで作成したHandlerオブジェクトをワーカースレッドのRunnableオブジェクトに渡し、 ワーカースレッド側でUIスレッドで動作させる処理(=Runnableオブジェクト)を、 HandlerがもつLooperオブジェクトに送信(=post())してもらうことで、 ワーカースレッドでの処理を終えた後、UIスレッドでの処理続行が可能になる。 このことから、Handlerは「スレッド間の通信を行うクラス」とも言われる。 Looper 特定のスレッド内での処理(=Runnableオブジェクト)を先入先出法で管理し、 監視しているメッセージキューに処理がある限り、Handlerに実行するよう命令するクラス。 Executorを利用した非同期処理の実装 スレッドプールへのスレッド追加 定義 // 1スレッドの追加 Executors.newSingleThreadExecutor(): ExecutorService // 指定したスレッド数のスレッドプールに変更 Executors.newFixedThreadPool(nThreads: Int): ExecutorService // パラメータ // nThreads: スレッド数 // 再利用可能なスレッドを必要数に応じて追加 Executors.newCachedThreadPool(): ExecutorService // 指定したスレッド数をもち指定時間おきに処理するスレッドプールを追加 Executors.newScheduledThreadPool(corePoolSize: Int): ScheduledExecutorService // パラメータ // corePoolSize: スレッド数 サンプルコード MainActivity.kt // 1スレッド追加したスレッドプールをもつExecutorServiceプロパティ val executeService = Executors.newSingleThreadExecutor() Handlerオブジェクトの生成 定義 HandlerCompat.createAsync(@NonNull looper: Looper): Handler // パラメータ // looper: HandlerにバインドするLooper サンプルコード MainActivity.kt // UIスレッドで最初に実行する処理 // -> UIスレッドで動作することを明示的に記述(@UiThread) @UiThread private fun receiveWeatherInfo(urlFull: String) { // UIスレッドで動作する処理を管理するHandlerオブジェクトの生成 // mainLooper: アクティビティ(=UIスレッド)がもつLooperオブジェクト val handler = HandlerCompat.createAsync(mainLooper) ... } Runnableオブジェクトの定義 Runnableインタフェースは「スレッドプールやHandlerによって自動実行される処理」を抽象的に定義したrun()メソッドをもつため、 Runnableインタフェースを実装するクラスではオーバーライドして処理を記述する。 その際、動作するスレッドをアノテーションで記述することで、指定したスレッドでの動作が保証される。 また、UIスレッドからHandlerオブジェクトを受け取る場合は、コンストラクタに組み込む必要がある。 サンプルコード // 非同期処理を行うRunnableオブジェクトを定義するクラス private inner class WeatherInfoBackgroundReceiver(handler: Handler, url: String): Runnable { // クラス内で扱うHandlerオブジェクト(=Handlerプロパティ) // -> クラス内での書き換えが発生しないよう、スレッドセーフ(読み込み専用のval)で定義 private val _handler = handler ... // スレッドプールによって自動的に実行する処理 // -> ワーカースレッドで動作することを明示的に記述(@WorkerThreadアノテーション) @WorkerThread override fun run() { ... // 処理内容 // 非同期処理の終了後にUIスレッドで行う処理(Runnableオブジェクト) val postExecutor = WeatherInfoPostExecutor(result) // UIスレッドでの続行処理をUIスレッドHandlerのLooperに送信 _handler.post(postExecutor) } } MainActivity.kt // ワーカースレッドでの処理後に、 // UIスレッドで動作するRunnableオブジェクトを定義するクラス private inner class WeatherInfoPostExecutor(result: String) : Runnable { ... // Handlerによって自動的に実行される処理 // -> UIスレッドで動作することを明示的に記述(@UiThreadアノテーション) @UiThread override fun run() { ... // 処理内容 } } スレッドプールに処理を送信 定義 ExecutorService.submit(task: Runnable): Future<?> // パラメータ // task: スレッドプールに送信するRunnableオブジェクト サンプルコード MainActivity.kt // UIスレッドで最初に実行する処理 // -> UIスレッドで動作することを明示的に記述(@UiThread) @UiThread private fun receiveWeatherInfo(urlFull: String) { // スレッド数2のスレッドプールを管理するExecutorServiceプロパティ val executeService = Executors.newSingleThreadExecutor() // UIスレッドで動作する処理を管理するHandlerオブジェクトの生成 // mainLooper: アクティビティ(=UIスレッド)がもつLooperオブジェクト val handler = HandlerCompat.createAsync(mainLooper) // ワーカースレッドで処理するRunnableオブジェクト val backgroundReceiver = WeatherInfoBackgroundReceiver(handler, urlFull) // スレッドプールにRunnableオブジェクトを送信 // -> 非同期処理の開始 executeService.submit(backgroundReceiver) } AndroidのWeb連携 Android端末からインターネット上のデータベースにアクセスする場合、 DBと直接データのやり取りを行うサーバーサイドWebアプリ(=Web API)を通じてDBにアクセスする。 Web APIを利用したデータ取得 Web APIにアクセスするためには、APIを利用するためのAPIキーが必要となる。 また、Web APIの利用にあたってHttpURLConnectionクラスを用いてHTTP接続を行う必要がある。 HTTP接続を行う手順は、以下の通り。 AndroidManifest.xmlに、HTTP接続を許可するパーミッションタグを記述 接続先URLを定義したURLオブジェクトをHttpURLConnectionオブジェクトに変換 HTTP接続に関する設定を記述し、HTTP接続を実行 InputStream型のレスポンスデータをString型に変換 JSONデータに変換したレスポンスデータの解析(=JSON解析) HttpURLConnectionオブジェクトを解放 マニフェストファイルでHTTP接続を許可 参考1: マニフェストファイルの概要 参考2: ネットワークへの接続 参考3: 研修1日目 アプリの基本情報を記述するマニフェストファイルに、HTTP接続を許可するパーミッションを記述する。 サンプルコード AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest ...> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> ... </manifest> URLオブジェクトをHttpURLConnectionオブジェクトに変換 接続先URLの文字列から生成したURLオブジェクトを、HttpURLConnectionオブジェクトに変換する。 HttpURLConnection HTTP接続に関する設定値(=プロパティ)をもち、HTTP接続を実行するメソッドを定義するクラス。 定義 URL.openConnection(): URLConnection サンプルコード MainActivity.kt // String型 → URL型への変換 val url = URL("<URL文字列>") // URL型 → URLConnection型 → HttpURLConnection型への変換 val con = url.openConnection() as? HttpURLConnection HTTP接続設定の定義・実行 HTTP接続に関する設定 (Http)URLConnectionの主なプロパティ プロパティ 内容 connectTimeout HTTP接続がタイムアウトするまでの時間[ms] readTimeout データ取得がタイムアウトするまでの時間[ms] requestMethod HTTPリクエストのメソッド responseCode HTTPステータスコード HTTP接続の実行 HTTP接続がうまくできなかった場合は例外が発生するため、例外処理を用いて記述する必要がある。 定義 URLConnection.connect() // Throws: // SocketTimeoutException: タイムアウトに関する例外 // IOException: 接続エラーに関する例外 サンプルコード MainActivity.kt // URLオブジェクト val url = URL("<URL文字列>") // HttpURLConnectionオブジェクト val con = url.openConnection() as? HttpURLConnection // HttpURLConnectionオブジェクトがnullでないことを保証 // -> let関数ブロック内では、nullチェック対象(=con)がitで置換 con?.let { // 例外が発生する可能性があるブロック try { // -- HTTP接続設定の定義開始 -- // 接続がタイムアウトするまでの時間[ミリ秒] // -> タイムアウトした場合はSocketTimeoutExceptionが発生 it.connectTimeout = 1000 // データ取得がタイムアウトするまでの時間[ミリ秒] // -> タイムアウトした場合はSocketTimeoutExceptionが発生 it.readTimeout = 1000 // HTTP接続メソッドの指定 it.requestMethod = "GET" // -- HTTP接続設定の定義終了 -- // HTTP接続の実行 // ->場合によってSocketTimeoutExceptionまたはIOExceptionが発生 // -> 接続時、HttpURLConnectionオブジェクトのinputStreamプロパティに // InputStream型のレスポンスデータが自動的に格納 it.connect() } // 例外が発生した場合のエラー処理 catch (ex: SocketTimeoutException) { ... // Logcatにログを出力 } } InputStream型→String型の変換 InputStream型のレスポンスデータは、Byteデータであるため、InputStreamReaderやBufferedReaderを用いて、 文字データとして読み込んでからString型に変換する必要がある。 また、String型に変換した後はInputStreamReaderをラップしたBufferedReaderオブジェクトや、InputStreamオブジェクトを解放することでリソース効率を向上させる。 InputStream 読み込み専用のByteデータを抽象的に定義するクラス。 InputStreamReader InputStream型のByteデータを読み込み、様々な文字コードに対応しながら文字データに変換し、 変換した文字データをオブジェクトとして保持できるクラス。 BufferedReader バッファリングを行うことで読み込みの高速化を実現しながら、 読み込んだデータをオブジェクトとして保持できるクラス。 定義 // InputStreamオブジェクトの解放 InputStream.close() サンプルコード(InputStream→Stringへの変換) MainActivity.kt // InputStream型 → String型 への変換処理 private fun is2String(stream: InputStream?): String { // 1行分の文字データを格納するStringBuilderオブジェクト // StringBuilder: 文字列を格納する(同一ポインタ内での)可変配列を定義するクラス val sb = StringBuilder() // Byteデータ → 文字データ への変換 val reader = BufferedReader(InputStreamReader(stream, "UTF-8")) // 1行分の文字データ // -> 1行目の読み込み var line = reader.readLine() // 最終行までループさせる処理 while (line != null) { // 読み込んだ1行分の文字データをStringBuilderオブジェクトに格納 sb.append(line) // 次の行を読み込む line = reader.readLine() } // BufferedReaderオブジェクトの解放 reader.close() // 読み込んだ文字データ(StringBuilder型)をString型に変換 return sb.toString() } サンプルコード(InputStreamオブジェクトの解放) MainActivity.kt // HttpURLConnectionオブジェクトによるレスポンスデータの取得 val stream = <HttpURLConnectionオブジェクト>.inputStream // InputStream型のレスポンスデータをString型に変換 result = is2String(stream) // InputStreamオブジェクトの解放 stream.close() JSON解析によるデータ取得 HTTP接続時にHttpURLConnectionオブジェクトに格納されたByteデータ(=InputStream)の中身は、 JSON(JavaScript Object Notation)で記述されたJSONデータであるため、 JSON解析によって取得するデータを含むマップを抽出し、 最終的に、get<T>()メソッドを用いてデータを取得する。 JSONデータ キー(=name)と値を保持するマップが、データ記述言語であるJSONによって記述された、階層構造をもつデータ。 String型に変換したJSON文字列 // JSONObject: {...}で囲まれたキー付きのマップ { "<name>":{ "<name>":<value>, "<name>":<value>, }, // JSONArray: [...]で囲まれたキー付きのリスト "<name>":[ { "<name>":<value>, "<name>":<value>, }, ], ... "<name>":<value>, "<name>":<value> } 定義 /* いずれのメソッドも、条件に一致するJSONデータが見つからない場合、 JSONExceptionが発生 */ // JSONObjectの生成 val rootJSON = JSONObject(json: String): JSONObject // パラメータ // json: JSONによって記述された文字列 // JSONArrayの取得 rootJSON.getJSONArray(name: String): JSONArray // name: JSONArrayのキー // JSONObjectに含まれるJSONObjectの取得 rootJSON.getJSONObject(name: String): JSONObject // name: JSONObjectのキー // データの取得 rootJSON.get<T>(name: String): <T> // name: マップのキー WebAPIを利用したデータ格納 Web APIを通じてDBのデータ更新処理を行う場合は、 HttpURLConnectionオブジェクトがもつOutputStreamオブジェクト(=outputStreamプロパティ)を通じて、ByteArray型に変換したリクエストパラメータを送信する。 サンプルコード MainActivity.kt // URLオブジェクト val url = URL("<URL文字列>") // HttpURLConnectionオブジェクト val con = url.openConnection() as? HttpURLConnection // 送信するリクエストパラメータ文字列 val postData = "name=${name}&comment=${comment}" // HttpURLConnectionオブジェクトがnullでないことを保証 // -> let関数ブロック内では、nullチェック対象(=con)がitで置換 con?.let { // 例外が発生する可能性があるブロック try { // -- HTTP接続設定の定義開始 -- // 接続がタイムアウトするまでの時間[ミリ秒] // -> タイムアウトした場合はSocketTimeoutExceptionが発生 it.connectTimeout = 1000 // データ取得がタイムアウトするまでの時間[ミリ秒] // -> タイムアウトした場合はSocketTimeoutExceptionが発生 it.readTimeout = 1000 // HTTP接続メソッドの指定 it.requestMethod = "POST" // リクエストパラメータの出力を可能にする it.doOutput = true // -- HTTP接続設定の定義終了 -- // HTTP接続の実行 // ->場合によってSocketTimeoutExceptionまたはIOExceptionが発生 // -> 接続時、HttpURLConnectionオブジェクトのinputStreamプロパティに // InputStream型のレスポンスデータが自動的に格納 it.connect() // OutputStreamオブジェクト val outStream = con.outputStream // String型のリクエストパラメータをByteArray型に変換し、 // OutputStreamオブジェクトに書き込む // -> 出力エラー時はIOExceptionが発生 outStream.write(postData.toByteArray()) // バッファリングされたリクエストパラメータを // OutputStreamオブジェクトに強制的に書き込む // -> 基本的にはwrite()で書き込みが完了しているが、 // 万が一に備えてflush()メソッドを記述 // -> 出力エラー時はIOExceptionが発生 outStream.flush() // OutputStreamオブジェクトの解放 // -> 実行エラー時はIOExceptionが発生 outStream.close() } catch { ... // エラー処理を記述 } // HttpURLConnectionオブジェクトの解放 it.disconnect() }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Groupie 基本系からドラッグ&ドロップ、スワイプなど

こんにちはめっしーです(Twitter:https://twitter.com/messi_0601) 最近JetpackComposeが世間的には話題になってますが、あえてGroupieを触れたので一連の使い方まとめました。 Groupieとは 複雑なRecyclerViewレイアウト用のシンプルで柔軟なライブラリ 導入 gradle implementation "com.github.lisawray.groupie:groupie:$groupie_version" implementation "com.github.lisawray.groupie:groupie-databinding:$groupie_version" //databindingとviewbindingを両方利用している場合は以下を使う implementation "com.github.lisawray.groupie:groupie-viewbinding:$groupie_version" allprojects { repositories { maven { url "https://jitpack.io" } } } xml activityかfragmentのxmlにRecyclerView追加 <androidx.recyclerview.widget.RecyclerView android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> 次にitemに表示させたいviewの作成 今回はtextのみを表示する <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="itemLabel" type="String" /> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="100dp"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{itemLabel}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout> Itemクラス viewbindingを利用の際にはinitializeViewBinding必要 class Item(private val itemLabel: String) : BindableItem<HogeBinding>() { override fun bind(viewBinding: ModelHogeItemBinding, position: Int) { viewBinding.itemLabel = itemLabel } override fun getLayout(): Int = R.layout.model_hoge_item //viewbindingの際には必要 override fun initializeViewBinding(view: View): ModelHogeItemBinding { return ModelHogeItemBinding.bind(view) } } activity/fragment databinding想定 val adapter = GroupieAdapter() with(binding) { recyclerView.adapter = adapter recyclerView.layoutManager = LinearLayoutManager(context) } adapterに対してdateの渡し方 addを使う Itemの1つ1つ入れていく形 livedateを使用してる。 viewModel.itemList.observe(viewLifecycleOwner) { for (item in it) { adapter.add(Item(item)) } } updateを使う updateにはItemクラスのcollectionが入る val hogeList = listOf<Group>( Item("test1"), Item("test2"), Item("test3") ) adapter.update(hogeList) click処理について Item自体のクリック OnItemClickListenerを継承でつける class GroupieCarouselItem( private val colorInt: Int, private val adapter: GroupieAdapter ) : BindableItem<ModelGroupieCarouselItemBinding>(), OnItemClickListener { //bindとgetLayout省略してますが必要です。 //以下2つを追加 init { adapter.setOnItemClickListener(this) } override fun onItemClick(item: Item<*>, view: View) { //ここにクリックした際の処理をかく } } アイテムの中にあるviewでクリック処理 Itemクラスのbindで定義する。 interfaceでListenerを作り、bindで指定する。 ここではItem内にinterfaceを作成した。 class GroupieItem( private val playgroundModel: GroupiePlaygroundModel, private val listener: Listener ) : BindableItem<ModelGroupieItemBinding>() { interface Listener { fun onItemClick() } override fun bind(viewBinding: ModelGroupieItemBinding, position: Int) { viewBinding.toGroupieView.setOnClickListener { listener.onItemClick() } } } fragmentにGroupieItem.Listenerを継承で渡す。 class GroupieFragment : Fragment(), GroupieItem.Listener { override fun onItemClick() { //クリック処理をかく } } データの削除 adapter.remove(item) 様々な種類のGroupie Groupieの種類を紹介していきます。 HeaderとBodyアイテムがあるタイプ val basicHeaderItem = listOf("フルーツ","動物") val basicFruitsItems = listOf("りんご","みかん","ばなな","メロン","スイカ","ぶどう","マスカット") val basicAnimalItems = listOf("ぞう","キリン","ペンギン","ライオン") private val _itemList = MutableLiveData<List<Group>>() val itemList: LiveData<List<Group>> = _itemList fun fetchData() { val list = mutableListOf<Group>() val bodyList = listOf( groupieSample.basicFruitsItems, groupieSample.basicAnimalItems ) //ここでlistに表示したい順に入れていく for ((index, title) in groupieSample.basicHeaderItem.withIndex()) { list += HeaderItem(title) for (item in bodyList[index]) { list += BodyItem(item) } } _itemList.value = list } Carousel layoutManagerをLinearLayoutManager.HORIZONTALに変える binding.recyclerView.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) スワイプ削除とドラッグ移動 Itemクラス スワイプ動作 スワイプ方向を返すgetSwipeDirs()を追加 ドラッグ移動 ドラッグ方向を返すgetDragDirs()を追加 class GroupieSwipeItem() : BindableItem<ModelGroupieSwipeItemBinding>() { override fun bind(viewBinding: ModelGroupieSwipeItemBinding, position: Int) {} override fun getLayout(): Int = R.layout.model_groupie_swipe_item //追加 override fun getSwipeDirs(): Int = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT override fun getDragDirs(): Int = ItemTouchHelper.UP or ItemTouchHelper.DOWN } fragment/activity override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) //... //追加:recyclerViewでドラッグ&ドロップ移動やスワイプ動作をする際に加える ItemTouchHelper(itemTouchHelper).attachToRecyclerView(binding.recyclerView) } private val itemTouchHelper: TouchCallback by lazy { object : TouchCallback() { override fun onMove( recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder ): Boolean { val fromPosition = viewHolder.adapterPosition val toPosition = target.adapterPosition binding.recyclerView.adapter?.notifyItemMoved(fromPosition, toPosition) return false } override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { val item = adapter.getItem(viewHolder.adapterPosition) adapter.remove(item) } } } TouchCallbackについて ドラッグとスワイプの方向で作成できるデフォルトのコールバックの単純なラッパー。 このクラスはフラグのコールバックを処理します。ユースケースに応じて、onMoveまたはonSwipedをオーバーライドする必要があります。 https://developer.android.com/reference/androidx/recyclerview/widget/ItemTouchHelper.SimpleCallback?hl=ja onMove override fun onMove( recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder ): Boolean { //adapterPositionでアイテムのポジションをIntで取ってる val fromPosition = viewHolder.adapterPosition val toPosition = target.adapterPosition binding.recyclerView.adapter?.notifyItemMoved(fromPosition, toPosition) return false } ドラッグアンドドロップが完了した際に呼び出されるメソッド 引数 説明 recyclerView ドラッグアンドドロップが発生したRecyclerViewのオブジェクト。 viewHolder 開始時のオブジェクト。開始時のポジション情報を保持している。 target 完了時のオブジェクト。完了時のポジション情報を保持している。 最後に返り値として正常にドラッグアンドドロップが終了した際にtrueかfalseを返す 以下ドキュメント /** * @return True if the {@code viewHolder} has been moved to the adapter position of * {@code target}. * @see #onMoved(RecyclerView, ViewHolder, int, ViewHolder, int, int, int) */ 上記のに書いてあるようにtureの場合onMovedメソッドが呼び出される 呼び出す必要がない場合はfalseで問題ない。 onSwiped override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { val item = adapter.getItem(viewHolder.adapterPosition) //スワイプでアイテムを消してる adapter.remove(item) } 引数 説明 viewHolder スワイプされたViewHolderのオブジェクト direction viewHolderがスワイプされる方向をとる サンプル こちらは自分が行ってきた技術サンプルが詰まってます。 その中のGroupieフォルダーに今回のコードが書かれてあります。ぜひご覧ください。 参考 おわり ではまた次回の記事で Twitterフォローも是非よろしくお願いします! https://twitter.com/messi_0601
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AndroidStudio リストデータが「固定」と「不変」の違い

リストデータが固定の場合 リストデータとして、strings.xmlに記述した文字列リストを使用する。 サンプルコード strings.xml <resources> <string name="app_name">リスト選択サンプル</string> <string-array name="lv_menu"> <item>唐揚げ定食</item> <item>ハンバーグ定食</item> <item>生姜焼き定食</item> <item>ステーキ定食</item> <item>野菜炒め定食</item> <item>とんかつ定食</item> <item>ミンチかつ定食</item> <item>コロッケ定食</item> <item>回鍋肉定食</item> <item>麻婆豆腐定食</item> <item>青椒肉絲定食</item> <item>焼き魚定食</item> <item>焼肉定食</item> </string-array> </resources> Activity_main.xml <?xml version="1.0" encoding="utf-8"?> <ListView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/lvMenu" android:layout_width="match_parent" android:layout_height="match_parent" android:entries="@array/lv_menu"/> 実行すると、以下の画面になる。 リストデータが可変の場合 Kotlinで記述する必要がある。 まずはサンプルコードを記載します。 サンプルコード activity_main.xml <?xml version="1.0" encoding="utf-8"?> <ListView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/lvMenu" android:layout_width="match_parent" android:layout_height="match_parent"/> MainActivity.kt package com.websarva.wings.android.listviewsample2 import android.os.Bundle import android.widget.ArrayAdapter import android.widget.ListView import androidx.appcompat.app.AppCompatActivity class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) //ListViewオブジェクトを取得 val lvMenu = findViewById<ListView>(R.id.lvMenu) //リストビューに表示するリストデータを作成 var menuList = mutableListOf("からあげ定食","ハンバーグ定食","生姜焼き定食","ステーキ定食", "野菜炒め定食","とんかつ定食","ミンチかつ定食","チキンかつ定食","コロッケ定食","回鍋肉定食", "麻婆豆腐定食","青椒肉絲定食","焼き魚","焼肉定食") //アダプタオブジェクトを生成 val adapter = ArrayAdapter(this@MainActivity,android.R.layout.simple_list_item_1,menuList) //リストビューにアダプトオブジェクトを設定 lvMenu.adapter = adapter } } 上記のコードを実行すると、固定の場合と同じ画面になる。 解説 アクティビティ中でリストデータを生成する場合の手順 1,リストデータを用意する 2,上記リストデータをもとにアダプタオブジェクトを生成する 3,ListViewにアダプタオブジェクトをセットする 1,リストデータを用意する var menuList = mutableListOfが該当します。 2,上記リストデータをもとにアダプタオブジェクトを生成する val adapter = ArrayAdapterが該当します。 アダプタとは、リストビューに表示するリストデータを管理し、リストビューの各行にそのリストデータを当てはめていく動きをするオブジェクトです。 イメージ画像 参照 アダプタオブジェクトの生成 アダプタオブジェクトを生成するには、adapterインターフェイスを実装したクラスを利用します。 主なadapterインターフェイス3種類 インターフェイス名 内容 ArrayAdapter 元データとしてArrayまたは、MutableListを利用 SimpleAdapter 元データとしてMutableList>を利用  XMLデータやJSONデータの解析結果を格納するのに便利 SimpleCursorAdapter 元データとしてCursorオブジェクトを利用 Cursorオブジェクトは、Android端末内のDBを利用する際、SELECT文の結果が格納されたもの 今回は、定食リストをMutableListで生成しているので、ArrayAdapterを使用している。 3,ListViewにアダプタオブジェクトをセットする lvMenu.adapter = adapterが該当する ListViewにアダプタオブジェクトをセットするには、ListViewのadapterプロパティを利用する。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AndroidStudio イベントリスナについて

基本的な用語 イベント ユーザが画面に対して行う操作 (タップ、スライド etc) MVCモデルでは、View に対して行う操作 イベントハンドラ イベントに対して行う操作(タップやスライドしたときに行う処理) MVCモデルでは、Controller に近い機能 リスナ イベントの検出 MVCモデルでは対応するところなし 参照 リスナ設定手順 1,それぞれのイベントに対応したリスナクラスを作成する。 2,リスナクラス内の所定のメソッドに処理を記述する。 3,リスナクラスのインスタンスを生成してリスナ設定メソッドの引数として渡す。 サンプルコード activity_main.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/tv_name"/> <EditText android:id="@+id/etName" android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="text"/> <Button android:id="@+id/btClick" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/bt_click"/> <Button android:id="@+id/btClear" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/bt_clear"/> <TextView android:id="@+id/tvOutput" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="25dp" android:text="" android:textSize="25sp"/> </LinearLayout> MainActiviity.kt package com.websarva.wings.android.hellosample import android.annotation.SuppressLint import android.os.Bundle import android.view.View import android.widget.Button import android.widget.EditText import android.widget.TextView import androidx.appcompat.app.AppCompatActivity class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) //表示ボタンであるButtonオブジェクトを取得。 val btClick = findViewById<Button>(R.id.btClick) //リスナクラスのインスタンスを生成 val listenr = HelloLisner() //表示ボタンにリスナを設定 btClick.setOnClickListener(listenr) //クリアボタンであるButtonオブジェクトを取得 val btClear = findViewById<Button>(R.id.btClear) //クリアボタンにリスナを設定。 btClear.setOnClickListener(listenr) } //ボタンをクリックした時のリスナクラス //リスナクラスはインターフェイスを実装する private inner class HelloLisner : View.OnClickListener { @SuppressLint("SetTextI18n") override fun onClick(view: View) { //名前入力欄であるEditTextオブジェクトを取得。 val input = findViewById<EditText>(R.id.etName) //メッセージを表示するTextViewオブジェクトを取得。 val output = findViewById<TextView>(R.id.tvOutput) //idのR値に応じて処理を分岐。 when(view.id) { //表示ボタンの場合... R.id.btClick -> { //入力された名前文字列を取得。 val inputStr = input.text.toString() //メッセージを表示。 output.text = inputStr + "さん、こんにちは!" } //クリアボタンの場合... R.id.btClear -> { //名前入力欄を空文字に設定。 input.setText("") //メッセージ入力欄を空文字に設定。 output.text ="" } } } } } オブジェクト式とラムダ式 Kotlinでは、オブジェクト式という記述方法がある。 サンプルコード Sample.kt btClick.setOnClickListener(object: View.onClickListener{ overrride fun onClick(view: View) { : } }) View.onClickListenerインターフェイスのように、メソッドを1つしか持たないインターフェイスの場合は、ラムダ式を使って以下のように書き換える事が出来る。 Sample.kt btClick.setOnClickListener{view: View -> : } ラムダ式を使用することで、コード数を減らすことが出来るが、可読性が下がってしまう。 参考 https://memodays.jp/241/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Android10以上でError inflating class androidx.appcompat.widget.FitWindowsLinearLayoutと出てクラッシュする対策

こんにちは。もぐめっとです。 本日は表題のようなエラーが出てActivityが立ち上がらずcrashしていたのでその対策について記載します。 どんなエラー? こんなエラーが発生していました。 Caused by java.lang.NullPointerException Attempt to invoke virtual method 'java.lang.Object java.lang.reflect.Field.get(java.lang.Object)' on a null object reference uk.co.chrisjenx.calligraphy.ReflectionUtils.getValue (ReflectionUtils.java:29) uk.co.chrisjenx.calligraphy.CalligraphyLayoutInflater.createCustomViewInternal (CalligraphyLayoutInflater.java:203) uk.co.chrisjenx.calligraphy.CalligraphyLayoutInflater.access$000 (CalligraphyLayoutInflater.java:20) uk.co.chrisjenx.calligraphy.CalligraphyLayoutInflater$PrivateWrapperFactory2.onCreateView (CalligraphyLayoutInflater.java:302) android.view.LayoutInflater$FactoryMerger.onCreateView (LayoutInflater.java:239) android.view.LayoutInflater.inflate (LayoutInflater.java:659) uk.co.chrisjenx.calligraphy.CalligraphyLayoutInflater.inflate (CalligraphyLayoutInflater.java:60) android.view.LayoutInflater.inflate (LayoutInflater.java:534) androidx.appcompat.app.AppCompatActivity.setContentView (AppCompatActivity.java:195) ... 具体的な原因はこのエラーの模様 Caused by android.view.InflateException Binary XML file line #23 in com.hoge:layout/abc_screen_simple: Error inflating class androidx.appcompat.widget.FitWindowsLinearLayout 最近アプリをアップデートする際に一気にライブラリなどもアップグレードしたのですが、それをえいやとリリースしたらAndroid10以上で発生していました(自分の端末はAndroid8だったため気づかなかった) 対策 ぐぐったら下記に答えがありました。 More specifically, both Calligraphy and ViewPump need to be updated: implementation 'io.github.inflationx:calligraphy3:3.1.1' implementation 'io.github.inflationx:viewpump:2.0.3' calligraphyをアップデートしろってことですね これにアップデートすると書き方も新しく書き直さないと行けないです。 Install Activity.kt @Override public void onCreate() { super.onCreate(); ViewPump.init(ViewPump.builder() .addInterceptor(new CalligraphyInterceptor( new CalligraphyConfig.Builder() .setDefaultFontPath("fonts/Roboto-RobotoRegular.ttf") .setFontAttrId(R.attr.fontPath) .build())) .build()); //.... } Inject Activity.kt @Override protected void attachBaseContext(Context newBase) { super.attachBaseContext(ViewPumpContextWrapper.wrap(newBase)); } これで無事に治ります。 再発防止策 ではどうすれば起きてなかったというところを考えると、FirebaseのTest Labを使ったり、Play Consoleのリリース前レポートをちゃんと確認していれば早期に発見できていたかもしれません。 あとは、10%リリースなどちょっとずつリリースすることによってユーザへの被害も最小限に抑えられたかもしれませんね。 まとめ ライブラリをアップデートした時は慎重にリリースしよう! 最後に、ワンナイト人狼オンラインというゲームを作ってます!よかったら遊んでね! 他にもCameconやOffchaといったサービスも作ってるのでよかったら使ってね! また、チームビルディングや技術顧問、Firebaseの設計やアドバイスといったお話も受け付けてますので御用の方は弊社までお問い合わせください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む