I want to create a signing activity in my app using Firebase phone authentication. The authentication has three phases:
Sending verification code with PhoneAuthProvider.verifyPhoneNumber(options)
Verify the code with PhoneAuthProvider.getCredential(verificationId!!, code)
Sign in the user with auth.signInWithCredential(credential)
I want to use Coroutines to handle the signing process. I know how to handle code verification using await(), which wrap the Task returned by auth.signInWithCredential(credential).
My problem is with PhoneAuthProvider.verifyPhoneNumber(options) function which is a void function. I must use callbacks to handle this method.
val options = PhoneAuthOptions.newBuilder(auth)
.setPhoneNumber(phoneNumber)
.setTimeout(60L, TimeUnit.SECONDS)
.setCallbacks(callback)
.build()
PhoneAuthProvider.verifyPhoneNumber(options)
where callbacks is:
callbacks = object : PhoneAuthProvider.OnVerificationStateChangedCallbacks(){
override fun onVerificationCompleted(credential: PhoneAuthCredential) {
Timber.i("onVerificationCompleted:$credential")
signInWithPhoneAuthCredential(credential)
}
override fun onVerificationFailed(e: FirebaseException) {
Timber.i(e,"onVerificationFailed")
}
override fun onCodeSent(verificationId: String, token: PhoneAuthProvider.ForceResendingToken) {
Timber.i("onCodeSent:$verificationId")
storedVerificationId = verificationId
resendToken = token
}
}
The question is: is there a way to use await() with verifyPhoneNumber function?
Otherwise, how can I use coroutines with callbacks to block the function until callbacks triggered?
You can use suspendCoroutine to wrap Firebase callback into a coroutine suspend function like this:
sealed class PhoneAuthResult {
data class VerificationCompleted(val credentials: PhoneAuthCredential) : PhoneAuthResult()
data class CodeSent(val verificationId: String, val token: PhoneAuthProvider.ForceResendingToken)
: PhoneAuthResult()
}
private suspend fun performPhoneAuth(
phoneNumber: String,
firebaseAuth: FirebaseAuth): PhoneAuthResult =
suspendCoroutine { cont ->
val callback = object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
override fun onVerificationCompleted(credential: PhoneAuthCredential) {
Timber.i("onVerificationCompleted:$credential")
cont.resume(
PhoneAuthResult.VerificationCompleted(credential)
)
}
override fun onVerificationFailed(e: FirebaseException) {
Timber.i(e, "onVerificationFailed")
cont.resumeWithException(e)
}
override fun onCodeSent(verificationId: String, token: PhoneAuthProvider.ForceResendingToken) {
Timber.i("onCodeSent:$verificationId")
cont.resume(
PhoneAuthResult.CodeSent(verificationId, token)
)
}
}
val options = PhoneAuthOptions.newBuilder(firebaseAuth)
.setPhoneNumber(phoneNumber)
.setTimeout(60L, TimeUnit.SECONDS)
.setCallbacks(callback)
.build()
PhoneAuthProvider.verifyPhoneNumber(options)
}
You can use the below solution for this problem
First of all, you must define an interface FcmService
interface FcmService {
suspend fun initFcmToken() : Flow<String>
}
for next section define FcmServiceImpl
class FcmServiceImpl : FcmService {
override suspend fun initFcmToken(): Flow<String> =
callbackFlow {
FirebaseMessaging.getInstance().token.addOnCompleteListener(
OnCompleteListener { task ->
if (!task.isSuccessful) {
Logger.error(
"Device Manager" +
"Fetching FCM registration token failed" +
task.exception
)
return#OnCompleteListener
}
if (task.isComplete) {
trySend(task.result)
}
})
awaitClose { cancel() }
}
}
and then for use
fcmService.initFcmToken().collectLatest {
// your token is here
}
I hope help
Related
I have this function :
fun createBiometricPrompt(
activity: AppCompatActivity,
processSuccess: (BiometricPrompt.AuthenticationResult) -> Unit,
callback: BiometricCallback
): BiometricPrompt {
var nbFailure = 0
val executor = ContextCompat.getMainExecutor(activity)
val callback = object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errCode: Int, errString: CharSequence) {
super.onAuthenticationError(errCode, errString)
Log.d(TAG, "errCode is $errCode and errString is: $errString")
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
Log.d(TAG, "User biometric rejected.")
nbFailure++;
if(nbFailure == MAX_BIOMETRICS_FAILURE){
nbFailure = 0
callback.onFailure()
}
}
which is using a callback callback: BiometricCallback, it's only for now using it for notifying that a failure happened by sending callback.onFailure()
Callback is called in the calling Fragment by doing this:
Fragment.kt
biometricPrompt =
BiometricPromptUtils.createBiometricPrompt(requireActivity() as AppCompatActivity, ::encryptAndStoreServerToken, object: BiometricCallback {
override fun onFailure() {
biometricPrompt.cancelAuthentication()
viewModel.loginError.set("Error Biometrics")
}
})
The onFailure is used then to cancel the process and display an error.
The Callback interface is defined as :
interface BiometricCallback {
fun onFailure()
}
I have been asked to use a suspend function instead of a callback but I have no clue how to do it properly. Should I use LiveData ? If any idea, please help
Thanks
you can use suspendCancellableCoroutine or suspendCoroutine to convert any callback based Api in direct style to make it more kotlin friendly plus it provides more encapsulation you will be just returned with the result all complexity is handled inside the function only
suspend fun authenticate():BiomatricPrompt.AuthenticationResult? {
return suspendCancelableCoroutine { continuation ->
biometricPrompt = BiometricPrompt(this, executor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int,
errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
continuation.resume(null,null)
}
override fun onAuthenticationSucceeded(
result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
continuation.resume(result,null)
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
continuation.resume(null,null)
}
})
promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Biometric login for my app")
.setSubtitle("Log in using your biometric credential")
.setNegativeButtonText("Use account password")
.build()
biometricPrompt.authenticate(promptInfo)
}
}
usage
val authenticationResult = authenticate()
if(authenticationResult == null){
//authentication failed
}else{
//authenticated successfully
}
you can use custom object as result type to handle more use cases
more information
I am currently building an app using AWS SDK. One of the API is a sign in and is requiring, in addition to email and password, a Callback in order to get back the status of the request. The issue is that I am not able to send back the result.
This is my code:
override suspend fun signIn(email: String, password: String): Result<SignInResult> =
withContext(ioDispatcher) {
try {
api.signIn(email, password, object : Callback<SignInResult> {
override fun onResult(result: SignInResult?) {
Result.Success(result!!)
}
override fun onError(e: Exception?) {
Result.Error(e!!)
}
})
} catch (e: Exception) {
Result.Error(e)
}
}
The issue is that coroutine sign in is requiring a return of Result but I do not know what to return because I should only return when onResult, onError and when catching an exception.
Any idea how to make it works ?
Thanks
You can use suspendCoroutine or suspendCancellableCoroutine to work with callbacks:
override suspend fun signIn(email: String, password: String): Result<SignInResult> =
suspendCoroutine { continuation ->
try {
api.signIn(email, password, object : Callback<SignInResult> {
override fun onResult(result: SignInResult) {
// Resume coroutine with a value provided by the callback
continuation.resumeWith(Result.Success(result))
}
override fun onError(e: Exception) {
continuation.resumeWith(Result.Error(e))
}
})
} catch (e: Exception) {
continuation.resumeWith(Result.Error(e))
}
}
suspendCoroutine suspends coroutine in which it executed until we decide to continue by calling appropriate methods - Continuation.resume.... suspendCoroutine mainly used when we have some legacy code with callbacks.
There is also suspendCancellableCoroutine builder function, it behaves similar to suspendCoroutine with additional feature - provides an implementation of CancellableContinuation to the block.
I would like to refresh-token and send the request again. I can make request if it is success there is no problem but if refresh-token response gets fail I would like to show error message and forward user to login screen.
I also do not have context in TokenAuthenticator class and it's not possible because it provides in my Hilt NetworkModule.
I have tried create a MutableLiveData in Session and postvalue true in below class but while i observe it in BaseActivity, it goes infinite loop and trigger every time after one time postValue.
How can i solve this problem?
class TokenAuthenticator(
val preferenceHelperImp: PreferenceHelperImp,
private val tokenApi: RefreshTokenApi,
) : Authenticator{
override fun authenticate(route: Route?, response: Response): Request? {
GlobalScope.launch {
getUpdatedRefreshToken(RefreshTokenRequest(preferenceHelperImp.getRefreshToken())
).collect {
when (it) {
is State.Success -> {
preferenceHelperImp.setCurrentUserLoggedInMode(Constants.App.LoggedInMode.LOGGED_IN_MODE_SERVER)
preferenceHelperImp.setAccessToken(it.data.body()?.payload?.accessToken)
preferenceHelperImp.setRefreshToken(it.data.body()?.payload?.refreshToken)
preferenceHelperImp.setUserInfo(Gson().toJson(TokenInfo.initFromToken(
it.data.body()?.payload?.accessToken!!)))
Session.current.userInfo =
Gson().fromJson(preferenceHelperImp.getUserInfo(),
TokenInfo::class.java)
response.request.newBuilder()
.header("Authorization", it.data.body()?.payload?.accessToken!!)
.build()
}
is State.Fail -> {
Session.current.isRefreshTokenFail.postValue(true)
}
is State.Error -> {
Session.current.isRefreshTokenFail.postValue(true)
}
}
}
}
return null
}
private fun getUpdatedRefreshToken(refreshTokenRequest: RefreshTokenRequest): Flow<State<LoginResponse>> {
return object :
NetworkBoundRepository<LoginResponse>() {
override suspend fun fetchFromRemote(): retrofit2.Response<LoginResponse> =
tokenApi.getRefreshToken(refreshTokenRequest)
}.asFlow()
}
}
Could you try with typealias?
typealias OnAuthSuccess = () -> Unit
typealias OnAuthFailure = () -> Unit
class TokenAuthenticator (){...
override fun authenticate(onAuthSuccess: OnAuthSuccess,onAuthFailure: OnAuthFailure, route: Route?, response: Response): Request? {
when (it) {
is State.Success -> {
onAuthSuccess.invoke()
}
is State.Fail -> {
onAuthFailure.invoke()
}
}
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 trying to change all my callbacks to coroutines, I have readed about them and they are fasinating !
What I want to accomplish is just login a user, but if the login logic fails, notify it to my presenter.
This is what I have made
LoginPresenter.kt
class LoginPresenter #Inject constructor(private val signInInteractor: SignInInteractor) : LoginContract.Presenter, CoroutineScope {
private val job = Job()
override val coroutineContext: CoroutineContext = job + Dispatchers.Main
override fun signInWithCoroutines(email: String, password: String) {
launch {
view?.showProgress()
withContext(Dispatchers.IO){
signInInteractor.signInWithCoroutinesTest(email,password)
}
view?.hideProgress()
}
}
}
And now the problem is in my interactor, since its a suspend function, I would love to return an error response in order to do a view.showError(errorMsg) from my presenter
SignInInteractor.kt
override suspend fun signInWithCoroutinesTest(email: String, password: String) {
FirebaseAuth.getInstance()?.signInWithEmailAndPassword(email, password).addOnCompleteListener {
if(it.isSuccessful){
//Want to notify the presenter that the coroutine has ended succefully
}else{
//want to let the courutine know about it.exception.message.tostring
}
}
}
The way I was doing it was with callbacks that notify my presenter
override fun signInWithCoroutinesTest(email: String, password: String) {
FirebaseAuth.getInstance()?.signInWithEmailAndPassword(email, password,listener:OnSuccessCallback).addOnCompleteListener {
if(it.isSuccessful){
listener.onSuccess()
}else{
listener.onFailure(it.exception.message.toString())
}
}
}
Question
How to return if the operation has succeded from coroutines and notify my presenter?
Thanks
You must explicitly suspend the coroutine:
override suspend fun signInWithCoroutinesTest(
email: String, password: String
) = suspendCancellableCoroutine { continuation ->
FirebaseAuth.getInstance()?.signInWithEmailAndPassword(email, password).addOnCompleteListener {
if (it.isSuccessful) {
continuation.resume(Unit)
} else {
continuation.resumeWithException(it.exception)
}
}
}
Also, since your code is suspendable and not blocking, don't run it withContext(IO). Simply call it directly from the main thread, that's the beauty of coroutines.
Think of coroutines as you would normal, synchronous code. How would you write this if the background work completed immediately? Maybe something like this:
override fun signInWithCoroutinesTest(email: String, password: String) {
view?.showProgress()
if(!signInInteractor.signIn(email,password)) view?.showSignInError()
view?.hideProgress()
}
Or if you want to catch the error, something like this
override fun signInWithCoroutinesTest(email: String, password: String) {
view?.showProgress()
try {
signInInteractor.signIn(email,password))
} catch(e: AuthenticationException) {
view?.showError(e.message)
}
view?.hideProgress()
}
With coroutines, you just write the exact same code but the methods themselves suspend rather than block threads. So in this case signIn would be a suspending function and will need to be called from a coroutine or other suspend function. Based on that, you probably want the outer function to suspend and then you would launch that coroutine rather than trying to launch inside of signInWithCoroutinesTest.
The original example isn't completely realistic but normally you would launch this kind of thing from an existing scope, perhaps associated with your activity or viewModel. Ultimately, it would look something like this:
fun hypotheticalLogic() {
...
viewModelScope.launch {
signInWithCoroutinesTest(email, password)
}
...
}
override suspend fun signInWithCoroutinesTest(email: String, password: String) {
view?.showProgress()
try {
signInInteractor.signIn(email,password))
} catch(e: AuthenticationException) {
view?.showError(e.message)
}
view?.hideProgress()
}
The key point is just to think of coroutines the same as you would "normal" sequential code.