ラベル Kotlin の投稿を表示しています。 すべての投稿を表示
ラベル Kotlin の投稿を表示しています。 すべての投稿を表示

2025年5月5日月曜日

Android gradle.properties に定数を入れる

どうも。どっことです。今回はgradle.propertiesを使った設定値の管理方法について紹介します。

gradle.propertiesで設定値を管理する

gradle.propertiesを用意し定数値感覚で設定すれば、アプリのバージョンやtargetVersion/minSDKVersionなどの値を一つのファイルで管理させることができます。

設定方法

例えば以下のbuild.gradleにあるマジックナンバーを定数化しgradle.propertiesに移して参照しましょう。

android {
    namespace = "com.mkt120.sampleapplication"
    compileSdk = 35
    defaultConfig {
        applicationId = "com.mkt120.sampleapplication"
        minSdk = 26
        targetSdk = 35
        versionCode = 100
        versionName = "1.0.0"
        ... 
    }
    ... 

gradle.propertiesに外出しする定数を記述します。

ANDROID_SDK_VERSION=35
ANDROID_MIN_SDK_VERSION=26
ANDROID_VERSION_NAME="1.0.0"
ANDROID_VERSION_CODE=100

上で配置した定数をbuild.gradleで参照します。

android {
    namespace = "com.mkt120.sampleapplication"
    compileSdk = Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION)

    defaultConfig {
        applicationId = "com.mkt120.sampleapplication"
        minSdk = Integer.parseInt(project.ANDROID_MIN_SDK_VERSION)
        targetSdk = Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION)
        versionCode = Integer.parseInt(project.ANDROID_VERSION_CODE)
        versionName = project.ANDROID_VERSION_NAME
        ...        
    }
    ...

gradle.propertiesに記述した定数はproject.XXXXXの形式で参照することができます。

まとめ

今回はgradle.propertiesを使った設定値の管理方法について紹介しました。

参考

2025年4月19日土曜日

Android そういえば Kotlin Coroutine ってどう使うのが良いの?

どうも。どっことです。今回はKotlinCoroutineのテーマです。

Kotlin Coroutine

Kotlin Coroutineは非同期処理などのコールバック地獄を解消してくれたり、可読性の高いコードを実装できるようになる便利な機能ですが、「実際のところ、どう使うのが良いの?」と考えました。今回はそんな使うと便利だけど実際どう使うかイマイチ分からない人向けに、ベストプラクティスのページの内容を簡単に紹介したいと思います。

TL;DR(結論)

ViewModelを実装してviewModelScope.launch/View側ではlifecycleScope.launchを使うのがよさそうです。

Google開発者サイトでコルーチンに関するベストプラクティスが紹介されています。コルーチンにフォーカスしたものだと、以下が挙げられています。

  1. Dispatcherを外から注入できるようにする
  2. ViewModelでコルーチンを作成する
  3. GlobalScopeは使わない
  4. コルーチンをキャンセルできるようにする

Dispatcherを外から注入できるようにする

withContextなどで引数に設定するDispatcher(どのスレッドで処理するかの指定)は、ハードコードせず外から注入できる形にした方が良いです。テストコードを実装するときにテストが容易にできるようになります。

// OK:処理するDispatcherを外から指定する
class SampleRepository(private val ioDispatcher: CoroutineDispatcher =  Dispatchers.IO) {
    suspend fun veryHeavyTask() = withContext(ioDispatcher) {
        // very heavy task
    }
}
class SampleRepository() {
    // NG: Dispatcherをハードコード(直接指定)しない
    suspend fun veryHeavyTask() = withContext(Dispatchers.IO) {
        // very heavy task
    }
}

ViewModelでコルーチンを作成する

ViewModelを作成し、そこでコルーチンを作成→非同期処理を実装するのがよいとのことです。

そして画面側に非同期処理が必要なことを意識させることがないよう、suspend関数として公開しないよう実装するのが良いとのことです。

class SampleViewModel(): ViewModel() {
    private val repository = SomeRepository()

    // NG:UI側にsuspend関数(=非同期処理)として公開しない
    suspend fun badSample() = repository.veryHeavyTask()

    // OK:ViewModel内でコルーチンを生成し非同期処理を実行する
    fun sample() {
        viewModelScope.launch {
            repository.veryHeavyTask()
        }
    }
}

GlobalScopeを使わない

テストコードの実装しにくさ、GlobalScopeをハードコードしていることによりDispatcherもハードコードされやすくなる点などから、GlobalScopeを利用しないことを推奨しています。

class SampleRepository() {
    fun veryHeavyTask() {
        // NG:GlobalScopeをハードコードしない
        GlobalScope.launch {
            // very heavy task
        }
    }
}

GlobalScopeの利用が正当であるケースはかなり稀で、アプリが生存期間中にアクティブな状態を維持しなければいけない状態とのことです。また、現在のスコープより長い生存期間が必要な処理であれば、CoroutineScopeを外部から指定するような構成とすることを検討すべきとのことです。

