Skip to content

Conversation

@codebutler
Copy link
Owner

Summary

This is a major modernization of the FareBot build system and Android codebase. The project has been migrated from Groovy-based Gradle to Kotlin DSL, updated to use Jetpack Compose for UI, and upgraded all dependencies to current versions. The build now uses Koin for dependency injection instead of Dagger, and Kotlinx Serialization instead of Gson.

Key Changes

Build System & Dependencies

  • Migrated build.gradlebuild.gradle.kts (Kotlin DSL)
  • Removed dependencies.gradle and inlined dependency management
  • Updated Android Gradle Plugin from 3.5.0-alpha13 to 8.8.0
  • Updated Kotlin from 1.3.31 to 2.1.20
  • Updated compile/target SDK from 28 to 35, minSdk to 21
  • Updated Java source/target compatibility from 1.7 to 17
  • Removed Fabric/Crashlytics integration (replaced with simple logging)
  • Removed jcenter() repository (deprecated)
  • Added Compose Gradle plugin and Compose BOM

Dependency Injection

  • Replaced Dagger with Koin for simpler, more maintainable DI
  • Created AndroidModule.kt with all Android-specific bindings
  • Refactored FareBotApplication to use Koin's startKoin()
  • Removed generated DaggerFareBotApplicationComponent

Serialization

  • Replaced Gson with Kotlinx Serialization
  • Created KotlinxCardKeysSerializer for card key serialization
  • Updated all card/transit classes to use Instant instead of java.util.Date

UI & Platform

  • Added Jetpack Compose support with Material 3
  • Created AndroidPlatformActions for platform-specific operations (clipboard, file picker, NFC settings, etc.)
  • Updated sample cards to use StringResource abstraction instead of Android Context
  • Refactored NfcStream to use Kotlin Flow for reactive tag events

Manifest & Configuration

  • Removed package attribute from AndroidManifest (now in build.gradle.kts namespace)
  • Added android:exported="true" to activities (required for API 31+)
  • Removed Fabric API key metadata
  • Updated farebot-android/build.gradle.kts with modern Android DSL

Documentation

  • Added comprehensive CLAUDE.md with proxy configuration guide for authenticated HTTP proxy environments
  • Includes troubleshooting section for common Gradle/proxy issues

Cleanup

  • Removed .gitmodules (nfc-felica-lib submodule)
  • Added .kotlin to .gitignore
  • Removed deprecated Fabric/Crashlytics dependencies

Notable Implementation Details

  • Proxy Configuration: The new CLAUDE.md documents a sophisticated local proxy setup for environments behind authenticated HTTP proxies, with health checks and credential rotation support
  • Kotlin Multiplatform Ready: The DI and serialization layers are now structured to support future multiplatform expansion
  • Compose Integration: Full Compose support with Material 3, navigation, and lifecycle integration
  • Coroutines: Integrated Kotlin Coroutines for async operations (NFC, database, etc.)
  • SQLDelight: Migrated to SQLDelight 2.1.0 for type-safe database access

https://claude.ai/code/session_016GBryRuV3PUzuQhuW2gArG

claude added 30 commits January 29, 2026 14:19
- Gradle: 5.4.1 → 8.11.1
- AGP: 3.5.0-alpha13 → 8.7.3
- Kotlin: 1.3.31 → 2.1.0
- compileSdk/targetSdk: 28 → 35, minSdk: 16 → 21
- Java compatibility: 7 → 17
- AutoDispose: 0.8.0 → 1.4.0
- AutoValue: 1.6.5 → 1.11.0
- Dagger: 2.22.1 → 2.52
- Room: 2.1.0-alpha07 → 2.6.1
- Material: 28.0.0 → 1.12.0
- Play Services Maps: 16.1.0 → 19.0.0
- Guava: 27.1-android → 33.3.1-android

Migrated to AGP 8.x namespace convention, removed Fabric/Crashlytics
(replaced with android.util.Log), fixed Kotlin 2.x nullability and API
changes, excluded old support-v4 transitive dependency from rxbroadcast,
and added android:exported for Android 12+ target compatibility.

https://claude.ai/code/session_016GBryRuV3PUzuQhuW2gArG
….serialization

