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

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のベストプラクティスの記事からさまざまなページにアクセスできるので、ぜひ参考にしてください。

参考

2025年4月6日日曜日

Android CustomTab(ChromeCustomTab)の使い方を紹介

どうも。どっことです。今回はCustomTab(ChromeCustomTab)の使い方を紹介します。

CustomTabについて

CustomTabはアプリ内で利用できるブラウザ機能です。CustomTabが登場する前はWebViewという機能がアプリでのブラウザとしての役目を果たしておりましたが、セキュリティや表示速度などの観点から現在はCustomTabを使うことが推奨されています。

実装

実装手順は以下の通りです。

  1. app/build.gradleに依存関係を追加する。
  2. CustomTabIntent.Builderを使ってCustomTabIntentを生成する。
  3. CustomTabIntent.launchUrlを実行する。

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

app/build.gradleを実装する

まずはapp/build.gradleに依存関係を追加します。

dependencies {
    // 既存にある dependencies に以下を追加する
    implementation "androidx.browser:browser:1.8.0"
}

CustomTabIntent.Builderを生成する

次にCustomTabIntent.Builderを使ってCustomTabIntentを生成します。

val builder = CustomTabsIntent.Builder()
val customTabIntent = builder.build()

CustomTabIntent.launchUrlを実装する

最後にCustomTabIntentにあるlaunchUrl関数を使って、アクセスしたいWebページを表示します。

customTabIntent.launchUrl(this@MainActivity, Uri.parse("https://www.google.co.jp/"))

その他

CustamTabの表示をカスタマイズしたい

CustomTabIntent.Builderに対して、パラメータを設定することでカスタマイズすることができます。今回は参考欄に詳細の項目のページを記載する程度で割愛。

認証に利用したい

現在2025/4時点ではまだalpha版ですが、AuthTabIntentが将来的に利用できるようです。

良くある認証フローでは、認証が完了したタイミングでアプリに戻るためのリダイレクトURLを発火してもらうことが一般的ですが、そのハンドリングの一部をAuthTabIntentで制御してくれるようです。

ChromeCustomTabから戻ってきたことを検知したい

activityResultLauncherを利用すれば実現できます。参考はこちら

ただ前述したように、CustomTabIntentlaunchUrlのインターフェースが実装されている点から、これは推奨される利用方法ではない印象です。

val intent = customTabIntent.intent.also {
    it.setData(Uri.parse("https://www.google.co.jp/"))
}
// registerForActivityResult で生成したactivityResultLauncherを使って呼び出し
launcher.launch(intent) 

まとめ

今回はCustomTabについて解説しました。WebViewよりも優れている点が多く、シンプルに使う分には簡単に実装できるためぜひ活用してみてください。

参考

2025年4月5日土曜日

Android Admobのためのライブラリ UMPの実装方法を解説

どうも。どっことです。今回はAdmobを利用するためのUMP(UserMessagingPlatform)ライブラリについて解説します。

UMPライブラリ

Androidアプリ内に広告を表示するためには、Admobライブラリを使って広告表示用のViewを組み込む必要がありますが、それと合わせてユーザに許諾を得る処理も必要です。

今回はユーザに許諾を得るためのUMP(UserMessagingPlatform)ライブラリの使い方について解説します。

実装概要

実装の手順は以下の通りです。

  1. app/build.gradledependenciesに依存関係を追加
  2. AndroidManifest.xmlmeta-dataを追加
  3. ユーザから許諾を得る処理を実装

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

依存関係の追加

app/build.gradledependenciesに依存関係を追加します。

dependencies { 
    ... 
    // 既存のdependenciesスコープに以下を追加
    implementation("com.google.android.ump:user-messaging-platform:2.0.0")
}

meta-dataの追加

AndroidManifest.xmlmeta-dataを追加します。valueに設定する値はAdmobのページから取得してください。

<!--Sample app ID: ca-app-pub-3940256099942544~3347511713-->
<meta-data android:name="com.google.android.gms.ads.APPLICATION_ID"
           android:value="ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy"/>

ユーザから許諾を得る処理を実装

ここからは広告を表示するにあたってユーザから許諾を得るための処理を実装します。

同意情報のオブジェクトを取得し更新する

UserMessagingPlatform.getConsentInformationconsentInformationを取得します。

