🛠️

個人開発に Android Lint と Dependabot を入れて得た効果

に公開

はじめに

個人開発でも 品質と保守性を自動で守る仕組み を先に用意しておくと、実装に集中できてモチベーションを落としにくくなります。本稿では、個人アプリ VtuberCamera に導入した Android LintDependabot の設定と運用、導入効果をまとめます。

導入背景

  • ライブラリ更新や警告チェックを 手作業で追いかけ続けるのは負担が大きい。自動化して抜け漏れを防ぎたい。
  • 個人開発は「とりあえず動けばOK」で放置しがち。最低限の品質ゲートをつくりたい。
  • 依存関係は放置するとすぐ陳腐化し、最悪ビルド不能になる。早めの小さな更新を習慣化したい。
  • YAML を 1 ファイル置くだけで GitHub Actions が動作するため、初期コストが低い

Android Lint — PR にコメントで返す仕組み

PR 作成/更新時に ./gradlew lintDebug を実行し、結果 XML を Python スクリプトでエラーフォーマット(efm)に変換して reviewdog で PR コメントとして通知します。ビルドは失敗させず(-fail-on-error=false)、まずは 気づけることを最優先 にしています。

lint
name: Android Lint Review

on:
  pull_request:
    types: [opened, synchronize, reopened]
  workflow_dispatch:

jobs:
  lint:
    runs-on: ubuntu-24.04
    permissions:
      contents: read
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: 17
          cache: gradle
      - name: Grant execute permission for gradlew
        run: chmod +x gradlew
      - name: Run Android Lint
        run: ./gradlew lintDebug || true
      - name: Install reviewdog
        uses: reviewdog/action-setup@v1
        with:
          reviewdog_version: latest
      - name: Convert Android Lint XML to efm lines
        if: always()
        run: |
          set -euo pipefail
          python3 <<'PY' > lint.out
          import glob, xml.etree.ElementTree as ET, sys
          files = glob.glob("**/lint-results-*.xml", recursive=True)
          if not files:
            sys.exit(0)
          sev_map = {"Error":"e", "Fatal":"e", "Warning":"w", "Information":"i", "Informational":"i", "Ignore":"i"}
          for xml_path in files:
            try:
              root = ET.parse(xml_path).getroot()
            except Exception as e:
              print(f"# failed to parse {xml_path}: {e}", file=sys.stderr)
              continue
            for issue in root.findall("issue"):
              iid = issue.get("id","UNKNOWN")
              msg = issue.get("message","").replace("\n"," ").strip()
              sev = sev_map.get(issue.get("severity","Warning"), "w")
              locs = issue.findall("location")
              if not locs:
                f = issue.get("file", xml_path)
                ln = issue.get("line","1")
                print(f"{f}:{ln}: {sev}: [{iid}] {msg}")
                continue
              for loc in locs:
                f = loc.get("file", xml_path)
                ln = loc.get("line","1")
                col = loc.get("column")
                if col:
                  print(f"{f}:{ln}:{col}: {sev}: [{iid}] {msg}")
                else:
                  print(f"{f}:{ln}: {sev}: [{iid}] {msg}")
          PY
          wc -l lint.out || true
      - name: Report lint results with reviewdog
        if: always()
        env:
          REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          reviewdog \
            -efm="%f:%l:%c: %t: %m" \
            -efm="%f:%l: %t: %m" \
            -name="Android Lint" \
            -reporter=github-pr-review \
            -level=warning \
            -fail-on-error=false \
            < lint.out

この設計のポイント

  • コメント駆動の修正:PR 上で指摘箇所が即座に見える。
  • 開発を止めない:まずは警告レベルで回し、しきい値や必須化は後から段階的に。
  • ノイズ制御:必要になったら lint.xml でルールの有効/無効や severity を調整。

Android Lint の reviewdog コメント例

PR 上で Android Lint の結果が reviewdog によってコメントとして表示される様子

Dependabot — 依存更新を「まとめて、計画的に」

Gradle と GitHub Actions の 2 系列を毎週月曜にチェック。関連ライブラリをグループ化して 1 本の PR に束ね、レビューと検証のコストを抑えています。破壊的変更が入りやすいものは ignore でメジャー更新を除外。

