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" fieldPlatform Behavior Differences:
| Platform | Firebase SDK | Serialization Behavior |
|---|---|---|
| Android | Native Firebase SDK | Strips “is” prefix from Boolean properties → "public" ✅ |
| iOS | GitLive Firebase wrapper | Preserves 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
isPublicproperty name (follows Kotlin conventions) - Firestore: Stores as
"public"field (matches query and security rules) - Cross-platform: Consistent behavior on both Android and iOS
Related Changes
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>>→ SwiftAsyncSequence - 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
@SerialNameannotations 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:
- Add comprehensive logging in Kotlin data source
- Build and link framework to iOS
- Run in Xcode and monitor console output
- 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:
publicis 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 toisPublicproperty - 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)
-
Firebase Authentication on iOS
- Use same
@SerialNamepattern for User model - Test GitLive Auth SDK on iOS
- Verify token management cross-platform
- Use same
-
Tournament Creation from iOS
- Implement TournamentCreationView (SwiftUI)
- Use shared validation logic from KMP
- Test Firestore writes from iOS
-
Live Scoring on iOS
- Port scoring UI to SwiftUI
- Use shared scoring calculation logic
- Test real-time score updates
-
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.mdin 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
Related Documentation
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:
- KMP-NativeCoroutines - Flow to AsyncSequence conversion
- GitLive Firebase SDK - Cross-platform Firebase for KMP
- Kotlinx Serialization - Kotlin serialization library
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.int64Valuefor 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:
- ios-firebase-setup - Complete Firebase setup guide for iOS
- kotlin-swift-interop - Extended with new type conversion patterns
- 2025-11-18-ios-phase2-build-fixes - Debugging session summary
Roadmap:
- ios-development-roadmap - Comprehensive plan for remaining iOS features (Phases 3-7)
Shared Code Audit Results
Investigation into remaining features revealed high KMP coverage:
| Feature Area | Shared Code Coverage | Status |
|---|---|---|
| Tournament Management | 100% | ✅ Phase 2 Complete |
| Equipment Management | 95% | 🔄 10 presenters + 11 DAOs ready |
| Round Scoring | 90% | 🔄 Presenter + DAOs exist |
| Statistics & Analytics | 50% | 🔄 DAOs exist, logic Android-specific |
| Authentication | 40% | 🔄 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)