取得したconsentInformationに対して同意情報を更新します。その後、処理終了が非同期に通知されます。

val params = ConsentRequestParameters.Builder()
    .setTagForUnderAgeOfConsent(false)
    .build()
val consentInformation = UserMessagingPlatform.getConsentInformation(activity)
consentInformation.requestConsentInfoUpdate(activity, params, {
    // 処理終了。後述。
}, { error ->
    // Handle the error.
})

表示が必要なフォーム(許諾情報)があるかチェックし、更新する

処理終了の通知を受けてフォームの利用可否をチェックし、可能であればフォーム更新処理に進みます。

val params = ConsentRequestParameters.Builder()
    .setTagForUnderAgeOfConsent(false)
    .build()
val consentInformation = UserMessagingPlatform.getConsentInformation(activity)
consentInformation.requestConsentInfoUpdate(activity, params, {
    // 処理終了。
    if (consentInformation.isConsentFormAvailable) {
        // 同意フォーム利用可能。
        // 更新処理へ。後述。
        loadForm(activity, consentInformation)
    } else {
        // 同意フォーム利用不可。
        // 設定、フォーム更新中など。
        // 再更新を促すのが無難。
    }
}, { error ->
    // Handle the error.
})

同意フォームを更新し、同意フォームを表示、ユーザに許諾を得る

UserMessagingPlatform.loadConsentFormで同意フォームを更新します。これはメインスレッドで実施するよう、サンプルにコメントされています。

更新処理が終了すると非同期に通知されるので、それを契機に許諾状態を確認しましょう。その結果、未許諾(REQUIRED)であればフォームオブジェクトに表示を依頼します。これにより、ユーザに同意フォームが表示されるはずです。

ユーザ操作を契機にフォームが非表示になったことがアプリに通知されますので、これを契機に許諾状態を再度取得してください。これでユーザに許諾してもらえていれば、晴れてアプリ内で広告表示可能という状態になります。

private fun loadForm(consentInformation: ConsentInformation) {
  // Loads a consent form. Must be called on the main thread.
  UserMessagingPlatform.loadConsentForm(activity, { consentForm ->
    if (consentInformation.consentStatus == ConsentInformation.ConsentStatus.REQUIRED) {
      // 同意フォームを表示しユーザに許諾を得る。
      consentForm.show(activity) {
        if (consentInformation.consentStatus == ConsentInformation.ConsentStatus.OBTAINED) {
          // 無事にユーザから許諾を得られた!
        } else {
          // 同意を得られなかった...
          // 同意をもらえなかったことによる制御へ
        }
      }
    }
  }, { error ->
    // Handle the error.
  })
}

まとめ

今回はAdmobを利用するための UMPライブラリについて解説しました。広告をアプリ内に組み込むためにはAdmobによる広告表示処理と合わせてユーザに同意を得る処理も必要不可欠ですので、参考にしていただけると幸いです。

参考

2025年3月30日日曜日

Android Studio VCS Operations Popupをカスタマイズする方法を解説

どうも。どっことです。今回はAndroid StudioにあるVCS Operations Popupにカスタマイズ方法ついて解説します。

VCS Operations Popopとは

VCS Operations PopupはAndroid Studioでバージョン管理ツールの操作するときのポップアップです。control+vのショートカットで表示することができます。メニューから操作する手間が省けるので、多用している人もいるかと思いますが、今回はこのポップアップをカスタマイズする方法を解説します。

VCS Operations Popopのカスタマイズ

カスタマイズですが、実はAndroid Studioがカスタマイズするための設定項目を用意してくれています。なので謎の設定ファイルの書き換えや、ターミナルなどでの謎のコマンド操作は不要です。(地味に嬉しい)

Android StudioのSettings | Appearance & Behavior | Menus and Toolbarsにアクセスしてください。プロジェクトにGitを利用している場合は以下のようなポップアップが表示されると思います。

この画面で項目をカスタマイズすることができます。一覧の中にあるVCS Operations PopupからVCS.Operations.Popup.VcsAwareを選択している状態で+ボタンをクリックしましょう。

ここから項目を追加することができます。たとえば、Update projectをリストに追加してみましょう。一覧からUpdate projectを探してOKをクリックしてください。それで完了です。

それではVCS Operations Popupを表示してみましょう。control+vを入力すると...