コルーチンをキャンセル可能にする

コルーチンはキャンセル可能ですが、実際コルーチン内でキャンセルされたか確認したり、停止したりするまではコルーチンはキャンセルされないとのことです。ensureActiveを使用することでキャンセルの確認ができます。

class SampleViewModel(): ViewModel() {
    private val repository = SampleRepository()

    fun sample(value: Int) {
        viewModelScope.launch { 
            val result = repository.validateValue(value)
            // キャンセルできるタイミングでアクティブか確認
            ensureActive()
            if (result) {
                repository.submit(value)
            }
        }
    }
}

まとめ

今回はKotlinCoroutineのベストプラクティスについて紹介しました。より詳しい内容は参考に記載したGoogleのベストプラクティスの記事からさまざまなページにアクセスできるので、ぜひ参考にしてください。

参考

2024年7月6日土曜日

Android テストコードの実装について解説

どうも。どっことです。今回は、テストコードの実装について解説したいと思います。

テストコード

テストコードは実機やエミュレータに開発段階のアプリをインストールせずとも、プログラムとしてテストを実行できるようにするものです。テストコードを実装せずとも、世の中に公開するアプリを開発することができるので、より難しい機能の実装や複雑なドメインロジックをプログラムに落とし込むなどのスキルを優先して獲得するというロードマップも考えとしてはあると思います。

ただ、昨今のCI/CDの重要性の高まりを鑑みると、テストコードを実装できることもエンジニアとして必要なスキルとして需要が上がってきているように見受けられます。そしてそんな需要が高まっている一方で、テストコードを実装できるエンジニアはまだまだ多くありません。ぜひテストコードを実装できるようになり、より広い知見をもつエンジニアへと一歩を進めましょう。

説明に入る前に(テストコードの種類)

Androidの開発においては、大きく2種類のテストコードがあります。

ユニットテスト

開発PC上で実行されるテストコードです。Androidの機能に依存しないビジネスロジックやアプリケーションロジックに対するテストコードを実装する際のものとなります。開発PC上で実行されるため、後述のインストルメントテストよりも高速に実行・実施ができます。

インストルメントテスト

実機やエミュレーター上で実行されるテストコードです。こちらは、Androidの機能に依存するUIまわりのテストや、外部機能などとの連携に対するテストコードとなります。より現実に近い環境でのテストとなるため、ユーザーストーリーを意識したテストに向いています。一方で、インストールなどのステップがビルド時に含まれるため、実行時間が比較的遅めになります。

今回は、ユニットテストの実装方法について解説します。

ユニットテストを実装する

ユニットテストの実装方法について、以下の流れで説明します。

  • ライブラリの準備
  • 実装
  • その他
    • Robolecticの紹介

ライブラリの準備

ユニットテストのテストコードを実装するためには以下をbuild.gradleに追加が必要です。プロジェクトを新規作成する際にAndroid Studioが自動で追加している場合がありますので、追加が必要か確認してください。

dependencies {
    testImplementation("junit:junit:4.13.2")
}

実装

それでは実装していきましょう。

テスト対象

テスト対象は以下のクラス・メソッドとします。

class Sample {
   fun test(input: Int?): Boolean {
       if (input == null) {
           throw IllegalArgumentException()
       }
       return input > 0
    }
}

テスト観点

テスト対象となるメソッドは、以下の観点でテストコードを実装することができます。

  • trueが返される確認テスト
  • falseが返される確認テスト
  • 例外が投げられるテスト

上をベースに確認するテストコードを追加していきましょう。

テストクラスを追加する

テストを実装するため、テストクラスを実装します。テスト対象のクラスで option + Enter からCreate Testを選択すればテストクラスを作成できます。SampleTestという名前にすると、testディレクトリに以下のようなクラスが作成できると思います。

class SampleTest {
}

trueが返される確認テストを実装する

まずはtrueが返されることのテストです。

@Test
fun testTrue() {
    val sample = Sample()
    val value = sample.test(1)
    Assert.assertEquals(value, true)
}

以降の実装も同様ですが、テストメソッドを実装し@Testアノテーションを付与します。

そして、実際にテストしたい内容を実装します。今回は、入力値に1以上を指定した場合はtrueが返却されるので、それを確認するためのテストを実装します。

falseが返される確認テストを実装する

次にfalseが返されることのテストです。

@Test
fun testFalse() {
    val sample = Sample()
    val value = sample.test(0)
    Assert.assertEquals(value, false)
}

同様に、falseが返却されるケースを実装します。入力値に0以下を指定した場合はfalseが返却されるので、それを確認するためのテストを実装します。

例外が投げられるテストを実装する

そして、例外が投げられることのテストです。

@Test(expected = IllegalArgumentException::class)
fun testException() {
    val sample = Sample()
    sample.test(null)
    Assert.fail()
}

例外が発生するテストは@Testアノテーションのパラメータexpectedを指定します。そのパラメータには発生する例外のクラスを指定します。