- Replace AutoValue with Kotlin data classes across all card and transit modules
- Replace Guava collections with Kotlin stdlib (ImmutableMap/List -> Map/List)
- Migrate UI from Magellan/RxJava/Views to Jetpack Compose with Navigation
- Replace Dagger 2 with Hilt for dependency injection
- Replace GSON with kotlinx.serialization for JSON serialization
- Add @serializable annotations to all data classes
- Add @JvmStatic to companion object methods for Java interop
- Remove old View-based screens, adapters, layouts, and menus
- Remove RxJava, AutoDispose, KotterKnife, and other legacy dependencies

https://claude.ai/code/session_016GBryRuV3PUzuQhuW2gArG
- Convert all 65 remaining Java source files to Kotlin (excluding third_party)
- Remove @JvmStatic annotations from 89 files (no Java callers remain)
- Convert interfaces (CardSerializer, CardKeys, RawCard, CardPersister, etc.)
- Convert enums (CardType), abstract classes (Trip, Refill, TransitInfo, etc.)
- Convert utility classes (ByteArray, ByteUtils, IOUtils, etc.)
- Convert tag readers, protocols, and transit factories to Kotlin
- Only third_party/nfc-felica-lib Java files remain (external library)

https://claude.ai/code/session_016GBryRuV3PUzuQhuW2gArG
- Remove nfc-felica-lib git submodule, absorb source directly into project
- Convert all 15 Java files to Kotlin (FeliCaLib, FeliCaTag, NfcTag, etc.)
- Add kotlin-android plugin and kotlinStdlib dependency to build.gradle
- Zero Java source files remain in the entire project

https://claude.ai/code/session_016GBryRuV3PUzuQhuW2gArG
- Resolve ByteArray naming conflict with kotlin.ByteArray using FQN
- Fix nullability mismatches (ByteArray? parameters, nullable returns)
- Correct Byte vs Int type mismatches in FeliCaLib and FeliCaLiteTag
- Add missing override modifiers and remove final method overrides
- Add @contextual annotation for FareBotUiTree serialization

https://claude.ai/code/session_016GBryRuV3PUzuQhuW2gArG
- Fix Card subclass property overrides (override val instead of override fun)
- Fix nullable safe calls across all transit factories and tag readers
- Fix platform declaration clashes (val + fun with same JVM signature)
- Fix property access vs function call mismatches throughout codebase
- Migrate Room and Hilt from kapt to KSP for Kotlin 2.1.0 compatibility
- Update Dagger/Hilt from 2.52 to 2.56.2 for Kotlin metadata support
- Add KSP plugin (2.1.0-1.0.29) to build configuration
- Add @contextual annotations for kotlinx.serialization on abstract types
- Remove unnecessary @serializable from EasyCardTransitInfo
- Add missing string resources and fix drawable references
- Fix IOUtils.kt uninitialized variable errors
- Fix NfcTag, FeliCaTag, and response class compilation issues

https://claude.ai/code/session_016GBryRuV3PUzuQhuW2gArG
farebot-base, farebot-card, farebot-transit, and farebot-app-persist
had appcompat declared but never imported anything from it. Replaced
with the lightweight androidx.annotation dependency in farebot-base
(needed for @stringres).

https://claude.ai/code/session_016GBryRuV3PUzuQhuW2gArG
- Replace android.content.res.Resources with StringResource interface
  across all library modules (farebot-base, farebot-card*, farebot-transit*)
- Create AndroidStringResource adapter in farebot-app to bridge Android
  Context.getString() to the platform-agnostic StringResource interface
- Replace android.text.TextUtils with Kotlin stdlib equivalents
  (isNullOrEmpty, joinToString)
- Migrate from Room to SQLDelight for KMP-compatible persistence
  - Replace Room entities/DAOs with SQLDelight .sq schema files
  - Update DbCardPersister and DbCardKeysPersister for SQLDelight APIs
  - Remove Room dependencies, add SQLDelight 2.0.2 with Android driver
- Remove @stringres annotation dependency from farebot-base
- Update FareBotUiTree and UiTreeBuilder to use StringResource

https://claude.ai/code/session_016GBryRuV3PUzuQhuW2gArG
- Change all 25 library modules from com.android.library + kotlin-android
  to org.jetbrains.kotlin.multiplatform + com.android.library
