I have a use case that is as simple as this: Look for a Book in the Remote source, if it doesn't exist, create it.
So this is my approach that used to work with rxJava1:
public void handleBook(int id) {
getBookById(id)
.flatMap(new Func1<Book, Observable<Book>> {
#Override
public Observable<Book> call(Book book) {
if(book != null) // it exists
...
else // it's null - it doesn't exist ...
}
}
}
public Observable<Book> getBookById(int id) {
Book book = remoteSource.book(id)
return Observable.just(book)
}
In this case if the object book is null, the Observable.just call throws an exception. It's explicitly checking against null values.
Back for rxJava 1 I could pass a null as value to Observable.just (or other accesoars), then in the map or flatMap operators I'd check if the value I get is null (meaning result does not exist) or not (meaning I got some result).
With this limitation it seems that I cannot do this check anymore. I tried returning Observable.empty() in case the book object is null but then the whole thread would complete and finish when the returned value is Observable.empty().
How can I check in a rx-chain of execution if something I need is there and branch the chain of execution afterwards?
Instead of using Observable<Book> as a return type use Single<Book>. Single type emits either an object or an error
NOTE: Right now I don't have an IDE so the code will probably have some compiler fails. I assume you'll fix it easily
public Single<Book> getBookById(int id) {
return Single.create((e) => {
Book book = remoteSource.book(id)
if (book != null)
e.emit(book);
else
e.fail();
}
}
public void handleBook(int id) {
getBookById(id)
.onErrorResume(e => createBook(...))
...
}
Like a said earlier I'm not sure about the exact code but you can read it as if it was pseudo-code
Related
I have Boolean booleanCheckAvailabilityData to check availability data in my activity to create add/remove favorite. then i create
dataFavoriteMovieById = favoriteMovieViewModel.getAllFavoriteMovieById(idMovie);
to get data by id. so i make conditional statement to check avaiability data then put the result to boolean and i use the boolean later to add or remove the favorite.
if (dataFavoriteMovieById == null) {
booleanCheckAvailabilityData = false;
} else {
booleanCheckAvailabilityData = true;
}
In the first run, it work. my dataFavoriteMovieById is null
But, after i add or remove favorite. it always always contains data (RoomTrackingLiveData).
How can i solve this...
my code link : https://github.com/komangss/Submission-Menjadi-Android-Developer-Expert/blob/master/app/src/main/java/com/dicoding/submissionmade2_1/activity/DetailMovieActivity.java
I played with your app (thanks for providing a github link) and here are my results.
Latest app version
Your latest implementation doesn't produce an NPE anymore since you use getAllFavoriteMovieById in a more consistent way. You no longer initialize a LiveData instance in FavoriteMovieRepository by yourself but delegate it to Room to do it for you. So, you won't get an NPE since Room will always create a list to return results. If there're no items, it will return an empty list. So, you can safely remove a try/catch here:
try {
favoriteMovieViewModel.getAllFavoriteMovieById(idMovie).observe(this, new Observer<List<FavoriteMovie>>() {
#Override
public void onChanged(List<FavoriteMovie> favoriteMovies) {
booleanCheckAvailabilityData = favoriteMovies.size() != 0;
}
});
} catch (NullPointerException e) {
Log.d("ini bug nya", e.getMessage());
}
Original app version
In addition to what #Paul Ost said about how favoriteMovieViewModel should be used properly (by listening to it, not using it directly), I will explain why you actually had an NPE.
In that version, you were running into a NullPointerException because you returned the favoriteMovieById LiveData before it was actually initialized in your GetFavoriteMovieByIdAsyncTask.
So, here what was happening in detail. First, once your DetailMovieActivity had been created, favoriteMovieViewModel called getAllFavoriteMovieById() as below:
DetailMovieActivity.java
...
favoriteMovieViewModel = ViewModelProviders.of(this).get(FavoriteMovieViewModel.class);
dataFavoriteMovieById = favoriteMovieViewModel.getAllFavoriteMovieById(idMovie);
...
FavoriteMovieViewModel.java
FavoriteMovieViewModel instance, in turn, delegated the call to FavoriteMovieRepository instance as below:
public LiveData<List<FavoriteMovie>> getAllFavoriteMovieById(int idMovie) {
return repository.getFavoriteMovieById(idMovie);
}
FavoriteMovieRepository.java
Finally, getFavoriteMovieById started a GetFavoriteMovieByIdAsyncTask and returned favoriteMovieById:
public LiveData<List<FavoriteMovie>> getFavoriteMovieById(int id_movie) {
new GetFavoriteMovieByIdAsyncTask(favoriteMovieDao).execute(id_movie);
return favoriteMovieById;
}
But that's wrong, since your favoriteMovieById was set to null by default, and so on the first run, you were always getting it.
Your AsyncTask was eventually setting a non-null value, but it was too late:
...
private static class GetFavoriteMovieByIdAsyncTask extends AsyncTask<Integer, Void, Void> {
...
#Override
protected Void doInBackground(Integer... integers) {
FavoriteMovieRepository.favoriteMovieById = favoriteMovieDao.getFavoriteMovieById(integers[0]);
return null;
}
}
...
From what I can see in your code - getAllFavoriteMovieById works as expected. The thing is - you are using LiveData as a return type of getAllFavoriteMovieById thus it returns not the value itself but a LiveData wrapper. But if you will try to observe this LiveData object you will(presumably since I haven't seen relevant code) receive null instead of favourite value. The only correct place to assign value to your booleanCheckAvailabilityData inside this observer(depending on your DAO code of course).
favouriteMovieViewModel.getAllFavoriteMovieById().observe(this, Observer { data ->
if (data == null) {
booleanCheckAvailabilityData = false;
} else {
booleanCheckAvailabilityData = true;
}
})
Something like that(once again it depends on your DAO code and getAllFavoriteMovieById implementation)
Hope it helps.
In ROOM Try Deleting the old TABLE before inserting the new data. In that case the old data will be deleted as we are deleting the old data
class TestReturnFunction{
fun convertIntToString(intVal:Int?):String{
intVal?.let {
return it.toString()
}
}
}
In the above TestReturnFunction, there is a naive function which converts int to String . Inside the function we use let keyword , which will run a code block only when the argument to let is non null . So, the return statement inside the let will only run when the argument is non null.
The problem with above is that there is no return statement for the case when intVal is null and the Android studio does not give any error .
If we check the documentation for let keyword in kotlin, it says:
Calls the specified function [block] with this value as its argument and returns its result.
Further, the issue is that if we look into the java bytecode for above kotlin code , it looks like below
public final class TestReturnFunction {
#NotNull
public final String convertIntToString(#Nullable Integer intVal) {
if (intVal != null) {
int it = ((Number)intVal).intValue();
return String.valueOf(it);
} else {
return null;
}
}
}
Now , the problem with this code is that the function returns null in the case intVal is null but the function return type is of non nullable type .
Is this the bug or feature? anyone faced this issue before ?
This is a known bug which was present in Kotlin version 1.3.0.
see https://youtrack.jetbrains.com/issue/KT-28061
If possible upgrade to a Kotlin version >= 1.3.20
I'm currently refactoring legacy code to use Android Architecture Components and set up a room db and volley requests within a kind of repository pattern.
So the presentation/domain layer asks the repository to get LiveData-Objects to observe or tell him to synchronize with the server, after which old db entries are deleted and all current ones refetched from the server.
I've written tests for the synchronization part, so I'm sure, that the objects get fetched and inserted to the database correctly. But when writing a test to observe the entries of that db table (and test if the objects were saved correctly with everything there needs to be done before putting them into db) the LiveData> I'm observing, doesn't get triggered.
In the following snippet you can assume, that the synchronizeFormsWithServer(...) method does work correctly and is performing database operations asynchronously. It contains operations which deletes all Form-Objects from the db which are not present in the list of Forms fetched from the server and inserts all new ones. Since at the start of the test the database is empty this shouldn't matter that much
The test in which the observer doesn't get triggered:
#Test
public void shouldSaveFormsFromServerIntoDb() throws Exception
{
Lifecycle lifecycle = Mockito.mock(Lifecycle.class);
when(lifecycle.getCurrentState()).thenReturn(Lifecycle.State.RESUMED);
LifecycleOwner owner = Mockito.mock(LifecycleOwner.class);
when(owner.getLifecycle()).thenReturn(lifecycle);
final CountDownLatch l = new CountDownLatch(19);
formRepository.allForms().observe(owner, formList ->
{
if (formList != null && formList.isEmpty())
{
for (Form form : formList)
{
testForm(form);
l.countDown();
}
}
});
formRepository.synchronizeFormsWithServer(owner);
l.await(2, TimeUnit.MINUTES);
assertEquals(0, l.getCount());
}
The FormRepository code:
#Override
public LiveData<List<Form>> allForms()
{
return formDatastore.getAllForms();
}
The datastore:
#Override
public LiveData<List<Form>> getAllForms()
{
return database.formDao().getAllForms();
}
The formDao code (database is implemented how you'd expect it from room):
#Query("SELECT * FROM form")
LiveData<List<Form>> getAllForms();
It may very well be, that I didn't understand something about the LiveData-Components, because this is my first time using them, so maybe I got something fundamentally wrong.
Every bit of help is very much appreciated :)
PS: I stumbled across THIS post, which discusses a similar issue, but since I'm currently not using DI at all and just use a single instance of the formrepository (which has only one instance of formDao associated) I don't think it's the same problem.
Ok, so I found the solution, although I don't know, why it behaves that way.
Remember when I said "don't worry about the synchronize method"? Well... turns out there were a couple of things wrong with it, which delayed the solution further.
I think the most important error there was the method to update the objects in the database when the network response came in.
I used to call
#Update
void update(Form form)
in the dao, which for unknown reasons doesn't trigger the LiveData-Observer. So I changed it to
#Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(Form form);
After doing this I could get the Form-LiveData from my repository as easy as
LiveData<List<Form>> liveData = formRepository.allForms();
Then subscribe to it as usual.
The previously failed test looks like this now:
#Test
public void shouldSaveFormsFromServerIntoDb() throws Exception
{
Lifecycle lifecycle = Mockito.mock(Lifecycle.class);
when(lifecycle.getCurrentState()).thenReturn(Lifecycle.State.RESUMED);
LifecycleOwner owner = Mockito.mock(LifecycleOwner.class);
when(owner.getLifecycle()).thenReturn(lifecycle);
final CountDownLatch l = new CountDownLatch(19);
final SortedList<Form> sortedForms = new SortedList<Form>(Form.class, new SortedList.Callback<Form>()
{
#Override
public int compare(Form o1, Form o2)
{
return o1.getUniqueId().compareTo(o2.getUniqueId());
}
#Override
public void onChanged(int position, int count)
{
Log.d(LOG_TAG, "onChanged: Form at position " + position + " has changed. Count is " + count);
for (int i = 0; i < count; i++)
{
l.countDown();
}
}
#Override
public boolean areContentsTheSame(Form oldItem, Form newItem)
{
return (oldItem.getContent() != null && newItem.getContent() != null && oldItem.getContent().equals(newItem.getContent())) || oldItem.getContent() == null && newItem.getContent() == null;
}
#Override
public boolean areItemsTheSame(Form item1, Form item2)
{
return item1.getUniqueId().equals(item2.getUniqueId());
}
#Override
public void onInserted(int position, int count)
{
}
#Override
public void onRemoved(int position, int count)
{
}
#Override
public void onMoved(int fromPosition, int toPosition)
{
}
});
LiveData<List<Form>> ld = formRepository.allForms();
ld.observe(owner, formList ->
{
if (formList != null && !formList.isEmpty())
{
Log.d(LOG_TAG, "shouldSaveFormsFromServerIntoDb: List contains " + sortedForms.size() + " Forms");
sortedForms.addAll(formList);
}
});
formRepository.synchronizeFormsWithServer(owner);
l.await(2, TimeUnit.MINUTES);
assertEquals(0, l.getCount());
}
I know that exactly 19 Forms will get fetched from the server and then every Form will get changed once (first time I load a list containing all Forms with reduced data, and the second time I load every item from the server again replacing the old value in the db with the new value with more data).
I don't know if this will help you #joao86 but maybe you have a similar issue. If so, please make sure to comment here :)
You have to use the same database instance at all places.
=> Use a singleton for that
I had a similar issue with yours --> LiveData is not updating its value after first call
Instead of using LiveData use MutableLiveData and pass the MutableLiveData<List<Form>> object to the Repository and do setValue or postValue of the new content of the list.
From my experience with this, which is not much, apparently the observer is connected to object you first assign it too, and every change must be done to that object.
regarding my code example down, what shold I do if one Locable's variables is null? In example, now if l.getZoom() returns null, I got NullPointerException.
#Override
public void writeToParcel(Parcel parcel, int arg1) {
parcel.writeInt(count);
for(Locable l:locableArr){
parcel.writeInt(l.getOriginId());
parcel.writeInt(l.getLocableType());
parcel.writeInt(l.getZoom());
parcel.writeDouble(l.getLatituda());
parcel.writeDouble(l.getLongituda());
parcel.writeString(l.getTitle());
parcel.writeString(l.getSnipet());
}
}
Thanks!
You can use Parcel.writeValue for marshalling generic object with null value.
I'm using a Parcelable class that has Integer and Boolean fields as well, and those fields can be null.
I had trouble using the generic Parcel.writeValue method, particularly when I was trying to read it back out via Parcel.readValue. I kept getting a runtime exception that said it couldn't figure out the type of the parceled object.
Ultimately, I was able to solve the problem by using Parcel.writeSerializable and Parcel.readSerializable with a type cast, as both Integer and Boolean implement the Serializable interface. The read and write methods handle null values for you.
This is the solution I came up with to write strings safely:
private void writeStringToParcel(Parcel p, String s) {
p.writeByte((byte)(s != null ? 1 : 0));
p.writeString(s);
}
private String readStringFromParcel(Parcel p) {
boolean isPresent = p.readByte() == 1;
return isPresent ? p.readString() : null;
}
Most serialization code that I've seen uses either flags to indicate the presence/absence of a value OR precedes the value with a count field (for example, when writing arrays) where the count field is just set to zero if the value doesn't exist at all.
Examining the source code of Android core classes reveals code like this (from Message class):
if (obj != null) {
try {
Parcelable p = (Parcelable)obj;
dest.writeInt(1);
dest.writeParcelable(p, flags);
} catch (ClassCastException e) {
throw new RuntimeException(
"Can't marshal non-Parcelable objects across processes.");
}
} else {
dest.writeInt(0);
}
or this (from Intent class):
if (mCategories != null) {
out.writeInt(mCategories.size());
for (String category : mCategories) {
out.writeString(category);
}
} else {
out.writeInt(0);
}
My suggestion: In your code, if there is no functional difference between "zoom == null" and "zoom == 0", then I would just declare zoom as a primitive (int instead of Integer) OR initialize it to zero in the constructor and ensure that you never set it to null (then you can be guaranteed that it will never be null and you won't have to add special code to deal with that in your serialization/deserialization methods).
I have method to return object of Place, but first i have to check if Place object exist in database then return him or otherwise fetch Place object from rest service. How the easiest way I can check it out ?
public Observable<Place> getPlace(final String id) {
// Both method from repository and rest are: Observable<Place> getPlace(String id);
// if placeDatabaseRepository.getPlace(id) != null then:
return placeDatabaseRepository.getPlace(id);
// else Place == NULL then:
return placeRest.getPlace(id);
}
Dan Lew wrote an excellent article about this very problem.
The gist of it is that you use two Observable<Place> instances - one that returns the result from the database if it's not null, another that returns the result from the REST call. You chain them together with the concat() operator and only take the first emitted item with the first() operator, kind of like so:
Observable<Place> dbSource = Observable
.just(placeDatabaseRepository.getPlace(id))
.filter(place -> place != null);
Observable<Place> restSource = Observable
.just(placeRest.getPlace(id));
return Observable
.concat(dbSource, restSource)
.first();