追加したUpdate projectがポップアップに追加されていることを確認できました!これでいちいちVCSメニューまでマウス移動させる手間が省けますね!

まとめ

今回はAndroid StudioにあるVCS Operations Popupのカスタマイズ方法について解説しました。Android Studioや、そのベースとなっているIntellij IDEAでは、このポップアップに限らず、さまざまな項目をカスタマイズする設定画面を提供しているので、作業効率がより良くなるようどんどんカスタマイズしていきたいですね。

参考

2025年3月22日土曜日

Android/Jetpack Compose Imageに関するパラメータを紹介

どうも。どっことです。今回はJetpack ComposeのImageコンポーサブルについてチートシートを紹介したいと思います。

Imageコンポーサブルとは

Imageコンポーサブルは、名前の通り画像やイメージを表示するコンポーサブルです。まずはAndroidViewのImageViewで実装していたことが、Jetpack Composeでも同様に実現できるのかが気になるところですが、結局ImageViewで重要な設定であったscaleType、つまりイメージの見せ方についての設定がそれに相当するcontentScaleで実現でき、それ以外はModifierを使えば済んでしまうようです。(つまりImageに関する紹介がほとんどない...)

このページではscaleTypeに相当するcontentScaleについて軽く触れ、その設定がどのような見え方をするのかは公式サイトの方にお任せしようと思います。

Imageコンポーサブルを使うときのパラメータチートシート

まずは、必要最小限で設定できるImageのパラメータです。

Image(
  painter = painterResource(id = R.drawable.dog), // リソースから画像表示
  contentDescription = stringResource(id = R.string.dog_content_description) // 説明文
)

contentScale

contentScaleにはいくつかの設定項目があるので、一覧で紹介します。

  • ContentScale.Fit
    • アスペクト比を維持しながら拡大・縮小
    • scaleTypeのfitCenter相当
  • ContentScale.Crop
    • 空きスペースがなくなるように画像を拡大表示(くり抜き)
  • ContentScale.FillHeight
    • アスペクト比を維持しながら、高さに合わせて拡大・縮小
  • ContentScale.FillWidth
    • アスペクト比を維持しながら、幅に合わせて拡大・縮小
  • ContentScale.FillBounds
    • Viewの大きさに合わせて拡大・縮小(画像が歪む)
    • scaleTypeのfitXY相当
  • ContentScale.Inside
    • アスペクト比を維持しながら、縮小(拡大はしない)
  • ContentScale.None
    • 画像に対して何もしない
    • scaleTypeのcenter相当

まとめ

今回はImageコンポーサブルについて紹介しました。正直Image自体には目新しいパラメータがなかったので、contentScaleに関する紹介程度となりましたが、アプリ開発においてイメージの表示は必須機能なので、contentScaleのパラメータについては理解を進ませておきたいところです。

参考


2025年2月22日土曜日

Android/Jetpack Compose Modifierに関するパラメータを紹介

どうも。どっことです。今回はJetpack ComposeのModifierについてチートシートを紹介したいと思います。

前回はTextコンポーサブルを紹介しましたが、「そもそも共通で設定するパラメータってどこで管理するねん」となりまして、調べたらModifierというオブジェクトがあることがわかりました。 今回はこのModifierについて、チートシートとして紹介したいと思います。

Modifierオブジェクトとは

Modifierは、コンポーサブルに対して設定可能な共通のパラメータを管理しているオブジェクトです。 AndroidViewがわかる人なら、Viewに対して設定できる項目くらいの理解で問題ないと思います。

Modifierを使うときのパラメータチートシート

Modifierで設定できるパラメータは(よく使うものは)以下です。

    // ****** 横幅指定関連 ****** // 
