LoginSignup
8
9

Androidアプリ開発を何も知らない人がキャッチアップするまでの記録

Last updated at Posted at 2023-04-07

ニュース

Android15リリース

2024/3 にプレビューが公開。4月からベータ期間となり、6月からは新規機能の公開は終了してバグ修正のみとなっていき、8月に正式公開となる。

  • 広告SDKが機密情報を窃取したりしないよう、広告SDKなども通常アプリと同じくサンドボックス空間内に閉じ込めるPrivacy Sandbox機能
  • 開発者向け アプリ内に埋め込んだファイルの改竄検出のためのAPIが追加された。

Android アップデート対応

こちらの記事で一通りまとめてくれている。
Zenn - Androidでの新規リリースとアップデート対応

参考書

書籍については、内容をまとめるのは著作権的に問題があるのと、量も多く難しいため、読んだ書籍と内容のポイント、自分で調べ直した部分があればそこを記載します。

Androidアプリ開発の極意, 木田学 ほか, 技術評論社, 2017

やや古い本だが、Activityベースのアプリについて様々な実務ポイントを紹介している。

Activity Launchmode

Activityの表示形式について5種類がある。この本の執筆時に比べて一種類増えている。

Google - App Manifest File - Activity

Activity 名やアイコンの動的な変更

Acticity Aliasを利用することで、一つのActivityについて、名前やアイコンを動的に変えることもできる。

Androidのアプリアイコンとタイトルを動的に変更する

Service

UIを持たないクラスで、アクティビティが終了してもバックグラウンドで動作を続けられるクラス。例えば、通信などを行うクラスなど。

【Android】サービスの概要とサンプルコード
Google Developers - サービスについて
Google Developers - サービスをテストする

ブロードキャスト

Android developers - ブロードキャストの概要

パーミション

カメラアクセス、連絡先アクセス、位置情報取得など、ユーザーに対してセンシティブな影響のある機能は、ユーザーの許諾を得る必要がある。

Google Develoer - Android での権限
Google Developers - Manifest.permission

ユーザーがNever Ask Again(二度と権限許可ダイアログを表示するな)
を選択したかを判定するために、権限ダイアログを出す前にshouldShowRequestPermissionRationaleを呼んでおくことが推奨される。

Android M Permissions : Confused on the usage of shouldShowRequestPermissionRationale() function

Android Mより、許可がないのに許可が必要な操作をしようとすると、SecurityExceptionが発生するようである。

Stack Over Flow - Android security exception for permission listed in manifest file

Dozeモード、App Standby

アプリを使わずにバッテリーに接続もせずに放置していると、これらの省電力モードに突入し、アプリの機能が制限される。

Doze とアプリ スタンバイ用に最適化する

RelativeLayout, LinearLayout

RelativeLayoutは親、各部品同士の相対的な位置関係を比較的自由に設定できる。Linear Layoutは、横一列または縦一列の単純な配置となる。

メモ:ビューの配置 - LinearLayout と RelativeLayout
LinearLayoutとRelativeLayoutの配置属性メモ
LinearLayoutとRelativeLayoutの使い方をまとめてみた

部品同士に固定サイズの隙間を設定したい場合はSpaceを設定できる。可変長の隙間を設定したい場合はViewを利用できる。

Adding blank spaces to layout

パフォーマンスについて、一般には複雑になればなるほどパフォーマンス的な問題が発生する可能性がある。あまりに複雑な画面の場合、減らせる部品は減らすなどの配慮がいるだろう。

Is a RelativeLayout more expensive than a LinearLayout?

Android ユニットテスト ヒッチハイク・ガイド, okuzawats, 2024, Retrieved from https://zenn.dev/okuzawats/books/android-unit-testing

Android 依存性注入 ヒッチハイク・ガイド, okuzawats, 2024, Retrieved from https://zenn.dev/okuzawats/books/android-dependency-injection

公式実装例

公式UIガイドライン

Introducing the Android Design Hub: The ultimate resource for building exceptional user interfaces across all form factors

Material Design (3)
androidx.compose.material3

その他、実装例

  • アーキテクチャ、UI(Jetpack Compose)の参考など

kliment-jonceski/JetPackPlaygroundApp

Intent

インテントとは、「何かを行う意図」という意味で、幅広い役割として利用可能ですが、特定のアクションをリクエストする時に使用できる、メッセージングオブジェクトのことを意味しています。例えば別の画面を開きたい時に、AndroidではActivity単位で画面が作られますが、「開いている画面に対して別の画面を開いてください」とアプリにメッセージとして伝えることで、別の画面を開くことが可能になります。
 今回は基本的な使い方として、「Activityを起動する」、「Serviceを起動する」、「ブロードキャストを配信する」の3点をIntentを使った例として紹介します。

【Androidアプリ開発】Intentとは、Intentの使い方とは。

Jetpack Compose

Android版の
flutter, SwiftUIとでもいうべき、宣言的に簡潔にUIが記述できるライブラリ。https://qiita.com/kota_2402/items/7bbdd87be8024785e25b
Compose でのレイアウト

@Composableアノテーションのついた関数によって、宣言的にUIを構築する。

バージョン2と3があるが(2023/7現在)、新しくアプリを作り始めるのであれば最新版を選択すべきだろう。

Compose でマテリアル 2 からマテリアル 3 に移行する

基本の考え方

  • 何か一つのパラメータが変更されたからといって全ての@Composable関数が再描画されるわけではない。それだと膨大なコストがかかってしまう。一部の@Composable関数のみが再描画されることがありうる。それどころか、一つの@Composable関数の中のUI部品のうち、必要なUI部品のみが再描画の対象となり、他の部分は再描画されないということさえありうる。

  • 再描画が終わらないうちにもう一度再描画の指示がされた場合は、終わっていなかった再描画はキャンセルされる。

  • 場合によっては、@Composable関数が UI アニメーションのフレームごとに実行され、一秒間に数百回も実行される可能性がある。高価な副作用を@Composable関数に書いてしまうと、パフォーマンスが悪化する。

    • そのため、@Composable関数のなかに「UIを描画する」以外の副作用は一切書くべきではない。例えば、共有の領域にある変数を変更するなど。そのような副作用が必要な場合は、@Composable関数のなかで直接行うのではなく、onClickなどのコールバックを@Composable関数に渡して行うようにする。
  • 以下、引用

Test.kt
@Composable
@Deprecated("Example with bug")
fun ListWithBug(myList: List<String>) {
    var items = 0

    Row(horizontalArrangement = Arrangement.SpaceBetween) {
        Column {
            for (item in myList) {
                Text("Item: $item")
                items++ // Avoid! Side-effect of the column recomposing.
            }
        }
        Text("Count: $items")
    }
}
  • 以上、引用 Compose の思想より
  • この@Composable関数の中では、itemsというプロパティを更新しているが、再描画が行われる回数はスキップされたりし厳密なものではないことから、このような副作用を記載してはいけない。

状態 State について

↑で@Composable関数の中に変数を作ってそれを変更することで状態管理などをしてはいけないと述べた。普通の変数ではなく、remember または rememberAsSaveableという関数を用いて、MutablState<T>という型の変数を設ければ、@Composable関数の中で状態管理をしても良い。

例えば以下のようになる。

example.kt
val mutableState = remember { mutableStateOf(default) }
// or
var value by remember { mutableStateOf(default) }
// or
val (value, setValue) = remember { mutableStateOf(default) }

全体の例としては以下のようになる。テキストフィールドにユーザーが何か入力すると、その文字によって表示を変える例である。remember関数を用いて、MutableStateという型の状態nameを保持させている。