また、例外が発生しなかった場合がテストがNGとなるように、例外がthrowされる直後のタイミングでAssert.fail()を実装しておきます。

これにて、該当クラスメソッドのテストが実装できました。あとはテストを実行すれば期待した結果になるかを確認する処理が実行されます。

その他

ここまでテストコードの実装について解説しましたが、実は1点問題があります。ユニットテストは開発PC上で実行されますが、その都合により、AndroidSDKに含まれる機能を利用することができません。これが結構ネックになり、例えばよくある実装の一つであるFragmentのインスタンス生成メソッドは、Fragmentやそれに渡すBundleがAndroidSDKに含まれるものなので、テストコードを実装することができません。

ではどうするかというと、一つはインストルメントテストで実装することです。実機やエミュレーター上でテストすることで、テストコードを実行することができます。

もう一つはRobolectricを利用することです。RobolectricはユニットテストでもAndroidSDKに含まれる機能を利用することができます。

Robolectricの紹介

今回は説明を省略しますが、公式サイトを紹介します。またRobolectricの利用方法についても、後日解説を予定しています。

まとめ

今回はテストコード、特にユニットテストの実装方法について解説しました。テストコードやその実装の重要性は今後も高いものと予想されるので、今後もこれをテーマとした投稿をしていこうと考えていますので、よろしくお願いします。

参考

2024年6月22日土曜日

Android 通知領域に通知を表示する方法を解説

どうも。どっことです。今回は、通知を実際に表示する実装方法を解説します。

通知領域に通知を表示する

通知チャンネルも実装できて、いよいよ通知を表示する方法まで来ました。さっそく実装方法を解説していきます。

通知を表示する実装の流れ

  • AndroidManifest.xmlにパーミッションの追加
  • 通知の表示の許諾確認
    • 拒否された時はさらに確認
  • 通知の表示処理

AndroidManifest.xmlにパーミッションの追加

まずはAndroidManifest.xmlに今回の通知表示に必要となるpermission.POST_NOTIFICATIONSの権限を追加します。このpermission.POST_NOTIFICATIONSが実装時に忘れやすいので、実装時にはぜひ備忘録としてご確認いただけると嬉しいです。

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

通知の表示の権限許諾確認

権限許諾ですが、実装方法は通常のパーミッションチェックと同様です。つまりregisterForActivityResultを使ってlauncherを生成し、Manifest.permission.POST_NOTIFICATIONSの許諾確認をします。このManifest.permission.POST_NOTIFICATIONSも実装時に忘れやすいので、ぜひ参考にしてください。

private val launcher = registerForActivityResult(
  ActivityResultContracts.RequestPermission()
) { 
  /** 許諾ダイアログが閉じられた。何かやりたいことがあればここに実装。*/ 
}

...

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    TODO("あとで追加実装")
    launcher.launch(Manifest.permission.POST_NOTIFICATIONS)
}

拒否された時の再許諾確認

権限を確認したにもかかわらず拒否されてしまった場合ですが、一般的には次回などで「この権限がXXXという理由で必要なんだ」という権限を必要としている理由を説明した上で再度権限を確認する流れとなります。前項のTODO("あとで追加実装")に追記していきます。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    if (checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) {
        // 権限を許諾してもらっている。後続処理に流さないでOK
        return
    }
    val shouldShow = shouldShowRequestPermissionRationale(
        Manifest.permission.POST_NOTIFICATIONS
    )
    if (shouldShow) {
        // 過去に許諾確認したけど、許可してくれなかった人。
        // ダイアログなどで権限を必要としている理由を説明した上で再度依頼する。
    } else {
        // それ以外。初回のケース、または「もう表示しない」設定としたケース。
        launcher.launch(Manifest.permission.POST_NOTIFICATIONS)
    }
}

通知の表示処理

そしてManifest.permission.POST_NOTIFICATIONSの許諾を確認をした上で、問題なければNotificationを表示するようNotificationManagerに依頼します

if (checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) {
    val notification = Notification.Builder(this, "id")
        .setContentTitle("this is title")
        .setSmallIcon(R.mipmap.ic_launcher)
        .setContentText("this is text")
        .build()
    NotificationManagerCompat.from(this)
        .notify(null, 0, notification)
}

実際にうまく動作していれば、上記の場合以下のような通知が通知領域に表示されます。

まとめ

今回は通知領域に通知を表示する方法を解説しました。実装はそんなに複雑ではないものの、参照すべき定数を毎回忘れてしまいがちなので、ぜひ参考にしていただけると嬉しいです。

2024年6月17日月曜日

Android 通知チャンネルを作成する方法を解説

どうも。どっことです。今回は通知チャンネルを作成するための実装方法について解説します。

通知チャンネルを作成する方法

Android 8(Oreo)から 通知領域に通知を表示するためには、その通知に対応する通知チャンネルをあらかじめ作成しておかなければならなくなりました。

