【Kotlin/Android】Spotlessでktlintを使用してコードフォーマットを実装する
この記事からわかること
- Kotlin/AndroidでSpotlessでktlintを使用してコードフォーマットを実装する方法
index
[open]
\ アプリをリリースしました /
環境
- Android Studio:Narwhal Feature Drop
- Kotlin:2.1.10
- Material3
- AGP:8.9.2
- Gradle:8.11.1
- Mac M1:Sequoia 15.6.1
Spotlessとは
公式リファレンス:spotless/plugin-maven
「Spotless(スポットレス)」はGradle用の静的解析(Lint) & コード整形(Format)ツールのプラグインです。Java / Kotlin / Groovy / Scala / Markdown / XMLなどの複数言語に対応しています。コード整形はJavaであれば「Google Java Format」、Kotlinであれば「ktlint」などをSpotlessと連携させることでSpotlessがフォーマットを呼び出す形で活用されます。
ktlintと一緒に導入する
実際にKotlinベースのプロジェクトで活用できるように実装してみます。「build.gradle.kts(Project)」に対して以下のように記述します。
plugins {
/** Spotless */
id("com.diffplug.spotless") version "7.2.1"
}
// Spotless + ktlint の設定
// 解析: ./gradlew spotlessCheck
// 反映: ./gradlew spotlessApply
spotless {
kotlin {
// ----- Kotlin ファイルに対する整形ルール -----
// フォーマット対象のファイルを指定
target("**/*.kt")
// ktlint のバージョンを指定
ktlint("0.50.0")
// ktlint の設定を上書き(以下は例)
.editorConfigOverride(
mapOf(
// インデントのスペース数を指定(4スペース)
"indent_size" to "4",
// ファイル末尾に改行を強制
"insert_final_newline" to "true"
)
)
}
kotlinGradle {
// ----- build.gradle.kts ファイルに対する整形ルール -----
// フォーマット対象の Gradle Kotlin DSL ファイルを指定
target("**/*.gradle.kts")
ktlint("0.50.0")
}
}
解析(Check)をかけてみる
導入が完了したら実際にコードに対して解析をかけてみます。コマンドラインから./gradlew spotlessCheckを実行すると解析結果が表示されます。成功すればBUILD SUCCESSFUL、失敗(違反があれば)すればBUILD FAILEDになります。
$ ./gradlew spotlessCheck
実際にプロジェクトで実行してBUILD FAILEDになると以下のようなログが出力されます。
$ ./gradlew spotlessCheck
> Task :spotlessKotlinCheck FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':spotlessKotlinCheck'.
> The following files had format violations:
app/src/main/java/com/XXXXXX/XXXXXXX/repositories/di/RepositoryModule.kt
@@ -21,11 +21,11 @@
····@Provides·//·対象のインターフェースにどの実態を渡すかを定義する
····fun·provideSeriesRepository(
-········impl:·RoomSeriesRepositoryImpl
+········impl:·RoomSeriesRepositoryImpl,
····):·SeriesRepository·=·impl
····@Provides
····fun·provideImageFileRepository(
-········impl:·ImageFileRepositoryImpl
+········impl:·ImageFileRepositoryImpl,
····):·ImageRepository·=·impl
-}
+}
app/src/main/java/com/XXXXXX/XXXXXXX/services/ImageService.kt
@@ -19,7 +19,7 @@
········return·FileProvider.getUriForFile(
············context,
············"${context.packageName}.provider",
-············photoFile
+············photoFile,
········)
····}
}
app/src/main/java/com/XXXXXX/XXXXXXX/ui/theme/Color.kt
@@ -17,4 +17,4 @@
//·======·ダークモード·======
val·ExTextDark·=·Color(0xFFFFFFFF)
val·ExBaseDark·=·Color(0xFF222222)
-val·ExFoundationDark·=·Color(0x000000)
+val·ExFoundationDark·=·Color(0x000000)
app/src/main/java/com/XXXXXX/XXXXXXX/ui/theme/Theme.kt
@@ -11,7 +11,7 @@
····onPrimary·=·ExWhite,
····primaryContainer·=·ExBaseDark,
····background·=·ExFoundationDark,
-····onBackground·=·ExTextDark
+····onBackground·=·ExTextDark,
)
private·val·LightColorScheme·=·lightColorScheme(
@@ -19,13 +19,13 @@
····onPrimary·=·ExWhite,
····primaryContainer·=·ExBase,
····background·=·ExFoundation,
-····onBackground·=·ExText
+····onBackground·=·ExText,
)
... (18 more lines that didn't fit)
Violations also present in:
app/src/main/java/com/XXXXXX/XXXXXXX/ui/theme/Type.kt
app/src/main/java/com/XXXXXX/XXXXXXX/views/components/layout/HeaderView.kt
app/src/main/java/com/XXXXXX/XXXXXXX/views/mydata/MyDataScreen.kt
app/src/main/java/com/XXXXXX/XXXXXXX/views/series/SeriesDetailScreen.kt
app/src/main/java/com/XXXXXX/XXXXXXX/views/series/SeriesListScreen.kt
app/src/main/java/com/XXXXXX/XXXXXXX/views/series/input/CategoryInputScreen.kt
app/src/main/java/com/XXXXXX/XXXXXXX/views/series/input/SeriesInputScreen.kt
app/src/main/java/com/XXXXXX/XXXXXXX/views/settings/SettingsScreen.kt
Run './gradlew :spotlessApply' to fix these violations.
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.
BUILD FAILED in 8s
自動整形(Format)をかける
違反していた内容に対して自動整形(フォーマット)をかけるには./gradlew spotlessApplyを実行します。こちらも整形が全て完了すればBUILD SUCCESSFUL、失敗(完了しなければ)すればBUILD FAILEDになります。違反の中には自動整形で修正しきれないものもあるのでその場合は手動で修正してあげる必要があります。
$ ./gradlew spotlessApply
実際にプロジェクトで実行してみると以下のような感じになります。
$ ./gradlew spotlessApply
> Task :spotlessKotlinApply FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':spotlessKotlinApply'.
> There were 34 lint error(s), they must be fixed or suppressed.
app/src/androidTest/java/com/XXXXXX/XXXXXXX/ExampleInstrumentedTest.kt:L5 ktlint(standard:no-wildcard-imports) Wildcard import
app/src/main/java/com/XXXXXX/XXXXXXX/models/Domain/Dao/CapsuleToyDao.kt:L1 ktlint(standard:package-name) Package name contains a disallowed character
app/src/main/java/com/XXXXXX/XXXXXXX/models/Domain/Dao/CategoryDao.kt:L1 ktlint(standard:package-name) Package name contains a disallowed character
app/src/main/java/com/XXXXXX/XXXXXXX/models/Domain/Dao/LocationDao.kt:L1 ktlint(standard:package-name) Package name contains a disallowed character
app/src/main/java/com/XXXXXX/XXXXXXX/models/Domain/Dao/SeriesCategoryCrossRefDao.kt:L1 ktlint(standard:package-name) Package name contains a disallowed character
app/src/main/java/com/XXXXXX/XXXXXXX/models/Domain/Dao/SeriesDao.kt:L1 ktlint(standard:package-name) Package name contains a disallowed character
app/src/main/java/com/XXXXXX/XXXXXXX/models/Domain/Database/AppDatabase.kt:L1 ktlint(standard:package-name) Package name contains a disallowed character
app/src/main/java/com/XXXXXX/XXXXXXX/models/Domain/Entity/CapsuleToy.kt:L1 ktlint(standard:package-name) Package name contains a disallowed character
app/src/main/java/com/XXXXXX/XXXXXXX/models/Domain/Entity/Category.kt:L1 ktlint(standard:package-name) Package name contains a disallowed character
app/src/main/java/com/XXXXXX/XXXXXXX/models/Domain/Entity/Location.kt:L1 ktlint(standard:package-name) Package name contains a disallowed character
app/src/main/java/com/XXXXXX/XXXXXXX/models/Domain/Entity/Relation/SeriesCategoryCrossRef.kt:L1 ktlint(standard:package-name) Package name contains a disallowed character
app/src/main/java/com/XXXXXX/XXXXXXX/models/Domain/Entity/Relation/SeriesWithRelations.kt:L1 ktlint(standard:package-name) Package name contains a disallowed character
app/src/main/java/com/XXXXXX/XXXXXXX/models/Domain/Entity/Series.kt:L1 ktlint(standard:package-name) Package name contains a disallowed character
app/src/main/java/com/XXXXXX/XXXXXXX/models/Domain/RoomConverters.kt:L1 ktlint(standard:package-name) Package name contains a disallowed character
app/src/main/java/com/XXXXXX/XXXXXXX/models/Enum/AppScreen.kt:L1 ktlint(standard:package-name) Package name contains a disallowed character
app/src/main/java/com/XXXXXX/XXXXXXX/repositories/impl/ImageFileRepository.kt:L1 ktlint(standard:filename) File 'ImageFileRepository.kt' contains a single class and possibly also extension functions for that class and should be named same after that class 'ImageFileRepositoryImpl.kt'
app/src/main/java/com/XXXXXX/XXXXXXX/repositories/impl/RoomSeriesRepository.kt:L1 ktlint(standard:filename) File 'RoomSeriesRepository.kt' contains a single class and possibly also extension functions for that class and should be named same after that class 'RoomSeriesRepositoryImpl.kt'
app/src/main/java/com/XXXXXX/XXXXXXX/repositories/repository_interface/ImageRepository.kt:L1 ktlint(standard:package-name) Package name must not contain underscore
app/src/main/java/com/XXXXXX/XXXXXXX/repositories/repository_interface/SeriesRepository.kt:L1 ktlint(standard:package-name) Package name must not contain underscore
app/src/main/java/com/XXXXXX/XXXXXXX/view_models/CategoryInputScreenViewModel.kt:L1 ktlint(standard:package-name) Package name must not contain underscore
app/src/main/java/com/XXXXXX/XXXXXXX/view_models/RootEnvironment.kt:L1 ktlint(standard:package-name) Package name must not contain underscore
app/src/main/java/com/XXXXXX/XXXXXXX/view_models/SeriesDetailScreenViewModel.kt:L1 ktlint(standard:package-name) Package name must not contain underscore
app/src/main/java/com/XXXXXX/XXXXXXX/view_models/SeriesInputScreenViewModel.kt:L1 ktlint(standard:package-name) Package name must not contain underscore
app/src/main/java/com/XXXXXX/XXXXXXX/view_models/SeriesListScreenViewModel.kt:L1 ktlint(standard:package-name) Package name must not contain underscore
app/src/main/java/com/XXXXXX/XXXXXXX/views/common/App.kt:L1 ktlint(standard:package-name) Package name contains a disallowed character
app/src/main/java/com/XXXXXX/XXXXXXX/views/common/MainActivity.kt:L1 ktlint(standard:package-name) Package name contains a disallowed character
app/src/main/java/com/XXXXXX/XXXXXXX/views/components/ui_parts/CustomAlertDialog.kt:L1 ktlint(standard:package-name) Package name must not contain underscore
app/src/main/java/com/XXXXXX/XXXXXXX/views/components/ui_parts/CustomText.kt:L1 ktlint(standard:package-name) Package name must not contain underscore
app/src/main/java/com/XXXXXX/XXXXXXX/views/components/ui_parts/DataEmptyView.kt:L1 ktlint(standard:package-name) Package name must not contain underscore
app/src/main/java/com/XXXXXX/XXXXXXX/views/components/ui_parts/ThemeIconButton.kt:L1 ktlint(standard:package-name) Package name must not contain underscore
app/src/main/java/com/XXXXXX/XXXXXXX/views/components/ui_parts/ThemeInputBox.kt:L1 ktlint(standard:package-name) Package name must not contain underscore
app/src/main/java/com/XXXXXX/XXXXXXX/views/components/ui_parts/ThemeTextFiled.kt:L1 ktlint(standard:package-name) Package name must not contain underscore
app/src/main/java/com/XXXXXX/XXXXXXX/views/components/ui_parts/WhiteBackStackView.kt:L1 ktlint(standard:package-name) Package name must not contain underscore
app/src/test/java/com/XXXXXX/XXXXXXX/ExampleUnitTest.kt:L3 ktlint(standard:no-wildcard-imports) Wildcard import
Resolve these lints or suppress with `suppressLintsFor`
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.
BUILD FAILED in 9s
違反の種類を制限する
発生した違反に関してはktlint側の設定を変更することで違反を制限することが可能です。
ファイル単位で無視する
@file:Suppress("ktlint:standard:no-wildcard-imports")
package com.XXXXXXXX.XXXXXXXX.models.domain.entity
import com.XXXXXXXX.XXXXXXXX.models.domain.entity.* // これも無視される
行単位で無視する
// ktlint-disable no-wildcard-imports
import com.XXXXXXXX.XXXXXXXX.models.domain.entity.* // これだけ無視される
行範囲で無視する
// ktlint-disable no-wildcard-imports
import com.XXXXXXXX.XXXXXXXX.models.domain.entity.*
// ktlint-enable no-wildcard-imports
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。