HelloContent.kt
@Composable
fun HelloContent() {
   Column(modifier = Modifier.padding(16.dp)) {
       var name by remember { mutableStateOf("") }
       if (name.isNotEmpty()) {
           Text(
               text = "Hello, $name!",
               modifier = Modifier.padding(bottom = 8.dp),
               style = MaterialTheme.typography.h5
           )
       }
       OutlinedTextField(
           value = name,
           onValueChange = { name = it },
           label = { Text("Name") }
       )
   }
}

また、Androidで用いられるオブザーバブルな型から値を持ってきて、Composeで使うMutableStateへと簡単に変換する関数も用意されている:

@Composable関数内部で、rememberなどを用いて状態を持っている場合これをステートフルと呼ぶ。逆に、@Composable関数内部では状態を持たず、むしろ関数への引数として必要な値やクロージャーを渡して運用するようにする場合もあり、これはステートレスと呼ぶ。ステートレスの場合は、例えばViewModelなど外側で状態管理をすることになるだろう。

慣習として、表示内容は同じだが、ステートレスとステートフルな関数2つを用意する場合がある。↓

example.kt
@Composable
fun HelloScreen() {
    var name by rememberSaveable { mutableStateOf("") }

    HelloContent(name = name, onNameChange = { name = it })
}

@Composable
fun HelloContent(name: String, onNameChange: (String) -> Unit) {
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            text = "Hello, $name",
            modifier = Modifier.padding(bottom = 8.dp),
            style = MaterialTheme.typography.h5
        )
        OutlinedTextField(
            value = name,
            onValueChange = onNameChange,
            label = { Text("Name") }
        )
    }
}

以上、コードはこのページより引用した: 状態と Jetpack Compose

ComponentActivity

LaunchedEfect

  • 一つ目の引数(key)に何らかの変更が加わるたびに、二つ目の引数で指定したクロージャ内の処理を実行する。実行中にもう一度実行指示が出た場合は、実行中の処理をキャンセルし、新しい処理の方に行く。

Modifier

ComposeのUI部品について、背景色、パディング、高さや横のサイズ、輪郭などなど、さまざまな設定を付与できる。

Android developer - Compose 修飾子のリスト
Qiita - Jetpack Compose Modifier(修飾)

複数のModifierを使った場合、Modifierが適用されるのは、上から順である。

Modifiers in Jetpack Compose — Basic Concepts to Get You Started

部品の角を丸くしたい。

.clipモディファイアを使えば可能である。四隅のうち、特定の角のみを丸くすることも可能である。以下は、下方の角のみを丸くした例。

home.kt
        modifier = modifier
            .clip(shape = RoundedCornerShape(0.dp, 0.dp, 25.dp, 25.dp))

Icons

ボタンなどで使えるアイコンとしてMaterial Iconが用意されている。

Material Icons

以下のように用いる。

HomeScreen.kt
Icon(Icons.Rounded.Favorite, contentDescription = "Favorite")

Scaffold

UI部品の最下層に設置することが多く、他の画面(UI部品)の他、アプリバー、フローティングアクションボタン、スナックバー、ドロワーなどを設定できる。

Scaffoldのラムダ式には引数としてpaddingValuesという値が渡され、子UI部品にModifier経由でパディングとして設定することで、アプリバーなどの分の余白を考慮したデザインとできる。

  • 以下、引用
Screen.kt
Scaffold(/* ... */) { contentPadding ->
    // Screen content
    Box(modifier = Modifier.padding(contentPadding)) { /* ... */ }
}

AppBar

Scaffoldに設定できるアプリ上部のバー。
androidx.compose.material - TopAppBar

Material3 ではCenterTopAppBarというのがあるので、これを使うとタイトル部分を中央寄りにできる。

CenterAlignedTopAppBar
How to align title at layout center in TopAppBar?

Column, Row, LazyColumn, LazyRow, LazyVerticalGrid

要素数が固定のリストを表示したいときはColumn, RowでOKだが、要素数がいくつかが実行時まで決まらず、多くの要素数があると想定される場合は、パフォーマンス上の問題が生じることから、LazyColumn, LazyRow, LazyVerticalGridを代わりに使う。

Android Developers - リストとグリッド
Jetpack Compose Playground - LazyVerticalGrid

Column, Rowで空いたスペースを画面いっぱいに埋めるようにしたい。

Column, Rowの子として以下のようなSpacerを入れれば良い。

Spacer(modifier = Modifier.weight(1.0f))

なおweightがついた子が複数ある場合は、weightの値の割合に基づいて空いているスペースが分配されるとのこと。

Google Developers - 修飾詞

Box

縦(Z軸)にUI要素を重ねていきたいときに使える。以下は、丸い背景の上にアイコン画像を重ねてみた例である。

HomeScreen.kt
            Box(
                modifier = Modifier
                    .size(90.dp)
                    .clip(CircleShape)
                    .background(colorResource(id = type.colorResourse())),
                contentAlignment = Alignment.Center,
            ) {
                Image(
                    imageVector = type.icon(),
                    contentDescription = type.contentDescription(),
                    modifier = Modifier
                        .size(45.dp)// Making this Image 90.dp cause the icon gettting big so much, so setting 45.dp. Instead, made Box 90.dp.
                        .clip(CircleShape)
                        .background(colorResource(id = type.colorResourse())),
                    colorFilter = ColorFilter.tint(Color.White)
                )
            }

Button, OutlinedButton, TextButton

見た目の目立つ度合いに応じて、三種のボタンがJetpack Composeにて用意されている。

Button OutlinedButton TextButton
スクリーンショット 2023-04-02 17.47.38.png スクリーンショット 2023-04-02 17.47.31.png スクリーンショット 2023-04-02 17.47.26.png

※ 画像はAndroid公式ドキュメントより引用

  • 色を変える場合はcolors引数にButtonColorsという型のオブジェクトを渡す。
test.kt
Button(
    onClick = tappedPasteButton,
    shape = ButtonDefaults.filledTonalShape,
    colors = ButtonDefaults.buttonColors(
        colorResource(id = R.color.light_gray),
        colorResource(id = R.color.white),
        colorResource(id = R.color.black),
        colorResource(id = R.color.black),
    ),
) {
// content
}

ButtonDefaults
JETPACK COMPOSE: BUTTONコンポーザブルを使う

Typograhy

Textなどの文字の大きさを変えることができます。

Typography - Material Design3
[Jetpack Compose]TextStyle を適用する Text コンポーザブルを予め定義しておくと便利

使い方

  • Android studioで「File > New > Activity > Empty Compose Activity」を選択するなどして、「ComponentActivity」を継承したActivityを作るなどする。
  • @Composableマークのついた関数を作る。これによりレイアウトが作られる。
  • @Previewマークをつければ、Android Studioでプレビューが即確認できるようになり大変便利である。
  • @RequiresOptInがついているマークがついているクラスを使いたい場合は、@OptIn
    マークをつけて使う必要がある。

Android Compose のチュートリアル

子部品の配置を決定する際、親部品の配置を参考にしたい。

BoxWithConstraintsを利用するとできる。

Home.kt
            BoxWithConstraints {
                print("maxHeight $maxHeight")
                inputArea(
                    height = maxHeight - bottomUpperHeight,
                )
            }

Composeでのレイアウト - レスポンシブレイアウト
BoxWithConstraintsScope

google/accompanist

Composeで正式に採用されていないような機能をComposeで使えるようにするという目的で実装された第三者ライブラリの集まり。https://github.com/google/accompanist

ViewModel

ViewModel の概要

