Home > Developer Guide > Architecture > Week 28 iOS Firebase Integration


Week 28: iOS Firebase Integration Milestone

Status: ✅ Complete - iOS app successfully loading tournaments from Firebase Firestore

Significance: Cross-platform KMP architecture verified working on both iOS and Android platforms

Overview

Week 28 marked a major milestone in the Archery Apprentice project: the iOS app successfully loading real tournament data from Firebase Firestore, validating the entire cross-platform Kotlin Multiplatform (KMP) architecture end-to-end.

Achievement: iOS and Android apps now share the same Kotlin codebase for:

  • Domain models with proper serialization
  • Data sources (GitLive Firebase SDK)
  • Real-time data synchronization via Flow/AsyncSequence
  • Business logic and validation

Duration: Week 28 multi-session effort (Phase 1: Framework setup, Phase 2: Firebase integration)


Problem Statement

Initial Symptom

After completing the iOS framework integration with mock data (Phase 1), attempting to connect to real Firebase data resulted in:

Error: Missing or insufficient permissions

Confounding Factor: Android app worked perfectly with the same Firebase project and security rules.

Investigation

Added extensive logging to GitLiveRemoteTournamentDataSource to trace the Firebase query:

// Debug logging revealed the issue
logger.d(TAG, "Firestore query: collection('tournaments').whereEqualTo('public', true)")
logger.d(TAG, "Retrieved document fields: ${document.data}")

Discovery: The query looked correct, but permissions error persisted on iOS while Android succeeded.


Root Cause Analysis

The Serialization Mismatch

Root Cause: Field name serialization inconsistency between GitLive Firebase SDK on iOS vs Android native SDK.

Kotlin Property:

data class Tournament(
    val isPublic: Boolean = true  // Kotlin naming convention
)

Firestore Document:

{
  "name": "Sample Tournament",
  "isPublic": true,  // ❌ iOS SDK serialized with "is" prefix
  "public": true     // ✅ What the query expected
}

Firestore Query:

collection("tournaments")
    .where("public", equalTo = true)  // Looking for "public" field

Platform Behavior Differences:

PlatformFirebase SDKSerialization Behavior
AndroidNative Firebase SDKStrips “is” prefix from Boolean properties → "public"
iOSGitLive Firebase wrapperPreserves property name as-is → "isPublic"

Result:

  • Android: Query matched because native SDK automatically converted isPublic"public"
  • iOS: Query failed permissions check because field mismatch triggered security rule evaluation error

Solution Implementation

The Fix: Explicit Field Mapping

Added @SerialName annotation to explicitly control Firestore field name across all platforms:

// File: shared/domain/src/commonMain/kotlin/com/archeryapprentice/domain/models/tournament/Tournament.kt
 
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
 
@Serializable
data class Tournament(
    val id: String = "",
    val name: String = "",
 
    // Access control - explicit field mapping
    @SerialName("public")  // ✅ Firestore field name
    val isPublic: Boolean = true,  // Kotlin property name
 
    val joinCode: String = generateJoinCode(),
    // ... rest of fields
)

Why This Works:

  • Kotlin code: Uses idiomatic isPublic property name (follows Kotlin conventions)
  • Firestore: Stores as "public" field (matches query and security rules)
  • Cross-platform: Consistent behavior on both Android and iOS

PR: #249 - fix: Add @SerialName annotation to Tournament.isPublic for iOS Firebase compatibility

Files Modified:

  • shared/domain/src/commonMain/kotlin/com/archeryapprentice/domain/models/tournament/Tournament.kt

Verification:

  • Removed debug logging after fix confirmed
  • Tested on iOS Simulator: ✅ Tournaments load successfully
  • Tested on Android: ✅ No regressions
  • Real-time updates: ✅ Working on both platforms

Architecture Verification

Validated Components

This milestone verified the entire KMP stack works end-to-end on iOS:

✅ GitLive Firebase SDK (iOS)

  • Cross-platform Firebase wrapper functional on iOS
  • Firestore queries execute correctly
  • Real-time listeners work with Flow

✅ KMP-NativeCoroutines

  • Kotlin Flow<List<Tournament>> → Swift AsyncSequence
  • Automatic conversion working seamlessly
  • No manual bridging code needed in Swift