以前の記事で「作成した通知チャンネルの設定画面に遷移する」実装方法を解説しましたが、そもそも通知チャンネルの追加方法を解説していなかったので、今回はこちらをテーマに解説していこうと思います。

通知チャンネルの作成

通知チャンネルは以下の手順で作成します。

  1. NotificationManagerのインスタンスを取得
  2. 通知チャンネルインスタンスを生成
  3. NotificationManagerに通知チャンネルの作成を依頼

順番に解説していきます。

NotificationManagerのインスタンスを取得

まずはNotificationManagerのインスタンスを取得します。androidxではNotifcationManagerCompatというクラスが提供されており、今まではAndroidのバージョンがO未満かどうかで制御をする必要がありましたが、このクラスが提供されたことによりAndroidのバージョンを気にせず制御することができるようになりました。

呼び出し方も単純で、以下でOKです。

val manager = NotificationManagerCompat.from(this)

通知チャンネルインスタンスを生成

次に実際に作成する通知チャンネルのインスタンスを生成します。最小の構成では、ID、Importanceとタイトルさえあれば作成できるはずです。

val channel = NotificationChannelCompat.Builder("id", NotificationManagerCompat.IMPORTANCE_DEFAULT)
    .setName("ChannelName")
    .setDescription("ChannelDescription")
    .build()

実装するとき一番よく忘れるのが、Importanceです。定数はNotificationManagerCompatで定義されていますので、そちらを参照してください。以下が定義されています。

  • NotificationManagerCompat.IMPORTANCE_MAX
  • NotificationManagerCompat.IMPORTANCE_HIGH
  • NotificationManagerCompat.IMPORTANCE_DEFAULT
  • NotificationManagerCompat.IMPORTANCE_LOW
  • NotificationManagerCompat.IMPORTANCE_MIN
  • NotificationManagerCompat.IMPORTANCE_NONE
  • NotificationManagerCompat.IMPORTANCE_UNSPECIFIED

NotificationManagerに通知チャンネルの作成を依頼

最後に生成した通知チャンネルのインスタンスをNotificationManager#createNotificationChannel()で渡して、通知チャンネルの作成を依頼します。

manager.createNotificationChannel(channel)

まとめ

今回は通知チャンネルの作成手順を解説しました。Androidの通知に関する処理は、最新のバージョンになるに連れて必要な実装が増えていくのが辛いところですが、素敵なアプリには必要不可欠な要素の一つだと思いますので、是非ともマスターしたいところですね。

おまけ:通知チャンネルの削除を依頼する

作るのと同じで、NotificationManager#deleteNotificationChannel(id)を呼ぶことで、通知チャンネルの削除依頼することができます。

manager.deleteNotificationChannel(channel)

参考

2024年6月11日火曜日

Android ディスプレイがスリープされるのを無効にする方法を解説

どうも。どっことです。今回は、ディスプレイスリープを無効にする実装方法を解説します。

ディスプレイスリープを無効に設定する

動画の再生中やドキュメントの表示中、タイマー表示中などの画面を常に表示しておきたいケースでディスプレイがスリープしてしまうのは避けたいと思います。アプリ側でディスプレイがスリープしてしまうことを無効にする設定があるので、今回はこちらを解説したいと思います。ただし、スリープを無効にすることは電力消費の観点から必要最低限の範囲で設定することを心がけましょう。

スリープを無効に設定する

スリープを無効にするためにはActivityから参照できるwindowインスタンスに対して、FLAG_KEEP_SCREEN_ONのフラグを設定します。

window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)

スリープ無効設定をクリアする

スリープ無効設定をクリアするためには、windowインスタンスに対して、FLAG_KEEP_SCREEN_ONのフラグをクリアしてあげます。

window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)

まとめ

今回はディスプレイがスリープされる設定を無効にする実装方法を解説しました。使い所が限られる機能ですが、動画再生などディスプレイをスリープさせたくないような利用方法をアプリに提供する場合、必要な実装になりますので参考にしていただけると幸いです。

参考

2023年12月15日金曜日

Android ソフトキーボードを閉じる実装について解説

どうも。どっことです。今回はAndroidでキーボードを閉じる実装を解説します。

ソフトキーボードを閉じる実装

以前このブログで、iOSでのキーボードを閉じる実装について解説しました。今回はそのAndroid版です。

解説

さっそくサンプルを載せます。InputMethodManagerにアクセスし、キーボードを閉じるようhideSoftInputFromWindowで依頼する、という具合ですね。

val inputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as? InputMethodManager
inputMethodManager?.hideSoftInputFromWindow(v.windowToken, 0)

「いうほど難しいか?」と言われればそうでもなさそうですが、
「キーボード消すの、どのマネージャーに依頼するんだっけ...?」とか
InputMethodManagerに依頼するのはいいけど、どのメソッドだっけ...?」とか
「引数には何を渡すんだっけ..」とか、ちょっとした煩わしさが見え隠れしているように感じます。。

まとめ