Android アプリの場合、Activity や Fragment があり、これらに View があったり、ロジックがあったり、一時的なデータを保持したりしています。
Activity や Fragment は、リソース確保や画面回転などにより、 Android フレームワークによって破棄されることがあります。そのときに、Activity や Fragment の内部で保持していたデータもいっしょに破棄されてしまいます。
しかし、アプリ内部の一時的なデータを Activity や Fragment ではなくて ViewModel に持たせることで、Activity や Fragment が一時的に破棄されてしまっても、一時的なデータを保持し続けることができるようになります。

build.gradle
dependencies {

    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0"
    implementation "androidx.activity:activity-ktx:1.4.0"
    implementation "androidx.fragment:fragment-ktx:1.4.1"
}

// by で ViewModel インスタンスを取得する。
val viewModel: MainViewModel by viewModels()

// ViewModelProvider で ViewModel インスタンスを取得する。
val viewModel = ViewModelProvider(this)[MainViewModel::class.java]

【Android × Kotlin】ViewModel の基本的な使い方
ViewModelコンポーネントの使い方編【Android Jetpack】

LifeCycle Component

Activity, Fragmentはライフサイクルを持っているが、そこに直接いろいろなコードを書いていくとごちゃごちゃして肥大化していく。そこで、別のクラスからこれらのクラスのライフサイクルを参照してスッキリ書けるようにできるもの。

testLifeCycle
class MyObserver : DefaultLifecycleObserver {
    override fun onResume(owner: LifecycleOwner) {
        connect()
    }

    override fun onPause(owner: LifecycleOwner) {
        disconnect()
    }
}

myLifecycleOwner.getLifecycle().addObserver(MyObserver())

LiveData

LiveData の概要
LiveDataコンポーネントの使い方編【Android Jetpack】

ライフサイクルに応じて自動で購読破棄などができるデータ監視の仕組みである。

注意点はいろいろあるが、FragmentでObserveするときは、LifecycleOwner引数に対して自分自身(this)ではなく、viewLifeCycleOwnerを渡すこと。Fragmentのライフサイクルの状態が DESTROYED に変わるまでに、onCreateViewより後は複数回コールされる可能性があるため、前の監視が開放されずに残ってしまい、そのまま新しく監視が開始してしまう場合がある。

Navigation

画面遷移を簡単にできるようにしたもの。

10分で作る、Navigationによる画面遷移

Composeと組み合わせて使用する場合
  • 以下、引用
  • NavControllerクラスインスタンスの作成
val navController = rememberNavController()

  • NavHostの作成
NavHost(navController = navController, startDestination = "profile") {
    composable("profile") { Profile(/*...*/) }
    composable("friendslist") { FriendsList(/*...*/) }
    /*...*/
}

  • 移動の実行

navigate()関数を用いる。

navController.navigate("friendslist")

// Pop everything up to the "home" destination off the back stack before
// navigating to the "friendslist" destination
navController.navigate("friendslist") {
    popUpTo("home")
}

// Pop everything up to and including the "home" destination off
// the back stack before navigating to the "friendslist" destination
navController.navigate("friendslist") {
    popUpTo("home") { inclusive = true }
}

// Navigate to the "search” destination only if we’re not already on
// the "search" destination, avoiding multiple copies on the top of the
// back stack
navController.navigate("search") {
    launchSingleTop = true
}

Android ViewBinding

xml上においてあるUI部品に、idで検索するというタイポがありうる形式ではなく、直接アクセスできるようにする機能。
公式 https://developer.android.com/topic/libraries/view-binding#kotlin
【Android Studio】View Bindingの使い方(Kotlin編)

Lyra

Googleが開発している、非常に小さい容量で高音質な音声データをエンコード・でコードできるコーデック。※ コーデックとは、映像や音声データをエンコード(符号化)/デコード(復号化)するプログラムのことです。https://www.stream.co.jp/blog/blogpost-13900/

Logcat

Android Studio Dolphin よりさらに便利に使えるようになった、ログを出すAndroid Studioの機能。https://zenn.dev/yumemi_inc/articles/2022-09-29-explalin-new-logcat

Custom Chrome Tabs

ChromeなどのブラウザをAndroidアプリでアプリ内の画面のようにして出せる機能。

公式デモ https://github.com/GoogleChrome/android-browser-helper/tree/main/demos/custom-tabs-example-app
解説 https://qiita.com/droibit/items/66704f96a602adec5a35

なお一般的にはwebコンテントを使うにはWebViewや、外部のブラウザを開くなどの方法がある。https://developer.android.com/develop/ui/views/layout/webapps

Google Play In-App Review API

Google playでのレビューをお願いするウィンドウをAndroidアプリ内で表示させることが出来ます。実装方法はシンプルですが、動作確認のほうがめんどくさいとのこと。

Activity

アクティビティの概要

  • iOSでいうViewController
  • main() メソッドで起動するアプリをプログラミングする際の枠組みとは異なり、1 つのアプリが別のアプリを呼び出すとき、呼び出し元のアプリは、別のアプリ全体をひとまとめとして呼び出すのではなく、その中のアクティビティを呼び出す。
  • アクティビティの実装は Activity クラスのサブクラスとして行う。
  • アクティビティにより、アプリが UI を描画するウィンドウが用意される。通常、このウィンドウは画面全体に表示されますが、画面よりも小さく、他のウィンドウの上に重なることもある。
  • アクティビティをアプリで使用するには、アプリのマニフェストにその情報を登録する必要がある。
  • インテント フィルタ: 明示的なリクエストだけでなく、暗黙的なリクエストに基づいてアクティビティを起動する機能。この機能を使用するには、 要素の中の 属性を宣言する。
  • ライフサイクルは、onCreate()、onStart()などがある。

Adapter

Android Developers - Adapter
Android Developers - AdapterView
アンドロイド アプリ開発講座 - アンドロイド Adapter について
Androidのお勉強 第二回 ListViewと独自Adapterについて

  • ListView, GridView, Spinner, Gallery など、AdapterViewを継承クラスする画面部品のクラスを使う際に、それらのクラスに渡すものとしてAdapterが必要となる。
  • UI部品とデータソースを仲立ちする役目を持っている。
  • Android公式で用意しているAdapterクラスをそのまま使っても良いし、自分でAdapterを継承してクラスを作っても良い。

Contract

Medium - Model-View-Presenter: Android guidelines
Android Architecture Blueprints

  • View, Presenterのインターフェースをまとめて記載しておく場所。

API Level, SDK Versionの関係

Android SDK API Level 一覧

画像、文字列などのリソース

通常、/resフォルダ内にさまざまなサブフォルダを作り、その中に配置することとなっている。どのようなサブフォルダにどのようなリソースを置くかは、下記参照。

アプリのリソースの概要

新規のリソース(xmlファイル)はandroid studioの[File] > [New] > [Android resource file]で追加できる。

参照: アプリのリソースを追加する
ベクター型ドローアブルの概要

フォント

res/fontフォルダ以下に配置することとなる。.ttfファイルや、.xmlファイルを置ける。

XML フォント

アプリアイコン(mipmap)

mipmapフォルダにアプリアイコンを配置できます。

各種のピクセル密度をサポートする

スタイル・テーマ

スタイル・テーマ

Room

SQLiteをAndroidで扱うためのライブラリである。

【Android】はじめてのRoom
Google Developers - Room
github - iTakahiro/ArchitectureFootball

DataStore

Jetpackを使っている場合で、データベースに入れるまでもないちょっとしたデータを保存したい場合はDataStoreを使うと良い。

上記のリンクは公式ドキュメントであるが,Proto DatastoreについてProtobufというライブラリが必要なのにそのことが書いていないので、むしろこのCodeLabを見たほうが良いと思われる。

なおJetpackを使っていない場合はSharedPreferenceを利用する。

