月: 2023年9月

AndroidStudioでパッチを作成する

通常の開発ではあまり使わないと思いますが、CI実行時にコードを変更してからビルドしたい、またその際にミスしないようにしたいなどの時にパッチを用意しておくと便利です。まずはパッチにしたい内容にコードを変更します。

import android.util.LogとLog.i(“launch”, “起動したよ”)を追加しました。コードの変更や削除でも良いです。

Shift2回を押下して[Create Patch from Local Changes…]を選択するかAndroidStudio上部の[Git] – [Patch] – [Create Patch from Local Changes…]を選択します。

パッチにしたいファイル、CommitMessageに変更内容を記載、Diffで変更内容を確認し、[Create Patch…]をクリックします。

出力ファイル名を確認して[OK]を押下します。

以上でパッチが作成されました。動作確認のために一旦変更を削除します。(git checkout .を実行する等)

パッチ適用はターミナルで[git apply パッチファイル名]を実行します。今回の場合だと[git apply 起動確認ログ.patch]と入力するとコードが自動で書き換わります。またAndroidStudioからパッチを適用するにはShift2回押下して[Apply Patch…]を選択するかAndroidStudio上部の[Git] – [Patch] – [Apply Patch…]を選択してください。

適用したいパッチのファイルを選択して[Open]を押下します。

OKを押下するとパッチが適用されます。

パッチファイルをGitレポジトリに入れておけばCIからgit applyコマンドでパッチ適用できます。

説明は以上になります。


JetpackComposeのユニットテストを書く

Fragment全体のユニットテストを書こうとするとAndroid公式のやり方そのままやっても動かなかったりRobolectricが必要になったりして複雑になりがちです。またユニットテストだと実機やエミュレータの画面を利用しないテストとなるので画面を見ずにユニットテストを書くことになり難易度も高いです。

https://developer.android.com/guide/fragments/test?hl=ja

AndroidViewではなくJetpackComposeを使って実装すると比較的簡単にユニットテストが書けるようになります。

https://developer.android.com/jetpack/compose/testing?hl=ja

例として下記のようなNavigationRailのComposeがあるとします。

sealed class NavigationItem(val index: Int, @StringRes val labelRes: Int, @DrawableRes val iconRes: Int) {
    object Rule : NavigationItem(0, R.string.rule_label, R.drawable.ic_rule)
    object AppSetting : NavigationItem(1, R.string.app_setting_label, R.drawable.ic_app_setting)
    companion object {
        val items = listOf(Rule, AppSetting)
    }
}

@Composable
fun NavigationRail(uiState: MainUiState, onClick: (index: Int, item: NavigationItem) -> Unit) {
    NavigationRail {
        NavigationItem.items.forEachIndexed { index, item ->
            NavigationRailItem(
                icon = { Icon(painterResource(item.iconRes), stringResource(item.labelRes)) },
                label = { Text(stringResource(item.labelRes)) },
                selected = uiState.navigationItemIndex == index,
                onClick = { onClick.invoke(index, item) }
            )
        }
    }
}

選択されているアイテムはMainUiStateのnavigationItemIndexで保存しています。

Android公式サイトにはandroidTestImplementation(“androidx.compose.ui:ui-test-junit4:$compose_version”)と書いてありますがtestImplementation(androidx.compose.ui:ui-test-junit4:$compose_version”)とbuild.gradle.ktsに書くことでユニットテストでcreateComposeRule()が利用できます。

ユニットテストを書く前にJetpackCompose用の抽象クラスを用意しておきます。

import androidx.compose.ui.test.junit4.createComposeRule
import org.junit.Rule

abstract class AbstractComposeRule {
    @get:Rule
    val composeRule = createComposeRule()
}

抽象クラスを継承してユニットテストを書きます。

import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import [your_package].viewmodel.MainUiState
import [your_package].AbstractComposeRule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class NavigationRailSpec : AbstractComposeRule() {

    @Test
    fun click_rules() {
        composeRule.setContent {
            NavigationRail(MainUiState()) { index, item ->
                val expectedItem = NavigationItem.Rule
                assertThat(index).isEqualTo(expectedItem.index)
                assertThat(item).isEqualTo(expectedItem)
            }
        }
        composeRule.onNodeWithText("Rules", useUnmergedTree = true).performClick()
    }

    @Test
    fun click_settings() {
        composeRule.setContent {
            NavigationRail(MainUiState()) { index, item ->
                val expectedItem = NavigationItem.AppSetting
                assertThat(index).isEqualTo(expectedItem.index)
                assertThat(item).isEqualTo(expectedItem)
            }
        }
        composeRule.onNodeWithText("Settings", useUnmergedTree = true).performClick()
    }
}

NavigationRail上の”Rules”または”Settings”という文字列をクリックするとコールバックが実行され、渡された値をアサーションするという流れです。コールバックでViewModelの特定の関数が実行されたか確認したいなどもできると思います。

Fragmentのユニットテストを書くより簡単で高速に実行できるのでComposeのユニットテストを積極的に書いていきましょう。


addManifestPlaceholdersを使ってBuildVariant毎にAndroidManifest.xmlの内容を変更する

debugとprodというproductFravorsが存在する場合

app/build.gradle.kts

android {    
  productFlavors {
    debug {
      addManifestPlaceholders([exported: "true"])
    }
    prod {
      addManifestPlaceholders([exported: "false"])
    }
  }
}

app/src/main/AndroidManifest.xml

<manifest>        
  <application>
    <activity
       android:name=".ui.activity.MainActivity"
       android:exported="${exported}"/>
    </activity>
  </application>
</manifest>