dependabot
version: 2
updates:
  - package-ecosystem: "gradle"
    directory: "/"
    schedule:
      interval: "weekly"
      day: "monday"
      time: "09:00"
      timezone: "Asia/Tokyo"
    groups:
      androidx:
        patterns: ["androidx.*"]
        exclude-patterns: ["androidx.compose.*"]
      compose:
        patterns: ["androidx.compose.*", "*compose*"]
      camerax:
        patterns: ["androidx.camera:*", "*camera*"]
      navigation:
        patterns: ["androidx.navigation:*"]
      kotlin:
        patterns: ["org.jetbrains.kotlin*", "*kotlin*"]
      material:
        patterns: ["com.google.android.material:*", "*material*"]
    ignore:
      - dependency-name: "com.android.tools.build:gradle"
        update-types: ["version-update:semver-major"]
      - dependency-name: "androidx.compose.compiler:compiler"
        update-types: ["version-update:semver-major"]
      - dependency-name: "androidx.core:core-ktx"
        update-types: ["version-update:semver-major"]
    open-pull-requests-limit: 8
    labels: ["dependencies","android","vtuberCamera"]
    assignees: ["your_account_name"]
    commit-message:
      prefix: "deps"
      prefix-development: "deps-dev"
      include: "scope"

  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"
      day: "monday"
      time: "10:00"
      timezone: "Asia/Tokyo"
    labels: ["github-actions","ci-cd"]
    assignees: [""]
    open-pull-requests-limit: 3
    commit-message:
      prefix: "ci"
      include: "scope"
// CODEOWNERS
# vtuberCamera プロジェクトのコードオーナー設定
# このファイルは、プルリクエストの自動レビュアー割り当てに使用されます
# Dependabotのプルリクエストもこの設定に基づいてレビュアーが割り当てられます

# デフォルト: すべてのファイルのオーナー
* @your_account_name

# Android関連の依存関係とビルド設定
*.gradle @your_account_name
gradle.properties @your_account_name
build.gradle.kts @your_account_name
app/build.gradle.kts @your_account_name

# GitHub Actions設定
.github/ @your_account_name

# プロジェクト設定ファイル
*.xml @your_account_name
*.yml @your_account_name
*.yaml @your_account_name
*.json @your_account_name

# Kotlin/Javaソースコード
*.kt @your_account_name
*.java @your_account_name

# リソースファイル
app/src/main/res/ @your_account_name

# マニフェストファイル
app/src/main/AndroidManifest.xml @your_account_name

この設計のポイント

  • グループ化で PR 氾濫を抑制:AndroidX、Compose、CameraX、Navigation、Kotlin、Material を用途別に分離。
  • 壊れやすいところは除外:AGP、Compose Compiler、core-ktx のメジャー更新は手動確認。
  • 定期運用:毎週月曜の朝に Gradle、10:00 に Actions をチェック。習慣化して「やり忘れゼロ」。

YAML は AI に 8:2 で作ってもらった

初期ドラフトは AI に生成してもらい、リポジトリ事情に合わせて最小の手直しだけを実施。Dependabot は一発で稼働、Lint は reviewdog のエラーハンドリングを含めた再生成で安定運用に到達しました。

導入して得られた効果

  • 品質の底上げ:警告や潜在バグに即気づける。
  • 保守の安心感:小さな更新を積み重ね、ビルド崩壊や大規模追従を避けられる。
  • 集中力の維持:定型作業を自動化し、機能開発に時間を使える。
  • モチベーションの維持:週次で PR が届き、進捗が可視化されることで「前に進んでいる感」を得やすい。

今後の展望

  • Lint のしきい値を段階的に引き上げ、将来的にはエラーでビルド失敗へ。
  • 変更影響が大きい依存(Kotlin、AGPなど)の検証手順テンプレート化。
  • lint.xmlbaseline の整備でノイズをさらに削減。

個人開発こそ、自動化で"守り"を固めて"攻め"に集中する 価値があります。同じ悩みを持つ方の参考になれば幸いです。

関連記事

この記事で触れたVtuberCameraの開発記録です。AIツールを取り入れながらリビルドした2週間の体験談。

https://zenn.dev/j____takumi/articles/vibe-coding-2-weeks-experience

Copilotを業務で使って得た教訓。Android開発での失敗談と学び。

https://zenn.dev/j____takumi/articles/how_to_use_copilot_in_my_job

Discussion