I use FirebaseAuth for registration new user
class FirebaseAuthenticationServiceImpl(): FirebaseAuthenticationService {
override fun registerWithEmailAndPassword(email: String, password: String): Boolean {
val registration = FirebaseAuth.getInstance().createUserWithEmailAndPassword(email, password)
.addOnSuccessListener {
println(it.additionalUserInfo?.providerId.toString())
}.addOnFailureListener {
println(it.message.toString())
}
return registration.isSuccessful
}
}
I call function above and every time I get false. After some time I get true
coroutineScope {
try {
if (firebaseService.registerWithEmailAndPassword(email, password)) {
openHomeActivity.offer(Unit)
} else {}
} catch (e: Exception) {}
}
How can I wait for uth result (success/failure) and afer that get that value?
Where is FirebaseAuthenticationService from? Do you need it? The official getting started guide just uses Firebase.auth. With this, you can authenticate using the await() suspend function instead of using the callback approach.
// In a coroutine:
val authResult = Firebase.auth.registerWithEmailAndPassword(email, password).await()
val user: FirebaseUser = authResult.user
if (user != null) {
openHomeActivity.offer(Unit)
} else {
// authentication failed
}
If you are using coroutines you can use suspendCoroutine which is perfect bridge between traditional callbacks and coroutines as it gives you access to the Continuation<T> object, example with a convenience extension function for Task<R> objects :
scope.launch {
val registrationResult = suspendCoroutine { cont -> cont.suspendTask(FirebaseAuth.getInstance().createUserWithEmailAndPassword(email, password) }
}
private fun <R> Continuation<R>.suspendTask(task: Task<R>) {
task.addOnSuccessListener { this.success(it) }
.addOnFailureListener { this.failure(it) }
}
private fun <R> Continuation<R>.success(r : R) = resume(r)
private fun <R> Continuation<R>.failure(t : Exception) = resumeWithException(t)
Related
I'm new to Android development and trying to understand Coroutines and LiveData from various example projects. I have currently setup a function to call my api when the user has input a username and password. However after 1 button press, the app seems to jam and I can't make another api call as if its stuck on a pending process.
This is my first android app made with a mash of ideas so please let me know where I've made mistakes!
Activity:
binding.bLogin.setOnClickListener {
val username = binding.etUsername.text.toString()
val password = binding.etPassword.text.toString()
viewModel.userClicked(username, password).observe(this, Observer {
it?.let { resource ->
when (resource.status) {
Status.SUCCESS -> {
print(resource.data)
}
Status.ERROR -> {
print(resource.message)
}
Status.LOADING -> {
// loader stuff
}
}
}
})
}
ViewModel:
fun userClicked(username: String, password: String) = liveData(dispatcherIO) {
viewModelScope.launch {
emit(Resource.loading(data = null))
try {
userRepository.login(username, password).apply {
emit(Resource.success(null))
}
} catch (exception: Exception) {
emit(Resource.error(exception.message ?: "Error Occurred!", data = null))
}
}
}
Repository:
#WorkerThread
suspend fun login(
username: String,
password: String
): Flow<Resource<String?>> {
return flow {
emit(Resource.loading(null))
api.login(LoginRequest(username, password)).apply {
this.onSuccessSuspend {
data?.let {
prefs.apiToken = it.key
emit(Resource.success(null))
}
}
}.onErrorSuspend {
emit(Resource.error(message(), null))
}.onExceptionSuspend {
emit(Resource.error(message(), null))
}
}.flowOn(dispatcherIO)
}
API:
suspend fun login(#Body request: LoginRequest): ApiResponse<Auth>
You don't need to launch a coroutine in liveData builder, it is already suspend so you can call suspend functions there:
fun userClicked(username: String, password: String) = liveData(dispatcherIO) {
emit(Resource.loading(data = null))
try {
userRepository.login(username, password).apply {
emit(Resource.success(null))
}
} catch (exception: Exception) {
emit(Resource.error(exception.message ?: "Error Occurred!", data = null))
}
}
If you want to use LiveDate with Flow you can convert Flow to LiveData object using asLiveData function:
fun userClicked(username: String, password: String): LiveData<Resource<String?>> {
return userRepository.login(username, password).asLiveData()
}
But I wouldn't recommend to mix up LiveData and Flow streams in the project. I suggest to use only Flow.
Using only Flow:
// In ViewModel:
fun userClicked(username: String, password: String): Flow<Resource<String?>> {
return userRepository.login(username, password)
}
// Activity
binding.bLogin.setOnClickListener {
val username = binding.etUsername.text.toString()
val password = binding.etPassword.text.toString()
lifecycleScope.launch {
viewModel.userClicked(username, password).collect { resource ->
when (resource.status) {
Status.SUCCESS -> {
print(resource.data)
}
Status.ERROR -> {
print(resource.message)
}
Status.LOADING -> {
// loader stuff
}
}
}
}
}
Remove suspend keyword from the login function in Repository.
lifecycleScope docs.
here is my code, I am using it for logging in user with google,
This is my viewModel code
fun signInWithGoogle(account: GoogleSignInAccount): LiveData<Resource<Any>> {
val credential = GoogleAuthProvider.getCredential(account.idToken, null)
return liveData (IO){
authRepo.firebaseSignInWithGoogle(credential, object : FetchUser {
override suspend fun onUserDataFetch(user: User) {
this#liveData.emit(Resource.success(user))
}
override suspend fun onError(error: AppError?) {
this#liveData.emit(Resource.error(error, null))
}
})
}
}
This is my code authRepository where i am logging the user in and checking if user already exits in database or not according to that performing the work
suspend fun firebaseSignInWithGoogle(googleAuthCredential: AuthCredential, userCallBack: FetchUser) {
coroutineScope {
firebaseAuth.signInWithCredential(googleAuthCredential).await()
createUpdateUser(userCallBack)
}
}
private suspend fun createUpdateUser(userCallBack: FetchUser) {
val firebaseUser = firebaseAuth.currentUser
if (firebaseUser != null) {
userIntegrator.getUserById(firebaseUser.uid, object : OnDataChanged {
override suspend fun onChanged(any: Any?) {
if (any != null && any is User) {
any.isNew = false
userIntegrator.createUpdateUser(any, userCallBack)
} else {
val user = User()
user.id = firebaseUser.uid
user.name = firebaseUser.displayName
user.email = firebaseUser.email
user.isNew = true
userIntegrator.createUpdateUser(
user,
userCallBack
)
}
}
})
}
}
This is my last class where I am updating the user in database
suspend fun createUpdateUser(user: User, userCallBack: FetchUser) {
if (user.id.isNullOrEmpty()) {
userCallBack.onError(AppError(StatusCode.UnSuccess, ""))
return
}
val dp = databaseHelper.dataFirestoreReference?.collection(DatabaseHelper.USERS)?.document()
dp?.apply {
dp.set(user.toMap()).await().apply {
dp.get().await().toObject(User::class.java)?.let {
userCallBack.onUserDataFetch(it)
}?: kotlin.run {
userCallBack.onError(AppError(StatusCode.Exception,"Unable to add user at the moment"))
}
}
}
}
Now here whole thing is that, I am using a FetchUser interface which look like this
interface FetchUser {
suspend fun onUserDataFetch(user: User)
suspend fun onError(error: AppError?)
}
I just want to get rid of it and looking for something else in coroutines.
Also I just wanted to know the best practice here,
What should I do with it.
Also I want to make it unit testable
There are 2 ways, if you want to call and get result directly, you could use suspendCoroutine. Otherway, if you want to get stream of data like, loading, result, error,... you could try callbackFlow
Exp:
suspend fun yourMethod() = suspendCoroutine { cont ->
// do something
cont.resume(result)
}
suspend fun yourMethod() = callbackFlow {
val callbackImpl = object: yourInterace {
// your implementation
fun onSuccess() {
emit(your result)
}
fun onFailed() {
emit(error)
}
}
handleYourcallback(callbackImpl)
}
I want to implement firebase realtime database with coroutines, so I need to use flow because firebase just accept callbacks. the problem is the .collect{} block never gets executed
here is my code
#ExperimentalCoroutinesApi
override suspend fun getProduct(barcode: String): ProductItem? {
return withContext(Dispatchers.Default) {
println("Hi")
var item: ProductItem? = null
productFlow(barcode).collect {
//this never gets called
print("Getting product")
item = it
}
println("Ending product request ${item?.name}")
Log.i("GetProduct",item?.name)
item
}
}
#ExperimentalCoroutinesApi
private fun productFlow(barcode: String): Flow<ProductItem?> = callbackFlow {
val database = FirebaseDatabase.getInstance()
val productRef = database.getReference("products/$barcode")
val callback = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
for(snapshot in dataSnapshot.children){
Log.i("Source", snapshot.value.toString())
}
val product = dataSnapshot.getValue(ProductItem::class.java)
Log.i("Source",product?.name) //everything is good until here
sendBlocking(dataSnapshot.getValue(ProductItem::class.java)) //after this i dont get anything on the collect{} block
}
override fun onCancelled(databaseError: DatabaseError) {
println("cancelling")
sendBlocking(null)
}
}
try {
productRef.addListenerForSingleValueEvent(callback)
} catch (e: FirebaseException) {
println("Firebase exception")
sendBlocking(null)
}
awaitClose{
println("Closing")
productRef.removeEventListener(callback)
}
}
First I would suggest to use the catch method to check if there is an error or not. Second, for callbackflow I remember using offer() instead of sendBlocking
hi this is my user repository
class UserRepository(private val appAuth: FirebaseAuth) : SafeAuthRequest(){
suspend fun userLogin(email: String,password: String) : AuthResult{
return authRequest { appAuth.signInWithEmailAndPassword(email,password)}
}
}
this is the SafeAuthRequest class
open class SafeAuthRequest {
suspend fun<T: Any> authRequest(call : suspend () -> Task<T>) : T{
val task = call.invoke()
if(task.isSuccessful){
return task.result!!
}
else{
val error = task.exception?.message
throw AuthExceptions("$error\nInvalid email or password")
}
}
}
calling above things like that
/** Method to perform login operation with custom */
fun onClickCustomLogin(view: View){
authListener?.onStarted()
Coroutines.main {
try {
val authResult = repository.userLogin(email!!,password!!)
authListener?.onSuccess()
}catch (e : AuthExceptions){
authListener?.onFailure(e.message!!)
}
}
}
and my authListener like this
interface AuthListener {
fun onStarted()
fun onSuccess()
fun onFailure(message: String)
}
I am getting an error as the task is not completed
is the correct way to implement the task
I'm using MVVM architectural pattern, so the example I'm going to provide is called from my ViewModel class, that means I have access to viewModelScope. If you want to run a similar code on Activity class, you have to use the Coroutines scope available for your Activity, for example:
val uiScope = CoroutineScope(Dispatchers.Main)
uiScope.launch {...}
Answering your question, what I've done to retrieve login from user repository is this:
//UserRepository.kt
class UserRepository(private val appAuth: FirebaseAuth) {
suspend fun userLogin(email: String, password: String) : LoginResult{
val firebaseUser = appAuth.signInWithEmailAndPassword(email, password).await() // Do not forget .await()
return LoginResult(firebaseUser)
}
}
LoginResult is a wrapper class of firebase auth response.
//ClassViewModel.kt
class LoginFirebaseViewModel(): ViewModel(){
private val _loginResult = MutableLiveData<LoginResult>()
val loginResult: LiveData<LoginResult> = _loginResult
fun login() {
viewModelScope.launch {
try {
repository.userLogin(email!!,password!!).let {
_loginResult.value = it
}
} catch (e: FirebaseAuthException) {
// Do something on firebase exception
}
}
}
}
The code on Activity class would be like this:
// Function inside Activity
fun onClickCustomLogin(view: View){
val uiScope = CoroutineScope(Dispatchers.Main)
uiScope.launch {
try {
repository.userLogin(email!!,password!!).let {
authResult = it
}
}catch (e : FirebaseAuthException){
// Do something on firebase exception
}
}
}
One of the main benefits of using Coroutines is that you convert asynchronous code in sequential one. That means you don't need listeners or callbacks.
I hope this help you
I'm newbie with RxJava2.
I have next code:
fun signIn(): Completable = getCredentials() // get saved token
.onErrorResumeNext { makeLockSignInRequest() } // if token not saved then get it
.flatMap { refreshToken(it) } // refresh token
.doOnSuccess { credentialsManager.saveCredentials(it) } // save updated token
.doFinally { lock?.onDestroy(context) }!!
.toCompletable()
private fun getCredentials() = Single.create(SingleOnSubscribe<Credentials> {
credentialsManager.getCredentials(object : BaseCallback<Credentials, CredentialsManagerException> {
override fun onSuccess(payload: Credentials?) = it.onSuccess(payload!!)
override fun onFailure(error: CredentialsManagerException?) = it.onError(error!!)
})
})
private fun makeLockSignInRequest() = Single.create(SingleOnSubscribe<Credentials> {
lock = Lock.newBuilder(auth0, object : AuthenticationCallback() {
override fun onAuthentication(credentials: Credentials?) = it.onSuccess(credentials!!)
override fun onCanceled() { }
override fun onError(error: LockException?) = it.onError(error!!)
})
.withScheme("demo")
.withScope("email openid offline_access")
.withAudience(ApiServiceProvider.DOMAIN + "/api/")
.closable(true)
.build(context)
context.startActivity(lock!!.newIntent(context))
})
private fun refreshToken(storedCredentials: Credentials) = Single.create(SingleOnSubscribe<Credentials> {
apiClient.renewAuth(storedCredentials.refreshToken!!)
.addParameter("scope", "openid email offline_access")
.start(object : BaseCallback<Credentials, AuthenticationException> {
override fun onSuccess(receivedCredentials: Credentials?) {
val newCredentials = Credentials(receivedCredentials!!.idToken, receivedCredentials.accessToken, receivedCredentials.type, storedCredentials.refreshToken, receivedCredentials.expiresAt, receivedCredentials.scope)
it.onSuccess(newCredentials)
}
override fun onFailure(error: AuthenticationException?) {
it.onError(Exception("Error refresh token: ${error!!.description!!}"))
}
})
})
This code gets saved token and refresh it.
Also if user just logged in it refresh token.
I want to add filter like follows:
fun signIn(): Completable = getCredentials()
.onErrorResumeNext { makeLockSignInRequest() }
.filter { OffsetDateTime.now(ZoneOffset.UTC).toEpochSecond() > it.expiresAt!!.time } // if token alive then do nothing
.flatMapSingle { refreshToken(it) }
.doOnSuccess { credentialsManager.saveCredentials(it) }
.doFinally { lock?.onDestroy(context) }!!
.toCompletable()
This code will fail with error: NoSuchElementException
So how can I filter token?
.filter changes your Single to Maybe. If there is no item in Maybe (because filter requirements are not met) after transforming it with flatMapSingle your code will return error with NoSuchElementException exception.
What I would do with it is:
fun signIn(): Completable = getCredentials()
.onErrorResumeNext { makeLockSignInRequest() }
.filter { OffsetDateTime.now(ZoneOffset.UTC).toEpochSecond() > it.expiresAt!!.time } // if token alive then do nothing
.flatMapCompletable { refreshToken(it).doAfterSuccess{credentialsManager.saveCredentials(it)}.toCompletable() }
.doFinally { lock?.onDestroy(context) }!!