Hello guys, I've raised into that problem for a days, but i think I've found one solution, maybe this isn't the better way to do that, but... I will share it. (This is inspired in the Firebase multiplatform repo -> https://github.com/RubyLichtenstein/Kotlin-Multiplatform-Firebase, I'm just summarizing that)
If you want to call some code from a DataSource
or Repository
from your common code, and the libraries you're using use callbacks,
you need to wrap them and there are some problems:
Let me explain it with an example:
expect class RemoteDataSource() {
suspend fun getUser(): Either<Error, User>
}
In Android, this is pretty easy to solve, we can do something like this:
actual class RemoteDataSource() {
suspend fun getUser(): Either<Error, User> =
try { Either.Right(User(email = FirebaseAuth.getInstance().signIn().await().email) }
catch (e: Exception) { Either.Left(Error.Default) }
}
But.. the problem comes from iOS, we can't implement suspend functions
on Swift and iOS (Or I don't know how to do that), so... we have a problem
The solution I've found is this one (Still WiP):
- Declare a result callback
interface Result<T> {
fun onComplete(success: Either.Right<Error, T>)
fun onError(error: Either.Left<Error, T>)
}
(Don't focus on Either
, this is because I'm using this FP container)
- Create this function
suspend fun <T> await(result: (Result<T>) -> Unit): Either<Error, T> =
suspendCancellableCoroutine { cont ->
result(object : Result<T> {
override fun onComplete(success: Either.Right<Error, T>) {
print("onComplete")
cont.resume(success)
}
override fun onError(error: Either.Left<Error, T>) {
print("onError")
cont.resume(error)
}
})
}
- Create a Bridge (worst name ever):
interface RemoteBridge {
fun getUser(result: Result<User>)
}
And... the datasource will looks like this:
actual class RemoteDataSource(private val bridge: RemoteBridge) {
actual suspend fun getUser(): Either<Error, User> = await { bridge.getUser(it) }
}
Finally, this is the result from the iOS side:
class IosBridge: RemoteBridge {
private lazy var auth: Auth = Auth.auth()
func createUser(result: Result) {
auth.signInAnonymously {result, error in
if(error == nil) { // Just an example, to show the two calls
result.onComplete(success: Either.Right(success: Success()))
} else {
result.onError(error: Either.Left(error: Error.Default.self))
}
}
}
This is the best solution I've found from now, if you have another one, please share it!!
This solution comes to another problem, and is taht you will need to specify the dispatcher for every, imagine that you have some thing like this:
class CommonRepo(private val network, private val realtime): Repo {
fun getUser(): Either<Error, User> = realtime.getUser().flatMap { withContext(executor.bg) { network.saveUser(it) } }
}```
To call the "network" call you need to do it in background, and you manually need to specify that because the "callback" system is not ready to work in another thread.