How to assign values inside a lambda expression - android

In Android, I was learning authentication in firebase. I want to store a Boolean value to a variable which is whether the task was successful or not. Here is my code:-
fun signIn(userEmail: String, userPassword: String): Boolean {
var successful = false
mAuth.signInWithEmailAndPassword(userEmail, userPassword)
.addOnCompleteListener {
successful = it.isSuccessful
Log.d(TAG, "successful = $successful")
}
Log.d(TAG, "successful = $successful")
return successful
}
When this method is invoked, the successful variable inside the higher-order function changes to true but it has no effect when it comes outside. Here is my log file:-
D/UserAuthentication: successful = true
D/UserAuthentication: successful = false
How do I fix this?

Your inner Log is called after your outer Log because the method signInWithEmailAndPassword is Asynchronous
Do something like this:
fun signIn(userEmail: String, userPassword: String, callback: (Task<AuthResult>) -> Unit) {
mAuth.signInWithEmailAndPassword(userEmail, userPassword)
.addOnCompleteListener {
callback.invoke(it)
}
}
Then call this function like this:
signIn("your_username", "your_password") {
if(it.isSuccessfull){
//Your login was successfull
}
}

Related

Kotlin coroutine does not run synchronously

In all cases that I have been using corrutines, so far, it has been executing its "lines" synchronously, so that I have been able to use the result of a variable in the next line of code.
I have the ImageRepository class that calls the server, gets a list of images, and once obtained, creates a json with the images and related information.
class ImageRepository {
val API_IMAGES = "https://api.MY_API_IMAGES"
suspend fun fetch (activity: AppCompatActivity) {
activity.lifecycleScope.launch() {
val imagesResponse = withContext(Dispatchers.IO) {
getRequest(API_IMAGES)
}
if (imagesResponse != null) {
val jsonWithImagesAndInfo = composeJsonWithImagesAndInfo(imagesResponse)
} else {
// TODO Warning to user
Log.e(TAG, "Error: Get request returned no response")
}
...// All the rest of code
}
}
}
Well, the suspend function executes correctly synchronously, it first makes the call to the server in the getRequest and, when there is response, then composes the JSON. So far, so good.
And this is the call to the "ImageRepository" suspension function from my main activity:
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) { neoRepository.fetch(this#MainActivity) }
Log.i(TAG, "After suspend fun")
}
The problem is that, as soon as it is executed, it calls the suspension function and then displays the log, obviously empty. It doesn't wait for the suspension function to finish and then display the log.
Why? What am I doing wrong?
I have tried the different Dispatchers, etc, but without success.
I appreciate any help.
Thanks and best regards.
It’s because you are launching another coroutine in parallel from inside your suspend function. Instead of launching another coroutine there, call the contents of that launch directly in your suspend function.
A suspend function is just like a regular function, it executes one instruction after another. The only difference is that it can be suspended, meaning the runtime environment can decide to halt / suspend execution to do other work and then resume execution later.
This is true unless you start an asynchronous operation which you should not be doing. Your fetch operation should look like:
class ImageRepository {
suspend fun fetch () {
val imagesResponse = getRequest(API_IMAGES)
if (imagesResponse != null) {
val jsonWithImagesAndInfo = composeJsonWithImagesAndInfo(imagesResponse)
} else {
// TODO Warning to user
Log.e(TAG, "Error: Get request returned no response")
}
... // All the rest of code
}
}
-> just like a regular function. Of course you need to all it from a coroutine:
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) { neoRepository.fetch() }
Log.i(TAG, "After suspend fun")
}
Google recommends to inject the dispatcher into the lower level classes (https://developer.android.com/kotlin/coroutines/coroutines-best-practices) so ideally you'd do:
val neoRepository = ImageRepository(Dispatchers.IO)
lifecycleScope.launch {
val result = neoRepository.fetch()
Log.i(TAG, "After suspend fun")
}
class ImageRepository(private val dispatcher: Dispatcher) {
suspend fun fetch () = withContext(dispatcher) {
val imagesResponse = getRequest(API_IMAGES)
if (imagesResponse != null) {
val jsonWithImagesAndInfo = composeJsonWithImagesAndInfo(imagesResponse)
} else {
// TODO Warning to user
Log.e(TAG, "Error: Get request returned no response")
}
... // All the rest of code
}
}

How to make LiveData observe only once in Android [Kotlin]

I have a situation where I want the livedata to be observed only once in the app. The problem is that I am working on the authentication for an app using some Node.js backend.
As I am sending the values to receive the response from the backend it's working fine till now. I observe that response and based on that I make changes to my fragment ( that is if the response received is true then move to next fragment, otherwise if it is false show a toast message ).
Now the problem is that :
Case 1: I opened the app, entered the right credentials and pressed the button, received true response from the server and goes to the next fragment.
Case 2: I opened the app, but entered the wrong credentials, I received a false from server and based on that the Toast is shown.
Case 3 (The issue): I opened the app, entered the wrong credentials and then without closing the fragment screen entered the right credentials by editing them, the app crashes and at the same time I receive multiple responses from the server via LiveData.
My observation: Looking more into that I found that the LiveData is attached to the fragment/activity and therefore it shows the last state. So as in case 3 the the last state was receiving the false value from backend it was used again and we were shown the error instead of going to the next screen.
Can anyone guide me how to solve this. Thanks
Some code that might be needed:
binding.btnContinue.setOnClickListener {
val number = binding.etMobileNumber.text.toString().toLong()
Timber.d("Number: $number")
authRiderViewModel.authDriver(number)
checkNumber()
}
Function which checks the number :
private fun checkNumber() {
authRiderViewModel.response.observe(viewLifecycleOwner, Observer {
Timber.d("Response: $it")
if (it!!.success == true) {
val action = LoginFragmentDirections.actionLoginFragmentToOtpFragment()
findNavController().navigate(action)
Timber.d("${it.message}")
} else {
Toast.makeText(requireContext(), "Number not registered", Toast.LENGTH_SHORT).show()
binding.etMobileNumber.setText("")
}
})
}
ViewModel code:
private val _response = MutableLiveData<AuthResponse>()
val response: LiveData<AuthResponse>
get() = _response
fun authDriver(number: Long) = viewModelScope.launch {
Timber.d("Number: $number")
myRepo.authDriver(number).let {
_response.postValue(it)
}
}
P.S I have tried using something called SingleLiveEvent but it doesn't seem to work.
I would create a separate class that tracks the UI state you need and update it when the state is consumed. Something like the following. I don't really know what the parameter is for authDriver, so this is a more generic example.
sealed interface AuthState {
object NotYetRequested: AuthState
object AwaitingResponse: AuthState
class ResponseReceived(val response: AuthResponse): AuthState {
var isHandled = false
private set
fun markHandled() {
isHandled = true
}
}
}
// In ViewModel:
private val _authState = MutableLiveData<AuthState>().also {
it.value = AuthState.NotYetRequested
}
val authState: LiveData<AuthState> get() = _authState
fun requestAuthentication() = viewModelScope.launch {
_authState.value = AuthState.AwaitingResponse
val response = myRepo.authenticate()
_authState.value = AuthState.ResponseReceived(response)
}
// In Fragment:
viewModel.authState.observe(viewLifecycleOwner) { authState ->
when (authState) {
AuthState.NotYetRequested -> ShowUiRequestingAuthentication()
AuthStateAwaitingResponse -> ShowIndeterminateProgressUi()
is AuthStateResponseReceived -> when {
authState.isHandled -> {} // do nothing? depends on your setup, might need to navigate to next screen if handled response is successful
authState.response.isSuccessful -> {
goToNextScreen()
authState.markHandled()
}
else -> {
showErrorToast()
ShowUiRequestingAuthentication()
authState.markHandled()
}
}
}
}

SuspendCoroutine code not reachable when using with Firebase auth

I have a suspendCoroutine in my repository with which I want to send data back to my ViewModel -
suspend fun sendPasswordResetMail(emailId: String): Boolean {
return withContext(Dispatchers.IO) {
suspendCoroutine { cont ->
firebaseAuth?.sendPasswordResetEmail(emailId)
?.addOnCompleteListener {
cont.resume(it.isSuccessful)
}
?.addOnFailureListener {
cont.resumeWithException(it)
}
}
}
}
However, neither of the listeners are called. Debugger says no executable code found at line where 'cont.resume(it.isSuccessful)' or 'cont.resumeWithException(it)' are.
I tried 'Dispatchers.IO', 'Dispatchers.Main' and 'Dispatchers.Default' but none of them seem to work. What could I be doing wrong?
My ViewModel code -
isEmailSent : LiveData<Boolean> = liveData {
emit(firebaseAuthRepo.sendPasswordResetMail(emailId))
}
and
fragment -
viewModel.isEmailSent.observe(viewLifecycleOwner, { flag ->
onResetMailSent(flag)
})
I believe you are calling
isEmailSent : LiveData<Boolean> = liveData {
emit(firebaseAuthRepo.sendPasswordResetMail(emailId))
}
this piece of code everytime for sending email
and
viewModel.isEmailSent.observe(viewLifecycleOwner, { flag ->
onResetMailSent(flag)
})
this piece only once.
Assuming that's true what you are essentially observing is the initial live data that was created with the model while it is being replaced everytime when resent is called. Instead call
isEmailSent.postValue(firebaseAuthRepo.sendPasswordResetMail(emailId))
from inside of a coroutine.
Also for the debugger not showing anything try adding a log above the cont.resume call and cont.resumeWithException call since it has worked for me in the past.
I think the easier way to achieve this is by using firebase-ktx and the await() function (which does what you are trying under the hood):
suspend fun sendPasswordResetMail(emailId: String): Boolean {
try {
firebaseAuth?.sendPasswordResetEmail(emailId).await()
return true
} catch(e: Exception) {
return false
}
}
Another way would be to use flow:
suspend fun sendPasswordResetMail(emailId: String): Boolean = flow<Boolean {
firebaseAuth?.sendPasswordResetEmail(emailId).await()
emit(true)
}.catch { e: Exception -> handleException(e) }
You could then observe this in your fragment by putting the code inside your viewmodel and calling .asLiveData()

Mockk - MockKException when testing password against regex

I've just started to do Unit testing in Kotlin using Mockk.
I'm trying to test the following function:
fun evaluatePredicate(regEx: String, passwordInserted: String) : Boolean {
return passwordInserted.matches(regEx.toRegex())
}
My test look like this:
#Test
fun evaluatePredicate_shouldContainLowerCase_trueExpected() {
//given
val regEx = ".*[a-z]+.*" //lower case
val password = "password"
every { password.matches(regEx.toRegex()) } returns true
every { SUT.evaluatePredicate(regEx, password) } returns true
//when
val evaluate = password.matches(regEx.toRegex())
val result = SUT.evaluatePredicate(regEx, password)
//then
assertEquals(evaluate, result)
}
But I'm getting :
io.mockk.MockKException: Missing calls inside every { ... } block.
at line:
every { password.matches(regEx.toRegex()) } returns true
I've tried to use Mockk Matcher any() instead of matches(regEx.toRegex()) but nothing changed.
I'm not sure if I'm using the right tools for the job here.
Any suggestion is welcome.

document.exists() returns false from firestore

I'm trying to check whether a document in the firestore exists. I copied an exact location of the document (col3) from the firestore console, so it must correct.
The document.exists() returns false despite the document being saved in the database. I followed Google guide from this site.
I've set the break point and checked the DocumentSnapshot object, but it very hard to follow e.g zza, zzb, zzc...
private fun nameExists(userId: String, colName: String): Boolean{
val nameExists = booleanArrayOf(false)
val docRefA = fbDb!!.document("users/X9ogJzjJyOgGBV0kmzk7brcQXhz1/finalGrades/col3")
val docRefB = fbDb!!.collection("users")
.document(userId)
.collection("finalGrades")
.document(colName)
docRefA.get().addOnCompleteListener { task ->
if (task.isSuccessful) {
val document = task.result
if (document.exists()) {
nameExists[0] = true
}else{
Log.d(TAG, "no such document")
}
} else {
Log.d(TAG, "get failed with ", task.exception)
}
}
return nameExists[0]
}
Thanks to #Frank van Puffelen hints, I was able to debug the code. From what I researched, the following code is a classic approach to solve this type of problem.
Perhaps someone will find it useful.
Step 1.
Define a functional interface with a parameter of the same type as the primitive value or object you want to return from the asynchronous operation.
interface OnMetaReturnedListener{
fun onMetaReturned(colExists: Boolean)
}
Step 2. Create a method passing an interface reference as an argument. This method will be running the synchronous operation. Once the value is retrieved, call the functional interface method and pass the retrieved value/object as the method argument.
private fun nameExists(metaReturnedListener: OnMetaReturnedListener, colName: String){
val userId = fbAuth!!.uid!!
val docRefB: DocumentReference = fbDb!!.collection("users")
.document(userId)
.collection("finalGrades")
.document(colName)
docRefB.get().addOnCompleteListener { task ->
if (task.isSuccessful) {
val doc: DocumentSnapshot = task.result!!
val colExists = doc.exists()
metaReturnedListener.onMetaReturned(colExists)
} else {
Log.d(TAG, "get failed with ", task.exception)
}
}
}
Step 3.
Call the method defined in step 2. Pass an object that you defined in step 1. This object will be invoked from the method in step 2, so the overridden method will receive the value/object as a parameter, which you can then assign to a variable of the same type. You can also pass other parameters of different types, but you have to update the method signature in step 2
fun saveModulesToFirestore(colName: String, saveCode: String) {
var collectionExists = false
if (saveCode == FirebaseHelper.ADD_TO_COLLECTION){
nameExists(object: OnMetaReturnedListener{
override fun onMetaReturned(colExists: Boolean) {
collectionExists = colExists
if (collectionExists){
val dialog = CollectionExistsDialogFragment.newInstance()
dialog.show(fragmentManager, DIALOG_COLLECTION_EXISTS)
return
} else{
addNewCollectionToFirebase(colName)
}
}
}, colName)
}
}

Categories

Resources