- Restructure source directories: pure Kotlin code to src/commonMain/kotlin/,
  Android-specific code (NFC, SQLite, etc.) to src/androidMain/kotlin/
- Move Android resources and manifests to src/androidMain/
- Move SQLDelight .sq files to src/commonMain/sqldelight/
- Add Compose Multiplatform plugin (1.7.3) to root build.gradle classpath
- Add SQLDelight runtime dependency for commonMain
- Split SeqGoUtil into SeqGoDateUtil (commonMain) for pure date parsing
- Remove android.util.Log from OctopusTransitInfo for multiplatform compat
- Move SuicaTrip to androidMain due to SQLite dependency chain
- Configure androidTarget jvmTarget globally in root build.gradle
- Keep nfc-felica-lib as Android-only library (NFC is inherently platform-specific)

https://claude.ai/code/session_016GBryRuV3PUzuQhuW2gArG
…onMain, and port nfc-felica-lib to KMP

- Convert all build.gradle files to build.gradle.kts (Kotlin DSL)
- Add gradle/libs.versions.toml version catalog for centralized dependency management
- Create settings.gradle.kts with dependencyResolutionManagement
- Use buildscript/classpath in root for plugin resolution compatibility
- Remove all JVM dependencies from commonMain source sets:
  - Replace java.util.Date/Calendar with kotlinx-datetime Instant
  - Replace NumberFormat/Currency with pure Kotlin CurrencyFormatter
  - Replace java.lang.Long/Integer helpers with Kotlin stdlib
  - Remove ArrayUtils, Charsets, IOUtils from commonMain (moved to androidMain where needed)
- Port nfc-felica-lib to KMP:
  - Split into commonMain (constants, data classes) and androidMain (NFC operations)
  - Remove Parcelable from IDm/PMm, serialize via byte arrays
- Fix compilation errors in TagReader files (Date -> Clock.System.now())
- Fix sample card files to use Instant instead of Date
- Build passes: assembleDebug succeeds

https://claude.ai/code/session_016GBryRuV3PUzuQhuW2gArG
Replace Android R.string references with Compose Multiplatform Res.string
across all KMP library modules. Copy strings.xml files from androidMain/res
to commonMain/composeResources for all locales. Update StringResource
interface to use CMP StringResource type instead of Android int resource IDs.

https://claude.ai/code/session_016GBryRuV3PUzuQhuW2gArG
Replace Android-specific DBUtil/SQLiteDatabase station lookups with
SQLDelight-generated type-safe queries. Add .sq schema files for
felica, OVC, and SEQ Go station databases. Create expect/actual
BundledDatabaseDriverFactory for opening pre-bundled .db3 files.
Remove legacy DBUtil, FelicaDBUtil, OVChipDBUtil, and SeqGoDBUtil.

https://claude.ai/code/session_016GBryRuV3PUzuQhuW2gArG
Create common NFC technology interfaces (NfcTechnology, CardTransceiver,
ClassicTechnology, UltralightTechnology, NfcFTechnology) in commonMain
with Android wrapper implementations. Move DesfireProtocol and
CEPASProtocol to commonMain using CardTransceiver interface. Refactor
all TagReaders to use common technology interfaces instead of Android
NFC API types directly.

https://claude.ai/code/session_016GBryRuV3PUzuQhuW2gArG
- Add iosX64, iosArm64, iosSimulatorArm64 targets to all 26 KMP modules
- Replace expect/actual DateFormatting with pure Kotlin implementation using kotlinx-datetime
- Add iOS BundledDatabaseDriverFactory actual using NativeSqliteDriver
- Create farebot-shared module with Compose Multiplatform UI and iOS framework export
- Add iOS app entry point using ComposeUIViewController
- Add XcodeGen project configuration for iOS app
- Add SQLDelight native driver dependency for iOS modules

https://claude.ai/code/session_016GBryRuV3PUzuQhuW2gArG
Restructured to emphasize verifying the proxy before building, since
Gradle aggressively caches dependency resolution failures. Added
troubleshooting sections for the most common failure modes: stale
Gradle metadata caches, dead/restarted proxy, DNS failures, and
the --refresh-dependencies footgun.

