I want to add file path to Db and when a file already exists in DB show a Toast message. In ViewModel class:
public void addFile(SharedFile file) {
DefaultExecutorSupplier.getInstance().forBackgroundTasks()
.execute(() -> {
long result = fileRepository.insert(file);
insertResult.postValue(result);
}
);
}
public MutableLiveData<Long> getInsertResult() {
return insertResult;
}
and in the Fragment onViewCreated:
viewModel.getInsertResult().observe(getViewLifecycleOwner(), aLong -> {
if (aLong == -1) {
Toast.makeText(getContext(), getString(R.string.already_exist_file), Toast.LENGTH_LONG).show();
}
});
It works and when I add a repetitive file it Toasts the message, but the problem is when I open another fragment and back to the current fragment it again Toasts message.
This is because when (re)subscribing to a LiveData you always receive the value that was emitted last. See here under Always up to date data. Some ways around this are discussed here: Android LiveData prevent receive the last value on observe
Related
I have been facing this issue for quite sometime and would like to know a better approach to solve this problem. If you are aware of anything about how to solve it then please let me know.
I am building a project which takes data from an API and then following MVVM architecture I take the Retrofit instance to Repository, and then to ViewModel and further observe it from my fragment.
Now what I am working on currently is Login feature. I will send a number to the API and in response I will receive if the number is registered or not. If it is registered then I would move to the next screen.
Now the problem is that using one of the function in ViewModel I send that number to the API to get the response. And using a variable I observe that response.
Now I create a function which checks if the response was true or false and based on that I am using the logic to move to the next screen, but the issue is the returned value from the function. As LiveData works asynchronously in background it takes some time to return the value and in meantime the function returns the initial value which is false.
Function to verify response
private fun checkNumber(): Boolean {
var valid = false
authRiderViewModel.response.observe(viewLifecycleOwner, Observer {
Timber.d("Response: $it")
if (it.success == true) {
valid = true
}
})
Timber.d("Boolean: $valid")
return valid
}
Moving to next screen code:
binding.btnContinue.setOnClickListener {
val number = binding.etMobileNumber.text.toString().toLong()
Timber.d("Number: $number")
authRiderViewModel.authDriver(number)
if (checkNumber()) {
val action = LoginFragmentDirections.actionLoginFragmentToOtpFragment()
findNavController().navigate(action)
} else {
Toast.makeText(requireContext(), "Number not registered", Toast.LENGTH_SHORT).show()
}
}
So in case I received the true response from the server even then I would not move to the next screen because the initial value I received is false. I have spent few hours trying to fix it and any help would be appreciated. If you need any code let me know in comments. Thanks.
You have four distinct states:
The server returned a positive response
The server returned a negative response
The server failed (e.g., returned a 500, timed out)
You are waiting on the server
You are attempting to model that with two states: true and false. This will not work.
Instead, model it with four states. One approach is called "loading-content-error" and uses a sealed class to represent those states:
sealed class LoginState {
object Loading : LoginState()
data class Content(val isSuccess: Boolean) : LoginState()
object Error : LoginState()
}
Your LiveData (or your StateFlow, once you migrate to coroutines) would be a LiveData<LoginState>. Your observer can then use a when to handle Loading, Content, and Error as needed, such as:
For Loading, display a progress indicator
For Content, do whatever you are doing now with your boolean
For Error, display an error message
Actually, live data observation is an asynchronous operation. You have to code accordingly.
Just calling checkNumber() won't return since is asynchronous instead I give you some ideas to implement in a better way.
Just call the checkNumber when button click inside the check number do this instead of return valid
authRiderViewModel.response.observe(viewLifecycleOwner, Observer {
Timber.d("Response: $it")
if (it.success == true) {
val action = LoginFragmentDirections.actionLoginFragmentToOtpFragment()
findNavController().navigate(action)
} else {
Toast.makeText(requireContext(), "Number not registered", Toast.LENGTH_SHORT).show()
}
})
I'm trying to use RxJava with Android to asynchronously update my view. When user clicks the movie from the list in the RecyclerView, I want to present him first with the movie from the database, if present. Then I want to fetch the latest information and update the database as well as UI. I'm trying to use concat method and its variant but it does not work.
I have skipped other codes only to post the relevant RxJava methods that are fetching data as the rest is working fine.
When I disable network connection with the code below (hence remote returns error), the code below does not display data from the database at all. Only it reports the error. Which means the local is not resolving.
public Flowable<Movie> getMovie(final int id) {
return Single.concat(mLocal.getMovie(id), mRemote.getMovie(id).doOnSuccess(data -> {
mLocal.save(data);
})).onErrorResumeNext(error->{
return Flowable.error(error);
});
}
And in this code, it works fine, except now that I don't get the error message (and rightly so, since I have replaced it with new stream from the database)
public Flowable<Movie> getMovie(final int id) {
return Single.concat(mLocal.getMovie(id), mRemote.getMovie(id).doOnSuccess(data -> {
mLocal.save(data);
})).onErrorResumeNext(error->{
return mLocal.getMovie(id).toFlowable();
});
}
Now, how can I get database data first and then fire network call next to update data and get errors from the database or network call?
UPDATE
The latest method code
// calling getMovie on mLocal or mRemote returns Single
public Flowable<Movie> getMovie(final int id) {
return Single.concat(mLocal.getMovie(id), mRemote.getMovie(id).doOnSuccess(data -> {
mLocal.insertMovie(data);
})).onErrorResumeNext(error -> {
return Flowable.error(error);
});
}
Here is how I call them
public void loadMovie(int id)
{
Disposable d = mRepo.getMovie(id)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread(), true)
.doOnSubscribe(subscription -> {
subscription.request(Long.MAX_VALUE);
//post progress here
})
.subscribe(data -> {
//onNext
},
error -> {
//onError
},
() -> {
//onComplete
}
);
mDisposables.add(d);
}
With affirmation that my code works and guides on troubleshooting from #akarnokd I found the latest code (see OP) works flawlessly. The result of RxJava chain is posted to LiveData object which should update View. Unfortunately it only posts the latest data (which is an error) and skips the first (which is the data from the database).
I will deal with that but since the post deals with RxJava, I will consider this solved!
I am trying to perform some task based on the data retrieved from Firebase.
for (inti=0;i<dateList.size();i++)
{
attendanceDateRef= attendanceRef.child(dateList.get(i));
attendanceClassRef= attendanceDateRef.child(ViewAttendanceSelectClassActivity.selectedClass);
attendanceClassRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshotdataSnapshot) {
for (DataSnapshotdsp : dataSnapshot.getChildren())
{
attendanceList.add(dsp.getValue(String.class));
Log.e("attendanceListValues",dsp.getValue(String.class));
}
}
#Override
public void onCancelled(DatabaseErrordatabaseError) {
}
});
}
intindex=1;
for (inti=1;i<=attendanceList.size();i++)
{
if(i%4==0)
{
fullDateRangeList.add(dateList.get(index));
index++;
}
else
{
fullDateRangeList.add(dateList.get(index));
}
}
Log.e("fullDateRangeList",String.valueOf(fullDateRangeList.size()));
Log.e("attendanceList",String.valueOf(attendanceList.size()));
above code is written on the OnClick event of a Button , when I click on the Button following output is generated on logcat:
fullDateRangeList:0
attendanceList:0
attendanceListValues:Value1
attendanceListValues:Value2
attendanceListValues:Value3
attendanceListValues:Value4
.
.
.
attendanceListValues:ValueN
from the above output it looks like second loop is executing before data is retrieved from Firebase and that is why size of fullDateRange and attendanceList is 0.
is there any way i can prevent second loop from executing until data is stored in attendanceList?
You cannot stop that method or make it wait until you get the all the data from your database. This is the behaviour of an asynchronous method. You need to change the logic of your code a little bit by declaring and using that data only inside the onDataChange() method, otherwise it will be always empty.
Also there is another approach. If you want, you can dive into the asynchronous world and use my answer from this post.
I create an app with Firebase. There is an issue that i can't solve, and didn't find it talked here.
In this method I want to check if some data is already in the server. If not - I want to add it (the code of adding works well. The Firebase database is being changed as I want). so I'm using onDataChange method as following:
public boolean insertNewList(String title)
{
System.out.println("start: ");
final boolean[] result = new boolean[1];
result[0]=false;
final String group = title;
mRootRef = some reference...
mRootRef.addValueEventListener(new ValueEventListener()
{
#Override
public void onDataChange(DataSnapshot dataSnapshot)
{
System.out.println(0);
if (dataSnapshot.child(group).getValue() != null)
{
System.out.println(group + " is already exist");
System.out.println(1);
}
//write the data.
else
{
mRootRef=mRootRef.child(group);
mRootRef.push().setValue("some data");
System.out.println(2);
result[0]=true;
}
}
#Override
public void onCancelled(DatabaseError databaseError)
{
}
});
System.out.println(3);
return result[0];
}
But what realy happens is this output:
begin:
3 (just skip on onDataChange method and return false).
some print after calling the function
0 (goes back to function and enter to onDataChange method)
2 (finally goes where I want it to go)
0 (for some reason enters twice :( )
1
And because of that i receive wrong results in this function.
Can you help please?
Replace
mRootRef.addValueEventListener(new ValueEventListener()
with
mRootRef.addListenerForSingleValueEvent(new ValueEventListener()
When you add the the value to firebase, "addValueEventListener" called again, not like addListenerForSingleValueEvent that shots only once anywhy.
The output that you showed us looks normal to me. Let me try to explain:
begin: // a) this comes from the System.out.println("begin")
3 // b) you DECLARE your value event listener and then you run
// the System.out.print("3") statement
0 // c) you just added a listener so firebase calls you, and
// your listener fires
2 // d) this is the first time your listener fires, so you go
// into the block called "write the data"
0 // e) since you just wrote the data, firebase calls you again
1 // f) this time, since the data has been written, you go into
// the block called "is alraedy exist"
This is normal behaviour for firebase.
In c), firebase always calls you back one time when you declare a listener.
In e), firebase calls you because the data changed.
But in b), you are only declaring your listener, not yet running it, so the statements after this declaration are executed and you see "3" before anything else happens.
I'm playing around with RXJava, retrofit in Android. I'm trying to accomplish the following:
I need to poll periodically a call that give me a Observable> (From here I could did it)
Once I get this list I want to iterate in each Delivery and call another methods that will give me the ETA (so just more info) I want to attach this new info into the delivery and give back the full list with the extra information attached to each item.
I know how to do that without rxjava once I get the list, but I would like to practice.
This is my code so far:
pollDeliveries = Observable.interval(POLLING_INTERVAL, TimeUnit.SECONDS, Schedulers.from(AsyncTask.THREAD_POOL_EXECUTOR))
.map(tick -> RestClient.getInstance().getApiService().getDeliveries())
.doOnError(err -> Log.e("MPB", "Error retrieving messages" + err))
.retry()
.subscribe(deliveries -> {
MainApp.getEventBus().postSticky(deliveries);
});
This is giving me a list of deliveries. Now I would like to accomplish the second part.
Hope I been enough clear.
Thanks
Finally I found a nice way to do it.
private void startPolling() {
pollDeliveries = Observable.interval(POLLING_INTERVAL, TimeUnit.SECONDS, Schedulers.from(AsyncTask.THREAD_POOL_EXECUTOR))
.flatMap(tick -> getDeliveriesObs())
.doOnError(err -> Log.e("MPB", "Error retrieving messages" + err))
.retry()
.subscribe(this::parseDeliveries, Throwable::printStackTrace);
}
private Observable<List<Delivery>> getDeliveriesObs() {
return RestClient.getInstance().getApiService().getDeliveries()
.flatMap(Observable::from)
.flatMap(this::getETAForDelivery)
.toSortedList((d1, d2) -> {
if (d1.getEta() == null) {
return -1;
}
if (d2.getEta() == null) {
return 1;
}
return d1.getEta().getDuration().getValue() > d2.getEta().getDuration().getValue() ? 1 : -1;
});
}
Let's go step by step.
First we create an Observable that triggers every POLLING_INTERVAL time the method getDeliveriesObs() that will return the final list
We use retrofit to get an Observable of the call
We use flatMap to flattern the resut list and get in the next flatmap a Delivery item, one by one.
Then we get the estimated time of arrival set inside the Delivery object and return it
We sort the list to order by estimated time of arrival.
In case of error we print and retry so the interval does not stop
We subscribe finally to get the list sorted and with ETA inside, then we just return it or whatever you need to do with it.
It's working properly and it's quite nice, I'm starting to like rxjava :)
I haven't spent a lot of time with Java 8 lambdas, but here's an example of mapping each object to a different object, then getting a List<...> out at the other end in plain ol' Java 7:
List<Delivery> deliveries = ...;
Observable.from(deliveries).flatMap(new Func1<Delivery, Observable<ETA>>() {
#Override
public Observable<ETA> call(Delivery delivery) {
// Convert delivery to ETA...
return someEta;
}
})
.toList().subscribe(new Action1<List<ETA>>() {
#Override
public void call(List<ETA> etas) {
}
});
Of course, it'd be nice to take the Retrofit response (presumably an Observable<List<Delivery>>?) and just observe each of those. For that we ideally use something like flatten(), which doesn't appear to be coming to RxJava anytime soon.
To do that, you can instead do something like this (much nicer with lambdas). You'd replace Observable.from(deliveries) in the above example with the following:
apiService.getDeliveries().flatMap(new Func1<List<Delivery>, Observable<Delivery>>() {
#Override
public Observable<Delivery> call(List<Delivery> deliveries) {
return Observable.from(deliveries);
}
}).flatMap(...)