Skip to content

Commit 4889d26

Browse files
SDK parity: Auth, Firestore, Analytics, Remote Config (#1)
## Summary - **Auth:** emulator support, reauthenticate, auth state listener, ID tokens, profile/password update, reload user, unlink provider (9 methods, 5 signals) - **Firestore:** emulator support, collection listeners, WriteBatch (create/set/update/delete/commit), transactions, FieldValue helpers — serverTimestamp, arrayUnion, arrayRemove, increment, deleteField (14 methods, 3 signals) - **Analytics:** default event params, app instance ID (async w/ signal), consent management, session timeout (4 methods, 1 signal) - **Remote Config:** separate fetch/activate, configurable fetch interval & timeout, typed getters (getAll, getJson), value source, fetch status/time, real-time update listener (11 methods, 2 signals) - **Docs:** updated authentication.md and firestore.md, added analytics.md and remote_config.md - **Demo scenes:** added 33 new buttons across all 4 modules to test every new method; reordered Remote Config and Analytics demos to match iOS plugin layout for consistency Brings the Android plugin to feature parity with the iOS plugin across all four modules. ## Test plan - [x] `./gradlew assemble` compiles without errors - [x] Auth: test emulator, reauthenticate, auth state listener, ID token, profile update, password update, reload, unlink - [x] Firestore: test emulator, collection listener, query documents, batch write + commit, transaction, server timestamp, increment - [x] Analytics: test default event params, get instance ID, consent grant/deny, session timeout - [x] Remote Config: test separate fetch/activate, dev interval, fetch timeout, getAll, getJson, value source, fetch status, real-time listener - [x] Verify demo button ordering matches iOS plugin demo layout 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 85988ad commit 4889d26

26 files changed

Lines changed: 2320 additions & 51 deletions

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,4 @@ google-services.json
3333
*.hprof
3434

3535
# Claude Code
36-
.claude/
36+
.claude/settings.local.json

CLAUDE.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
GodotFirebaseAndroid is an Android plugin for Godot Engine (4.2+) that bridges GDScript and the native Android Firebase SDK. It follows a dual-layer architecture: Kotlin classes handle Firebase SDK interaction and async operations on the Android side, while thin GDScript wrappers in `firebase/export_scripts_template/` provide the developer-facing API via signals.
8+
9+
**Communication flow:** GDScript → Module .gd wrapper → Kotlin plugin singleton → Firebase SDK → Firebase Backend. All long-running operations use Godot signals (not blocking calls).
10+
11+
## Build Commands
12+
13+
```bash
14+
./gradlew assemble # Build debug+release AARs, copy to demo/addons/
15+
./gradlew build # Build AARs without copying
16+
./gradlew clean # Clean build outputs and demo addons
17+
```
18+
19+
The build produces `firebase-debug.aar` and `firebase-release.aar`, copies them to `demo/addons/GodotFirebaseAndroid/bin/`, and copies `export_scripts_template/` into the demo addon directory.
20+
21+
There are no tests or linters configured.
22+
23+
## Architecture
24+
25+
### Kotlin layer (`firebase/src/main/java/org/godotengine/plugin/firebase/`)
26+
27+
- **FirebasePlugin.kt** — Main Godot plugin class. Registers all signals and delegates calls to module classes. All module classes are instantiated here and receive the plugin reference.
28+
- **Authentication.kt** — Anonymous, email/password, Google Sign-In, email verification, password reset, account linking.
29+
- **Firestore.kt** — CRUD operations, collection queries, real-time listeners.
30+
- **RealtimeDatabase.kt** — Path-based CRUD, real-time listeners.
31+
- **CloudStorage.kt** — Upload/download, metadata, file listing.
32+
- **Analytics.kt** — Event logging, user properties/ID.
33+
- **RemoteConfig.kt** — Fetch, activate, typed getters.
34+
35+
Each Kotlin module emits signals back to Godot via `emitSignal()` for async results.
36+
37+
### GDScript layer (`firebase/export_scripts_template/`)
38+
39+
- **Firebase.gd** — Autoloaded singleton that initializes module wrappers.
40+
- **modules/*.gd** — One wrapper per Firebase module. Each connects to the Kotlin plugin singleton's signals and exposes snake_case methods.
41+
- **export_plugin.gd** — Godot export plugin that handles adding dependencies and `google-services.json` during Android export.
42+
- **plugin.cfg** — Godot plugin descriptor.
43+
44+
### Demo project (`demo/`)
45+
46+
Contains a Godot project with one scene per module for manual testing. The demo's `addons/` directory is auto-populated by the build.
47+
48+
## Key Details
49+
50+
- **Godot version:** 4.6+ (uses Godot Android plugin v2 API)
51+
- **Android:** minSdk 24, targetSdk 34
52+
- **Kotlin DSL** for all Gradle files
53+
- **No `google-services.json` in repo** — must be supplied per-project in `demo/android/build/`
54+
- **Fork:** origin is SomniGameStudios, upstream is syntaxerror247/GodotFirebaseAndroid
55+
- **PRs:** Always open pull requests against the fork repo (`SomniGameStudios/godot-firebase-android`), never against upstream
56+
57+
## Adding a New Firebase Module
58+
59+
1. Create Kotlin class in `firebase/src/main/java/.../firebase/` implementing the Firebase SDK calls with `emitSignal()` callbacks
60+
2. Register signals and expose methods in `FirebasePlugin.kt`
61+
3. Create GDScript wrapper in `firebase/export_scripts_template/modules/`
62+
4. Initialize the module in `Firebase.gd`
63+
5. Add Firebase dependency in `firebase/build.gradle.kts`
64+
6. Add demo scene in `demo/scenes/`

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ It supports Godot 4.2+
55

66
## Features
77

8-
- [x] Firebase Authentication (Anonymous, Email/Password, Google Sign-In)
9-
- [x] Cloud Firestore
8+
- [x] Firebase Authentication (Anonymous, Email/Password, Google Sign-In, reauthentication, profile management, auth state listener)
9+
- [x] Cloud Firestore (CRUD, queries, real-time listeners, WriteBatch, transactions, FieldValue helpers)
1010
- [x] Realtime Database
1111
- [x] Cloud Storage
12+
- [x] Firebase Analytics (event logging, user properties, consent management, session timeout)
13+
- [x] Remote Config (fetch/activate, typed getters, real-time updates, value source tracking)
1214
- [ ] Cloud Messaging (coming soon)
1315

1416
---

demo/scenes/analytics.gd

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ extends Control
33
@onready var output: RichTextLabel = %OutputPanel
44
var _collection_enabled := true
55

6-
76
func _notification(what: int) -> void:
87
if what == NOTIFICATION_WM_GO_BACK_REQUEST:
98
get_tree().change_scene_to_packed(load("res://main.tscn"))
109

11-
1210
func _ready() -> void:
11+
Firebase.analytics.app_instance_id_result.connect(_on_instance_id)
1312
_log("platform", "Android")
1413

14+
# --- Events ---
1515

1616
func _on_log_event_pressed() -> void:
1717
_log("action", "Logging custom event 'test_event'...")
@@ -22,7 +22,6 @@ func _on_log_event_pressed() -> void:
2222
})
2323
_log("done", "Event logged (check Firebase Console)")
2424

25-
2625
func _on_log_purchase_pressed() -> void:
2726
_log("action", "Logging purchase event...")
2827
Firebase.analytics.log_event("purchase", {
@@ -32,31 +31,64 @@ func _on_log_purchase_pressed() -> void:
3231
})
3332
_log("done", "Purchase event logged")
3433

34+
# --- User ---
3535

3636
func _on_set_user_id_pressed() -> void:
3737
_log("action", "Setting user ID to 'test_user_123'...")
3838
Firebase.analytics.set_user_id("test_user_123")
3939
_log("done", "User ID set")
4040

41-
4241
func _on_set_user_property_pressed() -> void:
4342
_log("action", "Setting user property 'favorite_food' = 'pizza'...")
4443
Firebase.analytics.set_user_property("favorite_food", "pizza")
4544
_log("done", "User property set")
4645

46+
func _on_set_default_params_pressed() -> void:
47+
_log("action", "Setting default event parameters...")
48+
Firebase.analytics.set_default_event_parameters({
49+
"app_version": "1.0.0",
50+
"platform": "android"
51+
})
52+
_log("done", "Default parameters set")
53+
54+
# --- Diagnostics ---
55+
56+
func _on_get_instance_id_pressed() -> void:
57+
_log("action", "Requesting app instance ID...")
58+
Firebase.analytics.get_app_instance_id()
59+
60+
func _on_instance_id(id: String) -> void:
61+
_log("instance_id", id if not id.is_empty() else "(empty)")
4762

4863
func _on_reset_analytics_pressed() -> void:
4964
_log("action", "Resetting analytics data...")
5065
Firebase.analytics.reset_analytics_data()
5166
_log("done", "Analytics data reset")
5267

53-
5468
func _on_toggle_collection_pressed() -> void:
5569
_collection_enabled = not _collection_enabled
5670
_log("action", "Setting analytics collection to %s..." % str(_collection_enabled))
5771
Firebase.analytics.set_analytics_collection_enabled(_collection_enabled)
5872
_log("done", "Collection %s" % ("enabled" if _collection_enabled else "disabled"))
5973

74+
# --- Consent & Session (Android-only) ---
75+
76+
func _on_set_consent_granted_pressed() -> void:
77+
_log("action", "Granting all consent...")
78+
Firebase.analytics.set_consent(true, true, true, true)
79+
_log("done", "All consent granted")
80+
81+
func _on_set_consent_denied_pressed() -> void:
82+
_log("action", "Denying all consent...")
83+
Firebase.analytics.set_consent(false, false, false, false)
84+
_log("done", "All consent denied")
85+
86+
func _on_set_session_timeout_pressed() -> void:
87+
_log("action", "Setting session timeout to 300s...")
88+
Firebase.analytics.set_session_timeout(300)
89+
_log("done", "Session timeout set to 300s")
90+
91+
# --- Logging ---
6092

6193
func _log(context: String, message: String) -> void:
6294
var t = Time.get_time_string_from_system()
@@ -68,6 +100,5 @@ func _log(context: String, message: String) -> void:
68100
return
69101
output.scroll_to_line(output.get_line_count())
70102

71-
72103
func _on_clear_output_pressed() -> void:
73104
output.text = ""

demo/scenes/analytics.tscn

Lines changed: 69 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ corner_radius_top_right = 20
2929
corner_radius_bottom_right = 20
3030
corner_radius_bottom_left = 20
3131

32-
[node name="Analytics" type="Control" unique_id=2105854314]
32+
[node name="Analytics" type="Control"]
3333
layout_mode = 3
3434
anchors_preset = 15
3535
anchor_right = 1.0
@@ -38,7 +38,7 @@ grow_horizontal = 2
3838
grow_vertical = 2
3939
script = ExtResource("1_analytics")
4040

41-
[node name="ColorRect" type="ColorRect" parent="." unique_id=1309976234]
41+
[node name="ColorRect" type="ColorRect" parent="."]
4242
layout_mode = 1
4343
anchors_preset = 15
4444
anchor_right = 1.0
@@ -47,7 +47,7 @@ grow_horizontal = 2
4747
grow_vertical = 2
4848
color = Color(0.133333, 0.133333, 0.133333, 1)
4949

50-
[node name="MarginContainer" type="MarginContainer" parent="." unique_id=1264128402]
50+
[node name="MarginContainer" type="MarginContainer" parent="."]
5151
layout_mode = 1
5252
anchors_preset = 15
5353
anchor_right = 1.0
@@ -59,22 +59,22 @@ theme_override_constants/margin_top = 20
5959
theme_override_constants/margin_right = 40
6060
theme_override_constants/margin_bottom = 20
6161

62-
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer" unique_id=1150462527]
62+
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
6363
layout_mode = 2
6464
theme_override_constants/separation = 10
6565

66-
[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer/VBoxContainer" unique_id=1508340636]
66+
[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer/VBoxContainer"]
6767
layout_mode = 2
6868
size_flags_vertical = 3
6969
horizontal_scroll_mode = 0
7070
script = ExtResource("2_scroll")
7171

72-
[node name="ButtonContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/ScrollContainer" unique_id=1513966250]
72+
[node name="ButtonContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/ScrollContainer"]
7373
layout_mode = 2
7474
size_flags_horizontal = 3
7575
theme_override_constants/separation = 20
7676

77-
[node name="log_event" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ButtonContainer" unique_id=385026694]
77+
[node name="log_event" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ButtonContainer"]
7878
custom_minimum_size = Vector2(120, 60)
7979
layout_mode = 2
8080
theme_override_font_sizes/font_size = 32
@@ -84,7 +84,7 @@ theme_override_styles/hover = SubResource("StyleBoxFlat_normal")
8484
theme_override_styles/focus = SubResource("StyleBoxFlat_focus")
8585
text = "Log Custom Event"
8686

87-
[node name="log_purchase" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ButtonContainer" unique_id=1360381640]
87+
[node name="log_purchase" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ButtonContainer"]
8888
custom_minimum_size = Vector2(120, 60)
8989
layout_mode = 2
9090
theme_override_font_sizes/font_size = 32
@@ -94,7 +94,7 @@ theme_override_styles/hover = SubResource("StyleBoxFlat_normal")
9494
theme_override_styles/focus = SubResource("StyleBoxFlat_focus")
9595
text = "Log Purchase Event"
9696

97-
[node name="set_user_id" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ButtonContainer" unique_id=1510039719]
97+
[node name="set_user_id" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ButtonContainer"]
9898
custom_minimum_size = Vector2(120, 60)
9999
layout_mode = 2
100100
theme_override_font_sizes/font_size = 32
@@ -104,7 +104,7 @@ theme_override_styles/hover = SubResource("StyleBoxFlat_normal")
104104
theme_override_styles/focus = SubResource("StyleBoxFlat_focus")
105105
text = "Set User ID"
106106

107-
[node name="set_user_property" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ButtonContainer" unique_id=196146543]
107+
[node name="set_user_property" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ButtonContainer"]
108108
custom_minimum_size = Vector2(120, 60)
109109
layout_mode = 2
110110
theme_override_font_sizes/font_size = 32
@@ -114,7 +114,27 @@ theme_override_styles/hover = SubResource("StyleBoxFlat_normal")
114114
theme_override_styles/focus = SubResource("StyleBoxFlat_focus")
115115
text = "Set User Property"
116116

117-
[node name="reset_analytics" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ButtonContainer" unique_id=1190070255]
117+
[node name="set_default_params" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ButtonContainer"]
118+
custom_minimum_size = Vector2(120, 60)
119+
layout_mode = 2
120+
theme_override_font_sizes/font_size = 32
121+
theme_override_styles/normal = SubResource("StyleBoxFlat_normal")
122+
theme_override_styles/pressed = SubResource("StyleBoxFlat_pressed")
123+
theme_override_styles/hover = SubResource("StyleBoxFlat_normal")
124+
theme_override_styles/focus = SubResource("StyleBoxFlat_focus")
125+
text = "Set Default Params"
126+
127+
[node name="get_instance_id" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ButtonContainer"]
128+
custom_minimum_size = Vector2(120, 60)
129+
layout_mode = 2
130+
theme_override_font_sizes/font_size = 32
131+
theme_override_styles/normal = SubResource("StyleBoxFlat_normal")
132+
theme_override_styles/pressed = SubResource("StyleBoxFlat_pressed")
133+
theme_override_styles/hover = SubResource("StyleBoxFlat_normal")
134+
theme_override_styles/focus = SubResource("StyleBoxFlat_focus")
135+
text = "Get Instance ID"
136+
137+
[node name="reset_analytics" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ButtonContainer"]
118138
custom_minimum_size = Vector2(120, 60)
119139
layout_mode = 2
120140
theme_override_font_sizes/font_size = 32
@@ -124,7 +144,7 @@ theme_override_styles/hover = SubResource("StyleBoxFlat_normal")
124144
theme_override_styles/focus = SubResource("StyleBoxFlat_focus")
125145
text = "Reset Analytics"
126146

127-
[node name="toggle_collection" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ButtonContainer" unique_id=2116133267]
147+
[node name="toggle_collection" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ButtonContainer"]
128148
custom_minimum_size = Vector2(120, 60)
129149
layout_mode = 2
130150
theme_override_font_sizes/font_size = 32
@@ -134,14 +154,44 @@ theme_override_styles/hover = SubResource("StyleBoxFlat_normal")
134154
theme_override_styles/focus = SubResource("StyleBoxFlat_focus")
135155
text = "Toggle Collection"
136156

137-
[node name="OutputPanel" type="RichTextLabel" parent="MarginContainer/VBoxContainer" unique_id=1459866916]
157+
[node name="set_consent_granted" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ButtonContainer"]
158+
custom_minimum_size = Vector2(120, 60)
159+
layout_mode = 2
160+
theme_override_font_sizes/font_size = 32
161+
theme_override_styles/normal = SubResource("StyleBoxFlat_normal")
162+
theme_override_styles/pressed = SubResource("StyleBoxFlat_pressed")
163+
theme_override_styles/hover = SubResource("StyleBoxFlat_normal")
164+
theme_override_styles/focus = SubResource("StyleBoxFlat_focus")
165+
text = "Grant All Consent"
166+
167+
[node name="set_consent_denied" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ButtonContainer"]
168+
custom_minimum_size = Vector2(120, 60)
169+
layout_mode = 2
170+
theme_override_font_sizes/font_size = 32
171+
theme_override_styles/normal = SubResource("StyleBoxFlat_normal")
172+
theme_override_styles/pressed = SubResource("StyleBoxFlat_pressed")
173+
theme_override_styles/hover = SubResource("StyleBoxFlat_normal")
174+
theme_override_styles/focus = SubResource("StyleBoxFlat_focus")
175+
text = "Deny All Consent"
176+
177+
[node name="set_session_timeout" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ButtonContainer"]
178+
custom_minimum_size = Vector2(120, 60)
179+
layout_mode = 2
180+
theme_override_font_sizes/font_size = 32
181+
theme_override_styles/normal = SubResource("StyleBoxFlat_normal")
182+
theme_override_styles/pressed = SubResource("StyleBoxFlat_pressed")
183+
theme_override_styles/hover = SubResource("StyleBoxFlat_normal")
184+
theme_override_styles/focus = SubResource("StyleBoxFlat_focus")
185+
text = "Set Session Timeout (300s)"
186+
187+
[node name="OutputPanel" type="RichTextLabel" parent="MarginContainer/VBoxContainer"]
138188
unique_name_in_owner = true
139189
custom_minimum_size = Vector2(0, 200)
140190
layout_mode = 2
141191
theme_override_font_sizes/normal_font_size = 24
142192
scroll_following = true
143193

144-
[node name="clear_output" type="Button" parent="MarginContainer/VBoxContainer" unique_id=797506553]
194+
[node name="clear_output" type="Button" parent="MarginContainer/VBoxContainer"]
145195
custom_minimum_size = Vector2(0, 50)
146196
layout_mode = 2
147197
theme_override_font_sizes/font_size = 24
@@ -155,6 +205,11 @@ text = "Clear Output"
155205
[connection signal="pressed" from="MarginContainer/VBoxContainer/ScrollContainer/ButtonContainer/log_purchase" to="." method="_on_log_purchase_pressed"]
156206
[connection signal="pressed" from="MarginContainer/VBoxContainer/ScrollContainer/ButtonContainer/set_user_id" to="." method="_on_set_user_id_pressed"]
157207
[connection signal="pressed" from="MarginContainer/VBoxContainer/ScrollContainer/ButtonContainer/set_user_property" to="." method="_on_set_user_property_pressed"]
208+
[connection signal="pressed" from="MarginContainer/VBoxContainer/ScrollContainer/ButtonContainer/set_default_params" to="." method="_on_set_default_params_pressed"]
209+
[connection signal="pressed" from="MarginContainer/VBoxContainer/ScrollContainer/ButtonContainer/get_instance_id" to="." method="_on_get_instance_id_pressed"]
158210
[connection signal="pressed" from="MarginContainer/VBoxContainer/ScrollContainer/ButtonContainer/reset_analytics" to="." method="_on_reset_analytics_pressed"]
159211
[connection signal="pressed" from="MarginContainer/VBoxContainer/ScrollContainer/ButtonContainer/toggle_collection" to="." method="_on_toggle_collection_pressed"]
212+
[connection signal="pressed" from="MarginContainer/VBoxContainer/ScrollContainer/ButtonContainer/set_consent_granted" to="." method="_on_set_consent_granted_pressed"]
213+
[connection signal="pressed" from="MarginContainer/VBoxContainer/ScrollContainer/ButtonContainer/set_consent_denied" to="." method="_on_set_consent_denied_pressed"]
214+
[connection signal="pressed" from="MarginContainer/VBoxContainer/ScrollContainer/ButtonContainer/set_session_timeout" to="." method="_on_set_session_timeout_pressed"]
160215
[connection signal="pressed" from="MarginContainer/VBoxContainer/clear_output" to="." method="_on_clear_output_pressed"]

0 commit comments

Comments
 (0)