✅ Typed Domain Models

  • Kotlin data classes available in Swift
  • Type safety preserved across platform boundary
  • No manual JSON parsing in Swift layer

✅ Real-Time Updates

  • Firebase real-time listeners functional
  • Data flows from Firestore → Kotlin Flow → Swift AsyncSequence → SwiftUI
  • UI updates automatically on data changes

✅ Serialization Consistency

  • @SerialName annotations ensure field name consistency
  • Cross-platform serialization works correctly
  • Pattern established for future model additions

Architecture Diagram

┌─────────────────────────────────────────────────────────────┐
│                     iOS App (Swift)                         │
│  ┌───────────────────────────────────────────────────────┐  │
│  │ TournamentListView (SwiftUI)                          │  │
│  │  └─ Observes: AsyncSequence<[Tournament]>            │  │
│  └────────────────────────┬──────────────────────────────┘  │
│                           │                                 │
│  ┌────────────────────────▼──────────────────────────────┐  │
│  │ TournamentListViewModel (Swift)                       │  │
│  │  └─ Uses: KMP-NativeCoroutines wrapper               │  │
│  └────────────────────────┬──────────────────────────────┘  │
└──────────────────────────┬┼──────────────────────────────────┘
                           ││
           ┌───────────────┼┼───────────────┐
           │  Shared.framework (Kotlin)    ││
           │  ┌────────────▼▼────────────┐ ││
           │  │ RemoteTournamentDataSource│ ││
           │  │  @NativeCoroutines        │ ││
           │  │  Flow<List<Tournament>>   │ ││
           │  └────────────┬──────────────┘ ││
           │               │                ││
           │  ┌────────────▼──────────────┐ ││
           │  │ Tournament (Domain Model) │ ││
           │  │  @SerialName("public")    │ ││
           │  │  val isPublic: Boolean    │ ││
           │  └────────────┬──────────────┘ ││
           │               │                ││
           │  ┌────────────▼──────────────┐ ││
           │  │ GitLiveFirebaseDataSource│ ││
           │  │  (GitLive SDK wrapper)    │ ││
           │  └────────────┬──────────────┘ ││
           └───────────────┼─────────────────┘
                           │
                ┌──────────▼──────────┐
                │ Firebase Firestore  │
                │  Collection:        │
                │   tournaments       │
                │    └─ "public": true│
                └─────────────────────┘

Key Learnings & Best Practices

1. Cross-Platform Serialization

Lesson: Always use @SerialName for explicit field mapping in KMP models that interact with external systems (Firebase, REST APIs, etc.).

Why:

  • Different platform SDKs may have different naming conventions
  • Kotlin property names may not match backend field names
  • Explicit mapping prevents subtle platform-specific bugs

Pattern to Follow:

@Serializable
data class MyModel(
    @SerialName("backend_field_name")  // Backend/Firestore name
    val myProperty: String,             // Kotlin property name
)

2. Platform Behavior Differences

Lesson: Native SDKs and cross-platform wrappers may handle the same data differently.

Android vs iOS Example:

  • Android Firebase SDK: Automatically strips “is” prefix from Boolean properties
  • GitLive on iOS: Uses property name as-is without transformation

Implication: Don’t rely on “it works on Android” - always test iOS explicitly, especially for data serialization.

3. iOS Debugging Techniques

Lesson: Console logs are critical for diagnosing KMP integration issues.

Effective Debug Strategy:

  1. Add comprehensive logging in Kotlin data source
  2. Build and link framework to iOS
  3. Run in Xcode and monitor console output
  4. Verify query parameters, field names, and data flow

Example:

logger.d(TAG, "Firestore query: $queryDetails")
logger.d(TAG, "Retrieved ${documents.size} documents")
logger.d(TAG, "Document fields: ${document.data}")

4. Architecture Validation

Lesson: This milestone proves the KMP architecture works end-to-end.

Validated Assumptions:

  • ✅ Single shared codebase can serve both platforms
  • ✅ No platform-specific data layer needed
  • ✅ Type safety preserved across boundaries
  • ✅ Real-time updates work cross-platform
  • ✅ Minimal Swift code required (just UI + ViewModel)

