Skip to content

Commit 35a2f89

Browse files
committed
Lost Hours Integrating Firebase Realtime Database
1 parent 8c6cfba commit 35a2f89

File tree

1 file changed

+89
-0
lines changed

1 file changed

+89
-0
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
---
2+
title: 'Lost Hours Integrating Firebase Realtime Database'
3+
author: 'Clive Liu'
4+
layout: post
5+
tags: [Swift, Python, Firebase, LostHours]
6+
---
7+
8+
Integrating Firebase Realtime Database with Swift’s `Codable` protocol should be straightforward, but a few non-obvious behaviors can lead to hours of frustrating debugging. Here are three key pitfalls to watch out for.
9+
10+
### Dictionary Keys Must Be Strings
11+
12+
- [FirebaseDataEncoder.swift#L1290](https://github.com/firebase/firebase-ios-sdk/blob/ea3f1293fbb32ec41ee2abd6ad363c1dffa6d23d/FirebaseSharedSwift/Sources/third_party/FirebaseDataEncoder/FirebaseDataEncoder.swift#L1290)
13+
14+
Firebase Realtime Database requires that all dictionary keys be `String` types. If you attempt to encode a model with a dictionary using a non-string key, like `Int`, the operation will fail.
15+
16+
For example, this `Codable` struct will cause an encoding error:
17+
18+
```swift
19+
// This will fail to encode.
20+
struct MyModel: Codable {
21+
let foo: [Int: String]
22+
}
23+
```
24+
25+
The seemingly simple fix is to convert the `Int` keys to `String`s. However, this introduces a subtle and confusing decoding issue.
26+
27+
```swift
28+
// This will encode, but may decode incorrectly.
29+
struct MyModel: Codable {
30+
let foo: [String: String]
31+
}
32+
```
33+
34+
The problem lies in how Firebase handles arrays. It stores them as dictionaries with integer keys represented as strings (e.g., "0", "1", "2"). Because of this, when the SDK's decoder encounters a dictionary with sequential, string-based integer keys, it interprets the data as an `Array`, not a `Dictionary`. This can cause your `[String: String]` property to be unexpectedly decoded as an `[String]`.
35+
36+
- [FChildrenNode.m#L202-L215](https://github.com/firebase/firebase-ios-sdk/blob/ea3f1293fbb32ec41ee2abd6ad363c1dffa6d23d/FirebaseDatabase/Sources/Snapshot/FChildrenNode.m#L202-L215)
37+
38+
### Empty Collections Are Omitted
39+
40+
Another quirk is that Firebase does not store empty arrays or dictionaries. When you save a model, any properties that are empty collections are simply omitted from the database. Consequently, when you fetch the data, those fields will be missing, causing the default `Codable` decoding to fail.
41+
42+
Consider this model:
43+
44+
```swift
45+
struct MyModel: Codable {
46+
let bar: [String]
47+
let foo: [String: String]
48+
}
49+
```
50+
51+
If you save an instance where `bar` or `foo` are empty, they won't exist in the fetched snapshot. To prevent decoding from failing, you must implement a custom `init(from:)` and use `decodeIfPresent`, providing a default empty value for any missing collections.
52+
53+
```swift
54+
struct MyModel: Codable {
55+
let bar: [String]
56+
let foo: [String: String]
57+
58+
// Manually handle potentially omitted empty collections.
59+
init(from decoder: Decoder) throws {
60+
let container = try decoder.container(keyedBy: CodingKeys.self)
61+
self.bar = try container.decodeIfPresent([String].self, forKey: .bar) ?? []
62+
self.foo = try container.decodeIfPresent([String: String].self, forKey: .foo) ?? [:]
63+
}
64+
}
65+
```
66+
67+
### Integer Serialization from Python
68+
69+
If you're writing data to the Realtime Database from a backend service, such as a Python Cloud Function, be aware of how integers are serialized. A standard `Int` in a Swift `Codable` struct expects a simple numeric value.
70+
71+
Your Swift model might expect this JSON structure:
72+
```json
73+
{
74+
"foo": 123
75+
}
76+
```
77+
78+
However, some Python admin SDK will instead store the integer as an object, including type metadata. This results in an incompatible structure that your Swift decoder cannot parse.
79+
80+
```json
81+
{
82+
"foo": {
83+
"@type": "type.googleapis.com/google.protobuf.Int64Value",
84+
"value": "123"
85+
}
86+
}
87+
```
88+
89+
This mismatch will cause decoding to fail, requiring you to either adjust your backend serialization logic or write a custom decoder on the client to handle this specific object format. 🙄🙄🙄

0 commit comments

Comments
 (0)