https://claude.ai/code/session_016GBryRuV3PUzuQhuW2gArG
Document health check endpoint, JWT credential refresh behavior,
and retry logic now available in the proxy script.

https://claude.ai/code/session_016GBryRuV3PUzuQhuW2gArG
Previously lived at /tmp/local_proxy2.py which is ephemeral. Checking
it in makes the CLAUDE.md setup instructions self-contained. Updated
all references in CLAUDE.md to point to tools/local_proxy.py.

https://claude.ai/code/session_016GBryRuV3PUzuQhuW2gArG
- Create iOS NFC implementations (IosCardTransceiver, IosNfcFTechnology,
  IosUltralightTechnology) using Core NFC with dispatch_semaphore bridging
- Add shared Compose Multiplatform screens in farebot-shared (Home, Card,
  CardAdvanced, History, Keys, Help) with state-driven architecture
- Add shared UI state models (HomeUiState, CardUiState, HistoryUiState,
  KeysUiState, CardAdvancedUiState, TransactionItem, SupportedCardInfo)
- Add platform abstractions (PlatformActions, NfcStatus) and shared theme
- Add Compose Multiplatform string resources in composeResources
- Rewire Android app screens as thin wrappers that delegate to shared
  Compose screens while keeping Android ViewModels and Hilt DI
- Update MainActivity to use shared FareBotTheme

https://claude.ai/code/session_016GBryRuV3PUzuQhuW2gArG
- Move Screen route definitions from farebot-app to farebot-shared
- Add navigation-compose dependency to farebot-shared for KMP navigation
- Replace custom stack-based navigation in App.kt with Jetpack NavHost
- Update MainActivity to import Screen routes from shared module
- Remove FareBotNavigation.kt (routes now live in shared module)

https://claude.ai/code/session_016GBryRuV3PUzuQhuW2gArG
Clarifies the module's role as the Android-specific application entry
point, distinct from farebot-shared (the KMP library) and a future
iosApp Xcode project.

https://claude.ai/code/session_016GBryRuV3PUzuQhuW2gArG
- Create shared TransitFactoryRegistry in farebot-shared using CardType
  enum as key instead of JVM-specific Class<> references
- Remove JVM-specific getParentClass() from Card.kt (commonMain)
- Create iOS tag readers for DESFire, CEPAS, Ultralight, and FeliCa
  in each card module's iosMain, reusing common protocol code
- Create IosNfcScanner wrapping Core NFC's NFCTagReaderSession with
  automatic tag type detection and dispatch to readers
- Create IosPlatformActions with openUrl, clipboard, share support
- Wire FareBotApp with CardScanner and TransitFactoryRegistry params
- Wire MainViewController with real iOS services (scanner, registry,
  platform actions, supported card list)
- Update Android TransitFactoryRegistry to use shared version
- Make NfcDataConversions public for cross-module iOS access

iOS supports: Orca, Clipper, HSL, Opal, Myki (DESFire); Edy, Octopus,
KMT (FeliCa); EZ-Link (CEPAS). MIFARE Classic cards (OVChip, SeqGo,
etc.) are excluded due to iOS hardware limitations.

https://claude.ai/code/session_016GBryRuV3PUzuQhuW2gArG
Move SuicaTransitFactory, SuicaTrip, and SuicaUtil from androidMain to
commonMain, replacing all JVM-specific APIs with multiplatform equivalents:
- Context parameter → FelicaStationsDb (injected directly)
- java.util.Locale → expect/actual getSystemLanguage()
- android.util.Log → silent exception handling
- java.text.NumberFormat → CurrencyFormatter
- Integer.toHexString() → Int.toString(16)
- String.format() → Kotlin string templates
- java.util collections → Kotlin stdlib

Add DefaultStringResource and SystemLocale expect/actual to farebot-base.
Register Suica factory in both Android and iOS transit registries.

https://claude.ai/code/session_016GBryRuV3PUzuQhuW2gArG
Migrate from Hilt (Android-only) to Koin (KMP) for dependency injection,
enabling fully cross-platform ViewModels, navigation, and DI. Android and
iOS now both use the shared FareBotApp composable with koinViewModel().

