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

【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

参考記事

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

#12 Kotlin Koans Introduction/Extensions on collections 解説

1 はじめに

Kotlin公式リファレンスのKotlin Koans/Extensions on collectionsの解説記事です。

Kotlin Koansを通してKotlinを学習される人の参考になれば幸いです。

ただし、リファレンスを自力で読む力を養いたい方は、
すぐにこの記事に目を通さないで下さい!

一度各自で挑戦してから、お目通し頂ければと思います:fist:

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の解説をします:muscle:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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()
                    }
                }
            }
        }
    }
}

カメラ起動

  1. カメラIDを取得します。Camera2 APIではCameraManagerでカメラIDを取得します。また、カメラIDはIntではなく、Stringになります。
  2. [注意点]:SurfaceViewを使う場合、プレビューサイズを設定することができません(TextureViewならできます)。そこで、SurfaceViewの形を正方形にし、幅と高さをもっとも長い辺に合わせることで、アスペクト比が保たれたプレビューを実現します。
  3. カメラに接続します。接続したら、cameraDeviceStateCallbackコールバックが呼ばれます。
  4. カメラのプレビューのリクエストを作ります。
  5. カメラキャプチャーのコールバックを設定します。
  6. カメラのキャプチャーを開始します。
MyActivity.kt
private 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.kt
private 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.kt
private 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.kt
override fun onStop() {
    super.onStop()

    cameraCaptureSession?.let {
        try {
            it.stopRepeating()
        } catch (cameraAccessException: CameraAccessException) {
            cameraAccessException.printStackTrace()
        }
        it.close()

        cameraCaptureSession = null
    }

    cameraDevice?.close()
    cameraDevice = null
}

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 へ接続する

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 Core

User 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 -> Create a list with RecyclerView(※日本語)

 リサイクルビューでリストを作成する

Android Developers -> Create a navigation drawer(※日本語)

 ナビゲーションドロワーを追加する

Android Developers -> Custom view components(※日本語)

カスタムビュー コンポーネント

Android Developers -> Build more accessible custom views(※日本語)

 カスタムビューのユーザー補助機能を強化する

Android Developers -> Styles and themes(※日本語)

 スタイルとテーマ

Android Developers -> setContentDescription()(※英語)

 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は色々読み込んでから追記します。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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が入っているフォルダを全て削除することでビルドすることができました

エラーの文言は人によって微妙に異なるかもしれませんが、似たようなエラーが出た方は参考にしてください

参考したサイトはこちらです

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む