Skip to content

Commit deabeef

Browse files
committed
add ios implementation for database
1 parent daac5be commit deabeef

File tree

1 file changed

+139
-78
lines changed
  • firebase-database/src/iosMain/kotlin/dev/teamhub/firebase/database

1 file changed

+139
-78
lines changed
Lines changed: 139 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,144 +1,205 @@
11
package dev.teamhub.firebase.database
22

3+
import cocoapods.FirebaseDatabase.*
4+
import cocoapods.FirebaseDatabase.FIRDataEventType.*
35
import dev.teamhub.firebase.Firebase
46
import dev.teamhub.firebase.FirebaseApp
7+
import dev.teamhub.firebase.database.ChildEvent.Type
8+
import dev.teamhub.firebase.database.ChildEvent.Type.*
59
import kotlinx.serialization.DeserializationStrategy
610
import kotlinx.serialization.SerializationStrategy
711
import kotlinx.serialization.ImplicitReflectionSerializer
12+
import kotlinx.cinterop.*
13+
import kotlinx.coroutines.channels.awaitClose
14+
import kotlinx.coroutines.flow.*
15+
import platform.Foundation.*
16+
import kotlinx.coroutines.CompletableDeferred
17+
import kotlinx.coroutines.coroutineScope
18+
import dev.teamhub.firebase.decode
19+
import kotlinx.coroutines.selects.select
820

9-
/** Returns the [FirebaseDatabase] instance of the default [FirebaseApp]. */
10-
actual val Firebase.database: FirebaseDatabase
11-
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
21+
fun encode(value: Any?) =
22+
dev.teamhub.firebase.encode(value, FIRServerValue.timestamp())
23+
fun <T> encode(strategy: SerializationStrategy<T> , value: T): Any? =
24+
dev.teamhub.firebase.encode(strategy, value, FIRServerValue.timestamp())
1225

13-
/** Returns the [FirebaseDatabase] instance for the specified [url]. */
14-
actual fun Firebase.database(url: String): FirebaseDatabase {
15-
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
16-
}
26+
actual val Firebase.database
27+
by lazy { FirebaseDatabase(FIRDatabase.database()) }
1728

18-
/** Returns the [FirebaseDatabase] instance of the given [FirebaseApp]. */
19-
actual fun Firebase.database(app: FirebaseApp): FirebaseDatabase {
20-
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
21-
}
29+
actual fun Firebase.database(url: String) =
30+
FirebaseDatabase(FIRDatabase.databaseWithURL(url))
2231

23-
/** Returns the [FirebaseDatabase] instance of the given [FirebaseApp] and [url]. */
24-
actual fun Firebase.database(
25-
app: FirebaseApp,
26-
url: String
27-
): FirebaseDatabase {
28-
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
29-
}
32+
actual fun Firebase.database(app: FirebaseApp) =
33+
FirebaseDatabase(FIRDatabase.databaseForApp(app.ios))
3034