- Add Koin 4.0.3 dependencies, remove Hilt/Dagger from all build files
- Create shared ViewModels: Home, Card, History, Keys (commonMain)
- Create shared Koin module (SharedModule) and platform modules
- Rewrite FareBotApp with koinViewModel() and koinInject()
- Create AndroidCardScanner wrapping NfcStream + TagReaderFactory
- Update IosNfcScanner to Flow-based CardScanner interface
- Update iOS MainViewController with Koin initialization
- Simplify MainActivity to use shared FareBotApp composable
- Remove 8 Android screen wrappers, 8 Android ViewModels
- Remove FareBotApplicationModule (Hilt), CardStream, ExportHelper
- Add AndroidPlatformActions, shared NavDataHolder

https://claude.ai/code/session_016GBryRuV3PUzuQhuW2gArG
Move platform-dependent code to commonMain so iOS has full functionality:

- Move custom ByteArray class from androidMain to commonMain, replacing
  Android Parcelable/Base64 with kotlin.io.encoding.Base64
- Move FareBotSerializersModule to shared commonMain, dropping the
  java.util.Date serializer (card models already use kotlinx.datetime.Instant)
- Move KotlinxCardSerializer to shared commonMain for cross-platform
  card serialization/deserialization
- Register CardSerializer in iOS Koin module
- Fix iOS BundledDatabaseDriverFactory to use real schema for app databases
  (farebot.db) and no-op schema only for pre-populated bundled databases
- Bundle felica_stations.db3 in iOS app resources
- Call initKoin() from Swift app entry point before UI initialization
- Link FareBotKit framework as dependency in project.yml

https://claude.ai/code/session_016GBryRuV3PUzuQhuW2gArG
…functions

Delete the custom com.codebutler.farebot.base.util.ByteArray immutable
wrapper class and NativeByteArray typealias. Replace all usages across
63 files with plain kotlin.ByteArray plus extension functions (hex(),
toBase64(), decodeBase64()) in a new ByteArrayExt.kt file.

Key changes:
- Remove .bytes() unwrap calls (no longer needed)
- Remove ByteArray.create() / ByteArray() wrapping calls
- Remove kotlin.ByteArray qualified types (no more shadowing)
- Remove FareBotByteArray alias in CEPAS module
- Update FareBotSerializersModule to serialize kotlin.ByteArray as base64
- Add hex() import where ByteArray.hex() extension is used

https://claude.ai/code/session_016GBryRuV3PUzuQhuW2gArG
Delete the following dead code:
- IOUtils.kt: Apache Commons IO copy, unused by any code
- Charsets.kt: Apache Commons charset constants, only used by IOUtils
- ErrorUtils.kt: Legacy Android error dialogs, unused after Compose migration
- ArrayUtil.kt: Array helpers (indexOf, contains, etc.), replaced by Kotlin stdlib
- FinderUtil.kt: Collection find/filter helpers, replaced by Kotlin stdlib
- IPredicate.kt: Predicate interface, replaced by Kotlin function types

Also remove the now-unnecessary checkstyle exclusions for the deleted
Java-origin files in build.gradle.kts.

https://claude.ai/code/session_016GBryRuV3PUzuQhuW2gArG
- Kotlin 2.1.0 → 2.1.20
- AGP 8.7.3 → 8.8.0
- KSP 2.1.0-1.0.29 → 2.1.20-2.0.1
- Compose Multiplatform 1.7.3 → 1.8.0 (iOS now stable)
- SQLDelight 2.0.2 → 2.1.0
- kotlinx-serialization 1.7.3 → 1.8.0
- kotlinx-coroutines 1.9.0 → 1.10.1
- kotlinx-datetime 0.6.1 → 0.6.2
- Compose BOM 2024.12.01 → 2025.01.01
- AndroidX Lifecycle 2.8.7 → 2.9.0
- AndroidX Navigation 2.8.5 → 2.9.0
- AndroidX Activity 1.9.3 → 1.10.0
- Koin 4.0.3 → 4.1.1
- Guava 33.3.1 → 33.4.0
- Checkstyle 10.20.1 → 10.21.0
- Gradle 8.11.1 → 8.12