今回はAndroidでソフトウェアキーボードを閉じる実装方法について解説しました。表示されるべき時に表示され、消えるべき時に消えるあるべき姿が、ユーザに良い体験を提供する考え方なので、意識していきましょう。

Android アプリ内にXMLを組み込む/読み込む実装を解説

どうも。どっことです。今回は、アプリ内へのXML組み込み/読み込みをする実装について解説します。

XMLをアプリ内に埋め込む・読み込む

AndroidのリソースにはXMLを配置することができます。AndroidResourceよりも単純なファイル管理として扱えるので、アプリの共通設定などの管理に利用することができます。

実際に配置する場所はapp/res/xmlフォルダです。無い場合は新規に追加して、XMLファイルを配置してください。

例えばsample.xmlを配置した場合、参照する時は以下のようにXMLファイルを指定します。

val parser : XmlResourceParser = context.resources.getXml(R.xml.sample)

Android開発ではAndroidResourceがよしなにやってくれるので、XmlResourceParserは使い馴染みのない人が多いかもしれませんが、getEventType()/next()を駆使してIterableのように解析するものです。

var eventType: Int = parser.getEventType();
// XMLドキュメントが終了になるまでwhile文を繰り返す
while (eventType != XmlPullParser.END_DOCUMENT) {
    if(eventType == XmlPullParser.START_DOCUMENT) {
        System.out.println("XMLドキュメントの読み取り開始");
    } else if(eventType == XmlPullParser.START_TAG) {
        System.out.println("新しいタグの参照:"+parser.getName());
    } else if(eventType == XmlPullParser.END_TAG) {
        System.out.println("タグが閉じられた:"+parser.getName());
    } else if(eventType == XmlPullParser.TEXT) {
    // タグに設定されている値を参照
        System.out.println("値:"+parser.getText());
    }
    // 次の要素の参照へ
    eventType = parser.next();
}

まとめ

今回はアプリ内にXMLを組み込み、それを参照する実装方法について解説しました。アプリの設定管理などの1案として挙げられるので、有効活用していきたいですね。

参考

2023年12月4日月曜日

Android Kotlin Coroutineを並行に実行する実装を解説

どうも。どっことです。今回はKotlin Coroutineの並行実行する処理の実装方法について解説していきます。

Kotlin Coroutineを並列に実行する

非同期処理をとっても素敵に実装できるKotlin Coroutineですが、いくつかの処理を同時に実行したいようなケースにも対応することができます。APIによる通信やデータベースデータの永続化といった、時間がかかる処理をできるだけ短縮していきましょう。

実装方法

実装だけなら以下の2パターンで実現することができます。

async/awaitによる実装

ひとつはasync/awaitを使って実装する方法です。

viewModelScope.launch {
    val apiList = listOf(
        // async でコルーチンを生成 その中でAPIを実行する
        async { api1() },
        async { api2() },
        async { api3() },
    )
    // すべてのコルーチンを実行。全部終わるまで待つ。
    apiList.awaitAll()
}

launch/joinAllによる実装

もうひとつはlaunch/joinAllを使って実装する方法です。

viewModelScope.launch {
    // lanuch でコルーチンを作成し、その中でAPIを実行する。
    val apiJobsList = listOf(
        launch { api1() },
        launch { api2() },
        launch { api3() },
    )
    // すべてのコルーチンを実行。全部終わるまで待つ。
    apiJobsList.joinAll()
}

まとめ

今回は、Kotlin Coroutineによる処理を並列実行する実装について解説しました。ユーザの待ち時間を減らせるように活用していただけると幸いです。

参考

2023年11月30日木曜日

Android DeprecatedされたPullToRefreshについて少しだけ紹介

???「PullToRefresh(PtR)、iOSと同じような見た目にしてほしいんだけど!」

私「AndroidにはSwipeRefreshLayoutというものがありまして...」

???「うるせえ!」

どうも。どっことです。deprecatedですが、iOSと同じような見た目のPtRがあります。SwipeRefreshLayoutが導入されたことにより、このライブラリはもうメンテナンスされていないようですが、用法用量を守って渋々いい感じに使いましょう。

参考

2023年10月18日水曜日

Android リップルエフェクト(Ripple Effect)を簡単に実装する方法を解説

どうも。どっことです。今回はリップルエフェクト(Ripple Effect)の実装方法について解説していきます。

リップルエフェクトを設定する

リップルエフェクトは、Viewにタップした時に水の波紋のように色が広がっていくような表示のことです。

これを設定しなくても動作に影響することはありませんが、ユーザにタッチしたことを知らせるには重要な効果だと思います。

実装方法

実装自体は簡単で、android:backgroundに以下を設定するだけです。

android:background="?attr/selectableItemBackground"

なお、すでに背景色を設定済みであれば、android:foregroundに設定することで同様に表示することができます。

android:foreground="?attr/selectableItemBackground"

まとめ

