I try to implement deleting user in FirebaseAuth using Kotlin flow (SharedFlow).
In onDeleteAccountClicked() there is delete() method called from FirebaseAuth which may throw AuthReauthenticationRequiredException. When the exception is thrown, app redirects to another fragment to reauthenticate, then call onDeleteAccountClicked() once again, but flow emits nothing.
ViewModel
private val _deleteAccount = MutableSharedFlow<() -> Unit>()
fun onDeleteAccountClicked() {
logd("outside the viewModelScope")
viewModelScope.launch {
logd("inside the viewModelScope")
_deleteAccount.emit {
logd("emitting log")
firebaseAuth.deleteUser()
//throw AuthReauthenticationRequiredException()
}
}
}
init {
viewModelScope.launch {
_deleteAccount
.onEach {
it()
}
.catch {
if (it is AuthReauthenticationRequiredException) {
_redirectToSignInMethodsScreen.emit(Unit)
}
}
.collect()
}
}
Logs "outside the viewModelScope" and "inside the viewModelScope" shows every time when the method is called, but "emitting log" only for the first time.
Am I even trying to do it the right way?
I just tested the code, and it works for me. I called onDeleteAccountClicked() three times with delay between calling, and all three logs "emitting log" inside emit lambda were printed. Try to remove calling firebaseAuth.deleteUser() inside emit lambda and test. Calling FirebaseUser.delete function when user is already deleted throws FirebaseAuthInvalidUserException exception. Maybe that's why you didn't see logs - because FirebaseUser.delete function throws an exception.
I think the structure you use for calling just one function is a bit complicated, I can suggest to get rid of _deleteAccount flow and just wrap firebaseAuth.deleteUser() inside try-catch (you even don't need to launch a coroutine for that):
fun onDeleteAccountClicked() {
try {
firebaseAuth.deleteUser()
} catch(e: AuthReauthenticationRequiredException) {
_redirectToSignInMethodsScreen.emit(Unit)
}
}
Related
I'm trying to implement One Tap, so I have created this function:
override fun oneTapSgnInWithGoogle() = flow {
try {
emit(Result.Loading)
val result = oneTapClient.beginSignIn(signInRequest).await()
emit(Result.Success(result))
} catch (e: Exception) {
emit(Result.Error(e.message))
}
}
//.flowOn(Dispatchers.IO)
And some programmer told me that I need to add .flowOn(Dispatchers.IO) to the above function, so it can be correct. My code work correct without it. Here is how I call this function in the ViewModel:
fun oneTapSignIn() = viewModelScope.launch {
repo.oneTapSignInWithGoogle().collect { response ->
oneTapSignInResponse = response
}
}
Is it really necessary to do that? I'm really confused.
You're calling beginSignIn which returns a Task, so it does its own stuff in the background. Now Task.await is suspending, not blocking, so it won't block the current thread while waiting for the task.
Therefore, the body of your flow doesn't contain any blocking stuff, so there is no reason to use flowOn(Dispatchers.IO) here.
I wrote code which works fine without kotlin flow, but i want to try kotlin flow and when i used it inside repository but somehow it is not even entering inside i could not find any solution since it is not throwing any error, it is just not entering inside function and i think it is because of flow collector. It is coming till repository but for repo it is not entering.
class NewsRepositoryImpl(private val newsService: NewsService) : NewsRepository {
override suspend fun getNews(search: String): Flow<ResultWrapper<List<Article>>> = flow {
emit(ResultWrapper.Loading)
try {
val news = newsService.getNews(search, BuildConfig.API_KEY)
emit(ResultWrapper.Success(news.articles.map { it.toArticle() }))
} catch (e: Exception) {
emit(ResultWrapper.Error(e.message))
}
}
}
Using flow builder you create a cold stream of data. The code inside it is executed only when the flow is being collected, i.e. terminal operators invoked, for example collect, collectLatest, first... .
coroutineScope.launch {
newsRepository.getNews("news").collectLatest { result ->
// use result
}
}
I am trying to run a test in which I want to wait till higher order function executes. As of now I am not able to figure out any ways to do it. Following is my code.
#Test
fun `test execute routine error`() = runBlocking(coroutineDispatcher) {
val observer = mock<Observer<String>>()
baseViewModel.error.observeForever(observer)
val httpException = HttpException(Response.error<String>(402, mock(ResponseBody::class.java)))
val function = baseViewModel.executeRoutine {
throw httpException
}
verify(observer).onChanged("Something went wrong. Please try again")
}
The problem with above snippet is that it jumps to the last line i.e. verify() before throwing an http exception for executeRoutine.
Update: Execute routine definition
fun executeRoutine(requestType: RequestType = RequestType.POST_LOGIN, execute: suspend () -> Unit) {
viewModelScope.launch {
withContext(Dispatchers.IO) {
_spinner.postValue(true)
try {
execute()
} catch (ex: HttpException) {
val errorHandler = errorHandlerFactory.create(requestType)
_error.postValue(errorHandler.getErrorMessageFrom(ex))
} catch (ex: Exception) {
_error.postValue(ex.localizedMessage)
Timber.e(ex)
} finally {
_spinner.postValue(false)
}
}
}
}
The problem is that the higher order function does execute, it just doesn't do what you think it does -- its execution is launching the task, not waiting for it to complete.
You will have to solve the problem another way, by either having your test wait until the change is observed, or having the callback complete a barrier to allow the test to proceed (e.g. completableJob.complete() at the end of the call back, and completableJob.join() waiting before proceeding with the test).
It might also be desirable to rearchitect your code so you don't have to do anything special, e.g. by making executeRoutine a suspend function executing the code rather than launching the code in another scope.
As said in title, does LiveDataScope block of code runs only one time after emit()?
Is it possible to make my LiveDataScope block of code run more than one time, because i need to make request to server, and if it fails i would like to call same code to try again.
Sample of code:
fun refreshLiveDataResource() = liveData(Dispatchers.Main){
val retriveRoutes = remoteDataSourceKt.getRoutes()
if(retriveRoutes.data != null){
routeList = retriveRoutes.data
}
emit(retriveRoutes)
}
when i call this function, after emit() if i call function again, never gets inside.
You should create a LiveData once and update its value whenever you need it instead of creating a new one everytime you want to retry. I would use a Flow instead, to update the LiveData, with a retry operator in case there is some error, like this:
val routesLiveData =
remoteDataSourceKt.getRoutes()
.onEach { retriveRoutes ->
if(retriveRoutes.data != null){
routeList = retriveRoutes.data
}
}
.retry(3) { e -> // retries up to 3 times; no argument means retrying forever
(e is IOException) // retry on any IOException but also introduce delay if retrying
.also {
if (it)
delay(1000)
}
}
.asLiveData()
You'll have to:
Make getRoutes() method return a Flow.
Make sure the Flow works on Dispatchers.IO.
Observe myLiveData from your UI.
I have a series of sequential Parse network calls that are all dependent on each other to save the final object all wrapped by an Observable so I can deal with everything as one call in my presenter.
If the user decides to change fragments, or leave the app or whatever, this network call is important enough that I'd like it to attempt to complete.
If I call disposables.dispose() the observable will throw an error (which I can catch) on the next .save method in the Observable and the network call does not finish.
If I don't dispose of it, the network call finishes, but it will call my onComplete and throw an error since the view is gone. I can stop that error from happening, but then I'm worried that I've created a memory leak.
I don't care if the oncomplete/onerror get called if the user gets into this situation but I would like to ensure it completes one way or another.
Is there any way to let the call complete, but not cause a memory leak by not disposing it?
fun doParseNetworkCall(){
return Observable.create<Boolean> { emitter ->
createParseObject1
createParseObject1.save()
createParseObject2
createParseObject2.add(key, createParseObject1)
createParseObject2.save()
createParseObject3
createParseObject3.add(key, createParseObject2)
createParseObject3.save()
emitter.onNext(true)
}
fun doNetworkCall(){
repo.doParseNetworkCall()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io()).subscribeBy(
onError = { error ->
//do something
},
onComplete = {
//do something
}
).addTo(disposable)
}
I was able to resolve my issue doing the following. I think it's memory safe since the 2nd set of observables don't subscribe until the first observable completes, and if the Composite Disposable in the presenter has been disposed of already, the 2nd set will not subscribe.
repo.saveSomething()
.map {
//Do some non view stuff
}
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io()).subscribeBy(
onError = { error ->
onErrorMethod()
},
onComplete = {
onSuccessMethod()
}
)
fun onSuccessMethod() {
Observable.just(true)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io()).subscribeBy(
onComplete = {
//Do view stuff
}).addTo(disposable)
}
fun onErrorMethod() {
Observable.just(true)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io()).subscribeBy(
onComplete = {
//Do view stuff
}).addTo(disposable)
}