https://claude.ai/code/session_016GBryRuV3PUzuQhuW2gArG
claude and others added 7 commits January 30, 2026 19:57
- Add TripMap composable entry with station location display
- Add AddKey composable entry with form UI and AddKeyViewModel
- Add shared Settings screen (used by iOS, Android keeps legacy prefs)
- Implement platform file picker/export via PlatformActions
- iOS: use UIActivityViewController for share instead of clipboard
- iOS: show auto-dismissing UIAlertController for toast
- Register file picker launcher in Android MainActivity

https://claude.ai/code/session_016GBryRuV3PUzuQhuW2gArG
… iOS app

Dependency upgrades:
- Kotlin 2.1.20 -> 2.3.0, KSP 2.1.20 -> 2.3.0
- AGP 8.8.0 -> 9.0.0 (with new android-kotlin-multiplatform-library plugin)
- Compose Multiplatform 1.8.0 -> 1.10.0, Compose BOM 2025.01.01 -> 2026.01.01
- SQLDelight 2.1.0 -> 2.2.1, Serialization 1.8.0 -> 1.10.0
- Lifecycle 2.9.0 -> 2.9.6, Navigation 2.9.0 -> 2.9.1
- Coroutines 1.10.1 -> 1.10.2, Datetime 0.6.2 -> 0.7.1
- Gradle wrapper updated, minSdk raised to 23
- Removed crashing gradle-versions-plugin
- Switched lifecycle/navigation to JetBrains KMP artifacts

KMP/iOS compilation fixes:
- Replace Java APIs in commonMain with Kotlin equivalents: Math.ceil,
  Integer.valueOf, System.arraycopy, String.format, System.currentTimeMillis,
  java.util.Comparator, @JvmField, String(ByteArray), toByteArray()
- Fix CoreNFC Kotlin/Native interop: protocol types (NFCMiFareTagProtocol,
  NFCFeliCaTagProtocol), method names (pollingWithSystemCode,
  readWithoutEncryptionWithServiceCodeList, beginSession), enum constants
  (NFCFeliCaPollingTimeSlotMax1), callback parameter types (Int -> Long)
- Fix Navigation KMP API: getString -> savedstate.read { getStringOrNull }
- Add ExperimentalForeignApi opt-in to BundledDatabaseDriverFactory
- Move nfc-felica-lib dependency from androidMain to commonMain

iOS app fixes:
- Add CADisableMinimumFrameDurationOnPhone to Info.plist (Compose requirement)
- Add -lsqlite3 linker flag for SQLite support
- Fix openUrl using async openURL API instead of deprecated synchronous one
- Fix About URL (github.com -> github.io)
- Add PRODUCT_NAME, code signing, and development team to project.yml
- Add NFC usage description to Info.plist

Supported Cards screen restoration:
- Move card images from Android drawables to Compose multiplatform resources
- Restore card images, preview/experimental labels, and card layout in HelpScreen
- Consolidate supported cards list into shared ALL_SUPPORTED_CARDS constant
- Fix Supported Cards home button navigating to NFC scan instead of help

Other UI fixes:
- Fix blank Preferences screen on iOS by implementing supportsLaunchFromBackground
  with NSUserDefaults persistence

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Implement complete shared UI layer with Compose Multiplatform screens
(Home, Card, History, Keys, Help, AddKey, TripMap, SupportedCards),
viewmodels, drawable resources, sample card data, analytics abstraction,
and platform-specific adapters for Android and iOS. Update DeSFIRe protocol,
NFC scanner, database driver, and transit factory registry.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Merge useful parts of third_party/nfc-felica-lib into farebot-card-felica
as clean multiplatform Kotlin. Introduce FeliCaTagAdapter interface with
platform-specific implementations (AndroidFeliCaTagAdapter using NfcF,
IosFeliCaTagAdapter using Core NFC) and a shared FeliCaReader algorithm.
Migrate IDm, PMm, constants, and utilities into the farebot-card-felica
module. Update all transit module imports (Suica, Edy, KMT, Octopus).
Delete the entire nfc-felica-lib third-party module.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Update libs.versions.toml with latest dependency versions. Configure iOS
project with NFC entitlements, update Info.plist, and sync Xcode project.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants