- 投稿日:2020-10-20T22:49:53+09:00
【Flutter】flavorで環境ごとに、google-services.json や アプリID, アプリ名を切り替える
アプリID, アプリ名をflavorで分ける
android/app/build.gradle
配下のファイルにproductFlavors
を追加する。android/app/build.gradle... android { compileSdkVersion 29 sourceSets { main.java.srcDirs += 'src/main/kotlin' } lintOptions { disable 'InvalidPackage' } defaultConfig { ... } buildTypes { ... } flavorDimensions "app" productFlavors { dev { dimension "app" resValue "string", "app_name", "DEV_{アプリ名}" applicationId "{開発環境のID}.dev" } qa { dimension "app" resValue "string", "app_name", "{アプリ名}" applicationId "{QA環境のID}" } } } flutter { source '../..' } dependencies { ... }ちなみに、
android/app/src/flavor名/res/values/strings.xml
からアプリ名を変更する方法は上手く変更できませんでした。google-service.jsonを環境ごとに切り替える方法
android/app/src
配下にflavor名のフォルダを作成し、'google-service.json'の名前でconfigファイルを設定すると、flavorを指定してbuildした際にそのディレクトリを参照してくれます。そのため、以下のようにディレクトリを配置するだけで、flavorごとのfirebaseの設定ファイルの切り分けは完了です。
app └── src ├── main │ ├── AndroidManifest.xml │ ├── java │ ├── kotlin │ └── res ├── dev │ └── google-servicesjson.json ├── qa │ └── google-services.json └── prd └── google-services.json参考記事
- 投稿日:2020-10-20T20:05:43+09:00
#12 Kotlin Koans Introduction/Extensions on collections 解説
1 はじめに
Kotlin公式リファレンスのKotlin Koans/Extensions on collectionsの解説記事です。
Kotlin Koansを通してKotlinを学習される人の参考になれば幸いです。
ただし、リファレンスを自力で読む力を養いたい方は、
すぐにこの記事に目を通さないで下さい!一度各自で挑戦してから、お目通し頂ければと思います
2 sortedDescending()関数
sortedDescending()関数はkotlinスタンダードライブラリ内のkotlin.collectionsパッケージに含まれる拡張関数です。
3 Introduction/Extensions on collectionsの解説
Kotlin Koans Introduction/Extensions on collectionsの解説です。
随時本サイトの内容を引用させていただきます。右側の本文を見てみましょう。
Kotlin code can be easily mixed with Java code. Thus in Kotlin we don't introduce our own collections, but use standard Java ones (slightly improved). Read about read-only and mutable views on Java collections.
In Kotlin standard library there are lots of extension functions that make the work with collections more convenient. Rewrite the previous example once more using an extension function sortedDescending.
左側のコードを見てみましょう。
fun getList(): List<Int> { return arrayListOf(1, 5, 2)//TODO("return the list sorted in descending order") }拡張関数sortedDescending()を用いて、前前章と前章で行った要素を降順に並べるコードの実装を行います。
sortedDescending()は呼び出しもとの要素を降順にして戻り値として返すので、
fun getList(): List<Int> { return arrayListOf(1, 5, 2).sortedDescending() }4 最後に
次回はKotlin Koans Conventions/Comparisonの解説をします
- 投稿日:2020-10-20T19:32:18+09:00
Androidカメラのプレビュー表示(Camera2 API + SurfaceView)
Camera2 APIによるカメラのプレビュー表示について
Androidカメラのプレビュー表示(Camera API + SurfaceView)をお読みいただければ、Camera APIを使えば、カメラのプレビュー表示が簡単に実装できるとわかるでしょう。しかし、Camera2 APIを使う場合、実装がずっと複雑になります。特に正しい大きさとアスペクト比を表示する実装がかなり大変です。
今回はCamera2 API + SurfaceViewによるカメラのプレビュー表示の実装を紹介します。
実装
レイアウト
Androidカメラのプレビュー表示(Camera API + SurfaceView)に載せたレイアウトと同じです。
main_fragment.xml<?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"> <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/main_container" android:layout_width="match_parent" android:layout_height="match_parent" android:keepScreenOn="true"> <SurfaceView android:id="@+id/surface_view" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout>カメラのパーミッション
AndroidManifest.xml
Camera2 APIを使う場合もAndroidManifest.xmlにandroid.permission.CAMERAを追加する必要があります。
AndroidManifest.xml<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="xxx"> ... <uses-permission android:name="android.permission.CAMERA" /> ... </manifest>パーミッション要請
カメラ権限要請の部分もAndroidカメラのプレビュー表示(Camera API + SurfaceView)と同じです。
MyActivity.kt// カメラ権限があるかどうかを確認し、ある場合はカメラを起動し、ない場合はユーザーに要請します。 private fun checkAndAskCameraPermission(context: Context) { if (ActivityCompat.checkSelfPermission(context, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { // ユーザーにカメラ権限を要請 requestPermissions(arrayOf(Manifest.permission.CAMERA), PERMISSION_REQUEST_CODE) } else { // カメラ権限があるので、カメラを起動 openCamera() } } // カメラ権限要請のコールバック override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<String>, grantResults: IntArray ) { when (requestCode) { PERMISSION_REQUEST_CODE -> { if (permissions.isNotEmpty() && grantResults.isNotEmpty()) { val resultMap: MutableMap<String, Int> = mutableMapOf() for (i in 0..min(permissions.size - 1, grantResults.size - 1)) { resultMap[permissions[i]] = grantResults[i] } when { resultMap[Manifest.permission.CAMERA] == PackageManager.PERMISSION_GRANTED -> { // ユーザーがカメラ権限を許可したので、カメラを起動 openCamera() } } } } } }カメラ起動
- カメラIDを取得します。Camera2 APIではCameraManagerでカメラIDを取得します。また、カメラIDはIntではなく、Stringになります。
- [注意点]:SurfaceViewを使う場合、プレビューサイズを設定することができません(TextureViewならできます)。そこで、SurfaceViewの形を正方形にし、幅と高さをもっとも長い辺に合わせることで、アスペクト比が保たれたプレビューを実現します。
- カメラに接続します。接続したら、cameraDeviceStateCallbackコールバックが呼ばれます。
- カメラのプレビューのリクエストを作ります。
- カメラキャプチャーのコールバックを設定します。
- カメラのキャプチャーを開始します。
MyActivity.ktprivate var cameraManager: CameraManager? = null private var cameraDevice: CameraDevice? = null private var captureRequest: CaptureRequest? = null private var cameraCaptureSession: CameraCaptureSession? = null // カメラの接続状態のコールバック private val cameraDeviceStateCallback = object : CameraDevice.StateCallback() { override fun onOpened(device: CameraDevice) { cameraDevice = device cameraDevice?.let { cameraDevice -> // 出力先 val surfaceList: ArrayList<Surface> = arrayListOf() surfaceHolder?.let { surfaceList.add(it.surface) } try { // カメラのプレビューを出力するリクエストをここで作っておく。cameraCaptureSessionStateCallbackの中で使う captureRequest = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW).apply { surfaceList.forEach { addTarget(it) } }.build() // カメラキャプチャーのコールバックを設定 cameraDevice.createCaptureSession( surfaceList, cameraCaptureSessionStateCallback, null ) } catch (cameraAccessException: CameraAccessException) { cameraAccessException.printStackTrace() } } } override fun onDisconnected(device: CameraDevice) { cameraDevice = null } override fun onError(device: CameraDevice, error: Int) { cameraDevice = null } } // カメラキャプチャーのコールバック private val cameraCaptureSessionStateCallback = object : CameraCaptureSession.StateCallback() { override fun onActive(session: CameraCaptureSession) { super.onActive(session) // カメラキャプチャーセッションはリソース解放時に使うので、保存しておく。 cameraCaptureSession = session } override fun onClosed(session: CameraCaptureSession) { super.onClosed(session) } override fun onConfigured(cameraCaptureSession: CameraCaptureSession) { val captureRequest: CaptureRequest = captureRequest ?: return try { // カメラのキャプチャーを開始 cameraCaptureSession.setRepeatingRequest( captureRequest, null, null ) } catch (cameraAccessException: CameraAccessException) { cameraAccessException.printStackTrace() } } override fun onConfigureFailed(session: CameraCaptureSession) { } override fun onReady(session: CameraCaptureSession) { super.onReady(session) } override fun onSurfacePrepared(session: CameraCaptureSession, surface: Surface) { super.onSurfacePrepared(session, surface) } } // Camera2 APIではCameraManagerでカメラIDを取得する。 // また、カメラIDはIntではなく、Stringになる private fun getCameraId(): String? { val cameraManager = cameraManager ?: return null cameraManager.cameraIdList.firstOrNull { cameraId -> cameraManager.getCameraCharacteristics(cameraId).get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_BACK }?.let { cameraId -> return cameraId } return null } private fun openCamera() { val context = context ?: return val cameraManager: CameraManager = cameraManager ?: return val cameraId = getCameraId() ?: return if (ActivityCompat.checkSelfPermission( context, Manifest.permission.CAMERA ) != PackageManager.PERMISSION_GRANTED ) { return } try { // [注意点]:SurfaceViewを使う場合、プレビューサイズを設定することができない(TextureViewならできる)。 // そこで、SurfaceViewの形を正方形にし、幅と高さをもっとも長い辺に合わせることで、アスペクト比が保たれたプレビューを実現する。 val size = max(binding.surfaceView.width, binding.surfaceView.height) updateSurfaceSize(binding.surfaceView, size, size) // CameraManager.openCameraでカメラに接続する // カメラの接続状態はコールバックに渡される cameraManager.openCamera(cameraId, cameraDeviceStateCallback, null) } catch (cameraAccessException: CameraAccessException) { cameraAccessException.printStackTrace() } }SurfaceView
基本的にAndroidカメラのプレビュー表示(Camera API + SurfaceView)と同じです。
MyActivity.ktprivate lateinit var binding: MainFragmentBinding private var surfaceWidth = 1 private var surfaceHeight = 1 override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { // データバインディングを使ったほうが楽 binding = DataBindingUtil.inflate<MainFragmentBinding>( inflater, R.layout.main_fragment, container, false ) // SurfaceViewにコールバックを設定 binding.surfaceView.holder.addCallback(object : SurfaceHolder.Callback { override fun surfaceCreated(holder: SurfaceHolder) { surfaceHolder = holder // Surfaceの生成が終わってから、カメラ権限を確認し、カメラを起動します。 checkAndAskPermission() } override fun surfaceChanged( holder: SurfaceHolder, format: Int, width: Int, height: Int ) { surfaceWidth = width surfaceHeight = height } override fun surfaceDestroyed(holder: SurfaceHolder) { } }) return binding.root }[注意点]Surfaceサイズの調整
SurfaceViewの形を正方形にし、幅と高さを同じ値に設定すれば、プレビューのアスペクト比が保たれます。
MyActivity.ktprivate fun updateSurfaceSize(view: View, width: Int, height: Int) { val param = view.layoutParams.apply { if (activity?.resources?.configuration?.orientation == Configuration.ORIENTATION_PORTRAIT) { this.width = height this.height = width } else { this.width = width this.height = height } } view.layoutParams = param }リソース解放
アプリから離れたら時にカメラのリソースを解放します。
MyActivity.ktoverride fun onStop() { super.onStop() cameraCaptureSession?.let { try { it.stopRepeating() } catch (cameraAccessException: CameraAccessException) { cameraAccessException.printStackTrace() } it.close() cameraCaptureSession = null } cameraDevice?.close() cameraDevice = null }参考
- 投稿日:2020-10-20T17:13:26+09:00
AndroidエミュレーターでVagrantで設定したドメインにアクセス
前提
開発環境: Mac OS, Vagrant
スマホ向けサイトを、Android Studioのエミュレーター上で確認したいが、エミュレーターと開発PCのhostsファイルが共有されていないので、ネームサーバーが機能しない。エミュレーター内のhostsファイルを直接書き換える必要がある。
また、開発環境はVagrantで作られており、Vagrantfileの設定は以下。
config.vm.network "private_network", ip: "192.168.11.22" config.vm.hostname = "hogehoge.local"
手順
① Android Studioでエミュレーターをつくる
API Level: 28
Target: Android9.0 (Google APIs)API Levelが新しいとhostsの書き換えができなかったり、
TargetがAndroid9.0 (Google Play)だと$adb root
が使えなかったりするので注意。② エミュレーターの名前を確認
$ emulator -list-avds Pixel_2_API_28③ 書き込み可能モードでエミュレーターを起動
起動はGUIでなくターミナルから行う。
$ emulator -avd Pixel_2_API_28 -writable-systemターミネルが占有されるので別タブを開く。
⑤ hostsファイルの書き換え
hostsファイルは、エミュレーター内の
/system/etc/hosts
にあるので、これを書き換える。
※最初にrootになって、remountするのが必須。$ adb root $ adb remount $ adb shell "echo 192.168.11.22 hogehoge.local >> /system/etc/hosts"⑥ 確認
catでhostsファイルの中を確認
$ adb shell cat /system/etc/hosts 127.0.0.1 localhost ::1 ip6-localhost 192.168.11.22 hogehoge.localこれで、エミュレーター内のChromeから、
http://hogehoge.local
にアクセスして、ローカルのサイトが表示されれば成功。
上記⑤の違う方法
hostsファイルを一度取り出して、手元で編集してから、エミュレーターに戻す方法もある。
⑤-1' 起動中のエミュレーター名を確認
最初に確認した名前と違うので注意
$ adb devices List of devices attached emulator-5554 device⑤-2' hostsファイルの取り出し
PCの
~/Desktop/
に保存$ adb -s emulator-5554 pull /system/etc/hosts ~/Desktop/取り出したら、hostsファイルをPC上で編集。
⑤-3' 編集したhostsファイルを戻す
$ adb root $ adb remount $ adb -s emulator-5554 push ~/Desktop/hosts /system/etc/hosts
参考にしたサイト
[Android] Android Virtual Device の /etc/hosts を書き換えて、Vagrant Virtual Machine へ接続する
- 投稿日:2020-10-20T13:52:49+09:00
Associate Android Developer スタディガイド翻訳②
Google公式のAssociate Android Developer認定試験のスタディガイドが英語ばかりなので翻訳してみる、その2です。
前回のAndroid Coreに続いて、スタディガイドのUser Interfaceを翻訳します。(内容は2020/10/20時点のものになります)
User Interface認定試験について(公式):
Google Developers Certification : Associate Android Developer①はこちら↓
Associate Android Developer スタディガイド翻訳①:Android CoreUser Interface
The Android framework enables developers to create useful apps with effective user interfaces. Developers need to understand Android’s activities, views and layouts to create attractive UIs that maximize usability and the overall user experience.
Androidフレームワークにより、効果的なUIを持つ実用的なアプリを作成することができます。使いやすさと全体的なユーザエクスペリエンスを最大限にする魅力的なUIを作成するために、開発者はAndroidのアクティビティ、ビュー、そしてレイアウトを理解する必要があります。
To prepare for the AAD certification exam, Android developers should:
AAD(訳注:アソシエイトAndroid開発者)認定試験のため、Android開発者には下記の条件が求められます。
- Understand the Android activity lifecycle
- Be able to create an Activity that displays a Layout
- Be able to construct a UI with ConstraintLayout
- Understand how to create a custom View class and add it to a Layout
- Know how to implement a custom app theme
- Be able to add accessibility hooks to a custom View
- Know how to apply content descriptions to views for accessibility
- Understand how to display items in a RecyclerView
- Be able to bind local data to a RecyclerView list using the Paging library
- Know how to implement menu-based navigation
- Understand how to implement drawer navigation
- Androidアクティビティのライフサイクルを理解している
- レイアウトを表示するアクティビティを作成できる
- ConstraintLayoutを使用したUIを構築することができる
- カスタムビュークラスを作成し、レイアウトに追加する方法を理解している
- カスタムアプリテーマの実装のしかたを知っている
- カスタムビューにaccessibility hooks の適用することができる
- アクセシビリティ向上のためのコンテンツラベルの追加方法を知っている
- リサイクルビューで項目を表示する方法を知っている
- ページングライブラリを使用してリサイクルビューにローカルのデータをバインドすることができる
- メニューベースのナビゲーションの実装方法を知っている
- ナビゲーションドロワーの実装方法を理解している
Resources
※関連リソースリンク
Android Developers -> Build a responsive UI with ConstraintLayout(※日本語)
ConstraintLayout でレスポンシブ UI を作成する(Android Jetpackの一部)
リサイクルビューでリストを作成する
ナビゲーションドロワーを追加する
カスタムビュー コンポーネント
Android Developers -> Build more accessible custom views(※日本語)
カスタムビューのユーザー補助機能を強化する
スタイルとテーマ
setContentDescription()
Android Developers -> Adding accessibility features to apps for blind and visually-impaired users(※英語動画)
Youtube:目の不自由なユーザ向けの補助機能をアプリに追加する
Android Tool Time - Building interfaces with ConstraintLayout in AS(※英語動画)
Youtube:ConstraintLayoutでインターフェースを作成する
Codelabs -> Activities and intents
Codelabs -> Your first interactive UI
Codelabs -> Themes and final touches
Codelabs -> RecyclerView
Codelabs -> Menus and pickers
Codelabs -> User navigation
Codelabs -> Material Components (Java)
Codelabs -> Material Components (Kotlin)
Codelabs -> Lifecycles
Codelabs -> Add user interactivity
Codelabs -> Constraint layout using the Layout Editor
Codelabs -> RecyclerView fundamentals (Kotlin)
今回はここまで。
例によってResourcesは色々読み込んでから追記します。
- 投稿日:2020-10-20T09:13:05+09:00
AndroidStudioでブランチ切り替えた時に起こった謎のエラー
経緯
AndroidStudioでのアプリ開発時、別ブランチのアプリをビルドする際に下記のエラーが発生しました
「Entry name 'res/interpolator/btn_checkbox_checked_mtrl_animation_interpolator_0.xml' collided」
探しても上記のファイルはどこにも見当たらず
結果
原因の詳細は分からなかったが、以前ビルドしたapkとの衝突が起きているようだったので、それらを削除してあげることで解消できました
Variantsによってフォルダは異なりますが、候補となるのは下記のフォルダと思われます
- projectFolder\build
- projectFolder\debug
- projectFolder\release
- projectFolder\app\build
- projectFolder\app\build\debug
- projectFolder\app\build\release
これらの中からapkが入っているフォルダを全て削除することでビルドすることができました
エラーの文言は人によって微妙に異なるかもしれませんが、似たようなエラーが出た方は参考にしてください
参考したサイトはこちらです