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

ActivityResultContractsを使ったパーミッションリクエストを改善したい

activity:1.2.0、fragment:1.3.0からstartActivityResultやrequestPermissionがdeprecatedになりましたね。代替手段としてActivityResultContractsを使うようにしましょう。ただ、パーミッションのリクエスト方法にちょっと引っかかりがあって改善したいなと思いました。 ActivityResultContractsを使ったパーミッションリクエスト ActivityResultContractsを使ったパーミッションリクエストの方法をおさらいしましょう。 ActivityやFragmentのonCreateの前に順序が保証される形でregisterForActivityResultを呼び出し、ActivityResultLauncherを取得します。 パーミッション一つだけの場合はRequestPermissionを使いコールバックを登録します。 private val launcher = registerForActivityResult(RequestPermission()) { // 結果の処理 } リクエストではlaunchに引数としてパーミッションを指定します。 launcher.launch(Manifest.permission.CAMERA) これで結果が、registerForActivityResultで指定したコールバックが呼び出されます。パーミッションは一つだけなので、獲得できた場合true、そうでなければfalseが引数として渡されます。 複数のパーミッションを同時にリクエストしたい場合は、RequestMultiplePermissionsを使いコールバックを登録します。 private val multipleLauncher = registerForActivityResult(RequestMultiplePermissions()) { // 結果の処理 } リクエストはlaunchに引数としてパーミッションの配列を指定します。 multipleLauncher.launch(arrayOf( Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION )) これで結果が、registerForActivityResultで指定したコールバックが呼び出されます。パーミッション名をキーとして、獲得できたかどうかのBooleanが値として入ったMapが引数として渡されます。 リクエストコードの管理をする必要がなく、ActivityやFragmentのメソッドのオーバーライドが不要と、従来のリクエスト方法よりシンプルで扱いやすくなっています。 改善点 ちょっと引っかかる部分というのが、コールバックを登録してActivityResultLauncherを取得した時点で、対象となるパーミッションが決まっていないというところですね。 registerForActivityResultをコールしている箇所と、launchをコールしている箇所は分かれていて、launchでパーミッションを指定するため、コールバックが想定していないパーミッションをリクエストできてしまう自由度が残ってしまっています。 ようするに、以下のようにregisterForActivityResultをコールした時点で対象とするパーミッションを固定できるようになっているべきではないかと言うことです。 private val launcher = registerForActivityResult( RequestPermissionContracts(Manifest.permission.CAMERA) ) { // 結果の処理 } private val launcher= registerForActivityResult( RequestMultiplePermissionsContracts( Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION ) ) { // 結果の処理 } launchでは渡すべき引数がないのでUnitにしておきましょうか。 launcher.launch(Unit) 改善クラス RequestPermission, RequestMultiplePermissionsはfinalクラスなので拡張できません。ActivityResultContractのサブクラスとして作ります。ただし、ComponantActivityのActivityResultRegistryの実装でRequestMultiplePermissionsが直接使用されているため、この定数と同じ値を使うなど、内部実装に依存した実装をする必要があります。 単一パーミッション用 RequestPermissionContracts.kt class RequestPermissionContracts( private val permission: String ) : ActivityResultContract<Unit, Boolean>() { override fun createIntent(context: Context, input: Unit?): Intent = Intent(ACTION_REQUEST_PERMISSIONS).putExtra(EXTRA_PERMISSIONS, arrayOf(permission)) override fun parseResult(resultCode: Int, intent: Intent?): Boolean { if (resultCode != AppCompatActivity.RESULT_OK) return false return intent ?.getIntArrayExtra(EXTRA_PERMISSION_GRANT_RESULTS) ?.getOrNull(0) == PackageManager.PERMISSION_GRANTED } override fun getSynchronousResult( context: Context, input: Unit? ): SynchronousResult<Boolean>? = if (permission.isGranted(context)) { SynchronousResult(true) } else { null } companion object { private const val ACTION_REQUEST_PERMISSIONS = RequestMultiplePermissions.ACTION_REQUEST_PERMISSIONS private const val EXTRA_PERMISSIONS = RequestMultiplePermissions.EXTRA_PERMISSIONS private const val EXTRA_PERMISSION_GRANT_RESULTS = RequestMultiplePermissions.EXTRA_PERMISSION_GRANT_RESULTS private fun String.isGranted(context: Context): Boolean = ContextCompat.checkSelfPermission(context, this) == PackageManager.PERMISSION_GRANTED } } 複数パーミッション用 RequestMultiplePermissionsContracts.kt class RequestMultiplePermissionsContracts( private vararg val permissions: String ) : ActivityResultContract<Unit, Map<String, Boolean>>() { override fun createIntent(context: Context, input: Unit?): Intent = Intent(ACTION_REQUEST_PERMISSIONS).putExtra(EXTRA_PERMISSIONS, permissions) override fun parseResult(resultCode: Int, intent: Intent?): Map<String, Boolean> { if (resultCode != Activity.RESULT_OK) return emptyMap() if (intent == null) return emptyMap() val permissions = intent.getStringArrayExtra(EXTRA_PERMISSIONS) val grantResults = intent.getIntArrayExtra(EXTRA_PERMISSION_GRANT_RESULTS) if (grantResults == null || permissions == null) return emptyMap() return permissions.indices.map { permissions[it] to (grantResults[it] == PackageManager.PERMISSION_GRANTED) }.toMap() } override fun getSynchronousResult( context: Context, input: Unit? ): SynchronousResult<Map<String, Boolean>>? { if (permissions.isEmpty()) return SynchronousResult(emptyMap()) var allGranted = true val grantState = permissions.map { val granted = it.isGranted(context) if (!granted) allGranted = false it to granted } if (allGranted) return SynchronousResult(grantState.toMap()) return null } companion object { private const val ACTION_REQUEST_PERMISSIONS = RequestMultiplePermissions.ACTION_REQUEST_PERMISSIONS private const val EXTRA_PERMISSIONS = RequestMultiplePermissions.EXTRA_PERMISSIONS private const val EXTRA_PERMISSION_GRANT_RESULTS = RequestMultiplePermissions.EXTRA_PERMISSION_GRANT_RESULTS private fun String.isGranted(context: Context): Boolean = ContextCompat.checkSelfPermission(context, this) == PackageManager.PERMISSION_GRANTED } } 使い方は前述のように、registerForActivityResultの第一引数として使い、対象のパーミッションをコンストラクタで指定します。 private val launcher = registerForActivityResult( RequestPermissionContracts(Manifest.permission.CAMERA) ) { // 結果の処理 } private val launcher= registerForActivityResult( RequestMultiplePermissionsContracts( Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION ) ) { // 結果の処理 } これで、コールバックとその処理するパーミッションを固定したActivityResultLauncherを取得することができます。 以上です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EditTextに入力された値を一回で全て取得できるようにする

前提条件 SDKバージョン: 30 Androidバージョン: 9.0 一般的なEditTextに入力された値を取得する方法 おそらく皆さんこんな感じかと思います。 var name = binding.editTextName.text.toString() var height = binding.editTextHeight.text.toString() var weight = binding.editTextWeight.text.toString() 私の場合はViewBindingを使用しているのでこんな感じでよく書きますが、findViewById()やKotlin extensionを利用していたとしてもさほどやり方は変わらないかと思います。要するに、取得したいeditTextのidを一つ一つ取得して、そのeditTextに入力された文字を受け取り、String型に直すという流れですね。「Android editText 値 取得」などで検索するといくらでも出てきます。 さて、入力されるeditTextが10個になったらこの作業を10回するのでしょうか。100個になったら100回するのでしょうか。一気に取得したくないですか? EditTextに入力された値を一回で全て取得する まず今回実現したいことは、画面に入力された3つのeditTextを一気に取得してcsvファイルに書き込むぞ!という処理です! 画面はこんな感じです。 <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/background_color" tools:context=".InputDataActivity"> <LinearLayout android:id="@+id/container_for_toolbar" android:layout_width="match_parent" android:layout_height="120dp" android:orientation="vertical" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"></LinearLayout> <LinearLayout android:id="@+id/container_for_edit_text" android:layout_width="400dp" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> <EditText android:id="@+id/edit_text_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" android:hint="名前" android:inputType="textPersonName" android:textSize="24sp" /> <EditText android:id="@+id/edit_text_height" android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" android:hint="身長" android:inputType="textPersonName" android:textSize="24sp" /> <EditText android:id="@+id/edit_text_weight" android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" android:hint="体重" android:inputType="textPersonName" android:textSize="24sp" /> <Button android:id="@+id/btn_send" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="8dp" android:paddingBottom="8dp" android:text="送信する" android:textColor="#393e46" android:textSize="24sp" android:textStyle="bold" app:backgroundTint="@color/light_green" app:rippleColor="@color/dark_green" /> </LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout> 入力して、送信ボタンを押すと、入力された値が一気に取得されてCSVファイルに書き込まれます。 以下のように書くと実現できます override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // bindingの初期化とsetContentViewを行う binding = ActivityInputDataBinding.inflate(layoutInflater) .apply { setContentView(this.root) } // 送信ボタンをクリック binding.btnSend.setOnClickListener { // 引数にeditTextを格納しているコンテナのidを入れる val patientsDataList = getAllInputData(binding.containerForEditText) createFile(patientsDataList) } } // ===== editTextに入力された値を全て取得する ===== private fun getAllInputData(group: ViewGroup): MutableList<String> { val count = group.childCount var editDataList = mutableListOf<String>() for (i in 0 until count) { val view = group.getChildAt(i) if (view is EditText) { Log.d("TAG", view.text.toString()) editDataList.add(i, view.text.toString()) } } return editDataList } // ===== ファイルを作成して書き込み ===== private fun createFile(patientsDataList: MutableList<String>) { // 出力ファイルの作成 val file = File(applicationContext.filesDir, fileName) val fw = FileWriter(file, false) val pw = PrintWriter(BufferedWriter(fw)) // ヘッダーの指定 pw.print("名前") pw.print(",") pw.print("身長") pw.print(",") pw.print("体重") pw.println() // データを書き込む val listSize = patientsDataList.size for (i in 0 until listSize) { if(i == listSize-1) { pw.print(patientsDataList[listSize - 1]) pw.println() } else { pw.print(patientsDataList[i]) pw.print(",") } } // ファイルを閉じる pw.close() } 上記のように書くと、getAllInputData()メソッドにてeditTextに入力された値が一気に取得されて、Listに入ります。Listに入ったデータはcreateFile()メソッドに渡されて、csvファイルに書き込みが行われ、内部ストレージに保存されます。このようにしてeditTextに入力された文字を一気に取得し、メソッドに一気に渡して処理してあげることができます。 getAllInputData()での処理ですが、これは単純にeditTextをfor文で回して一つ一つ取得しているようなイメージです。試しにLog.d("TAG", view.text.toString())の部分にブレークポイントを貼って、LogcatでTAGと検索し、ログを逐一確認してみましょう。入力された値が一つ一つ取得されているのが確認できます。 比較 もし今回のこの処理ですが、getAllInputData()を作成せずに書いたらどうなるのでしょうか override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // bindingの初期化とsetContentViewを行う binding = ActivityInputDataBinding.inflate(layoutInflater) .apply { setContentView(this.root) } // 送信ボタンをクリック binding.btnSend.setOnClickListener { // この部分が増える var name = binding.editTextName.text.toString() var height = binding.editTextHeight.text.toString() var weight = binding.editTextWeight.text.toString() val patientsDataList = mutableListOf(name, height, weight) createFile(patientsDataList) } } // ===== ファイルを作成して書き込み ===== private fun createFile(patientsDataList: MutableList<String>) { // 出力ファイルの作成 val file = File(applicationContext.filesDir, fileName) val fw = FileWriter(file, false) val pw = PrintWriter(BufferedWriter(fw)) // ヘッダーの指定 pw.print("名前") pw.print(",") pw.print("身長") pw.print(",") pw.print("体重") pw.println() // データを書き込む val listSize = patientsDataList.size for (i in 0 until listSize) { if(i == listSize-1) { pw.print(patientsDataList[listSize - 1]) pw.println() } else { pw.print(patientsDataList[i]) pw.print(",") } } // ファイルを閉じる pw.close() } どうでしょうか。増えるのはこの4行です // この部分が増える var name = binding.editTextName.text.toString() var height = binding.editTextHeight.text.toString() var weight = binding.editTextWeight.text.toString() val patientsDataList = mutableListOf(name, height, weight) この4行が増えましたが、代わりにprivate fun getAllInputData(...)の処理が消えたので一見こっちの方が処理があっさりして見えますね。でも、今回は取得するeditTextがたったの3つでしたのでスッキリ見えますがこれが100個になったらどうでしょう。100個分取得しないとダメです。 // この部分が増える var name = binding.editTextName.text.toString() var height = binding.editTextHeight.text.toString() var weight = binding.editTextWeight.text.toString() val patientsDataList = mutableListOf(name, height, weight) つまりこのコードが100行になります。 一方getAllInputData()を使用する場合はeditTextがいくら増えようがcontainer_for_edit_textのコンテナにeditTextが入っている限りは一回で取得できます。つまり途中でeditTextの数を増やしてもコードを変更する必要はないのです。 追記 コメントでのご指摘を受けてeditTextに入力された値を一気に取得するコードを以下のように修正しました。 // ===== editTextに入力された値を全て取得する ===== private fun getAllInputData(group: ViewGroup): MutableList<String> { var editDataList = mutableListOf<String>() group.children.forEachIndexed { i, view -> if (view is EditText) { Log.d("TAG", view.text.toString()) editDataList.add(i, view.text.toString()) } } return editDataList }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EditTextに入力された値を一括で取得できるようにする

前提条件 SDKバージョン: 30 Androidバージョン: 9.0 一般的なEditTextに入力された値を取得する方法 おそらく皆さんこんな感じかと思います。 var name = binding.editTextName.text.toString() var height = binding.editTextHeight.text.toString() var weight = binding.editTextWeight.text.toString() 私の場合はViewBindingを使用しているのでこんな感じでよく書きますが、findViewById()やKotlin extensionを利用していたとしてもさほどやり方は変わらないかと思います。要するに、取得したいeditTextのidを一つ一つ取得して、そのeditTextに入力された文字を受け取り、String型に直すという流れですね。「Android editText 値 取得」などで検索するといくらでも出てきます。 さて、入力されるeditTextが10個になったらこの作業を10回するのでしょうか。100個になったら100回するのでしょうか。一気に取得したくないですか? EditTextに入力された値を一回で全て取得する まず今回実現したいことは、画面に入力された3つのeditTextを一気に取得してcsvファイルに書き込むぞ!という処理です! 画面はこんな感じです。 <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/background_color" tools:context=".InputDataActivity"> <LinearLayout android:id="@+id/container_for_toolbar" android:layout_width="match_parent" android:layout_height="120dp" android:orientation="vertical" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"></LinearLayout> <LinearLayout android:id="@+id/container_for_edit_text" android:layout_width="400dp" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> <EditText android:id="@+id/edit_text_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" android:hint="名前" android:inputType="textPersonName" android:textSize="24sp" /> <EditText android:id="@+id/edit_text_height" android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" android:hint="身長" android:inputType="textPersonName" android:textSize="24sp" /> <EditText android:id="@+id/edit_text_weight" android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" android:hint="体重" android:inputType="textPersonName" android:textSize="24sp" /> <Button android:id="@+id/btn_send" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="8dp" android:paddingBottom="8dp" android:text="送信する" android:textColor="#393e46" android:textSize="24sp" android:textStyle="bold" app:backgroundTint="@color/light_green" app:rippleColor="@color/dark_green" /> </LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout> 入力して、送信ボタンを押すと、入力された値が一気に取得されてCSVファイルに書き込まれます。 以下のように書くと実現できます override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // bindingの初期化とsetContentViewを行う binding = ActivityInputDataBinding.inflate(layoutInflater) .apply { setContentView(this.root) } // 送信ボタンをクリック binding.btnSend.setOnClickListener { // 引数にeditTextを格納しているコンテナのidを入れる val patientsDataList = getAllInputData(binding.containerForEditText) createFile(patientsDataList) } } // ===== editTextに入力された値を全て取得する ===== private fun getAllInputData(group: ViewGroup): MutableList<String> { val count = group.childCount var editDataList = mutableListOf<String>() for (i in 0 until count) { val view = group.getChildAt(i) if (view is EditText) { Log.d("TAG", view.text.toString()) editDataList.add(i, view.text.toString()) } } return editDataList } // ===== ファイルを作成して書き込み ===== private fun createFile(patientsDataList: MutableList<String>) { // 出力ファイルの作成 val file = File(applicationContext.filesDir, fileName) val fw = FileWriter(file, false) val pw = PrintWriter(BufferedWriter(fw)) // ヘッダーの指定 pw.print("名前") pw.print(",") pw.print("身長") pw.print(",") pw.print("体重") pw.println() // データを書き込む val listSize = patientsDataList.size for (i in 0 until listSize) { if(i == listSize-1) { pw.print(patientsDataList[listSize - 1]) pw.println() } else { pw.print(patientsDataList[i]) pw.print(",") } } // ファイルを閉じる pw.close() } 上記のように書くと、getAllInputData()メソッドにてeditTextに入力された値が一気に取得されて、Listに入ります。Listに入ったデータはcreateFile()メソッドに渡されて、csvファイルに書き込みが行われ、内部ストレージに保存されます。このようにしてeditTextに入力された文字を一気に取得し、メソッドに一気に渡して処理してあげることができます。 getAllInputData()での処理ですが、これは単純にeditTextをfor文で回して一つ一つ取得しているようなイメージです。試しにLog.d("TAG", view.text.toString())の部分にブレークポイントを貼って、LogcatでTAGと検索し、ログを逐一確認してみましょう。入力された値が一つ一つ取得されているのが確認できます。 比較 もし今回のこの処理ですが、getAllInputData()を作成せずに書いたらどうなるのでしょうか override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // bindingの初期化とsetContentViewを行う binding = ActivityInputDataBinding.inflate(layoutInflater) .apply { setContentView(this.root) } // 送信ボタンをクリック binding.btnSend.setOnClickListener { // この部分が増える var name = binding.editTextName.text.toString() var height = binding.editTextHeight.text.toString() var weight = binding.editTextWeight.text.toString() val patientsDataList = mutableListOf(name, height, weight) createFile(patientsDataList) } } // ===== ファイルを作成して書き込み ===== private fun createFile(patientsDataList: MutableList<String>) { // 出力ファイルの作成 val file = File(applicationContext.filesDir, fileName) val fw = FileWriter(file, false) val pw = PrintWriter(BufferedWriter(fw)) // ヘッダーの指定 pw.print("名前") pw.print(",") pw.print("身長") pw.print(",") pw.print("体重") pw.println() // データを書き込む val listSize = patientsDataList.size for (i in 0 until listSize) { if(i == listSize-1) { pw.print(patientsDataList[listSize - 1]) pw.println() } else { pw.print(patientsDataList[i]) pw.print(",") } } // ファイルを閉じる pw.close() } どうでしょうか。増えるのはこの4行です // この部分が増える var name = binding.editTextName.text.toString() var height = binding.editTextHeight.text.toString() var weight = binding.editTextWeight.text.toString() val patientsDataList = mutableListOf(name, height, weight) この4行が増えましたが、代わりにprivate fun getAllInputData(...)の処理が消えたので一見こっちの方が処理があっさりして見えますね。でも、今回は取得するeditTextがたったの3つでしたのでスッキリ見えますがこれが100個になったらどうでしょう。100個分取得しないとダメです。 // この部分が増える var name = binding.editTextName.text.toString() var height = binding.editTextHeight.text.toString() var weight = binding.editTextWeight.text.toString() val patientsDataList = mutableListOf(name, height, weight) つまりこのコードが100行になります。 一方getAllInputData()を使用する場合はeditTextがいくら増えようがcontainer_for_edit_textのコンテナにeditTextが入っている限りは一回で取得できます。つまり途中でeditTextの数を増やしてもコードを変更する必要はないのです。 追記 コメントでのご指摘を受けてeditTextに入力された値を一気に取得するコードを以下のように修正しました。 // ===== editTextに入力された値を全て取得する ===== private fun getAllInputData(group: ViewGroup): MutableList<String> { var editDataList = mutableListOf<String>() group.children.forEachIndexed { i, view -> if (view is EditText) { Log.d("TAG", view.text.toString()) editDataList.add(i, view.text.toString()) } } return editDataList }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity + Android In-App Review pluginが動かない件の対策

こんにちは。virapture株式会社のもぐめっとです。 本日はAndroidでアプリ内レビューを表示することができるIn-App Review APIを動かそうとしたら動かなかったのでその解決tipsの紹介です。 どういうときに動くか In-AppReviewはストアからダウンロードした形でないと動くことがありません。 また、他にも下記のような条件があったりします。 ユーザー アカウントが内部テストトラックに含まれている。 ユーザー アカウントがメイン アカウントであり、Play ストアでそのアカウントが選択されている。 ユーザー アカウントが Play ストアからアプリをダウンロードした(アプリがユーザーの Google Play ライブラリに表示されている)。 現在、ユーザー アカウントがアプリをレビューしていない。 cf: つまり簡単に言うとテストするときは内部テストでリリースしないとテストができません!なんてやりづらいのでしょう! また、他にもUSBデバッグをOFFにしないと動かないといったこともあるみたいです。 ちなみに上記記事ではIn-AppReviewを出す前に1クッションいれた質問をいれてますが、In-AppReviewを出す前に質問を出すのはちょっとグレーです。 評価ボタンや評価カードを表示する前または表示中に質問をしない(「アプリを気に入りましたか?」といったユーザーの意見に関する質問や、「このアプリを 5 つ星と評価していただけますか?」といった予断を与える質問)。 更に動かない原因 先程説明した条件を満たしているのにも関わらず動かざることAndroidの如しで全くIn-App Reviewが表示されませんでした。 Crashlyticsで試しにログを飛ばしてみたらこんなエラーを飛んでいることが確認できました。 Non-fatal Exception: java.lang.Exception AndroidJavaException : java.lang.ClassNotFoundException: com.google.android.play.core.review.ReviewManagerFactory com.unity3d.player.UnityPlayer.nativeRender なぜかClassが見つからないようです。 解決法 ということで上記エラーを調べたら答えがありました。 つまり、proguardに妨害されてるので下記を除外すればおkとのこと。 -keep class com.google.android.play.core.** { *; } ということで自分のUnityプロジェクトのandroidのproguard設定に追記してビルドしたところ無事動くことが確認できました! まとめ produgardに設定を追加すればおk!! めんどくさがらずちゃんとcrashlyticsでロガーを仕込むことは大事だなと思いました。 最後に、ワンナイト人狼オンラインというゲームを作ってます!よかったら遊んでね! 他にもCameconやOffchaといったサービスも作ってるのでよかったら使ってね! また、チームビルディングや技術顧問、Firebaseの設計やアドバイスといったお話も受け付けてますので御用の方は弊社までお問い合わせください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

カスタムツールバーにオプションメニューを設置する方法

前提条件 SDKバージョン: 30 Androidバージョン: 9.0 カスタムツールバーとは カスタムツールバーとはカスタムビューで作成したツールバーです。もっと噛み砕いていうと、Androidがデフォルトで用意しているツールバーはデザインが固定されていて使い勝手が悪いので自分でカスタムしたツールバーのことです。 詳しくはこちらを参考に カスタムビュー:ToolBar(ActionBar)を自作しよう! この記事は 話を戻しまして、カスタムツールバーにオプションメニューを設置したいことがあると思います。例えばこんな感じで、画面の上部にカスタムツールバーを配置します。 このツールバーの右側のイメージボタンをタップすると以下のようにオプションメニューを表示させたいとします。 そしてこのオプションメニューをクリックしたら別の画面に画面遷移するようにしたい。この一連の流れをどのように実現させるかを本記事で解説します。 menuを作る まず表示させたいオプションメニューを作成します。今回で言うと「検診を終了する」と言う内容のメニューの部分です。 1) menuディレクトリを作成 2) menuリソースファイル追加 3) メニュー作成 の三段構成でできます。 1) menuディレクトリを作成 まずこちらの記事を参考に、 File→New→Folderの中にAssets Folderがあるので、それを選択し、namaeをmenuにしてassetsフォルダを作成します。すると、resの下にmenuというassetsフォルダができるはずです。こんな感じ。 参考: Android StudioでAssetsフォルダを簡単に作成する方法について 2) menuリソースファイル追加 このmenu assetsの中にmenuファイルを作成します。 追加したmenuフォルダで右クリックしてNew→Menu Resource Fileを選択します ファイル名は今回はmain_menuとします。すると以下のようにmenuフォルダの中にmain_menuファイルが作成されているはずです。 3) メニュー作成 PaletteのMenuItemをドラッグアンドドロップでComponentTreeに追加します。オプションメニューに表示したいメニューの数だけ追加します。今回は「検診を終了する」以外のメニューは表示させない予定なのでMenuItemは一つだけComponentTreeに追加します。するとまぁこんな感じになります。 これで表示するメニューの作成は完了です。 メニューを表示させる カスタムツールバーを作成した時に右のボタンをクリックした時の処理をどうするかは、その画面で決定しなければいけないようにしているので、インタフェースをオーバーライドしてきて、その中で以下のように実装します。この辺の詳細はこちらの記事を読んでいただけるとよく理解できると思います。 参考: カスタムツールバーが各画面で柔軟にデザインを変更できるように設定する override fun onClickedRightButton() { var menu = findViewById<View>(R.id.btn_right) val popup = PopupMenu(this, menu) menuInflater.inflate(R.menu.main_menu, popup.getMenu()) popup.show() popup.setOnMenuItemClickListener { when(it.itemId) { R.id.menu_item_complete -> { // メニューがクリックされたらファイル名を保持したまま確認画面へ画面遷移 val intent = Intent(this, ConfirmActivity::class.java) intent.putExtra("FILE_NAME", fileName) startActivity(intent) } } false } } ポイントは、メニューは紐づけたビューの近くに表示されるので、クリックするボタンのidを以下のように指定すること。 var menu = findViewById<View>(R.id.btn_right) また、openOptionsMenu()ではなく、PopupMenu()という関数を利用すること。 val popup = PopupMenu(this, menu) 最後に、popup.setOnMenuItemClickListener{}を用いて、オプションメニューがクリックされたときの処理を書くことです。今回の場合は別のActivityに画面遷移をするようにしています。 popup.setOnMenuItemClickListener { when(it.itemId) { R.id.menu_item_complete -> { // メニューがクリックされたらファイル名を保持したまま確認画面へ画面遷移 val intent = Intent(this, ConfirmActivity::class.java) intent.putExtra("FILE_NAME", fileName) startActivity(intent) } } false } 完成! 実際にはこんな感じになります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Flutter と Firebaseで開発者一人でマッチングアプリを開発する

こんにちは!CROSSERの開発者です 2021年4月にAndroid、2021年5月にiOSを語学マッチングアプリ「CROSSER」をリリースしました。 ここではFlutterとFirebaseを用いた開発〜リリース〜運用で参考にした資料、つまづいた点などを共有したいと思います。 アプリ概要 語学学習目的で語学を教えてくれるパートナー探し 世界規模 性別、年齢、国籍、自分からの距離で相手を探せる 収益方法はアイテム内課金、アプリ内広告 iOS、Androidモバイル対応(iPadはこれから) ダウンロードはこちらからお願いします https://instabio.cc/crosser チーム構成 開発者1名で開発しました。 非開発者(1名) 開発者(1名) バックエンド開発経験4年 フロントエンドの開発経験無し 主なアプリ機能 ログイン(電話番号、Facebook、Apple、LINE、Kakao) ログインアカウントをG-mail連携 アプリ内課金(消費型、サブスクリプション型) メッセージ ユーザーブロック ユーザー通報 カードスワイプ(Tinder風) 位置情報検索 一般的なマッチングアプリと同じような機能だと思います。 どうしてFlutterとFirebaseだったか? 開発者が1名、かつ半年以内にiOS版、Android版を同時リリースということが目標でした。(結果的には1年かかりましたが。。) 半年以内にiOS版、Android版をネイティブアプリで開発するのは厳しいなと思い、Cross Platformで開発をすることを考えました。(平日は会社にいるので、開発はほぼ土日しかありませんでした) 最初はMonacaを使ってサーバー側はゴリゴリに構築・開発していました。 。。が!バックエンド開発しかしたことがない者としてバリバリcss, jsを使って開発することがストレスで。。。半年、ずっと戦っていました。。。 そんなある日、会社の先輩がFlutterいいよ!と話があったので、ちょっと触って見たところ、おぉお!これだ!と感動し、ほぼMonacaで完成していたアプリをGitから削除し、Flutterで開発することを決心しました (Javaを触ったことがある方にFlutterは持ってこいです!しかもPackageも沢山あるため、簡単に映えるUIを作れます!) その流れで、リリース後のメンテを現実的に考えてFirebaseを採用しました。(←最初から考えとけよ) Firebaseで使用している機能 どれをどの機能に使ったかは別記事で詳しく説明します Firebaseの各機能についてはこちらの記事が分かりやすいです! https://qiita.com/shibukk/items/4a015c5b3296563ac19d RealTime Database Cloud Firestore Firebase Hosting Analytics Firebase Authentication Cloud Storage for Firebase Firebase Cloud Messaging Firebase In-App Messaging Firebase Remote Config まとめ Flutter、Firebase最高 Google最高 今後はこのページに新規記事のリンクをつけて知見を共有していこうと思いますので、よろしくお願いします。 みなさん、是非、語学マッチングアプリCROSSERのダウンロードお願いします 評価もしていただけると嬉しいです!(高評価だともっと嬉しです!) 追記 関連する追加記事情報を載せます。 - FirebaseでLINE/Kakaoログインする方法https://qiita.com/crosser202008/items/ed240474439aca7f7cec Firebaseでプッシュ通知をする方法 https://qiita.com/crosser202008/items/4074177b69960c67bcf5
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む