Confidence Gained: Future Firebase features (auth, tournament creation, scoring) can follow the same pattern.


Attempted Approaches

❌ Approach 1: Rename Property from isPublic to public

Attempted Change:

data class Tournament(
    val public: Boolean = true  // ❌ Not valid Kotlin
)

Why It Failed:

  • public is a reserved keyword in Kotlin
  • Causes compilation errors
  • Would break existing Android code

Outcome: Reverted immediately

✅ Approach 2: Add @SerialName("public") Annotation

Implemented Change:

data class Tournament(
    @SerialName("public")      // ✅ Firestore field name
    val isPublic: Boolean = true  // ✅ Valid Kotlin property
)

Why It Worked:

  • Preserves Kotlin naming conventions
  • Explicitly controls serialization
  • Works consistently on both platforms
  • No breaking changes to existing code

Outcome: Problem solved ✅


Files Modified/Created

Main Repository Files

Domain Model (Modified):

shared/domain/src/commonMain/kotlin/com/archeryapprentice/domain/models/tournament/Tournament.kt
  • Added @SerialName("public") annotation to isPublic property
  • Import added: kotlinx.serialization.SerialName

Data Source (Reference):

shared/data/src/commonMain/kotlin/com/archeryapprentice/data/datasource/remote/GitLiveRemoteTournamentDataSource.kt
  • Production Firebase data source implementation
  • Uses query: where("public", equalTo = true)
  • Debug logging added during investigation (later removed)

iOS ViewModel (Reference):

iosApp/ArcheryApprentice/ArcheryApprentice/TournamentListViewModel.swift
  • Swift ViewModel observing Kotlin Flow via KMP-NativeCoroutines
  • Displays tournaments in SwiftUI list

Pull Request

PR #249: fix: Add @SerialName annotation to Tournament.isPublic for iOS Firebase compatibility

  • Status: Merged ✅
  • Impact: Unblocks all iOS Firebase features
  • Breaking Changes: None
  • Migration Required: None

Next Steps for iOS Development

Immediate Follow-Ups (Unblocked by This Milestone)

  1. Firebase Authentication on iOS

    • Use same @SerialName pattern for User model
    • Test GitLive Auth SDK on iOS
    • Verify token management cross-platform
  2. Tournament Creation from iOS

    • Implement TournamentCreationView (SwiftUI)
    • Use shared validation logic from KMP
    • Test Firestore writes from iOS
  3. Live Scoring on iOS

    • Port scoring UI to SwiftUI
    • Use shared scoring calculation logic
    • Test real-time score updates
  4. Tournament Detail View

    • Display full tournament information
    • Join tournament functionality
    • Participant list with real-time updates

Long-Term iOS Roadmap

  • Full feature parity with Android app
  • iOS-specific UI polish (native navigation, gestures)
  • App Store submission preparation
  • iOS-specific testing and QA

Context & Background

Week 28 Development Phases

Phase 1: Framework Integration (Complete)

  • iOS Xcode project recreated
  • Kotlin framework builds for iOS simulator
  • CocoaPods integration (Firebase SDK)
  • First SwiftUI screen with mock data
  • IOSLoggingProvider implementation
  • See: WEEK_28_IOS_SESSION_SUMMARY.md in main repo

Phase 2: Real Firebase Integration (THIS MILESTONE)

  • Replaced mock data with real Firebase queries
  • Discovered and fixed field serialization issue
  • Validated entire KMP architecture on iOS
  • Established patterns for future features

Impact Assessment

Technical Impact:

  • ✅ KMP architecture validated end-to-end
  • ✅ iOS development unblocked for all Firebase features
  • ✅ Serialization best practices established
  • ✅ Debug methodology proven effective

Business Impact:

  • iOS app can now use all existing backend infrastructure
  • No need for iOS-specific backend/data layer
  • Faster feature development (write once, deploy both platforms)
  • Reduced maintenance burden (single codebase for business logic)

Developer Experience:

  • Clear pattern for adding new models
  • Confidence in cross-platform approach
  • Effective debugging techniques documented
  • Architecture decisions validated