今回はリップルエフェクトの実装方法について解説しました。もはや一行追加するだけという非常に簡単な設定だけなので、解説というにはあまりにもネタが少ないかなと頭を悩ませています。

2023年8月10日木曜日

Android 通知チャンネルの設定画面を表示する実装方法について解説

どうも。どっことです。今回は、通知チャンネルの設定画面を表示するための実装方法について解説します。

通知チャンネルの設定画面を表示する

Android O から追加された通知チャンネルですが、アプリからは追加・削除以外は通知チャンネルの設定値を修正・変更することができません。その代わりに端末の設定画面に遷移させるための導線を具備することで、ユーザに通知チャンネルまで簡単にアクセスできるようにしてあげましょう。

実装方法

アプリが追加したチャンネルの設定画面に遷移する方法は以下となります。

val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
intent.putExtra(Settings.EXTRA_CHANNEL_ID, channelId)
startActivity(intent)

通常のstartActivityと同様に、必要なActionやパラメーターを設定することで通知チャンネルの設定画面に遷移することができます。

まとめ

今回は、通知チャンネルの設定画面を表示するための実装方法について解説しました。このブログでは、アプリで通知を表示するために必要な実装として他にも投稿しているので、もしよろしければそちらも参考にしていただけると幸いです。

参考

2023年8月7日月曜日

Android Parcelableを実装する方法を解説

どうも。どっことです。今回はParcelableの実装方法について解説します。

Parcelableを実装する

ActivityFragmentなどの画面間におけるデータ受け渡しはアプリ全体の動きを考える上でなくてはならない処理ですが、自前で用意したデータクラスはそのままだとBundleに乗せることができません。

それではどうするかというと、そのデータクラスにParcelableSerializableを実装します。取り出すときにキャストが必要にはなるが、これでBundleに乗せることができるようになり画面間のデータ受け渡しができるようになります。

今回はその呪文の如きParcelableのインターフェース群の実装について、説明します。

実装

必要な実装ですが、データクラスに以下のクラス・メソッドを実装します。

  • Parcelableを実装する
  • describeContentsを実装する
  • 引数がParcelのコンストラクタを実装する
  • writeToParcel()を実装する
  • CREATOR : Parcelable.Creatorを実装する

サンプルとしてItemクラスにParcelableを実装する場合は以下となります。

data class Item(
    var title: String,
    var description: String,
) : Parcelable {
    constructor(parcel: Parcel) : this(
        parcel.readString()!!,
        parcel.readString()!!
    )

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(title)
        parcel.writeString(description)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<Item> {
        override fun createFromParcel(parcel: Parcel): Item {
            return Item(parcel)
        }

        override fun newArray(size: Int): Array<Item?> {
            return arrayOfNulls(size)
        }
    }
}

あとは実装したデータクラスのオブジェクトをBundle#putParcelable()Bundleに詰め

val bundle = Bundle().apply {
    putSerializable("適当なキー", item)
}

Bundle#getParcelable()Bundleから取り出すことで

val item = bundle.getParcelable("適当なキー", Item::class.java)

画面間でデータクラスをやり取りすることができます。

ここまで書いててですが…

Percelableをいい感じに実装してくれるアノテーションがあると公式サイトが謳っていました。

Parcelable 実装生成ツール

アノテーション付けるだけで上記の面倒臭い実装をしなくてよくなるなんて、なんて素晴らしいんだ!!ありがとうGoogle様!!!

まとめ

今回はParcelableの実装方法について解説しました。ボイラープレートなParcelableの実装ですが、すでに自動生成してくれるツールがあるので、そちらを使う方が無難でしょう。今回の紹介した内容がほぼ無用なものになってしまいますが、必要なものが簡単に実装できることの方が重要だと思うので、まあ仕方ないでしょう笑

2023年7月29日土曜日

Android 環境変数や定数を build.gradleに持たせる実装を解説

どうも。どっことです。今回は環境変数や定数を build.gradleで管理する実装を解説します。

build.gradleで環境変数・定数を管理する実装

Gradleが自動生成してくれるBuildConfigを使うことで、buildTypesproductFlavorごとに初期値や定数値を持たせることができます。よくあるのはAPIのドメインや、広告IDの切り分けなどですね。開発版(buildType:debug)とリリース版(buildType:release)で開発用と本番用で切り替えるなどに利用できます。今回はBuildConfigにアプリ内で参照できるパラメータを乗せる実装を解説します。

BuildConfigに定数を乗せる実装

buildTypes(debug/release)ごとに定義する場合は以下でOKです。

  • defaultConfigに初期値を設定する。
  • buildTypesごとに定数の値を上書き設定する。
android {
  // ...
  defaultConfig {
      // buildTypesで上書きがなかった場合に設定される値
      buildConfigField "String", "AD_UNIT_ID", "\"とりあえずな広告ID\""
  }
  // ...

  buildTypes {
    release {
      // 本物の広告IDを上書きして定義
      buildConfigField("String", "AD_UNIT_ID", "\"本物の広告ID\"")
      ...
    }
    // とりあえずな広告IDをdefaultConfigに書いていますが、
    // debugブロックに書いて、defaultConfigは空文字などを指定しても問題ありません。
  }
}