既存のスプラッシュ画面の実装を Android 12 以降に移行する

既存のスプラッシュ画面の実装を Android 12 以降に移行する
アンドロイド12以降では、アプリの最初のスプラッシュ画面をシステムが用意したものに置き換える必要がある模様。

Kotlin Coroutine

(メイン)スレッドをブロックすることなく、並行処理・非同期処理を行うための仕組み。

チートシート

  • 以下、引用

スクリーンショット 2023-03-13 20.48.53.png

  • 以上、引用 Kotlin Flows ~ an Android cheat sheetより

    • おおむね以下のような構成となっているようである。
    • Flow: 非同期データストリームで、正常終了またはエラー付き終了を行う。
    • SharedFlow: Flowを継承したもの。一つのデータフローを複数のサブスクライバーの間で購読するので、複数から購読されても一つのフローしか走らない。任意の数のリプレイキャッシュを持つ。
    • MutableSharedFlow: SharedFlowを継承したもので、値をフローに流すための関数も持つ。
    • StateFlow: SharedFlowを継承したもの。必ず初期値を持つ。リプレイ用のキャッシュの数は強制的に1個のみとなる。購読をせずに最新の値を取得する機能も持つ。
    • MutableStateFlow: StateFlowを継承したもので、値をフローに流すための関数も持つ。
  • Kotlin Coroutines Cheat Sheet

  • ARE KOTLIN COROUTINES ENOUGH TO REPLACE RXJAVA?: RxJava と kotlin croutineの対照表。

オペレータ

  • 一覧
    -Flow

  • マーブルダイアグラム(そのオペレータに値を流したらどうなるかを図にしたもの)

  • onStart

    • FlowがCollectを開始する前に呼ばれる
  • stateIn

    • coldである「Flow」をhotである「StateFlow」に変換することができる。

その他

  • Backpressure: RxJavaで使われていた用語のようで、パブリッシャーの出すイベントが多すぎる時などにサブスクライバが処理できるようにフィルタリングしたりするオペレータのことを指す。

Fragmentとは

使い回しができるUI部品である。
Android Developers - フラグメント
Android はじめてのFragment

Dagger

DIが便利にできる機能。
Android Developers - Dagger の基本
github - Dagger
Dagger - Documentation

【Android】はじめてのDagger2
Dagger 2 Tutorial For Android: Advanced

@Inject マーク

これをコンストラクタの前につけることで、どのように該当クラスを初期化すべきなのかDaggerに知らせることができる。

メンバ変数の前につけるやり方もあるが、その場合は@Component を用いる必要がある。

@Component マーク

これをインターフェース、宣言の前につけることで、公開する型が満たす必要がある依存関係をすべて持ったコンテナの生成を指示する。このコンテナを見ることで、repositoryなど各クラスに必要なクラスのオブジェクトが準備される。

具体的には、@ComponentをつけたHogehogeComponentというのを作ったのであれば、自動的に「DaggerHogehogeComponent」というクラスが生成され、そこからさまざまなクラスをbuild()メソッドで作っていくことができる。

@Provides マーク

@InjectではInterfaceは初期化できない・サードパーティのクラスは初期化できない・初期化時に設定が必要なものを設定できないことがあるため、そういう時に代わりに使用する。

@Bindsマーク

@Providesにおいて、インターフェースにマークをつける時、ただ特定のクラスを実装として返したいというだけの場合は、代わりに@Bindsマークをつけることが推奨されている。

@Binds@Providesが両方あるModuleは普通は作れないので、別に一個モジュールを作るか、どうしてもモジュールを一個だけにしたい場合はcompanion objectと@JVMStatic というマークを利用した書き方をする必要がある。詳しくは:

Using @Binds

@Moduleマーク

@Provides, @Bindsは全て@Moduleマークのついた括弧内に収まる必要がある。

@Singletonマーク

@Providesと一緒につけることでシングルトンオブジェクト化する + Applicationクラスをカスタマイズする必要がある。詳しくは下記参照。
Providing Singleton Values

Dagger2 Android拡張

  • Android向けにDagger2をさらに便利に描けるようにできる機能。

Dagger2のAndroidサポートが何をしているのか理解する

しかし、Android拡張を使って以下の手順を行うと、上記の定型的なコードを書く必要がなくなります。
ApplicationComponentの@Component.modulesにAndroidSupportInjectionModuleを追加する。
ActivityBindingModuleを作成し、ApplicationComponentの@Component.modulesに設定する。
ActivityBindingModuleでは、Member Injectionの対象となるActivityを戻り値とした引数なしメソッドを宣言する。そのメソッドには、@ContributeAndroidInjectorを付与する。これにより、ActivityにMember InjectionするためのSubComponent(=AndroidInjector<対象のActivity>)とAndroidInjector<対象のActivity>.Factoryが自動生成される。このAndroidInjector<対象のActivity>.Factoryは1.のAndroidSupportInjectionModule(厳密にはそれが保持している AndroidInjectionModule)のMapにActivityのクラス名をキーとしてMultiBindingされる。
ActivityでDaggerAppCompatActivityを継承する。
これにより、Activityは以下のように振る舞います。
ActivityのonCreateでベースクラスDaggerAppCompatActivityのAndroidInjection.inject(this)が実行される。
AndroidInjection.inject(this)で、Applicationが保持しているDispatchingAndroidInjector のinject(this)が実行される。
DispatchingAndroidInjector.injectでは、AndroidInjectionModuleによりDispatchingAndroidInjectorにInjectされたMapから、ActivityのAndroidInjector.Factoryを取得する。
取得したAndroidInjector.FactoryからActivityのAndroidInjectorを生成し、AndroidInjector.injectを呼び出し、ActivityにMember Injectionする。

Android Architecture BlueprintsでDagger2のAndroid拡張を理解する

DaggerAppCompatActivity

  • Android拡張を利用する際は、自分で作ったActivityをこのDaggerAppCompatActivityを継承させる必要がある。

Hilt

Daggerの上に構築されたDIライブラリ。Hiltとは剣のつかの意。Daggerにはないアノテーションマークがある。

Hiltを使ってViewModelに依存性を注入(DI)する

公式ドキュメントは以下
Hilt を使用した依存関係の注入
Dependency injection with Hilt

@HiltAndroidApp マーク

Hiltを使う場合は、「Application」を継承するクラスを作り、「@HiltAndroidApp」をつける必要がある。

@HiltViewModel マーク

ViewModelの宣言部分に@HiltViewModel マークをつける。コンストラクタには@Injectをつける。

hiltViewModel()

ViewModel初期化時にはhiltViewModel()を使う。

Springboot + kotlin

kotlinでwebアプリケーションを作るときによく使うDIフレームワークだそうだが、AndroidアプリのGoogleの公式実装例で使われていたので軽く参考サイトを記載するにとどめる。

【Springboot / Kotlin】アノテーションを学ぶ(DI系)
Spring BootでサーバーサイドKotlin入門
Spring Boot + Kotlin で依存性を注入する

@StringRes, @DrawableRes などアノテーションによるコード検査

Android開発において文字列、画像などのリソースにアクセスする際、int型のIDを使ってアクセスする。しかしただのint型なので、間違った値を渡してしまい、クラッシュするなどの危険がある。これを避けるため、androidx.annotationというパッケージが用意されており、文字列、画像などリソースの種類に応じたアノテーションをつけることで間違いを避けることができる。

他の Jetpack ライブラリを使用している場合は、androidx.annotation の依存関係を追加しなくてもアクセスできる場合がある。

アノテーションによるコード検査の改善

  • 以下、引用
