ニュース
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について、名前やアイコンを動的に変えることもできる。
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
アプリを使わずにバッテリーに接続もせずに放置していると、これらの省電力モードに突入し、アプリの機能が制限される。
RelativeLayout, LinearLayout
RelativeLayoutは親、各部品同士の相対的な位置関係を比較的自由に設定できる。Linear Layoutは、横一列または縦一列の単純な配置となる。
メモ:ビューの配置 - LinearLayout と RelativeLayout
LinearLayoutとRelativeLayoutの配置属性メモ
LinearLayoutとRelativeLayoutの使い方をまとめてみた
部品同士に固定サイズの隙間を設定したい場合はSpaceを設定できる。可変長の隙間を設定したい場合はViewを利用できる。
パフォーマンスについて、一般には複雑になればなるほどパフォーマンス的な問題が発生する可能性がある。あまりに複雑な画面の場合、減らせる部品は減らすなどの配慮がいるだろう。
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
公式実装例
-
さまざまなパターンのアーキテクチャによるAndroidアプリ(kotlin)の実装例を格納したGoogle公式リポジトリ
-
UI・マテリアルデザインのサンプルアプリ
-
Jetpackを利用したサンプル
-
ネットワークへのアクセスや認証など、実際にストアで公開されるアプリに近い機能を備えたアプリ
公式UIガイドライン
Material Design (3)
androidx.compose.material3
Material Designに乗っ取ったアプリのさまざまなデザインテンプレートが外部会社により販売されている。
MATERIAL DESIGN KIT
その他、実装例
- アーキテクチャ、UI(Jetpack Compose)の参考など
kliment-jonceski/JetPackPlaygroundApp
デザイン
デザイナーとエンジニアの連携ツールはいろいろあるがAndroid, iOSでおすすめなのはFigma。
- 紙っぺら一枚のデザインでは具体的にどう実装するのかわからずエンジニアがよしなに実装するなどという不具合が発生しうる。しかしFigmaは状態の変動に応じて同一の部品や画面が細かく変化するUIを表現できる。
- ツールを導入すれば、Jetpack Composeでどう実装するのかのコードを出力できたりもする。
参考
Intent
インテントとは、「何かを行う意図」という意味で、幅広い役割として利用可能ですが、特定のアクションをリクエストする時に使用できる、メッセージングオブジェクトのことを意味しています。例えば別の画面を開きたい時に、AndroidではActivity単位で画面が作られますが、「開いている画面に対して別の画面を開いてください」とアプリにメッセージとして伝えることで、別の画面を開くことが可能になります。
今回は基本的な使い方として、「Activityを起動する」、「Serviceを起動する」、「ブロードキャストを配信する」の3点をIntentを使った例として紹介します。
【Androidアプリ開発】Intentとは、Intentの使い方とは。
-
インテントとインテント フィルタ
- 他アプリから自分のアプリのIntentを呼び出されない様にするには、
exported
属性をfalse
にする。特に理由がなければfalseとすべきだろう。
- 他アプリから自分のアプリの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
関数に渡して行うようにする。
- そのため、
-
以下、引用
@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
関数の中で状態管理をしても良い。
例えば以下のようになる。
val mutableState = remember { mutableStateOf(default) }
// or
var value by remember { mutableStateOf(default) }
// or
val (value, setValue) = remember { mutableStateOf(default) }
全体の例としては以下のようになる。テキストフィールドにユーザーが何か入力すると、その文字によって表示を変える例である。remember関数を用いて、MutableStateという型の状態name
を保持させている。
@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へと簡単に変換する関数も用意されている:
-
Flow
の場合: collectAsStateWithLifeCycle関数 または collectAsStateを使う。 -
LiveData
の場合: observeAsState関数を使う。
@Composable
関数内部で、rememberなどを用いて状態を持っている場合これをステートフルと呼ぶ。逆に、@Composable
関数内部では状態を持たず、むしろ関数への引数として必要な値やクロージャーを渡して運用するようにする場合もあり、これはステートレスと呼ぶ。ステートレスの場合は、例えばViewModelなど外側で状態管理をすることになるだろう。
慣習として、表示内容は同じだが、ステートレスとステートフルな関数2つを用意する場合がある。↓
@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
- Compose-onlyのアプリで使うアクティビティだが、通常はサブクラスの
FragmentActivity
やAppCompatActivity
を使う。 - ComponentActivity vs AppCompactActivity in Android Jetpack Compose
LaunchedEfect
- 一つ目の引数(key)に何らかの変更が加わるたびに、二つ目の引数で指定したクロージャ内の処理を実行する。実行中にもう一度実行指示が出た場合は、実行中の処理をキャンセルし、新しい処理の方に行く。
Modifier
ComposeのUI部品について、背景色、パディング、高さや横のサイズ、輪郭などなど、さまざまな設定を付与できる。
Android developer - Compose 修飾子のリスト
Qiita - Jetpack Compose Modifier(修飾)
Jetpack Compose Modifier徹底解説
複数のModifierを使った場合、Modifierが適用されるのは、上から順である。
Modifiers in Jetpack Compose — Basic Concepts to Get You Started
部品の角を丸くしたい。
.clip
モディファイアを使えば可能である。四隅のうち、特定の角のみを丸くすることも可能である。以下は、下方の角のみを丸くした例。
modifier = modifier
.clip(shape = RoundedCornerShape(0.dp, 0.dp, 25.dp, 25.dp))
Icons
ボタンなどで使えるアイコンとしてMaterial Iconが用意されている。
以下のように用いる。
Icon(Icons.Rounded.Favorite, contentDescription = "Favorite")
Scaffold
UI部品の最下層に設置することが多く、他の画面(UI部品)の他、アプリバー、フローティングアクションボタン、スナックバー、ドロワーなどを設定できる。
Scaffoldのラムダ式には引数としてpaddingValuesという値が渡され、子UI部品にModifier経由でパディングとして設定することで、アプリバーなどの分の余白を考慮したデザインとできる。
- 以下、引用
Scaffold(/* ... */) { contentPadding ->
// Screen content
Box(modifier = Modifier.padding(contentPadding)) { /* ... */ }
}
- 以上、引用 マテリアル コンポーネントとレイアウト - Scaffold より
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の値の割合に基づいて空いているスペースが分配されるとのこと。
Box
縦(Z軸)にUI要素を重ねていきたいときに使える。以下は、丸い背景の上にアイコン画像を重ねてみた例である。
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 |
---|---|---|
※ 画像はAndroid公式ドキュメントより引用
- 色を変える場合は
colors
引数にButtonColors
という型のオブジェクトを渡す。
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コンポーザブルを使う
共通要素遷移(画面遷移の際に、あるUI要素が拡大縮小移動するなどして次の画面のUI要素へとシームレスに変形する画面遷移)
Jetpack composeではModifier.sharedElementというモディファイアを用いる。
Google Developers - 共有要素遷移をカスタマイズする
Jetpack Composeにおける
Shared Element Transitionsの実例と導入方法またその仕組み
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
マークをつけて使う必要がある。
子部品の配置を決定する際、親部品の配置を参考にしたい。
BoxWithConstraints
を利用するとできる。
BoxWithConstraints {
print("maxHeight $maxHeight")
inputArea(
height = maxHeight - bottomUpperHeight,
)
}
Composeでのレイアウト - レスポンシブレイアウト
BoxWithConstraintsScope
google/accompanist
Composeで正式に採用されていないような機能をComposeで使えるようにするという目的で実装された第三者ライブラリの集まり。https://github.com/google/accompanist
- Compose側で正式採用されたことによって不要になるというケースもあるので情報を見ておこう。
- 例:
SwipeRefresh
-> androidx.compose.material.pullrefresh
ViewModel
Android アプリの場合、Activity や Fragment があり、これらに View があったり、ロジックがあったり、一時的なデータを保持したりしています。
Activity や Fragment は、リソース確保や画面回転などにより、 Android フレームワークによって破棄されることがあります。そのときに、Activity や Fragment の内部で保持していたデータもいっしょに破棄されてしまいます。
しかし、アプリ内部の一時的なデータを Activity や Fragment ではなくて ViewModel に持たせることで、Activity や Fragment が一時的に破棄されてしまっても、一時的なデータを保持し続けることができるようになります。
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はライフサイクルを持っているが、そこに直接いろいろなコードを書いていくとごちゃごちゃして肥大化していく。そこで、別のクラスからこれらのクラスのライフサイクルを参照してスッキリ書けるようにできるもの。
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
画面遷移を簡単にできるようにしたもの。
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
}
- 以上、引用 Compose を使用したナビゲーション より
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
-
Log.e()
: エラーを出力 -
Log.w()
: 警告を出力 -
Log.i()
: 情報を出力 -
Log.d()
: 開発者向けの情報を出力。 -
Log.v()
: かなり細かい情報を出力。 - ログメッセージのほかタグ情報を渡せる。これは主にどのUI要素において発生したかを特定するために用いる。
MainActivity
など。
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の関係
画像、文字列などのリソース
通常、/res
フォルダ内にさまざまなサブフォルダを作り、その中に配置することとなっている。どのようなサブフォルダにどのようなリソースを置くかは、下記参照。
新規のリソース(xmlファイル)はandroid studioの[File] > [New] > [Android resource file]で追加できる。
参照: アプリのリソースを追加する
ベクター型ドローアブルの概要
フォント
res/fontフォルダ以下に配置することとなる。.ttfファイルや、.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
(メイン)スレッドをブロックすることなく、並行処理・非同期処理を行うための仕組み。
- Android での Kotlin コルーチン
- Android での Kotlin Flow
- 公式ドキュメント
- Kotlin の Coroutine で suspend 関数を理解する
- 初学者向けKotlin Coroutines Flow
- Coroutines Flow で ジェネリック ズンドコキヨシ
チートシート
- 以下、引用
-
以上、引用 Kotlin Flows ~ an Android cheat sheetより
- おおむね以下のような構成となっているようである。
-
Flow
: 非同期データストリームで、正常終了またはエラー付き終了を行う。 -
SharedFlow
: Flowを継承したもの。一つのデータフローを複数のサブスクライバーの間で購読するので、複数から購読されても一つのフローしか走らない。任意の数のリプレイキャッシュを持つ。 -
MutableSharedFlow
:SharedFlow
を継承したもので、値をフローに流すための関数も持つ。 -
StateFlow
:SharedFlow
を継承したもの。必ず初期値を持つ。リプレイ用のキャッシュの数は強制的に1個のみとなる。購読をせずに最新の値を取得する機能も持つ。 -
MutableStateFlow
:StateFlow
を継承したもので、値をフローに流すための関数も持つ。
-
ARE KOTLIN COROUTINES ENOUGH TO REPLACE RXJAVA?: RxJava と kotlin croutineの対照表。
オペレータ
-
一覧
-Flow -
マーブルダイアグラム(そのオペレータに値を流したらどうなるかを図にしたもの)
-
- FlowがCollectを開始する前に呼ばれる
-
- 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 というマークを利用した書き方をする必要がある。詳しくは:
@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
リアクティブプログラミングが可能。はRxJavaはRxKotlinを使いやすくしたものなので、前者の方がドキュメント・情報は多そう。
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ファイルは抜粋すると以下のようになっています。
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通りのビルドバリアントが作成されました。
- 以上、引用 Androidのビルドバリアントをイチから理解するより
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
-
apply plugin: 'com.android.application'
- Androidで開発する時は大体核と思われる。AAB対応後に役割が変わっている。Android App Bundle に対応できるように Instant App を移行する
-
plugins { id 'kotlin-android' }
- kotlin言語を使ってアプリを作るときに記述するもの。Kotlin を既存のアプリに追加する
-
apply plugin: 'kotlin-kapt'
- kaptというライブラリをkotolinで使うとき必要。kaptによりkotolinでも@などを使ったアノテーションを使えるようになる。Daggerなどのツールを使うときに必要。
- kaptのセットアップ方法&使い方
-
apply plugin: 'kotlin-android-extensions'
- Kotlin Android Extensions を使うために必要な記述・・・だが、これはすでにdeprecatedとなっており、代わりにViewBindingを使うことが推奨されている。
- Kotlin Android Extensions から View Binding に置き換える
- Migrate from Kotlin synthetics to Jetpack view binding
buildscript
, plugin
, depdendencies
プロダクトコードではなく、ビルドスクリプト( build.gradle )内で利用するプラグインや依存関係の定義については buildscript に定義します。
OSSなどを利用する場合は、プロダクトコード同様に repositories と dependencies に定義します。
Gradle (build.gradle) 読み書き入門 より
-
ライブラリをインストールするものとして
buildscript
とplugin
とdependencies
(buildscript
のカッコ内にあるdependencies
とはまた別のもの)の三つがある。-
buildscript
は、上述の通りbuild.gradle内で使いたいライブラリをインストールするために使用する。 -
plugin
も同様の機能を持つが、plugin
の方が新しくできた機能で簡単にできるため、ライブラリが対応していればこちらを使った方が良い。
-
-
以下、引用
//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'
}
}
-
dependencies
は、アプリ内で使うライブラリをインストールする時に使う。- 以下、引用
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`で使いやすい。
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',
]
android {
compileSdkVersion buildConfig.compileSdk
defaultConfig {
minSdkVersion buildConfig.minSdk
targetSdkVersion buildConfig.targetSdk
versionCode buildConfig.versionCode
versionName buildConfig.versionName
}
}
compileSdkVersion
, minSdkVersion
, targetSdkVersion
の区別
- compileSdkVersion: コンパイルに使うSDKバージョン。新しい機能を使えるようにするため、基本的には現時点での最新を指定する。
-
minSdkVersion: アプリの最低OSバージョン。古いOSをサポートする場合、こちらの値を小さくする。
-
targetSdkVersion: アプリを動作させるAPIレベルを指定。compileSdk と同じく基本的には現時点での最新を指定すればいい。例えばAndroid 12(APIレベル 31 )対応が完了していない場合、 targetSdk を 30 に指定すればAndroid 12でも11のように振る舞う。
-
基本をおさえる!Androidアプリで指定するバージョンについて(compileSdk,buildToolsVersion,....)
-
defaultConfig
-
全てのビルドフレーバー間で共通する設定を記載できるブロック。ビルドを設定する
- defaultConfigブロックで記載した設定は、各フレーバーでの設定を記載するproductFlavorsブロックでアクセスできる。 プロダクト フレーバーの設定
-
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- 最初から記載されている記述。インストゥルメント化テスト(UIテスト)のためJUnit4テストクラスを利用するとの意味である。インストゥルメント化単体テストを作成する
archivesBaseName
- AABを生成する場合に、最終的な生成ファイル名を変えることができる。指定しない場合は、プロジェクトの名前がそのまま使われることとなる。
- Gradle Javaプラグイン - 生成されるjarファイルの名前
- 【Android】生成するapk/aabのファイル名にバージョン名や実行日時を入れる
buildConfigField
vs resValue
- 両方、環境ごとの設定項目を設定するのに使える。前者の値はランタイムにプログラムからアクセスできる。後者の値も同様だが、アプリの
res
フォルダに追加される。
- What's the difference between buildConfigField, resValue and manifestPlaceholders?
`sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8`
- Androidの最小APIレベルを低めに設定している場合、でも、Java 8 言語機能を使うための設定。
- Java 8+ API の desugar のサポート(Android Gradle プラグイン 4.0.0+)
- 古めのAndroidバージョンをサポートしたままJava 8+ APIを使う方法
kotlinOptions
jvmTarget = '1.8'
- 上と同じくJava8対応だが、kotolinを使っているプロジェクトではこれが必要な模様。
- Java 8 言語機能のサポート(Android Gradle プラグイン 3.0.0+)
- 一部のktxを使うにはJava8対応が必要
dependencies
implementation
- ライブラリを追加するための記述。
- ビルド依存関係を追加する - 依存関係のタイプ
- Gradle の compile, api, implementation とかについて
-
implementation project(':mylibrary')
- ローカルにあるモジュールを参照する時。
-
implementation fileTree(dir: 'libs', include: ['*.jar'])
- ローカルにあるバイナリ(ここではjarファイル)を参照する時。
-
implementation 'com.example.android:app-magic:12.3'
- リモートにあるバイナリを参照する時。
allprojects
, subprojects
, project(":HogeProject")
- Gradleを使う場合、通常一つのルートプロジェクトと、その配下の一つ又は複数のサブプロジェクトという構造となっている。
-
allprojects { ... }
という書き方をすると、括弧内で書いたことを全プロジェクトに適用できる。 -
subprojects { ... }
という書き方をすると、括弧内で書いたことを全サブプロジェクトに適用できる。 -
project(":HogeProject") { ... }
という書き方をすると、括弧内で書いたことを特定プロジェクトに適用できる。 - Structuring and Building a Software Component with Gradle
- KtLint + Spotless + GitHub ActionsでPRにsuggested changeさせる
-
Java, Kotlin コンパイルオプションの書き方
- 以下のように記載する。以下、引用
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'"
]
}
}
}
}
-
Googleの公式サンプルアプリ Architecture Samples では以下のようにkotlin optionを指定していた。
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のワーニングを消すための設定。以下のように記載する。
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.incremental": "true"]
}
}
How to get rid of Incremental annotation processing requested warning?
sourceSets
設定ファイル、ソースの置く位置を通常から変えたい時に利用できる。
2つのBuild Typeで同じsrcを参照したい
ソースセットでビルドする
buildFeatures
さまざまなビルド時の設定をon/offできる。
buildFeatures {
compose true
}
gradlew
gradleのラッパーである。
-
gradlewとgradlew.batという2つの実行ファイルができる。前者はLinux/macOS用、後者はWindows用
-
gradleはHomebrewでインストールしたときのバージョンになるが、gradlewは特定のバージョンのGradleに固定化される。なので、もしローカルにgradleがあったとしても、バージョンが異なれば、gradleをインストールされる。
-
新規参加の開発者にgradleのインストールを頼まなくてもいい。(勝手にインストールされるから)
チームでgradleのバージョンが勝手に統一され、再現性の高いビルド環境になる。
CIなどでgradleを使えば一貫した信頼性のあるビルドになる。 -
通常、ビルド・クリーンなどの実行はAndroid Studioのボタンを押してやると思うが、コマンドから実行するときはgradlewを使える。
- クリーン:
./gradlew clean
- ビルド:
./gradlew build
- ビルド(デバッグ情報つき):
./gradlew build --stacktrace
,./gradlew build --info
,./gradlew build --debug
,./gradlew build --scan
- ビルド(デバッグ情報つき):
- クリーン:
local.properties
Android SDK がPCのどこにあるかを記載する。これは各PCごとに異なるべき情報であるから、このファイルをGitの変更管理に入れてはいけない。
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の推奨が置かれている。
多言語対応
strings.xml
デフォルトで存在するstrings.xmlにて、Android studioで右クリックし、"Open Translation Editor"をクリックすると、多言語の追加画面が開く。ここで、あるキーに対して、各言語でどのような文字列とするか指定できる。
無論、xmlファイルであるから直接ファイルを編集しても構わない。
文字列を使いたい時は、たとえばcomposeでは以下のように使う。
import com.example.exampleapp.R
import androidx.compose.ui.res.stringResource
Text(text = stringResource(id = R.string.home_title)
以下のように使う。
// 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
文法
return
の後ろの @
マーク
forEachIndexed
などの関数において、これはforループではないためbreakやcontinueが使えない。そのため、ラベル付きのreturn文としてreturn@forEachIndexed
のような書き方をする。これで、continueと同じような機能となる。(普通にreturn
と書けばbreakのような機能となる)
LinkedHashMap
, HashMap
キーバリュー形式で値を保持する。キーの重複は許さない。
Linkedの方は順番も保持する。普通のHashMap
の方は順番は保持しない。
JavaのLinkedHashMapクラスの使い方を現役エンジニアが解説【初心者向け】
sealed
クラス
enum に似ているが、状態を持つことができる。また、典型的には、その内部クラスとして自分自身を継承させたクラスを作ることで、用いる。また、よくwhen節と共に用いる。
/**
* 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 を利用することで冗長な記述をせず、簡単に操作を委譲させることができる。
- 以上、引用([Kotlin]
by
で 委譲する仕組みを理解する)
extension
- 以下、引用
fun main(args : Array<String>) {
"extensions".hoge()
}
fun String.hoge() {
println("<$this>")
}
- 以上、引用 Kotlin の Extensions について色々試すより
Unit
とは
JavaでいうVoid
のようなもので、何も返さないメソッドの戻り値として用いることがある。
可視性修飾詞
-
public
: どこからでも閲覧可能。何もつけなければこれとなる。 -
internal
: 同一モジュールからのみ閲覧可能。 -
private
: 同一ファイルないからのみ閲覧可能 -
protected
: クラス内での宣言のみ付加可能。private
に加え、サブクラスからも閲覧できる。
Nullableな値がnullかどうかチェックするには
セーフコールオペレータ?.
とエルビスオペレータ?:
を組み合わせると良さそう。
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
自分のアプリから、他のアプリを開きたい。
Deep links(インテントを使った、一般的な方法)と、AppLinks(こちらもインテントは使っているが、URLのスキーマはHTTP(S)かつ開かれるアプリ側で特殊な設定が必要)が利用できるようである。
targetSDK アップデート
近年は毎年8月に、Google Playにアップロードするアプリのtarget SDKを上げることが要求される。2023/8/1からはAPI 33 以上にすることが要求される。それに伴い様々な対応が必要になることもある。
参考: targetSdkVersion 33 対応でやったこと
ライブラリ
diffplug/spotless
コードのフォーマッター(コードをきれいにしてくれるやつ)
KtLint + Spotless + GitHub ActionsでPRにsuggested changeさせる
Timber
ロガー。
インストール
repositories {
mavenCentral()
}
dependencies {
implementation 'com.jakewharton.timber:timber:5.0.1'
}
使用方法
- 以下、引用
- アプリケーションクラスで、
Tree
クラスインスタンスを初期化する。
@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 を使っている場合の、画像の表示方法は以下となる。
AsyncImage(
model = "https://example.com/image.jpg",
contentDescription = null,
)
Lottie-Android
Json形式でアニメーションが定義されており、簡単にアニメーションを実装できる。なお、iOS, React Native, Webでも使える。
lottie-android
Lottie-Androidで素晴らしいアニメーション試す
ドキュメントは以下:
テスト
単体テスト・UIテスト
テストはデフォルトでも行える。WebViewのテストを行うときはデフォルトではやりきれないため、AppiumかEspressoなどライブラリを使うと良い。
単にWebViewのみテストする場合はAppium, NativeとWebViewのやりとりも含めてテストする場合はEspressoが推奨されている。
参考Espresso-Web
Appium
WebViewのテストを行うときはデフォルトではやりきれないため、Appiumなどライブラリを使うと良い。
導入がやや複雑であるため注意。
AppiumでAndroid/iOSアプリのテストコードを書いてみた
結合テスト・受入テスト
Google Play Storeからアプリを配布する方法として、アプリを公開する以外にも方法があり、これらの方法で受け入れテストなどをしていくことになる。内部テスト・クローズドテスト・オープンテストである。
Play Console ヘルプ - オープンテスト版、クローズド テスト版、内部テスト版をセットアップする
Google Playでの配信方法の整理
エラー
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に記載する必要がある。
@HiltAndroidApp
class MultiTranslationApplication : Application() {
override fun onCreate() {
super.onCreate()
}
}
<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
において、以下のテーマを最後の行のように記載すると治った。
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat">
アプリの一番上にアプリ名が書いてあるバーが表示されるが、消したい。
AndroidManifest.xml
において、使うテーマをActionBarなしのものとする。
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat.NoActionBar">
エミュレータが作動しない
理由はよくわからなかったが、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をラップしたクラスを注入する必要があった。
class LanguageSettingsDataStoreWraper @Inject constructor(@ApplicationContext context: Context) {
val dataStore = context.languageSettingsDataStore
}
このクラスはContext
を必要としている。HiltでContext
を提供するには@ActivityContext
か@ApplicationContext
がある。前者を用いたらこのようなエラーが発生した。しかし後者を用いたら解消した:
@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)
のcompileSdk
とtargetSdk
の値を上げたら治った。
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
が発生した。
以下のようなコードにおいて、このエラーが発生した。
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
を宣言すると解消した。
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が出たときの対処法
Android Emulator System Imageがダウンロードできない。
原因はいろいろありうると思うが、今回はタイムアウトとなってしまっていた。その場合、Android StudioのSettings > Languages & Frameworks > Android SDK でShow Package Detailsで詳細を表示し、必要なものを一つずつチェックし、Applyボタンを押していくと、一つずつダウンロードできるので、成功した。
参考: https://stackoverflow.com/a/65792765
.gradlew/ の実行時に、 The operation couldn’t be completed. Unable to locate a Java Runtime. と表示される。
JAVAが見つからないため、このようになっている。環境変数のpath(zshellなら.zprofile
など)としてJAVA_HOME
を追加すると解消した。
export JAVA_HOME=/Applications/Android\ Studio.app/Contents/jbr/Contents/Home
rememberメソッドを使ったところType 'MutableState<Int>' has no method 'setValue(Nothing?, KProperty<*>, Int)' and thus it cannot serve as a delegate for var (read-write property)
というエラーが出た。
import androidx.compose.runtime.setValue
とインポートをしたら解消した。