#!/usr/bin/env bash # # CloserApp — painterResource(non-vector XML) crash scanner. # # WHY THIS EXISTS: Compose's `painterResource(id)` picks its loader by file extension — any `.xml` # drawable is routed through the VectorDrawable loader, which throws at runtime: # java.lang.IllegalArgumentException: Only VectorDrawables and rasterized asset types are supported # the instant such a composable is rendered, if the XML's root is NOT (e.g. , # , , , , , ...). It compiles fine and is # invisible to unit tests — it only blows up on screen. This shipped once as O-ONBOARD-001: the app-icon # redesign (commit 334cb07) turned ic_launcher_foreground.xml from into a , crashing # every fresh install on the final onboarding slide + the login screen (both used # painterResource(R.drawable.ic_launcher_foreground)). Recurring QA missed it (logged-in emulators skip # onboarding/auth). Fix: point painterResource at the underlying raster, or use ImageVector/ImageBitmap. # # This scan is a cheap deterministic gate against the whole class. Exit 1 on any finding. # # Usage: ./scripts/painter-xml-scan.sh set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" SRC="$ROOT/app/src/main/java" RES_GLOB="$ROOT/app/src/main/res/drawable"* fail=0 found=0 # Collect every painterResource(R.drawable.NAME) reference in Kotlin source. while IFS= read -r line; do file="${line%%:*}" rest="${line#*:}" lineno="${rest%%:*}" name="$(printf '%s\n' "$rest" | grep -oE 'painterResource\(R\.drawable\.[A-Za-z0-9_]+' | head -1 | sed -E 's/.*R\.drawable\.//')" [ -z "$name" ] && continue # Is there an XML drawable resource of that name in any drawable* bucket? xml="" for d in $RES_GLOB; do [ -f "$d/$name.xml" ] && xml="$d/$name.xml" && break done [ -z "$xml" ] && continue # not an XML drawable (raster) → painterResource-safe # Root element of the XML drawable. root="$(grep -m1 -oE '<(vector|bitmap|layer-list|shape|selector|adaptive-icon|animated-vector|ripple|inset|clip|rotate|scale|transition|level-list|nine-patch|drawable)' "$xml" | head -1 | tr -d '<')" if [ "$root" != "vector" ]; then found=$((found+1)); fail=1 rel="${file#"$ROOT"/}" echo "🔴 CRASH RISK $rel:$lineno painterResource(R.drawable.$name) → <$root> ($name.xml)" echo " painterResource only loads XML or raster files; a <$root> root throws at render." echo " Fix: reference the underlying raster, or use ImageVector.vectorResource / ImageBitmap.imageResource." fi done < <(grep -rREn 'painterResource\(R\.drawable\.[A-Za-z0-9_]+' "$SRC" 2>/dev/null || true) if [ "$fail" -eq 0 ]; then echo "✅ painter-xml-scan: 0 painterResource() calls target a non- XML drawable." else echo "" echo "❌ painter-xml-scan: $found painterResource(non-vector XML) call(s) — each is a guaranteed runtime crash on render." fi exit $fail