アノテーション リソース ID の種類
@AnimatorRes R.animator.xxx で参照するリソース ID
@AnimRes R.anim.xxx で参照するリソース ID
@AnyRes いずれかの種類のリソースの ID(他の具体的なアノテーションを使用することを推奨)
@ArrayRes R.array.xxx で参照するリソース ID
@AttrRes R.attr.xxx で参照するリソース ID
@ColorRes R.color.xxx で参照するリソース ID
@DimenRes R.dimen.xxx で参照するリソース ID
@DrawableRes R.drawable.xxx で参照するリソース ID
@FontRes R.font.xxx で参照するリソース ID
@IdRes R.id.xxx で参照するリソース ID
@InterpolatorRes R.interpolator.xxx で参照するリソース ID
@LayoutRes R.layout.xxx で参照するリソース ID
@NavigationRes R.navigation.xxx で参照するリソース ID
@RawRes R.raw.xxx で参照するリソース ID
@StringRes R.string.xxx で参照するリソース ID
@StyleableRes R.styleable.xxx で参照するリソース ID
@StyleRes R.style.xxx で参照するリソース ID
@TransitionRes R.transition.xxx で参照するリソース ID

RecyclerView

iOSで言ったらTableView, CollectionView的なもので、 パフォーマンスへの影響を最小限に抑えながら、動的なリストを作成できる。ViewHolder, Adapterというクラスを一緒に使用する。

RecyclerView で動的リストを作成する
Recyclerview
RecyclerViewの基本

RxKotlin, RxJava

リアクティブプログラミングが可能。はRxJavaRxKotlinを使いやすくしたものなので、前者の方がドキュメント・情報は多そう。

Rxで知っておくと便利なSubjectたち
【Android / Kotlin】Rx を理解する(ストリーム編)
RxKotlin入門を目指してRxについて知る

CompositeDisposable

複数のDisposableを集めておき、一気に好きなタイミングでdisposeできる。
RxJava - Using CompositeDisposable

app/build.gradleにフレーバー毎の定数を用意する

可能である。

    /**
     * The defaultConfig block encapsulates default settings and entries for all
     * build variants, and can override some attributes in main/AndroidManifest.xml
     * dynamically from the build system. You can configure product flavors to override
     * these values for different versions of your app.
     */

-> defaultConfigブロックで設定したものがデフォルトの値となる。

    /**
     * The productFlavors block is where you can configure multiple product flavors.
     * This allows you to create different versions of your app that can
     * override the defaultConfig block with their own settings. Product flavors
     * are optional, and the build system does not create them by default.
     *
     * This example creates a free and paid product flavor. Each product flavor
     * then specifies its own application ID, so that they can exist on the Google
     * Play Store, or an Android device, simultaneously.
     *
     * If you declare product flavors, you must also declare flavor dimensions
     * and assign each flavor to a flavor dimension.
     */

-> productFlavorブロックに書いた、各フレーバー用の設定で上書きできる。
ビルドを設定するより

android {

 ...

 buildTypes {
 debug {
 ...

 resValue "string", "GOOGLE_MAPS_ANDROID_API_KEY", "(your development Maps API key)"
 }
 release {
 ...

 resValue "string", "GOOGLE_MAPS_ANDROID_API_KEY", "(your production Maps API key)"
 }
 }
}

import com.yourpackage.name.BuildConfig;

private void doSomethingWithUrlEndpoint() {
 String urlEndpoint = BuildConfig.URL_ENDPOINT;

 ...
}

Using String Constants Generated by Gradle Build Configurationsより

  • buildType(debug, releaseなど)およびflavorDimensions(buildTypeよりさらに細かい設定分け)の組み合わせで、さらに柔軟なフレーバーの作成が可能である。
  • 以下、引用

最後のパターン、プロダクトフレーバーのdimensionが複数ある場合です。
そんな複雑な構成は実務で生じるのか、と疑問に思うかもしれませんが、今回この記事を書くに至った理由が、実際のプロジェクトの現場でこの構成のビルドが必要になったためでした。

当該のプロジェクトではプロダクトフレーバーを用いて開発、ステージング、本番環境ごとのアプリを作成していました。
そこに、アプリで扱う商品のブランドごとにバージョン管理したいと言う要件が生じたため、さらにブランドのdimensionを追加してバージョン管理していました。

今回の検証では、color dimensionを追加して、色ごとにバージョン管理できるように設定してみます。
build.gradleファイルは抜粋すると以下のようになっています。

build.gradle
apply plugin: 'com.android.application'

android {
    //省略...

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
        debug {
            applicationIdSuffix ".debug"
            debuggable true
        }
    }

    flavorDimensions "plan", "color"
    productFlavors {
        paid {
            dimension "plan"
        }
        free {
            dimension "plan"
        }
        purple {
            dimension "color"
        }
        green {
            dimension "color"
        }
        red {
            dimension "color"
        }
    }

}

このようにflavorDimensionsはカンマで区切ることで複数定義できます。
さて、これをビルドすると… だんだん複雑になってきましたが、ビルドタイプ × プロダクトフレーバー(plan) × プロダクトフレーバー(color) の組み合わせで12通りのビルドバリアントが作成されました。

build.gradle, settings.gradle, gradle.properties の使い分け

  • build.gradle: Groovyファイルである。デフォルトで存在している、基本となる設定ファイル。各モジュールに適用される設定を記載する(Module:app)と、プロジェクト全体に適用される(Project:app)がある。

    • Module:app
      • apply plugin: 'com.android.application': Androidアプリ用のビルドであることを示す一文。
      • android {} ブロック: android のビルドにおける設定を記載する。典型的にはcompileSdkVersion buildToolsVersionなど。
        • defaultConfigブロックにおいては、debug, releaseといった各ビルド設定全部に共通する設定を記載する。
        • buildTypesブロックにおいては、debug, releaseといったビルド設定のうち、特定のビルド設定にのみ通用する設定を記載する。
      • dependencies {} ブロック: アプリが実行時に利用するライブラリを記載する。
    • Project:app
      • buildscript{} ブロック: ビルドツールに関する設定を記載する。
      • allprojects{} ブロック: 全プロジェクトで共通する設定を記載する。
    • What is the Gradle Scripts folder in an Android project? What is the difference between build.gradle(Module: app) and build.gradle(Project: app)? In-depth and simple explanation.
  • settings.gradle: Groovyファイルである。複数のモジュールがあるマルチプロジェクトにする時に用いる。どういったモジュールを使うのかを指定できる。一つのモジュールしかない時は、include ':app' などといった記述が1行だけで終わるが、複数モジュールを使うときはもっと記述が増えていく感じである。

  • gradle.properties: デフォルトでは存在しない。ビルドの過程においての設定を記載するためのファイル。 -Dgradle.user.home コマンドラインで指定した場所、GRADLE_USER_HOME, プロジェクトルート などにおくことができ、この順の優先順位で適応される。例えば、機密情報を直接build.gradleに書きたくないのでここに書いておいたり、環境ごとに違う設定値を適用したいのでここに書いておいたりということができる。key=value形式で記載していく。この値はコマンドラインから指定することもできる。最終的な優先順位は以下のようになる。以下、引用

    • コマンドラインで./gradlew <task-name> -PmyPropName1=myPropValue1 -PmyPropName2=myPropValue2のように指定したもの
    • Javaシステムプロパティで以下のように指定したもの: ./gradlew <task-name> -Dorg.gradle.project.myPropName1=myPropValue1 -Dorg.gradle.project.myPropName2=myPropValue2
    • 環境変数で以下のように指定したもの: ORG_GRADLE_PROJECT_myPropName1=myPropValue1 ORG_GRADLE_PROJECT_myPropName2=myPropValue2 ./gradlew publish
    • GRADLE_USER_HOMEディレクトリ(通常は~/.gradle/gradle.properties)に置いてあるgradle.propertiesに書いてある値
    • プロジェクトルートにおいてあるgradle.propertiesに書いてある値
    • 以上、引用: Gradle Project Properties Best Practices
    • 参考: Build Environment