あとは上記の定数を使いたいところで、以下ように参照すれば値を取得することができます。

const val AD_UNIT_ID_SIDE_BAR = BuildConfig.AD_UNIT_ID

まとめ

今回は、BuildConfigに定数を持たせる実装について解説しました。同じアプリでも開発環境やクライアントに見せる版、一般公開版など種別がさまざまになることが多いと思います。このような機能を利用して、ソースコード上に煩わしい実装が増やさないように心がけましょう。

参考

2023年7月27日木曜日

Android キーイベントを発行する実装方法を解説

どうも。どっことです。今回はキーイベントを発行する実装方法を解説します。

キーイベントを発行する

例えば方向キーのキーイベントを発行する場合、以下をつかいます。

KeyEventを生成して、dispatchKeyEventで投げるだけ。これだけです。

dispatchKeyEvent(KeyEvent(ACTION_KEY_DOWN, KEYCODE_DPAD_UP))

気をつけないといけないのは、KeyEventコンストラクタに渡している第一引数には ACTION_KEY_DOWN を指定することです。

そもそもACTION_KEY_DOWNはキー開始(画面やキーが押され始めた)、ACTION_KEY_UPはタッチ終了(画面やキーから離れた)を表すイベントですが、第一引数にACTION_KEY_UPを指定しても、OS側で制御してくれません。というのもOSからしてみれば

「押されてないのに離れたとか何事!?怖いから無視しとこ…」

という形に整理されていそうだからです。そのため、どうしてもACTION_KEY_UPでやりたかったら、ACTION_KEY_DOWNによりキーが押され始めたイベントも一緒に発行する必要があります。(どれだけ頑張る必要があるかは知らない…)ACTION_KEY_UPに思い入れしなければ、非常に簡単ですね。

まとめ

今回はキーイベントを発行する実装方法を解説しました。カスタムキーボードなどに役立てていただければと思います。

参考

2023年7月12日水曜日

Android OnClickListener はXMLレイアウトファイルからでも設定できる

どうも。どっことです。今回はXML上でクリックリスナーを設定する実装方法について解説します。

XMLでクリックリスナーを設定する

dataBindingでは、XMLでクリックリスナーを設定する処理はよくある実装ですが、実はdataBindingを使わない通常のケースでも似たような実装をすることができます。

解説

XMLでクリックリスナーを設定するには、任意のViewに対して android:onClickを設定してあげます。たとえば、TextViewに設定するときは以下のような形です。

<TextView
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:text="テスト"
   android:onClick="testClicked" />

あとはこのXMLを読み込むクラスに、testClicked(View v)関数を実装します。

override fun testClicked(v: View) {
  Log.d("TAG", "TextView テスト is clicked")
}

今回設定したonClickの値はtestClickedなのでそれに合わせていますが、実際に利用する際はTextViewがどんな表示をしているかなどを表現した関数名にしてあげると後々にコードを追いやすくなるかと思います。

まとめ

今回はXMLでクリックリスナーを設定する実装方法について解説しました。dataBindingでも似たような実装方法があると思いますが、そうでなくてもシンプルに利用することができるので、参考にしていただけると幸いです。

参考

2023年7月11日火曜日

Android ダークモードを実装するときの実装手順について解説

どうも。どっことです。今回はダークモード対応に関する実装について解説します。

ダークモードを実装する

アプリをいい感じに見せたいのであれば、ダークモード対応は今や必須の項目の一つではないでしょうか。ユーザへの見せ方をライト/ダークでそれぞれ管理する必要があるのは大変ですが、それ以上にユーザに好印象を与えることができるアプローチになります。今回はそんなダークモード対応をする時のアプローチ方法を解説します。

実装手順

必要最小限でダークモードを対応する場合、以下の手順になります。

  • ダークモードにした時にどのように見えるか確認
  • 修正が必要な箇所について色指定を修正
  • ライト/ダークモードの切り替え動線を用意

順番に解説していきます。

ダークモードにした時にどのように見えるか確認

まずは以下をApplication#onCreate()で呼び出してみましょう。

AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)

これを呼び出すとアプリがダークモードで表示されます。この状態でアプリがどのように見えるか確認しましょう。

修正が必要な箇所について色指定を修正

テキストが背景と同色で見えなくなっているなど、残念な表示となっている箇所は適宜修正しましょう。values-nightフォルダを作ってそこにcolors.xmlを用意し、色指定を追加してください。このときvalues/colors.xmlでの定義とvalues-night/colors.xmlで定義するnameは同じものを指定してください。そうすれば、ライト/ダークの切り替え時にそれぞれの色指定を参照してくれます。逆に異なるものを指定すると、ビルド時に警告表示されるはずです。

ライト/ダークモードの切り替え導線を用意

