iOS Phase 2: Build Error Resolution Session

Date: November 18, 2025 Session Type: Debugging & Build Fixes Scope: iOS Phase 2 post-merge integration issues PR: #278 - iOS Phase 2 build errors and Firebase initialization

Overview

This session focused on resolving build and runtime errors discovered after merging iOS Phase 2 PRs (#275 TournamentDetailViewModel, #276 TournamentDetailView). The integration testing phase revealed three categories of issues that required iterative debugging and fixes.

Context

Merged PRs

PR #275: TournamentDetailViewModel.swift (Agent 2/AAM)

  • Firebase Firestore integration
  • Tournament detail loading, join/leave operations
  • Transaction-based participant management

PR #276: TournamentDetailView.swift (Agent 1/AAP)

  • SwiftUI detail screen
  • Navigation from TournamentListView
  • UI components for tournament display and actions

Integration Status Pre-Session

  • ✅ Code reviews completed
  • ✅ Integration compatibility verified
  • ✅ Both PRs merged to main
  • ❌ Build errors on iOS framework compilation
  • ❌ Runtime crashes in iOS app

Issues Discovered & Fixed

Issue 1: KMP-NativeCoroutines Plugin Compatibility

Error Message:

NoSuchMethodError: 'void org.gradle.api.provider.Property.convention(Object)'

Symptoms:

  • iOS framework build failed during KMP compilation
  • Gradle task :shared:domain:podGenIOS failed
  • Build worked in previous sessions but broke after dependency updates

Root Cause:

  • kmp-nativecoroutines plugin version ALPHA-39 incompatible with Kotlin 2.2.21
  • Plugin uses deprecated Gradle API methods
  • Version mismatch between Kotlin compiler and coroutines plugin

Solution:

  • Updated kmp-nativecoroutines to version ALPHA-48 in gradle/libs.versions.toml
  • ALPHA-48 includes fixes for Kotlin 2.2.x compatibility

Files Changed:

  • gradle/libs.versions.toml

Learning:

  • KMP ecosystem requires close version alignment between Kotlin, coroutines plugin, and Gradle
  • ALPHA versions may have breaking changes between releases
  • Check plugin compatibility when updating Kotlin versions

Issue 2: Swift Type Conversion Errors (7 Total)

After resolving the build framework issue, Swift compilation revealed multiple type conversion errors in the iOS code.

Error Category A: Unused Value Bindings (2 errors)

Location: TournamentDetailViewModel.swift

Error Messages:

// Line 68
Immutable value 'tournament' was never used; consider replacing with '_' or removing it
 
// Line 139
Immutable value 'tournament' was never used; consider replacing with '_' or removing it

Problem:

  • guard let tournament = tournament else { return } pattern used
  • Variable binding created but never referenced in subsequent code
  • Only needed nil check, not the unwrapped value

Solution:

// Before
guard let tournament = tournament else {
    errorMessage = "Tournament not loaded"
    return
}
 
// After
guard tournament != nil else {
    errorMessage = "Tournament not loaded"
    return
}

Files Changed:

  • iosApp/ArcheryApprentice/ArcheryApprentice/TournamentDetailViewModel.swift (lines 68, 139)

Error Category B: Swift Int to Kotlin Int32 Conversion (3 errors)

Location: TournamentDetailViewModel.swift

Error Messages:

// Lines 298, 299, 313
Cannot convert value of type 'Int' to expected argument type 'Int32'

Problem:

  • Swift Int defaults to platform word size (64-bit on modern iOS)
  • Kotlin Int is always 32-bit
  • KMP shared module expects Int32 for Kotlin interop
  • Direct assignment causes type mismatch

Solution:

// Before
currentParticipants: currentParticipants,
maxParticipants: maxParticipants,
 
// After
currentParticipants: Int32(currentParticipants),
maxParticipants: Int32(maxParticipants),

Files Changed:

  • iosApp/ArcheryApprentice/ArcheryApprentice/TournamentDetailViewModel.swift (lines 298, 299, 313)

Pattern:

  • Always wrap Swift Int in Int32() when passing to Kotlin types
  • Applies to: participant counts, array sizes, numeric fields from Firebase

Error Category C: KotlinLong to Int64 Conversion (1 error)

Location: TournamentDetailView.swift

Error Message:

// Line 107
Value of type 'KotlinLong?' has no member 'longValue'

Problem:

  • Attempted to use .longValue property which doesn’t exist
  • KotlinLong uses .int64Value for Swift interop
  • Different naming convention than expected

Solution:

// Before
let date = Date(timeIntervalSince1970: Double(timestamp.longValue) / 1000.0)
 
// After
let date = Date(timeIntervalSince1970: Double(timestamp.int64Value) / 1000.0)

Files Changed:

  • iosApp/ArcheryApprentice/ArcheryApprentice/TournamentDetailView.swift (line 107)

Pattern:

  • Use .int64Value for KotlinLong → Swift Int64 conversion
  • Use .int32Value for KotlinInt → Swift Int32 conversion

Error Category D: Property Name Mismatch (1 error)

Location: TournamentDetailView.swift

Error Message:

// Line 181
Value of type 'Tournament' has no member 'public_'

Problem:

  • Kotlin property isPublic: Boolean
  • Swift code tried to access public_ (old naming convention)
  • Property name changed in shared module but iOS code not updated

Solution:

// Before
if tournament.public_ {
    // ...
}
 
// After
if tournament.isPublic {
    // ...
}

Files Changed:

  • iosApp/ArcheryApprentice/ArcheryApprentice/TournamentDetailView.swift (line 181)

Pattern:

  • Check Kotlin property names in shared module when accessing from Swift
  • Kotlin isPublic → Swift isPublic (no underscore suffix)

Issue 3: Firebase Initialization Issues

After fixing all build errors, runtime testing revealed Firebase initialization crashes.

Error Message (Runtime):

Fatal error: Failed to get FirebaseApp instance. Please call FirebaseApp.configure() before using Firebase.

Symptoms:

  • App launched successfully
  • Crash when navigating to TournamentListView
  • Firestore queries attempted before Firebase initialization

Root Cause Analysis:

Problem 1: Missing GoogleService-Info.plist

  • File required for Firebase iOS SDK configuration
  • Contains API keys, project ID, bundle ID mappings
  • Gitignored for security (not in repository)
  • Each developer needs their own copy

Problem 2: Improper initialization logic in ArcheryApprenticeApp.swift

// Original (WRONG)
guard let _ = FirebaseApp.app() else {
    print("⚠️ Firebase not configured")
    return AnyView(Text("Firebase not configured"))
}
// Execution stops here if Firebase not configured
// WindowGroup never reached

Problem 3: No graceful fallback in ViewModels

  • TournamentListViewModel immediately queries Firestore
  • TournamentDetailViewModel immediately queries Firestore
  • No check for Firebase availability
  • Caused crashes when Firebase not configured

Solutions Applied:

Fix 1: Updated ArcheryApprenticeApp.swift initialization logic

// New (CORRECT)
@main
struct ArcheryApprenticeApp: App {
    init() {
        if FirebaseApp.app() == nil {
            FirebaseApp.configure()
        }
    }
 
    var body: some Scene {
        WindowGroup {
            if FirebaseApp.app() != nil {
                ContentView()
            } else {
                VStack {
                    Text("⚠️ Firebase Configuration Required")
                    // ... instructions
                }
            }
        }
    }
}

Changes:

  • Use if/else instead of guard/return
  • Allow WindowGroup to render regardless of Firebase status
  • Show configuration instructions if Firebase not available
  • App remains functional (doesn’t crash)

Fix 2: Added Firebase availability checks in ViewModels

TournamentListViewModel.swift:

func loadTournaments() {
    guard FirebaseApp.app() != nil else {
        self.errorMessage = "Firebase not configured. Add GoogleService-Info.plist to project."
        self.isLoading = false
        return
    }
 
    // Proceed with Firestore query...
}

TournamentDetailViewModel.swift:

func loadTournamentDetails() {
    guard FirebaseApp.app() != nil else {
        self.errorMessage = "Firebase not configured. Add GoogleService-Info.plist to project."
        self.isLoading = false
        return
    }
 
    // Proceed with Firestore query...
}

Benefits:

  • Graceful error messages instead of crashes
  • Users see clear instructions
  • App remains usable for non-Firebase features
  • Developers can run app without Firebase setup

Files Changed:

  • iosApp/ArcheryApprentice/ArcheryApprentice/ArcheryApprenticeApp.swift
  • iosApp/ArcheryApprentice/ArcheryApprentice/TournamentListViewModel.swift
  • iosApp/ArcheryApprentice/ArcheryApprentice/TournamentDetailViewModel.swift

Documentation Created:

  • Firebase setup instructions in error message UI
  • Developer guide entry (see ios-firebase-setup)

Summary of Changes

Files Modified (9 total)

Gradle Configuration:

  1. gradle/libs.versions.toml - Updated kmp-nativecoroutines to ALPHA-48

iOS Swift Files: 2. iosApp/ArcheryApprentice/ArcheryApprentice/ArcheryApprenticeApp.swift - Firebase init logic 3. iosApp/ArcheryApprentice/ArcheryApprentice/TournamentListViewModel.swift - Firebase availability check 4. iosApp/ArcheryApprentice/ArcheryApprentice/TournamentDetailViewModel.swift - Type conversions + Firebase checks 5. iosApp/ArcheryApprentice/ArcheryApprentice/TournamentDetailView.swift - Type conversions + property names

Build Status

Before Session:

  • ❌ iOS framework build: FAILED (KMP plugin error)
  • ❌ Swift compilation: FAILED (7 type errors)
  • ❌ Runtime: CRASHED (Firebase not initialized)

After Session:

  • ✅ iOS framework build: SUCCESS
  • ✅ Swift compilation: SUCCESS (all 7 errors fixed)
  • ✅ Runtime: GRACEFUL (shows Firebase config instructions)

Testing Performed

  1. Build Verification:

    • Clean build with ./gradlew clean build
    • iOS framework generation successful
    • Xcode compilation successful
  2. Runtime Testing (Firebase not configured):

    • App launches successfully
    • Shows Firebase configuration message
    • No crashes
    • Graceful error handling
  3. Integration Points Verified:

    • Navigation from TournamentListView works
    • TournamentDetailView renders correctly
    • Type conversions working (Swift ↔ Kotlin)

Key Learnings

Swift-Kotlin Type Interoperability

Type Mapping Patterns:

// Kotlin → Swift
KotlinInt      Int32  (use .int32Value)
KotlinLong     Int64  (use .int64Value)
Kotlin String String (direct)
Kotlin Boolean Bool   (direct)
 
// Swift → Kotlin (for Shared module)
Swift Int Int32  (wrap with Int32())
Swift Int64 Long   (wrap with KotlinLong(longLong:))
Swift String String (direct)
Swift Bool Boolean(direct)

Property Access:

  • Kotlin isPublic: Boolean → Swift isPublic (no underscore)
  • Kotlin public: Boolean → Swift public_ (keyword collision, suffix added)

See: kotlin-swift-type-mappings

Firebase iOS Setup Requirements

Required Files:

  1. GoogleService-Info.plist - Must be in Xcode project
  2. Firebase SDK dependencies - In Podfile/SPM
  3. Initialization code - In app entry point

Best Practices:

  • Check Firebase availability before Firestore queries
  • Provide clear error messages for missing configuration
  • Don’t crash if Firebase not configured
  • Allow app to run in degraded mode

See: ios-firebase-setup

KMP Plugin Version Management

Critical Dependencies:

  • Kotlin compiler version
  • kmp-nativecoroutines plugin version
  • Gradle version
  • Must maintain compatibility across all three

When Updating Kotlin:

  1. Check kmp-nativecoroutines release notes
  2. Update plugin to compatible ALPHA version
  3. Test iOS framework build
  4. Document version combinations that work

Follow-Up Items

  • Add GoogleService-Info.plist template to repository
  • Create developer onboarding doc for iOS setup
  • Document working Kotlin + kmp-nativecoroutines version combinations
  • Consider CI check for Swift type conversions

References