build.gradle

総論

Groovyという、Javaを元にした言語で書かれているファイルで、Androidのビルドの設定などを記述できる。

Groovyはシンタックスシュガーが多く、一見しただけでは何故これで動いているのかよくわからないなどという事態が発生しがちなので、ある程度学んでおくと良い。

Gradle (build.gradle) 読み書き入門
Gradle User Manual
Android Studio の Gradle の備忘録
Groovyを知らない人のためのbuild.gradle読み書き入門
Gradleビルド言語リファレンス
Android Gradle plugin API reference

各論

apply plugin

Project レベルbuild.gradleにて、apply関数(plugin は引数)を呼ぶことで、特定のライブラリをそのプロジェクトに適用させることが可能。Using plugins

buildscript, plugin, depdendencies

プロダクトコードではなく、ビルドスクリプト( build.gradle )内で利用するプラグインや依存関係の定義については buildscript に定義します。
OSSなどを利用する場合は、プロダクトコード同様に repositories と dependencies に定義します。

Gradle (build.gradle) 読み書き入門 より

  • ライブラリをインストールするものとしてbuildscriptplugindependenciesbuildscriptのカッコ内にあるdependenciesとはまた別のもの)の三つがある。

    • buildscriptは、上述の通りbuild.gradle内で使いたいライブラリをインストールするために使用する。
    • pluginも同様の機能を持つが、pluginの方が新しくできた機能で簡単にできるため、ライブラリが対応していればこちらを使った方が良い。
  • 以下、引用

build.gradle
//pluginsの使用例

// 可能な限りファイルの最初の方で定義
plugins {
    id 'checkstyle'
}


//buildscriptの使用例

// 可能な限りファイルの最初の方で定義
buildscript {
    repositories {
        mavenCentral()
    }

    // buildscript内で使用するdependenciesは、あくまでbuildscript内で効力を持つものであり、
    // 通常のdependenciesの様にアプリケーションコード内で使用できるようにはならない
    dependencies {
        classpath platform('software.amazon.awssdk:bom:2.17.160')
        classpath 'software.amazon.awssdk:s3'
    }
}

build.gradle
allprojects {
    repositories {
        mavenCentral()
    }
}

subprojects {
    // 必要になる部分で定義
    dependencies {
        implementation "org.apache.httpcomponents:httpclient:4.5.13"
    }
}

repositories { google() mavenCentral() }

ライブラリのダウンロード元として、オープンソースライブラリ集として有名なmaven central(主にJavaライブラリ)とGoogle(主にAndroid専用ライブラリ)を指定する。これを、buildscriptの中に記載する。[Declaring repositories](https://docs.gradle.org/current/userguide/declaring_repositories.html)

buildconfig

- `buildConfigField`にさまざまな値を定義してあげることで、環境ごとの値を定義し、別の場所で(プロダクトコード上からも)参照できる。
- [カスタム フィールドとリソース値をアプリのコードと共有する](https://developer.android.com/studio/build/gradle-tips?hl=ja#share-custom-fields-and-resource-values-with-your-app-code)
- `root/build.gradle` で以下のように具体的な値を定義してあげると、`module/build.gradle`で使いやすい。
root/build.gradle
buildscript {

  ext.buildConfig = [
      compileSdk: 10,
      buildTools: "29.0.3",
      minSdk: 25,
      targetSdk: 10,
      versionCode: 200,
      versionName: "2.9.0",
      multiDex: true
  ]

  ext.versions = [
      android: [
          appcompat: '1.0.0',
          ktx: '1.3.0',
          constraintlayout: '1.1.9',
          recyclerview: '1.3.0',
          design: '1.2.0'
      ],
      kotlin: '1.5.32',
      lifecycle: '2.3.0',
      retrofit: '2.6.0',
      moshi: '1.2.0',
      kotshi: '2.4.3',
    ]

module/build.gradle
android {

  compileSdkVersion buildConfig.compileSdk

  defaultConfig {

    minSdkVersion buildConfig.minSdk
    targetSdkVersion buildConfig.targetSdk

    versionCode buildConfig.versionCode
    versionName buildConfig.versionName

  }
}

compileSdkVersion, minSdkVersion, targetSdkVersionの区別

defaultConfig

archivesBaseName

buildConfigField vs resValue

`sourceCompatibility JavaVersion.VERSION_1_8

    targetCompatibility JavaVersion.VERSION_1_8`

kotlinOptions jvmTarget = '1.8'

dependencies implementation

allprojects, subprojects, project(":HogeProject")

  • Gradleを使う場合、通常一つのルートプロジェクトと、その配下の一つ又は複数のサブプロジェクトという構造となっている。

Java, Kotlin コンパイルオプションの書き方

  • 以下のように記載する。以下、引用
build.gradle
allprojects {
    gradle.projectsEvaluated {

        // Javaコンパイル時
        tasks.withType(JavaCompile) {
            options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
        }

        // Kotlinコンパイル時
        tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
            kotlinOptions {
                freeCompilerArgs = [
                        "-Xjavac-arguments='-Xlint:unchecked -Xlint:deprecation'"
                ]
            }
        }
    }
}

build.gradle
subprojects {
    tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
        kotlinOptions {
            freeCompilerArgs += '-opt-in=kotlin.RequiresOptIn'
            freeCompilerArgs += '-opt-in=kotlin.Experimental'
        }
    }
}

arguments = ["room.incremental": "true"]

Roomのワーニングを消すための設定。以下のように記載する。

build.gradle
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = ["room.incremental": "true"]
            }
        }

How to get rid of Incremental annotation processing requested warning?

sourceSets

設定ファイル、ソースの置く位置を通常から変えたい時に利用できる。

2つのBuild Typeで同じsrcを参照したい
ソースセットでビルドする

buildFeatures

さまざまなビルド時の設定をon/offできる。

build.gradle
    buildFeatures {
        compose true
    }

BuildFeatures

gradlew

gradleのラッパーである。

  • gradlewとgradlew.batという2つの実行ファイルができる。前者はLinux/macOS用、後者はWindows用
  • gradleはHomebrewでインストールしたときのバージョンになるが、gradlewは特定のバージョンのGradleに固定化される。なので、もしローカルにgradleがあったとしても、バージョンが異なれば、gradleをインストールされる。
  • 新規参加の開発者にgradleのインストールを頼まなくてもいい。(勝手にインストールされるから)
    チームでgradleのバージョンが勝手に統一され、再現性の高いビルド環境になる。
    CIなどでgradleを使えば一貫した信頼性のあるビルドになる。

gradleとgradlewの違いより引用

local.properties

Android SDK がPCのどこにあるかを記載する。これは各PCごとに異なるべき情報であるから、このファイルをGitの変更管理に入れてはいけない。

local.properties
sdk.dir=/Users/XXXXXXX/Library/Android/sdk

AndroidManifest.xml

アプリに関する設定を記載するもの。パッケージ名やアプリケーションID、アプリで作成したコンポーネント、ユーザーに求めるパーミッション、アプリが必要とするハードウェア機能やソフトウェア機能のタイプなどなど。build.gradleと重複する部分もあり、その部分はbuild.gradleに書いた設定で上書きされる。公式マニュアルが充実しているのでそこを見た方が早いか。

  • 以下、引用