Modifier.width(20.dp)                    // 幅指定(layout_width)
    .fillMaxWidth()                      // 幅いっぱい(layout_width="match_parent")
    .widthIn(20.dp, 20.dp)               // 最小・最大幅指定(minWidth,maxWidth)

    //****** 高さ指定関連 ******//
    .height(20.dp)                       // 高さ指定(layout_height)
    .fillMaxHeight()                     // 高さいっぱい(layout_height="match_parent")
    .heightIn(20.dp, 20.dp)              // 最小・最大高さ指定(minHeight, maxHeight)
    
    //****** 縦横まとめて指定 ******//
    .size(20.dp)                         // 縦横指定(layout_width,layout_height) 
    .size(20.dp, 20.dp)                  // 縦横指定(layout_width,layout_height) 
    .fillMaxSize()                       // 縦横幅いっぱい (layout_width="match_parent"/layout_height="match_parent")
    .sizeIn(20.dp, 20.dp, 50.dp, 50.dp)  // 最小・最大縦横指定(minWidth, minHeight, maxWidth, maxHeight) 
    
    //****** background関連 ******//
    .background(Color.Red)               // 背景色指定(background)
    
    //****** padding関連 ******//
    .padding(top = 10.dp)                // 上部余白 (paddingTop)
    .padding(start = 10.dp)              // 左余白 (paddingStart)
    .padding(end = 10.dp)                // 右余白 (paddingEnd)
    .padding(bottom = 10.dp)             // 下部余白 (paddingBottom)
    .padding(horizontal = 10.dp)         // 左右余白 (paddingHorizontal)
    .padding(vertical = 10.dp)           // 上下余白 (paddingVertical)
    .padding(10.dp)                      // 上下左右余白 (padding)
    
    //****** 位置調整関連 ******//
    .align(Alignment.CenterHorizontally) // layout_gravity(左右位置, Columnコンポーネント内などで)
    .align(Alignment.CenterVertically)   // layout_gravity(上下位置, Rowコンポーネント内などで)
    
    //****** タップ処理関連 ******//
    .clickable { /** */ }                // タップ処理(setOnClickListener)
    
    //****** 枠線関連 ******//
    .border(BorderStroke(3.dp, Color.Blue)) // 枠線をつける(witdh, color)

    //****** 角丸関連 ******//
    .background(Color.Red, RoundedCornerShape(10.dp))                  // 角丸背景にする
    .border(BorderStroke(3.dp, Color.Blue), RoundedCornerShape(10.dp)) // 角丸枠線をつけるそ

その他

dimens.xmlに定義した値をpadding/marginの値として使いたい

dimensionResourceを使えばリソース定義を参照できます。以下、参考ページから転記。

// In the res/values/dimens.xml file
// <dimen name="padding_small">8dp</dimen>
// In your Compose code
val smallPadding = dimensionResource(R.dimen.padding_small)
Text(
    text = "...",
    modifier = Modifier.padding(smallPadding)
)

まとめ

今回はModifierオブジェクトで設定できるパラメータについてチートシートとして紹介しました。目を通してみると、角丸の実装がサポートされるようになったのは非常に嬉しいポイントだと思いました。この記事も参考になりそうな項目を見つけたら随時更新していく予定ですので、参考にしていただけますと幸いです。

次回やるとしたら、Imageコンポーネントについて同様に紹介しようと思います。

参考

2025年2月15日土曜日

Android/Jetpack Compose Textに関するパラメータを紹介

どうも。どっことです。今回はJetpack ComposeのTextコンポーサブルについてチートシートを紹介したいと思います。

これまでのAndroidViewから、Jetpack Composeに移行が完了できた人も少なくないのではないでしょうか。私はそんな移行できていない人間の一人なのですが、今回はそんなJetpack Composeで画面を実装する時に「これってどうやって実装するんだっけ!?」となってもすぐに解決できるよう、チートシートとしてこのページに整理したいと思います。

Textコンポーサブルとは

Textコンポーサブルは、AndroidViewでいうところのTextView、つまりキホンのキですね。テキストを表示せずにアプリを作ることはできないので、まずはここから入るのが王道だと思います。

Textコンポーサブルをつかうときのパラメータチートシート

Textコンポーサブルに設定できるパラメータは(よく使うものは)以下です。

Text(
  "Hello World", 
  fontSize = 24.sp,    // テキストサイズ
  lineHeight = 30.sp,  // 行の高さ
  maxLine = 3,         // 最大行数
  minLine = 1,         // 最小行数
  color = Color.Red,   // テキスト色
  fontStyle = FontStyle.Italic, // テキストスタイル(斜体)
  fontWeight = FontWeight.Bold, // テキストスタイル(太字)
  textAlign = TextAlign.Center, // テキストの位置(gravity)
  overflow = TextOverflow.Ellpsis // テキストが収まらない場合の省略表示(ellipsize)
)