Main Repository:

  • /docs/WEEK_28_IOS_SESSION_SUMMARY.md - Phase 1 framework integration details
  • /docs/IOS_DEV_STATUS.md - Overall iOS development status (if exists)

Architecture Decisions:

Firebase Integration:

External Resources:


Phase 2 Completion Update (2025-11-18)

Status:iOS Phase 2 COMPLETE - Tournament detail view and participation features implemented

What Was Completed

Following the initial Firebase integration (Phase 1), Phase 2 extended the iOS app with full tournament participation features:

PRs Merged:

  • PR #275: TournamentDetailViewModel (Firebase integration for tournament details, join/leave operations)
  • PR #276: TournamentDetailView (SwiftUI detail screen with participant management)
  • PR #278: iOS Phase 2 build error fixes (type conversions, Firebase initialization, KMP plugin compatibility)

Features Implemented:

  • ✅ Tournament detail screen (name, status, location, date, description, rules, participants)
  • ✅ Join tournament operation (transaction-based to prevent race conditions)
  • ✅ Leave tournament operation
  • ✅ Real-time participant list display
  • ✅ User participation status checking
  • ✅ Navigation from TournamentListView to TournamentDetailView

Technical Achievements

Swift-Kotlin Interop Patterns Established:

  • KotlinLong.int64Value for timestamp conversion (not .longValue)
  • Int32() wrapping for Swift Int → Kotlin Int conversion
  • Property name mapping patterns (isPublic vs public_)
  • Guard let vs nil check best practices

Firebase Best Practices:

  • Graceful error handling when Firebase not configured
  • Transaction-based participant updates (prevents duplicate joins)
  • Firebase availability checks in all ViewModels
  • Clear user-facing error messages

Build Error Resolution:

  • KMP-NativeCoroutines plugin updated (ALPHA-39 → ALPHA-48) for Kotlin 2.2.21 compatibility
  • 7 Swift type conversion errors fixed
  • Firebase initialization pattern established (if/else instead of guard/return)

Documentation Created

Guides:

Roadmap:

Shared Code Audit Results

Investigation into remaining features revealed high KMP coverage:

Feature AreaShared Code CoverageStatus
Tournament Management100%✅ Phase 2 Complete
Equipment Management95%🔄 10 presenters + 11 DAOs ready
Round Scoring90%🔄 Presenter + DAOs exist
Statistics & Analytics50%🔄 DAOs exist, logic Android-specific
Authentication40%🔄 Models exist, platform SDKs needed

Key Finding: Phases 4 (Round Scoring) and 5 (Equipment) can leverage extensive existing shared code, requiring primarily iOS UI implementation.

Next Steps

See ios-development-roadmap for complete feature parity plan:

Phase 3 (Next): Tournament Creation + Authentication (2-3 weeks)

  • Google/Apple Sign-In integration
  • TournamentCreationView implementation
  • Firebase Auth ViewModel

Phase 4: Active Scoring (4-5 weeks)

  • Leverage existing RoundDisplayPresenter
  • Implement ActiveScoringView
  • Offline-first round scoring

Phase 5: Equipment Management (3-4 weeks)

  • Wrap 10 existing equipment presenters
  • EquipmentListView, EquipmentDetailView
  • Bow setup management

Estimated Time to Feature Parity: 12-16 weeks


Conclusion

Week 28’s iOS Firebase integration represents a critical validation point for the Archery Apprentice project’s cross-platform architecture. The successful resolution of the field serialization issue not only unblocked iOS development but also established clear patterns and best practices for future cross-platform feature development.

Phase 1 Achievement: The @SerialName annotation pattern ensures consistent data serialization across platforms, preventing subtle platform-specific bugs and enabling true code sharing between iOS and Android.

Phase 2 Achievement: Tournament participation features fully implemented, demonstrating Swift-Kotlin interop patterns and Firebase transaction patterns for production-ready features.

Status: 🎉 iOS Phases 1 & 2 complete - Tournament discovery and participation features working. See ios-development-roadmap for remaining phases.


Last Updated: 2025-11-18 Week: 28 Phase 1 Status: Complete (2025-11-17) Phase 2 Status: Complete (2025-11-18)