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.
Related
I have a question... sometimes, I need to get data from ViewModel directly. For example, Let's say there's a isChecked() method in ViewModel. And I want to use it in the if condition.
if(viewModel.isChecked()){
// TODO:
}
So, what I am doing right now is:
fun isChecked(): Boolean = runBlocking {
val result = dbRepo.getData()
val response = apiRepo.check(result)
return response.isSuccessful
}
It uses runBlocking. So, it runs on MainThread. I don't think it's a good way because it can freeze the screen. But yes, if the condition needs to run, it needs to wait it until it gets the data from DB and Network.
Another way that I can think of is using LiveData. However, I can't use it in the condition. So, I needs to move the condition in the observer block. But sometimes, this can't be done because there can be something before the condition. And it doesn't seem to look direct but writing code here and there and finally get that data.
So, Is there any simpler way than this?
Your best bet if you have something slow or blocking like that is to rethink how you are using the data entirely. Instead of trying to return it, use LiveData or callbacks to handle the response asynchronously without causing your UI to hang or become laggy. In these cases you really only have three options:
Use a callback to handle when the response is received
Use observable data like LiveData to handle when the response is received
Change the method to a suspend function and call it from a coroutine
Forcing a method to wait to return on the main thread without using one of these is going to cause the app to hang.
Callback to get state
It's hard to say definitely what the best solution for you is without more details about how you are using isChecked(), but one pattern that could work would be to use a callback to handle what you were formerly putting in the if statement, like this (in the ViewModel):
fun getCheckedState(callback: (Boolean)->Unit) {
viewModelScope.launch {
// do long-running task to get checked state,
// using an appropriate dispatcher if needed
val result = dbRepo.getData()
val response = apiRepo.check(result)
// pass "response.isSuccessful" to the callback, to be
// used as "isChecked" below
callback(response.isSuccessful)
}
}
You would call that from the activity or fragment like this:
viewModel.getCheckedState { isChecked ->
if( isChecked ) {
// do something
}
else {
// do something else
}
}
// CAUTION: Do NOT try to use variables you set inside
// the callback out here!
A word of caution - the code inside the callback you pass to getCheckedState does not run right away. Do not try to use things you set inside there outside the callback scope or you fall into this common issue
Simpler Callback
Alternately, if you only want to run some code when isChecked is true, you could simplify the callback like this
fun runIfChecked(callback: ()->Unit) {
viewModelScope.launch {
// do long-running task to get checked state,
// using an appropriate dispatcher if needed
val result = dbRepo.getData()
val response = apiRepo.check(result)
// only call the callback when it's true
if( response.isSuccessful ) {
callback()
}
}
}
and call it with
viewModel.runIfChecked {
// do something
}
// Again, don't try to use things from the callback out here!
Use lifecyclescope.launch(Dispatcher.IO) instead of runblocking
Try this code on your ViewModel class:
suspend fun isChecked(): Boolean {
val response: Response? = null
viewModelScope.launch(Dispatchers.IO) {
val result = dbRepo.getData()
response = apiRepo.check(result)
}.join()
return response?.isSuccessful
}
From Activity:
// Suppose you have a button
findViewById<Button>(R.id.btn).setOnClickListener({
CoroutineScope(Dispatchers.Main).launch {
if (viewModel.isChecked()) {
Log.d("CT", "Do your others staff")
}
}
})
Hope it work file. If no let me comment
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 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.
I have got the following method:
operator fun invoke(query: String): Flow<MutableList<JobDomainModel>> = flow {
val jobDomainModelList = mutableListOf<JobDomainModel>()
jobListingRepository.searchJobs(sanitizeSearchQuery(query))
.collect { jobEntityList: List<JobEntity> ->
for (jobEntity in jobEntityList) {
categoriesRepository.getCategoryById(jobEntity.categoryId)
.collect { categoryEntity ->
if (categoryEntity.categoryId == jobEntity.categoryId) {
jobDomainModelList.add(jobEntity.toDomainModel(categoryEntity))
}
}
}
emit(jobDomainModelList)
}
}
It searches in a repository calling the search method that returns a Flow<List<JobEntity>>. Then for every JobEntity in the flow, I need to fetch from the DB the category to which that job belongs. Once I have that category and the job, I can convert the job to a domain model object (JobDomainModel) and add it to a list, which will be returned in a flow as the return object of the method.
The problem I'm having is that nothing is ever emitted. I'm not sure if I'm missing something from working with flows in Kotlin, but I don't fetch the category by ID (categoriesRepository.getCategoryById(jobEntity.categoryId)) it then works fine and the list is emitted.
Thanks a lot in advance!
I think the problem is that you're collecting infinite length Flows, so collect never returns. You should use .take(1) to get a finite Flow before collecting it, or use first().
The Flows returned by your DAO are infinite length. The first value is the first query made, but the Flow will continue forever until cancelled. Each item in the Flow is a new query made when the contents of the database change.
Something like this:
operator fun invoke(query: String): Flow<MutableList<JobDomainModel>> =
jobListingRepository.searchJobs(sanitizeSearchQuery(query))
.map { jobEntityList: List<JobEntity> ->
jobEntityList.mapNotNull { jobEntity ->
categoriesRepository.getCategoryById(jobEntity.categoryId)
.first()
.takeIf { it.categoryId == jobEntity.categoryId }
}
}
Alternatively, in your DAO you could make a suspend function version of getCategoryById() that simply returns the list.
Get an idea from the code below if your Kotlin coroutine flow gets lost with a continuation approximate peak alloc exception
fun test(obj1: Object,obj2: Object) = flow {
emit(if (obj1 != null) repository.postObj(obj1).first() else IgnoreObjResponse)
}.map { Pair(it, repository.postObj(obj2).first()) }
In my Android Kotlin project, I call a webservice in a coroutine (myWebservice is just a custom class that manages webservice calls):
fun searchForItems(userInput: String)
{
CoroutineScope(Dispatchers.IO + Job()).launch {
val listOfItems = myWebService.call(userInput)
}
}
That method is called everytime a user types a character in an EditText, so the app calls a webservice that returns a list of items matching his request. But I want to optimize that.
Let's say that the user types the word: "apple". In order to minimise the number of webservice calls, here is what I want to achieve:
when the user types the first letter (a), the webservice is called
when the user types the next letters, there is no new webservice call as long as the first called hasn't returned (let's assume that he has enough time to type the next letters (pple))
when the first webservice call is done, a new call is done automatically with the new user input (apple)
What would be the best practices to achieve that?
Or is there a better way to minimize the number of webservice calls?
Thanks.
Using Kotlin coroutines I solved it like this:
class SomeViewModel : ViewModel() {
private var searchJob: Job? = null
fun search(userInput: String) {
searchJob?.cancel() // cancel previous job when user enters new letter
searchJob = viewModelScope.launch {
delay(300) // add some delay before search, this function checks if coroutine is canceled, if it is canceled it won't continue execution
val listOfItems = myWebService.call(userInput)
...
}
}
}
When user enters first letter search() function is called, coroutine is launched and Job of this coroutine is saved to searchJob. Then delay(300) function is called to wait for another user input before calling the WebService. If user enters another letter before 300 milliseconds expire search() function will be called again and previous coroutine will be cancelled using searchJob?.cancel() function and WebService will not be called in the first coroutine.
You need debouncing. Coroutines or not, you shouldn't call the web service with each letter for every active user. That will eventually DDOS the web service if your app is used by many people at once.
Since you are using Kotlin, instead of coroutines you can use Flow. It comes with a built in debounce method. Also the stream of letters is easily modelled as a flow. It would be something like this (I'm not sure this even runs, but you get the idea):
textFlow = flow {
myTextView.doOnTextChanged { text, start, count, after -> emit(text)}
}.debounce(1000)
The more complex alternative is RxJava's debounce operator.
You can also try LiveData with lupajz's debounce extension
Or you can also roll your own solution.
To optimize web service calls you can use StateFlow and its debounce operator. For example in ViewModel:
val inputFlow = MutableStateFlow("")
init {
inputFlow
.debounce(300) // filters out values that are followed by the newer values within the given timeout. The latest value is always emitted.
.filterNot { userInput -> userInput.isEmpty() } // filter the unwanted string like an empty string in this case to avoid the unnecessary network call.
.distinctUntilChanged() // to avoid duplicate network calls
.flowOn(Dispatchers.IO) // Changes the context where this flow is executed to Dispatchers.IO
.onEach { userInput -> // go through each filtered userInput
val listOfItems = myWebService.call(userInput)
// do sth with listOfItems
}
.launchIn(viewModelScope)
}
fun searchForItems(userInput: String) {
inputFlow.value = userInput
}
You can achieve the same functionality using Rx Java's debounce operator. every time you will enter a text in Edit Text Rx Java's debounce will call the webservice and quickly will produce result and if Again user enter another text it will then call the web service.
Please refer the below link for batter understaing , So you can modify your code and achieve same functinolaity
https://blog.mindorks.com/implement-search-using-rxjava-operators-c8882b64fe1d
RxSearchObservable.fromView(searchView)
.debounce(300, TimeUnit.MILLISECONDS)
.filter(new Predicate<String>() {
#Override
public boolean test(String text) {
if (text.isEmpty()) {
textViewResult.setText("");
return false;
} else {
return true;
}
}
})
.distinctUntilChanged()
.switchMap(new Function<String, ObservableSource<String>>() {
#Override
public ObservableSource<String> apply(String query) {
return dataFromNetwork(query)
.doOnError(throwable -> {
// handle error
})
// continue emission in case of error also
.onErrorReturn(throwable -> "");
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<String>() {
#Override
public void accept(String result) {
textViewResult.setText(result);
}
});
var myApiUpdateTask: Deferred<Unit>? = null
fun getApiResponse() = viewModelScope.launch {
myApiUpdateTask?.cancel()
myApiUpdateTask = async {
delay(500)
// TODO: your api request
}
}
Using this structure inside the viewmodel; You can wait for 500 milliseconds and send the request you will send consecutively.