その他の使い方

リソースを参照してテキストを表示する

stringResourceを利用すると、リソースのテキストを参照できます。

Text(stringResource(R.string.app_name))

表示テキストを装飾する

buildAnnotatedStringを利用すると、装飾されたテキストを表示することができるらしいです。

今回は割愛。

まとめ

今回はTextコンポーサブルでよく使うであろうパラメータについて紹介しました。AndroidViewがDeprecatedになる前にJetpackComposeを身につけておきたいですね。

2025年1月4日土曜日

Android GridLayoutの使い方について解説

どうも。どっことです。今回はGridLayoutの使い方について解説します。

GridLayout

Androidのレイアウトを実装するとき、LinearLayoutやConstraintLayoutをよく利用しますが、Viewを格子状に配置する時はGridLayoutが便利です。 LinearLayoutを複数用意しなくても済みますし、ConstraintLayoutで必要なたくさんの属性値を設定しなくても済みます。また、一部のLayoutParam属性に初期値が設定されているので、実際に実装する時の設定項目が少なく済むのも良いところの一つかと思います。今回はそんなGridLayoutの使い方について解説します。

GridLayoutの使い方

今回は、レイアウトファイルでGridLayoutを利用する場合の実装を解説します。

<?xml version="1.0" encoding="utf-8"?>
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:columnCount="3"
    android:background="@android:color/holo_red_light"
    android:rowCount="1">

    <TextView
        android:layout_row="0"
        android:layout_column="0"
        android:padding="10dp"
        android:background="@android:color/holo_blue_bright"
        android:text="00" />
    <TextView
        android:layout_row="0"
        android:layout_column="1"
        android:padding="10dp"
        android:background="@android:color/holo_blue_dark"
        android:text="01" />
    <TextView
        android:layout_row="0"
        android:layout_column="2"
        android:padding="10dp"
        android:background="@android:color/holo_green_light"
        android:text="02" />
</GridLayout>
  1. GridLayout
    1. layout_width:match_content指定。画面幅いっぱい指定。
    2. layout_height:wrap_content指定。必要最小限の高さ指定。
    3. layout_row:1、layout_column:3をそれぞれ指定。1行3列の格子指定。
  2. GridLayout内の子View
    1. layout_width/layout_heightを指定していない。
      • これはGridLayoutが初期値としてwrap_contentを設定しているため。
    2. layout_columnlayout_rowにはそれぞれの配置位置を指定。

この実装でそのままレイアウトを確認すると、以下が表示されます。

GridLayout内のTextViewが全て左寄せに表示されました。子Viewに大きさを指定していないので、当たり前といったら当たり前ですね。

LinearLayoutのlayout_weightのように、要素を均等に広げるためには、layout_columnWeightlayout_rowWeightを使います。今回は横幅を均等に広げるため、layout_columnWeightを使います。

<?xml version="1.0" encoding="utf-8"?>
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@android:color/holo_red_light"
    android:columnCount="3"
    android:rowCount="1">

    <TextView
        android:layout_row="0"
        android:layout_column="0"
        android:layout_columnWeight="1"
        android:background="@android:color/holo_blue_bright"
        android:padding="10dp"
        android:text="00" />

    <TextView
        android:layout_row="0"
        android:layout_column="1"
        android:layout_columnWeight="1"
        android:background="@android:color/holo_blue_dark"
        android:padding="10dp"
        android:text="01" />

    <TextView
        android:layout_row="0"
        android:layout_column="2"
        android:layout_columnWeight="1"
        android:background="@android:color/holo_green_light"
        android:padding="10dp"
        android:text="02" />
</GridLayout>

これでレイアウトを表示すると以下になります。

均等幅に表示されました。

layout_rowWeightも同様です。上のケースは、GridLayoutの高さ(layout_height)がwrap_content指定になっているため、100dp指定に変更して試してみます。

まずは、何も指定せずに表示を確認します。

<?xml version="1.0" encoding="utf-8"?>
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="100dp"
    android:background="@android:color/holo_red_light"
    android:columnCount="1"
    android:rowCount="2">

    <TextView
        android:layout_row="0"
        android:layout_column="0"
        android:background="@android:color/holo_blue_bright"
        android:padding="10dp"
        android:text="00" />

    <TextView
        android:layout_row="1"
        android:layout_column="0"
        android:background="@android:color/holo_green_light"
        android:padding="10dp"
        android:text="10" />

