Skip to content

Commit bae4ce4

Browse files
authored
feat: Core: FirebaseAuthUI Singleton & DI (#2215)
* feat: Core: FirebaseAuthUI Singleton & DI * clean
1 parent 1e44a13 commit bae4ce4

File tree

2 files changed

+501
-0
lines changed

2 files changed

+501
-0
lines changed
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
/*
2+
* Copyright 2025 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the
10+
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
* express or implied. See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
15+
package com.firebase.ui.auth.compose
16+
17+
import androidx.annotation.RestrictTo
18+
import com.google.firebase.FirebaseApp
19+
import com.google.firebase.auth.FirebaseAuth
20+
import com.google.firebase.auth.ktx.auth
21+
import com.google.firebase.ktx.Firebase
22+
import java.util.concurrent.ConcurrentHashMap
23+
24+
/**
25+
* The central class that coordinates all authentication operations for Firebase Auth UI Compose.
26+
* This class manages UI state and provides methods for signing in, signing up, and managing
27+
* user accounts.
28+
*
29+
* <h2>Usage</h2>
30+
*
31+
* **Default app instance:**
32+
* ```kotlin
33+
* val authUI = FirebaseAuthUI.getInstance()
34+
* ```
35+
*
36+
* **Custom app instance:**
37+
* ```kotlin
38+
* val customApp = Firebase.app("secondary")
39+
* val authUI = FirebaseAuthUI.getInstance(customApp)
40+
* ```
41+
*
42+
* **Multi-tenancy with custom auth:**
43+
* ```kotlin
44+
* val customAuth = Firebase.auth(customApp).apply {
45+
* tenantId = "my-tenant-id"
46+
* }
47+
* val authUI = FirebaseAuthUI.create(customApp, customAuth)
48+
* ```
49+
*
50+
* @property app The [FirebaseApp] instance used for authentication
51+
* @property auth The [FirebaseAuth] instance used for authentication operations
52+
*
53+
* @since 10.0.0
54+
*/
55+
class FirebaseAuthUI private constructor(
56+
val app: FirebaseApp,
57+
val auth: FirebaseAuth
58+
) {
59+
companion object {
60+
/** Cache for singleton instances per FirebaseApp. Thread-safe via ConcurrentHashMap. */
61+
private val instanceCache = ConcurrentHashMap<String, FirebaseAuthUI>()
62+
63+
/** Special key for the default app instance to distinguish from named instances. */
64+
private const val DEFAULT_APP_KEY = "__FIREBASE_UI_DEFAULT__"
65+
66+
/**
67+
* Returns a cached singleton instance for the default Firebase app.
68+
*
69+
* This method ensures that the same instance is returned for the default app across the
70+
* entire application lifecycle. The instance is lazily created on first access and cached
71+
* for subsequent calls.
72+
*
73+
* **Example:**
74+
* ```kotlin
75+
* val authUI = FirebaseAuthUI.getInstance()
76+
* val user = authUI.auth.currentUser
77+
* ```
78+
*
79+
* @return The cached [FirebaseAuthUI] instance for the default app
80+
* @throws IllegalStateException if Firebase has not been initialized. Call
81+
* `FirebaseApp.initializeApp(Context)` before using this method.
82+
*/
83+
@JvmStatic
84+
fun getInstance(): FirebaseAuthUI {
85+
val defaultApp = try {
86+
FirebaseApp.getInstance()
87+
} catch (e: IllegalStateException) {
88+
throw IllegalStateException(
89+
"Default FirebaseApp is not initialized. " +
90+
"Make sure to call FirebaseApp.initializeApp(Context) first.",
91+
e
92+
)
93+
}
94+
95+
return instanceCache.getOrPut(DEFAULT_APP_KEY) {
96+
FirebaseAuthUI(defaultApp, Firebase.auth)
97+
}
98+
}
99+
100+
/**
101+
* Returns a cached instance for a specific Firebase app.
102+
*
103+
* Each [FirebaseApp] gets its own distinct instance that is cached for subsequent calls
104+
* with the same app. This allows for multiple Firebase projects to be used within the
105+
* same application.
106+
*
107+
* **Example:**
108+
* ```kotlin
109+
* val secondaryApp = Firebase.app("secondary")
110+
* val authUI = FirebaseAuthUI.getInstance(secondaryApp)
111+
* ```
112+
*
113+
* @param app The [FirebaseApp] instance to use
114+
* @return The cached [FirebaseAuthUI] instance for the specified app
115+
*/
116+
@JvmStatic
117+
fun getInstance(app: FirebaseApp): FirebaseAuthUI {
118+
val cacheKey = app.name
119+
return instanceCache.getOrPut(cacheKey) {
120+
FirebaseAuthUI(app, Firebase.auth(app))
121+
}
122+
}
123+
124+
/**
125+
* Creates a new instance with custom configuration, useful for multi-tenancy.
126+
*
127+
* This method always returns a new instance and does **not** use caching, allowing for
128+
* custom [FirebaseAuth] configurations such as tenant IDs or custom authentication states.
129+
* Use this when you need fine-grained control over the authentication instance.
130+
*
131+
* **Example - Multi-tenancy:**
132+
* ```kotlin
133+
* val app = Firebase.app("tenant-app")
134+
* val auth = Firebase.auth(app).apply {
135+
* tenantId = "customer-tenant-123"
136+
* }
137+
* val authUI = FirebaseAuthUI.create(app, auth)
138+
* ```
139+
*
140+
* @param app The [FirebaseApp] instance to use
141+
* @param auth The [FirebaseAuth] instance with custom configuration
142+
* @return A new [FirebaseAuthUI] instance with the provided dependencies
143+
*/
144+
@JvmStatic
145+
fun create(app: FirebaseApp, auth: FirebaseAuth): FirebaseAuthUI {
146+
return FirebaseAuthUI(app, auth)
147+
}
148+
149+
/**
150+
* Clears all cached instances. This method is intended for testing purposes only.
151+
*
152+
* @suppress This is an internal API and should not be used in production code.
153+
* @RestrictTo RestrictTo.Scope.TESTS
154+
*/
155+
@JvmStatic
156+
@RestrictTo(RestrictTo.Scope.TESTS)
157+
internal fun clearInstanceCache() {
158+
instanceCache.clear()
159+
}
160+
161+
/**
162+
* Returns the current number of cached instances. This method is intended for testing
163+
* purposes only.
164+
*
165+
* @return The number of cached [FirebaseAuthUI] instances
166+
* @suppress This is an internal API and should not be used in production code.
167+
* @RestrictTo RestrictTo.Scope.TESTS
168+
*/
169+
@JvmStatic
170+
@RestrictTo(RestrictTo.Scope.TESTS)
171+
internal fun getCacheSize(): Int {
172+
return instanceCache.size
173+
}
174+
}
175+
}

0 commit comments

Comments
 (0)