あとはユーザがライト/ダークモード(+端末の設定に従う設定)を切り替えられるよう、切り替えスイッチのような動線を用意しておきましょう。アプリ内に設定画面などがあれば、そちらに用意するのが適切かと思います。

切り替え時の処理

ユーザがスイッチなどで設定を変えた場合の処理が必要です。ユーザの切り替え設定に従い、テーマを更新します。また、設定した値はアプリ固有領域(例えばSharedPreferenceなど)に保持しておきましょう。以下はSharedPreferenceに保存した時のサンプルです。

/** 
 * ユーザが設定したテーマ。
 * AppCompatDelegate.MODE_NIGHT_YES, 
 * AppCompatDelegate.MODE_NIGHT_NO など 
 **/
val value = AppCompatDelegate.MODE_NIGHT_YES
AppCompatDelegate.setDefaultNightMode(value)
getSharedPreferences("sample",  MODE_PRIVATE)
  .edit().putInt("theme_setting", value).apply()

アプリ起動時の処理

アプリを起動した時に、ユーザの設定に合わせてテーマを更新する必要があります。先ほど実装したApplication#onCreate()の実装を以下に変更してください。

val value = getSharedPreferences("sample", MODE_PRIVATE)
            .getInt("theme_setting", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
AppCompatDelegate.setDefaultNightMode(value)

まとめ

今回は、アプリにダークモードを実装する時の実装手順について解説しました。ダークモードはユーザに良い印象を提供するためには必須の項目となっているので、今回の内容を参考にして是非対応してみてください。

参考

2022年8月23日火曜日

Android seletorにcolorを設定するときの注意点

android:textColorに設定する場合は以下。

<selector xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:state_selected="true" android:color="#aaa" />
  <item android:state_selected="false" android:color="@android:color/transparent" />
</selector>

一方で android:background に設定する場合は以下。

<selector xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:state_selected="true" >
    <color android:color="#aaa" />
  </item>
  <item android:state_selected="false">
    <color android:color="@android:color/transparent" />
  </item>
</selector>

参考

2022年8月3日水曜日

Android ディレクトリを作成する

どうも。どっことです。今回はアプリ固有のファイル領域にディレクトリを生成する実装方法を解説します。

さっそくですが、実装サンプルです。階層構造のあるディレクトリを生成するケースとして、階層ごとに生成するケースとまとめて生成するケースを紹介します。

ディレクトリ階層ごとにディレクトリを生成する

階層ごとに生成するケースを紹介します。最初にディレクトリを生成したあと、そのディレクトリを親ディレクトリとして指定する形で子ディレクトリを生成します。

val parent1 = File(requireContext().filesDir, "parent1")
// ディレクトリ(parent1)を作成する
parent1.mkdir()
val parent2 = File(parent1, "parent2")
// 作成したparent1ディレクトリを親にして、子ディレクトリ(parent2)を作成する
parent2.mkdir()

複数のディレクトリ階層があってもまとめて生成する

こちらはまとめて生成するケースです。ディレクトリ名の指定に親ディレクトリを含めることで、その親ディレクトリもまとめて生成してくれる。というわけです。唯一注意が必要なのは、ディレクトリを生成する関数が前のサンプルではmkdir()をつかっていましたが、今回はmkdirs()を使っている点でしょうか。

val parents = File(requireContext().filesDir, "parent1/parent2")
parents.mkdirs()

2021年4月19日月曜日

Android カスタムViewでもダークモード対応する

経緯

カスタムViewを作ったけど、ダークモードのことをまったく気にしなかったせいで、カスタムViewの背景が真っ白になってしまった。

思い

values-nightフォルダを用意してcolors.xmlを入れるのもいいけど、そこまでしなくても標準の設定で十分なんだよなぁ。じゃあ標準ってどうやればいいんだ?と思って開発者サイト見たら、しっかり書いてあった。

ダークテーマ

ハードコードした色を削除してください(白を指定した背景色など)。その代わりとして ?android:attr/colorBackground テーマ属性を使用します。

なーんだ。これ使えば楽勝じゃん。

残念。mergeタグにbackgroundは効かないんだな。

mergeタグってこういう所で融通きかないよね。duplicateParentStateつけないと正しく動作しない問題は解決してくれるけど。さて、となると?android:attr/colorBackgroundをコード上で参照しないといけないけど、どう実装すればいいんだ?

答え

ググったらバリバリ答えが書いてあるページが出てきた。

how to get background color from current theme programmatically

TypedValueを使えば参照できるらしい。というわけで最終的に実装した形がこれ

typedValue = TypedValue()
context.theme.resolveAttribute(android.R.attr.colorBackground, typedValue, true)
setBackgroundColor(typedValue.data)

全然ちょろかった。

移行予定

どうも。どっことです。 タイトルの通りですが、諸事情により GitHubPage に移行予定です。 https://mkt120.github.io/ この備忘録に記載の内容を転記しつつ、今後はこちらのページを更新していく予定です。