要素
必須の要素は manifest と application のみです。 これらの要素は一度だけ指定します。 その他のほとんどの要素は、一度も指定しなくても、複数回指定しても構いません。 ただし、マニフェスト ファイルを有用なものにするには、指定しなければならないものもあります。
すべての値は属性によって設定され、要素内に文字データが設定されることはありません。

通常、同じレベルにある要素に順番はありません。 たとえば、activity、provider、service の各要素を任意の順番で指定できます。 このルールには 2 つの重要な例外があります。

activity-alias 要素は、それがエイリアスとなる activity の後に指定しなければなりません。
application 要素は、manifest 要素内の最後の要素でなければなりません。

gitignore

gitignoreでmacos, android, androidstudioなどと指定して生成されたものを使えばいいのではないかと思われる。

gitignore.io - macos, android, androidstudio

あるいは、GitHubにAndroid.gitignoreの推奨が置かれている。

gitignore/Android.gitignore

多言語対応

strings.xml

デフォルトで存在するstrings.xmlにて、Android studioで右クリックし、"Open Translation Editor"をクリックすると、多言語の追加画面が開く。ここで、あるキーに対して、各言語でどのような文字列とするか指定できる。

截屏2023-07-26 13.56.57.png
截屏2023-07-26 13.58.47.png

無論、xmlファイルであるから直接ファイルを編集しても構わない。

文字列を使いたい時は、たとえばcomposeでは以下のように使う。

test.kt
import com.example.exampleapp.R
import androidx.compose.ui.res.stringResource