31-
actual class FirebaseDatabase {
32-
actual fun reference(path: String): DatabaseReference {
33-
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
34-
}
35+
actual fun Firebase.database(app: FirebaseApp, url: String) =
36+
FirebaseDatabase(FIRDatabase.databaseForApp(app.ios, url))
37+
38+
actual class FirebaseDatabase internal constructor(val ios: FIRDatabase) {
39+
40+
actual fun reference(path: String) =
41+
DatabaseReference(ios.referenceWithPath(path), ios.persistenceEnabled)
3542

3643
actual fun setPersistenceEnabled(enabled: Boolean) {
44+
ios.persistenceEnabled = enabled
3745
}
3846

39-
actual fun setLoggingEnabled(enabled: Boolean) {
40-
}
47+
actual fun setLoggingEnabled(enabled: Boolean) =
48+
FIRDatabase.setLoggingEnabled(enabled)
4149
}
4250

43-
actual open class Query {
44-
actual val valueEvents: kotlinx.coroutines.flow.Flow<DataSnapshot>
45-
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
51+
fun Type.toEventType() = when(this) {
52+
ADDED -> FIRDataEventTypeChildAdded
53+
CHANGED -> FIRDataEventTypeChildChanged
54+
MOVED -> FIRDataEventTypeChildMoved
55+
REMOVED -> FIRDataEventTypeChildRemoved
56+
}
4657

47-
actual fun childEvents(vararg types: ChildEvent.Type): kotlinx.coroutines.flow.Flow<ChildEvent> {
48-
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
49-
}
58+
actual open class Query internal constructor(
59+
open val ios: FIRDatabaseQuery,
60+
val persistenceEnabled: Boolean
61+
) {
62+
actual fun orderByKey() = Query(ios.queryOrderedByKey(), persistenceEnabled)
5063

51-
actual fun orderByKey(): Query {
52-
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
53-
}
64+
actual fun orderByChild(path: String) = Query(ios.queryOrderedByChild(path), persistenceEnabled)
5465

55-
actual fun orderByChild(path: String): Query {
56-
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
57-
}
66+
actual fun startAt(value: String, key: String?) = Query(ios.queryStartingAtValue(value, key), persistenceEnabled)
5867

59-
actual fun startAt(value: String, key: String?): Query {
60-
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
61-
}
68+
actual fun startAt(value: Double, key: String?) = Query(ios.queryStartingAtValue(value, key), persistenceEnabled)
69+
70+
actual fun startAt(value: Boolean, key: String?) = Query(ios.queryStartingAtValue(value, key), persistenceEnabled)
6271

63-
actual fun startAt(value: Double, key: String?): Query {
64-
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
72+
actual val valueEvents get() = callbackFlow {
73+
val handle = ios.observeEventType(
74+
FIRDataEventTypeValue,
75+
withBlock = { offer(DataSnapshot(it!!)) }
76+
) { close(DatabaseException(it.toString())) }
77+
awaitClose { ios.removeObserverWithHandle(handle) }
6578
}
6679

67-
actual fun startAt(value: Boolean, key: String?): Query {
68-
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
80+
actual fun childEvents(vararg types: Type) = callbackFlow {
81+
val handles = types.map { type ->
82+
ios.observeEventType(
83+
type.toEventType(),
84+
andPreviousSiblingKeyWithBlock = { it, key -> offer(ChildEvent(DataSnapshot(it!!), type, key)) }
85+
) { close(DatabaseException(it.toString())) }
86+
}
87+
awaitClose {
88+
handles.forEach { ios.removeObserverWithHandle(it) }
89+
}
6990
}
91+
92+
override fun toString() = ios.toString()
7093
}
7194

72-
actual class DatabaseReference : Query() {
73-
actual val key: String?
74-
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
95+
actual class DatabaseReference internal constructor(
96+
override val ios: FIRDatabaseReference,
97+
persistenceEnabled: Boolean
98+
): Query(ios, persistenceEnabled) {
7599

76-
actual fun push(): DatabaseReference {
77-
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
78-
}
100+
actual val key get() = ios.key
79101

80-
actual fun child(path: String): DatabaseReference {
81-
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
82-
}
102+
actual fun child(path: String) = DatabaseReference(ios.child(path), persistenceEnabled)
83103

84-
actual fun onDisconnect(): OnDisconnect {
85-
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
86-
}
104+
actual fun push() = DatabaseReference(ios.childByAutoId(), persistenceEnabled)
105+
actual fun onDisconnect() = OnDisconnect(ios, persistenceEnabled)
87106

88-
@ImplicitReflectionSerializer
89107
actual suspend fun setValue(value: Any?) {
108+
ios.await(persistenceEnabled) { setValue(encode(value), it) }
90109
}
91110

92111
actual suspend inline fun <reified T> setValue(strategy: SerializationStrategy<T>, value: T) {
112+
ios.await(persistenceEnabled) { setValue(encode(strategy, value), it) }
93113
}
94114

95-
@ImplicitReflectionSerializer
115+
@Suppress("UNCHECKED_CAST")
96116
actual suspend fun updateChildren(update: Map<String, Any?>) {
117+
ios.await(persistenceEnabled) { updateChildValues(encode(update) as Map<Any?, *>, it) }
97118
}
98119

99120
actual suspend fun removeValue() {
121+
ios.await(persistenceEnabled) { removeValueWithCompletionBlock(it) }
100122
}
101123
}
102124

103-
actual class DataSnapshot {
104-
actual val exists: Boolean
105-
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
106-
actual val key: String?
107-
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
125+
@Suppress("UNCHECKED_CAST")
126+
actual class DataSnapshot internal constructor(val ios: FIRDataSnapshot) {
108127

109-
@ImplicitReflectionSerializer
110-
actual inline fun <reified T> value(): T {
111-
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
112-
}
128+
actual val exists get() = ios.exists()
113129

114-
actual inline fun <reified T> value(strategy: DeserializationStrategy<T>): T {
115-
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
116-
}
130+
actual val key: String? get() = ios.key
117131

118-
actual fun child(path: String): DataSnapshot {
119-
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
120-
}
132+
actual inline fun <reified T> value() =
133+
decode<T>(value = ios.value)
134+
135+
actual inline fun <reified T> value(strategy: DeserializationStrategy<T>) =
136+
decode(strategy, ios.value)
121137

122-
actual val children: Iterable<DataSnapshot>
123-
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
138+
actual fun child(path: String) = DataSnapshot(ios.childSnapshotForPath(path))
139+
actual val children: Iterable<DataSnapshot> get() = ios.children.allObjects.map { DataSnapshot(it as FIRDataSnapshot) }
124140
}
125141

126-
actual class DatabaseException : RuntimeException()
127-
actual class OnDisconnect {
142+
actual class OnDisconnect internal constructor(
143+
val ios: FIRDatabaseReference,
144+
val persistenceEnabled: Boolean
145+
) {
128146
actual suspend fun removeValue() {
147+
ios.await(persistenceEnabled) { onDisconnectRemoveValueWithCompletionBlock(it) }
129148
}
130149

131150
actual suspend fun cancel() {
151+
ios.await(persistenceEnabled) { cancelDisconnectOperationsWithCompletionBlock(it) }
132152
}
133153

134-
@ImplicitReflectionSerializer
135154
actual suspend inline fun <reified T : Any> setValue(value: T) {
155+
ios.await(persistenceEnabled) { onDisconnectSetValue(encode(value), it) }
136156
}
137157

138158
actual suspend inline fun <reified T> setValue(strategy: SerializationStrategy<T>, value: T) {
159+
ios.await(persistenceEnabled) { onDisconnectSetValue(encode(strategy, value), it) }
139160
}
140161

141-
@ImplicitReflectionSerializer
142162
actual suspend fun updateChildren(update: Map<String, Any?>) {
163+
ios.await(persistenceEnabled) { onDisconnectUpdateChildValues(update.mapValues { (_, it) -> encode(it) } as Map<Any?, *>, it) }
164+
}
165+
}
166+
167+
actual class DatabaseException(message: String) : RuntimeException(message)
168+
169+
private suspend fun <T, R> T.awaitResult(whileOnline: Boolean, function: T.(callback: (NSError?, R?) -> Unit) -> Unit): R {
170+
val job = CompletableDeferred<R>()
171+
function { error, result ->
172+
if(result != null) {
173+
job.complete(result)
174+
} else if(error != null) {
175+
job.completeExceptionally(DatabaseException(error.toString()))
176+
}
177+
}
178+
return job.run { if(whileOnline) awaitWhileOnline() else await() }
179+
}
180+
181+
suspend fun <T> T.await(whileOnline: Boolean, function: T.(callback: (NSError?, FIRDatabaseReference?) -> Unit) -> Unit) {
182+
val job = CompletableDeferred<Unit>()
183+
function { error, _ ->
184+
if(error == null) {
185+
job.complete(Unit)
186+
} else {
187+
job.completeExceptionally(DatabaseException(error.toString()))
188+
}
189+
}
190+
job.run { if(whileOnline) awaitWhileOnline() else await() }
191+
}
192+
193+
private suspend fun <T> CompletableDeferred<T>.awaitWhileOnline(): T = coroutineScope {
194+
195+
val notConnected = Firebase.database
196+
.reference(".info/connected")
197+
.valueEvents
198+
.filter { !it.value<Boolean>() }
199+
.produceIn(this)
200+
201+
select<T> {
202+
onAwait { it.also { notConnected.cancel() } }
203+
notConnected.onReceive { throw DatabaseException("Database not connected") }
143204
}
144205
}

0 commit comments

Comments
 (0)