</GridLayout>

これでレイアウトを確認すると以下になります。

layout_columnWeightの時と同様、上寄せに要素が表示されました。layout_rowWeightを指定してみましょう。

<?xml version="1.0" encoding="utf-8"?>
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="100dp"
    android:background="@android:color/holo_red_light"
    android:columnCount="1"
    android:rowCount="2">

    <TextView
        android:layout_row="0"
        android:layout_rowWeight="1"
        android:layout_column="0"
        android:background="@android:color/holo_blue_bright"
        android:padding="10dp"
        android:text="00" />

    <TextView
        android:layout_row="1"
        android:layout_rowWeight="1"
        android:layout_column="0"
        android:background="@android:color/holo_green_light"
        android:padding="10dp"
        android:text="10" />

</GridLayout>

これでレイアウトの表示確認をすると以下になります。

子Viewが均等な高さで表示されました。

まとめ

今回はGridLayoutの使い方について解説しました。LinearLayoutやConstraintLayoutで大抵のことは実現できてしまうところではありますが、実はGridLayoutもうまく使えば同じように便利に利用することができます。実はGridLayoutだと、LinearLayoutでよく困るアレを解決することができるのですが...これは別の記事で紹介したいと思います。

参考

2024年11月1日金曜日

Android 久しぶりにzxing-android-embedded を使ってみたので紹介

どうも。どっことです。今回はzxingを使ってみたので紹介します。

zxingを使ってQRコード/バーコードリーダーを実装する

一時期流行りましたよね。バーコードとか、 QRコードとか読み取って、いい感じになにかやること。ネイティブアプリで。そんな時に、今回使用したzxingというライブラリが非常に使いやすく、何度も私を助けてくれました。今回は、久しぶりにこのライブラリを使ってQRコード読み取り画面を実装してみたので紹介したいと思います。(最近はAndroid標準のカメラがいろいろ読み取ってくれるからもう出番ないんだよなぁ。。。

zxingの組み込みと実装の手順

ライブラリの追加(app/build.gradleに追記)

ライブラリを追加するので、例の如くapp/build.gradledependenciesに以下を追加します。

dependencies {
  implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
}

AndroidManifest.xmlに追記

今回はライブラリ側で「この制御をいれてね」という案内があるのでそれに従います。

<application android:hardwareAccelerated="true" ... >

実装

ハードウェア(カメラ)の制御や、読み取った時の解析はすべてzxing側でやってくれるので、アプリ側からは呼び出す箇所と、読み取り結果を受け取るところだけを実装すればOKです。こちらも例の如くActivityResultLauncherを使ってライブラリの画面を呼び出し・読み取り結果を制御します。

private final ActivityResultLauncher<ScanOptions> barcodeLauncher = registerForActivityResult(new ScanContract(),result -> {
    if(result.getContents() == null) {
        Toast.makeText(this, "Cancelled", Toast.LENGTH_LONG).show()
    } else {
        Toast.makeText(this, "Scanned: " + result.getContents(), Toast.LENGTH_LONG).show()
    }
})
public void launch(View view) {
    barcodeLauncher.launch(new ScanOptions())
}

まとめ

今回はzxing-androidを使ってみたということで紹介しました。標準の機能が便利すぎるとはいえ、まだまだ使い所はあるかと思いますので、是非参考にしていただければと思います。

参考

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)

まとめ

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

参考

2024年5月7日火曜日

Android Retrofitを使った情報取得する実装を解説

どうも。どっことです。今回は、Retrofitを使った情報取得する実装について解説します。

Retrofitを使った通信処理の実装

他の言語ではいくつか紹介していましたが、そもそもAndroidを紹介していないことに気づきました。そんなわけで今回は、Androidのデファクトスタンダードの一つとなっているRetrofitというライブラリを使った通信処理の実装を解説します。

実装手順

以下の手順で実装していきます。

  • build.gradleにライブラリを追加
  • AndroidManifest.xmに通信するパーミッションを追加
  • Retrofitによる通信処理の実装1
  • 通信するためのIFと、レスポンスデータクラスを追加
  • Retrofitによる通信処理の実装2

順に解説していきます。