Text(text = stringResource(id = R.string.home_title)

以下のように使う。

example_activity.kt
// Get a string resource
val hello = resources.getString(R.string.hello_world)

参考

Android Developers - 各種の言語と文化をサポートする
Android Developers - アプリをローカライズする
Localizing String Resources / Translations Editor - Android Studio Tutorial
Unresolved reference: string
getString Outside of a Context or Activity

apkファイルを解析したい

Android StudioのBuild > Analyze apk をクリックし、apkファイルを指定すると、apkファイルの中身を解析できる。

参考: Analyze your build with the APK Analyzer

Kotlin

文法

30分で覚えるKotlin文法

return の後ろの @マーク

forEachIndexedなどの関数において、これはforループではないためbreakやcontinueが使えない。そのため、ラベル付きのreturn文としてreturn@forEachIndexedのような書き方をする。これで、continueと同じような機能となる。(普通にreturnと書けばbreakのような機能となる)

Kotlinの@[アットマーク]について

LinkedHashMap, HashMap

キーバリュー形式で値を保持する。キーの重複は許さない。
Linkedの方は順番も保持する。普通のHashMapの方は順番は保持しない。

JavaのLinkedHashMapクラスの使い方を現役エンジニアが解説【初心者向け】

sealed クラス

enum に似ているが、状態を持つことができる。また、典型的には、その内部クラスとして自分自身を継承させたクラスを作ることで、用いる。また、よくwhen節と共に用いる。

Result.kt
/**
 * A generic class that holds a value or error.
 * @param <T>
 */
sealed class Result<out R> {

    data class Success<out T>(val data: T) : Result<T>()
    data class Error(val exception: Exception) : Result<Nothing>()

    override fun toString(): String {
        return when (this) {
            is Success<*> -> "Success[data=$data]"
            is Error -> "Error[exception=$exception]"
        }
    }
}

!!: double-bang operator: not-null assertion operator

スコープ関数

- [Scope functions](https://kotlinlang.org/docs/scope-functions.html#takeif-and-takeunless)

by: 移譲を簡単にできるオペレータ。

  • 以下、引用
委譲とはあるオブジェクトの操作を一部他のオブジェクトに代替させる手法である
委譲ではあるオブジェクトの操作を他のオブジェクトに代替させるため冗長な記述が増えてしまいがち
だが Kotlinの by を利用することで冗長な記述をせず、簡単に操作を委譲させることができる。

extension

  • 以下、引用
main.kt
fun main(args : Array<String>) {
    "extensions".hoge()
}

fun String.hoge() {
    println("<$this>")
}

Unitとは

JavaでいうVoidのようなもので、何も返さないメソッドの戻り値として用いることがある。

可視性修飾詞

  • public: どこからでも閲覧可能。何もつけなければこれとなる。
  • internal: 同一モジュールからのみ閲覧可能。
  • private: 同一ファイルないからのみ閲覧可能
  • protected: クラス内での宣言のみ付加可能。privateに加え、サブクラスからも閲覧できる。

可視性修飾詞

Nullableな値がnullかどうかチェックするには

セーフコールオペレータ?.とエルビスオペレータ?:を組み合わせると良さそう。

test.kt
a?.let {
   println("not null")
   println("a: $it")
} ?: run {
    println("null")
}

Best way to null check in Kotlin?

複数の値が全部nullでないことをチェックする時はあまり良い書き方がなさそう。if (p1 != null && p2 != null)などと汚く書くしかないだろう。

Multiple variable let in Kotlin

targetSDK アップデート

近年は毎年8月に、Google Playにアップロードするアプリのtarget SDKを上げることが要求される。2023/8/1からはAPI 33 以上にすることが要求される。それに伴い様々な対応が必要になることもある。

参考: targetSdkVersion 33 対応でやったこと

ライブラリ

diffplug/spotless

コードのフォーマッター(コードをきれいにしてくれるやつ)

KtLint + Spotless + GitHub ActionsでPRにsuggested changeさせる

Timber

ロガー。

インストール

build.gradle
repositories {
  mavenCentral()
}

dependencies {
  implementation 'com.jakewharton.timber:timber:5.0.1'
}

使用方法

  • 以下、引用
  • アプリケーションクラスで、Treeクラスインスタンスを初期化する。
MyApplication.kt
@HiltAndroidApp
class MyApplication: Application() {

    override fun onCreate() {
        super.onCreate()
        if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
    }
}

  • Timberのスタティックメソッドをログを出したいところでよぶ。
  • 以上、引用 JakeWharton/timber

画像ダウンロード・キャッシュ関連のライブラリ

Glide / Picasso / Coil などが存在するが、Jetpack Composeに対応しているのはCoilである模様。

3つの画像読み込みライブラリ Glide / Picasso / Coil - JetpackCompose 対応の状況

Coil

gradleに以下の文言を追加してインストール。

.
implementation("io.coil-kt:coil-compose:2.4.0")

jetpack compose を使っている場合の、画像の表示方法は以下となる。

test.kt
AsyncImage(
    model = "https://example.com/image.jpg",
    contentDescription = null,
)

Lottie-Android

Json形式でアニメーションが定義されており、簡単にアニメーションを実装できる。なお、iOS, React Native, Webでも使える。

lottie-android
Lottie-Androidで素晴らしいアニメーション試す

ドキュメントは以下:

Lottie doc

エラー

Setting the namespace via a source AndroidManifest.xml's package attribute is deprecated. Please instead set the namespace (or testNamespace) in the module's build.gradle file

AndroidManifest.xmlのpackage要素はdeprecatedとなっているので、それを削除し、代わりにbuild.gradle(:app)にnamespace要素を追加すると治った。

Android Manifest merge error when removing package name for namespace

Activity at AndroidManifest.xml:20:9-30:20 duplicated with element declared at AndroidManifest.xml:11:9-19:20

誤って同じActivityを2回AndroidManifest.xmlに宣言してしまっていたので、一つにすると治った。

Expected @HiltAndroidApp to have a value. Did you forget to apply the Gradle Plugin?

build.gradleにおいて、annotationProcessOptionsという設定を上書きしてしまっていたために発生していた。annotationProcessOptions = という書き方ではなく、annotationProcessOptions += という書き方にしなくてはならない。

Expected @HiltAndroidApp to have a value. Did you forget to apply the Gradle Plugin?

[Dagger/MissingBinding] XXX cannot be provided without an @Provides-annotated method.

あるクラスXXXがDaggerでDIをするのに必要になる旨を自分で記載しているにもかかわらず、そのクラスをどのように初期化すればいいのかがDaggerに伝わっていない。指示通り、@Providesのついた初期化のための関数を作り、どこかのmoduleで定義すれば解決する。

Stack Over Flow - Dagger 2: Cannot be provided without an @Provides-annotated method

java.lang.ClassNotFoundException Didn't find class "XXXXXApplication" on path

AndroidManifest.xmlにおいて<application android:name="XXXXXXXApplication" の箇所で指定すべきファイルの箇所が間違っていた。該当ファイルを確認し、パッケージのパスを確認した上で、修正したところ、治った。

Stack Over Flow - java.lang.ClassNotFoundException: Didn't find class on path: dexpathlist

'Failed to install-write all apks'

実機でアプリをインストールしようとする場合はその実機、シミュレータの場合はコンピュータ、のメモリが足りなかったりする場合に起こる。メモリを開けたり、再起動したりすると治る。筆者の場合は再起動すると治った。

I am getting error- 'Failed to install-write all apks'

Flutter General error during conversion: Unsupported class file major version xx

Gradleのバージョンを新しくすると直りました

Flutter General error during conversion: Unsupported class file major version xxと出た場合の対処

flutter Namespace not specified. Please specify a namespace in the module's build.gradle file like so

namespace "com.example.myapp"という記述がbuild.gradleに必要なところ、書いていなかった。"com.example.myapp"という部分は、通常はアプリケーション ID と同じにする。

アプリ モジュールを設定する

java.lang.RuntimeException: Unable to start activity ComponentInfo Activity java.lang.IllegalStateException Hilt Activity must be attached to an @HiltAndroidApp Application Did you forget to specify your Application's class name in your manifest's 's android:name attribute?

Hiltに関するエラーで、@HiltAndroidAppマークをつけたクラスのクラス名を、AndroidManifest.xmlに記載する必要がある。

MultiTranslationApplication.kt
@HiltAndroidApp
class MultiTranslationApplication : Application() {

    override fun onCreate() {
        super.onCreate()
    }
}
AndroidManifest.xml
    <application
        android:name="MultiTranslationApplication"
        ....

Unable to start activity ComponentInfo java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity

MainActivityのテーマとして、Theme.AppCompatまたはそれを継承したテーマを使わないと、古い端末などではうまく働かないことがあるので、これを使うようにする。

AndroidManifest.xmlにおいて、以下のテーマを最後の行のように記載すると治った。

AndroidManifest.xml
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:theme="@style/Theme.AppCompat">

Geeks for Geeks - Android – You need to use a Theme.AppCompat theme (or descendant) with this activity

アプリの一番上にアプリ名が書いてあるバーが表示されるが、消したい。

AndroidManifest.xmlにおいて、使うテーマをActionBarなしのものとする。

AndroidManifest.xml
   <activity
            android:name=".MainActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:theme="@style/Theme.AppCompat.NoActionBar">

Android remove action bar

エミュレータが作動しない

理由はよくわからなかったが、Android studioよりdevice managerを起動し、一度該当端末のエミュレータを停止させてからもう一度動かすと出現した。

Could not get unknown property 'source' for generate-proto-generateDebugProto of type org.gradle.api.internal.file.DefaultSourceDirectorySet

Proto Datastoreを使う際に必要なprotobufライブラリのバージョンが古かったようなので、新しいもの(0.9.1)を指定したら治った。

Proto datastore jetpack compose - gradle dependencies exception

Execution failed for task app generateDebugProto Could not resolve all files for configuration app:protobufToolsLocator_protoc Could not find protoc-3.14.0-osx-aarch_64.exe

protobufライブラリの古いバージョンはM1 Macをサポートしていないのでこのようなエラーが発生するらしい。protobufライブラリを3.19.4に上げたら治った。

Protoc can not be executed on M1 based Macs #43

@dagger.hilt.android.qualifiers.ActivityContext android.content.Context cannot be provided without an @Provides-annotated method.

Hiltで依存性注入を行っていた。ProtoDatastoreをラップしたクラスを注入する必要があった。

LanguageSettingsDataStore.kt
class LanguageSettingsDataStoreWraper @Inject constructor(@ApplicationContext context: Context) {
    val dataStore = context.languageSettingsDataStore
}

このクラスはContextを必要としている。HiltでContextを提供するには@ActivityContext@ApplicationContextがある。前者を用いたらこのようなエラーが発生した。しかし後者を用いたら解消した:

DataModules.kt
@Module
@InstallIn(SingletonComponent::class)
object ObjectModule {
    @Provides
    @Singleton
    fun provideLanguageSettingsDataStoreWrapper(@ApplicationContext context: Context): LanguageSettingsDataStoreWraper {
        return LanguageSettingsDataStoreWraper(context)
    }
}

理由がよくわからない部分もあるが、SingletonComponentを用いる場合は、(アプリ起動時からアプリ終了時までずっと存続すべきクラスということになるので?)@ApplicationContextを用いろとの記述が公式ドキュメントにあった。

その他参考
:
How to inject application context in a repository with Hilt?
Dagger Hilt の Context まわりでハマったこと

Update this project to use a newer compileSdk of at least

下記記事の通り、build.gradle.kts (Module :app)compileSdktargetSdkの値を上げたら治った。

Update this project to use a newer compileSdk of at least , for example 解決方法

LiveData の MediatorLiveDataを使っているのだが、値が流れてこない。

MediatorLiveDataを購読しているアクティブなオブザーバーが一つ以上ないと、値を流さないようになっている。最低限一回は購読されるようにすると、値が流れた。
Stack Over Flow - MediatorLiveData onChanged not getting called

FileIntegrityManager(android.security)が初期化できない

Context(Activityなど)のgetSystemService(FileIntegrityManager::class.java)関数で初期化する。わからなかったので自分でStackOverFlowに質問した。

参考: How do I instantiate FileIntegrityManager in Android?

@HiltViewModelアノテーションで初期化しているViewModelについてなんらかのカスタム設定を手動でしたい。

init {}メソッドを設ければそのメソッドは初期化時に呼ばれるので、なんらかの設定を自由にその中で行うことができる。

なんらかの値をViewModelに渡し、その値を使って初期設定をしたいという場合は、@Assistedというアノテーションを利用すると、その値を利用して初期設定が可能となる。

Instantiate viewModel with run-time arguments using Hilt

LiveDataのMediatorLiveDataを使っていたところ、Attempt to invoke interface method 'void androidx.lifecycle.Observer.onChanged(java.lang.Object)' on a null object referenceが発生した。

以下のようなコードにおいて、このエラーが発生した。

NullPointerError.kt
    private val myMediator = MediatorLiveData<Int>().apply {
        addSource(someSource) {
            // Set something to value
        }
        observeForever(myObserver)
    }
    private val myObserver = Observer<Int> {
        Log.d("My App", "myObserver: new value: $it")
    }

observeForever(myObserver)としてmyObserverを使っているが、この際にはまだmyObserverが初期化されていないのでnullとなってしまう。observeForever(myObserver)よりも前にmyObserverを宣言すると解消した。

AfterFix.kt
    private val myObserver = Observer<Int> {
        Log.d("My App", "myObserver: new value: $it")
    }
    private val myMediator = MediatorLiveData<Int>().apply {
        addSource(someSource) {
            // Set something to value
        }
        observeForever(myObserver)
    }

参考: MediatorLiveDataでNullPointerExceptionが出たときの対処法

8
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
9