build.gradleにライブラリを追加

まずはbuild.gradledependenciesに以下を追加します。

dependencies {
    // moshiライブラリ。Jsonをデータオブジェクトに変換してくれる。
    implementation("com.squareup.moshi:moshi-kotlin:1.15.1")
    // retrofitライブラリ。通信処理してくれるライブラリ。
    implementation("com.squareup.retrofit2:retrofit:2.11.0")
    // moshi-converter。 retrofit で moshi をいい感じに使ってくれるためのもの。
    implementation("com.squareup.retrofit2:converter-moshi:2.11.0")
}

AndroidManifest.xmに通信するパーミッションを追加

アプリ上で外部通信する場合、AndroidManifest.xmlにパーミッションの宣言が必要です。

以下を追加します。

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

Retrofitによる通信処理の実装1

今回利用するAPIとして、サンプルAPIを公開している以下を利用させていただきました。

まずは実際に通信処理をするインスタンスを作るため、Retrofitのインスタンスを生成しましょう。

val retrofit = Retrofit.Builder()
  .baseUrl("https://api.sampleapis.com")
  .addConverterFactory(
      MoshiConverterFactory.create(
          Moshi.Builder()
              .add(KotlinJsonAdapterFactory())
              .build()
      )
  .build()
  • baseUrlは接続先URLのドメインを指定します。パスは後述するインターフェース側に記載します。
  • addConverterFactoryは、通信結果により取得したJsonを解析させるためのライブラリを指定します。今回はMoshiライブラリを指定しています。

用意したretrofitオブジェクトは後ほど利用します。

通信するためのIFと、レスポンスデータクラスを追加

通信するためのインターフェースと、レスポンスデータクラスを追加します。

インターフェースはメソッド(@GET)やretrofitオブジェクトのドメインからのパス(/coffee/hot)を指定し、レスポンスとしてどのようなデータを返却してもらうかを定義します。

interface ApiInterface {
  @GET("/coffee/hot")
  fun hotCoffee(): Call<List<Coffee>>
}

またレスポンスデータとしてCoffeeクラスのリストを定義します。今回は簡単のため、titleだけを解析対象としてデータ定義します。

data class Coffee(val title: String)

他のデータも取得したい場合は、対応するものをメンバ変数に定義すれば解析してくれます。

Retrofitによる通信処理の実装2

先ほど追加したインターフェースを渡して、実際に通信処理してくれるインスタンスを生成しましょう。

val api = retrofit.create(ApiInterface::class.java)

あとは、生成したインスタンスで通信処理を依頼します。enqueue()メソッドで通信処理を非同期に依頼します。

api.hotCoffee().enqueue(object : Callback<List<Coffee>> {
    override fun onResponse(p0: Call<List<Coffee>>, response: Response<List<Coffee>>) {
        Log.d("RetrofitActivity", "response.isSuccessful:${response.isSuccessful}")
        Log.d("RetrofitActivity", "response.code:${response.code()}")
        Log.d("RetrofitActivity", "response.message:${response.message()}")
        Log.d("RetrofitActivity", "response.raw:${response.raw()}")
        Log.d("RetrofitActivity", "response.body:${response.body()}")
    }
    override fun onFailure(p0: Call<List<Coffee>>, error: Throwable) {
      // エラーハンドリング
      Log.w("RetrofitActivity", "onFailure", error)
    }
})

なお同じ通信処理の依頼でexecute()というメソッドがありますが、こちらは同期的に通信処理を依頼します。Androidではメインスレッドで通信処理を実施するとクラッシュしてしまうので、利用するタイミングの検討が必要となります。

まとめ

今回は、Retrofitを使った情報取得する実装について解説しました。通信処理による情報取得・送信はモバイルアプリでは必須な機能となるので、ぜひ参考にしてくださいね。

参考

2024年5月5日日曜日

Android mockito-kotlinを使ったテストコードの書き方を紹介

どうも。どっことです。今回は、mockito-kotlinライブラリを使ったKotlinでの単体テストの書き方を紹介します。

mockito-kotlinを使ったモック化

CI/CDの重要性が注目されて久しいですが、単体テストをはじめとしたテストコードを実装できるエンジニアはまだまだ多くありません。またテストコードは実装経験あるけど、いざ実装しようとした時に「あれってどうやるんだっけ」となるような、テストコードの実装がまだ手に馴染んでいない人も多いかと思います。

今回は単体テストのテストコードを実装する上で必須のテクニックとなるモック化について紹介したいと思います。

モック化とは

他の機能の影響によりテストが不安定な状態となることを防ぐため、あらかじめ固定の値を返したり、特定の振る舞いをさせるように指定しておくことです。

実装方法

以下の順に解説していきます。

  • 使用するライブラリ(mockito-kotlinライブラリの紹介)
  • 導入方法
    • 実装方法

    使用するライブラリ

    オブジェクトをモック化するにあたり、JavaやKotlinではさまざまなモック化ライブラリが公開されています。今回はその中のひとつであるmockito-kotlinを使った導入・実装方法を解説していきます。

    mockito-kotlin

    もともとJavaのモックフレームワークとして利用されているMockitoライブラリを、Kotlinでも簡単に利用できるような機能を具備しているライブラリがmockito-kotlinです。公式ページを参考欄に載せておくので、詳しい情報をお探しの方はそちらを参照してください。

    導入方法

    いつものごとく、build.gradleのdependenciesに追記します。以下を追加してください。

    dependencies {
      testImplementation "org.mockito.kotlin:mockito-kotlin:5.3.1"
    }

    実装方法

    それでは、モック化する実装方法について解説します。

    モック化の実装①戻り値の固定化

    戻り値を固定化する実装は以下です。 mockでモックするクラス、onでモックするメソッドを指定します。そしてdoReturnで戻り値を指定します。

    val mock = mock<Sample> {
        on { sample() } doReturn "sample"
    }

    モック化の実装②例外

    メソッドが呼ばれた時に特定の例外を投げるようなモックの実装は以下です。doThrowで例外を指定します。

    val mock = mock<Sample> {
        on { sample() } doThrow IllegalStateException()
    }

    モック化の実装③振る舞いの指定

    メソッドが呼ばれた時に特定の振る舞いをさせるモックの実装は以下です。doAnswerで具体的な振る舞いを指定します。

    val mock = mock<Sample> {
        on { sample() } doAnswer {
            // do anything.
        }
    }

    まとめ

    今回は、テストコードを実装する上で必要不可欠なテクニックであるモック化をmockito-kotlinライブラリを使って実装する方法について解説しました。他の機能に依存しない疎結合なテストコードを実装するためにモック化は重要なテクニックとなるので、手に馴染むまで何度も実装しましょう。

    参考

    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案として挙げられるので、有効活用していきたいですね。

    参考

    Android 現在表示しているActivityやFragmentを確認するコマンドを紹介

    どうも。どっことです。今回は、現在表示しているActivityFragmentを確認するためのコマンドを紹介します。

    表示中のActivity/Fragmentを確認する

    Androidアプリの開発中に「今表示しているActivityFragmentはなんだ...?」と調べる機会があったので、調べた結果を紹介します。

    表示しているActivityやFragmentを確認する

    Activityを確認する

    以下のコマンドで確認できます。

    $ adb shell dumpsys activity top

    Fragmentを確認する

    以下のコマンドで確認できます。

    $ adb shell dumpsys activity top | grep 'Added Fragments' -A 10

    まとめ

    今回は表示しているActivityFragmentを確認するためのコマンドを紹介しました。Android Studioを使っているだけではあまり知る機会のないものですが、開発・デバッグに活かしていきたいですね。

    参考

    2023年12月12日火曜日

    Android テキスト周りの超細かい余白を取得する方法を解説

    余白をテキストのピクセル単位でデザイン要求してくるデザイナー、絶対に許さない。

    解説

    テキストのベースラインから、上の余白(top, ascent)や下の余白(descentやbottom)をピクセル単位で取得することができます。この値を使って、数ピクセル単位の余白のズレも調整することができます。

    val textPaint = Paint(Paint.ANTI_ALIAS_FLAG)
    textPaint.setTextSize(12)
    // FontMetricsの取得
    val fontMetrics : FontMetrics = textPaint.getFontMetrics()
    val topY = fontMetrics.top
    val ascentY = fontMetrics.ascent
    val descentY = fontMetrics.descent
    val bottomY = fontMetrics.bottom

    正直に言います。もう二度とやりたくありません。

    参考

    移行予定

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