Java Android LiveData calls Room query dependent on other LiveData - java

UPDATE:::
I've updated the question to include demo other LiveData that were also required:
so we have userLD that we need the value of to get the goalWeeklyLD, and we need the goalWeeklyLD value to get the remaining 4 LiveData values as they come from Room querys that use goalWeekly.dateproperties in the query
:::::
I've hit a problem where I have a fragment that has to populate LiveData that uses a query dependent on another LiveData value.
how can i get my live data to work correctly when it is dependent on other results?
Without using The Transitions.map() the view model throws an error because the values of the other live data are still null.
with the Transitions.map() in the view model the activities observer throws an error because the LiveData is still null.
I could possibly cheat my way past this by using a horrendously big nested query to return all i need in one custom DTO. but i'd rather understand whats going on here and how to handle this sort of situation properly.
Hopefully some code will make this clear
The Activity:
public class SomeFragment extends Fragment {
public static SomeFragment newInstance() {
return new SomeFragment();
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
someViewModel = ViewModelProviders.of(this).get(SomeViewModel.class);
//getting user details from previous activity
Intent intent = getActivity().getIntent();
if (intent != null){
if (intent.hasExtra(USER_ID)){
user = new User(intent.getStringExtra(USERNAME));
user.setId(intent.getLongExtra(USER_ID,0));
someViewModel.setUserLD(user);
}
}
someViewModel.getUserLD().observe(this, new Observer<User>() {
#Override
public void onChanged(#Nullable User userVal) {
user = userVal;
}
});
someViewModel.getGoalWeeklyLD().observe(this, new Observer<User>() {
#Override
public void onChanged(#Nullable User userVal) {
user = userVal;
}
});
//the below Observer calls throw an error because LiveData is null. makes sense.
//but how can i say "don't try and observe these until the transition.map has ran (because then it wont be null after if my understanding is right)" or something to that effect
someViewModel.getFirstLD(user.getId()).observe(this, new Observer<XObject>() {
#Override
public void onChanged(#Nullable Grades avgSportGradeVal) {
//Update UI
}
});
someViewModel.getSecondLD(user.getId()).observe(this, new Observer<XObject>() {
#Override
public void onChanged(#Nullable Grades avgBoulderGradeVal) {
// Update UI
}
});
someViewModel.getThriLD(user.getId()).observe(this, new Observer<XObject>() {
#Override
public void onChanged(#Nullable Grades avgBoulderGradeVal) {
// Update UI
}
});
someViewModel.getFourthLD(user.getId()).observe(this, new Observer<XObject>() {
#Override
public void onChanged(#Nullable Grades avgBoulderGradeVal) {
// Update UI
}
});
}}
The View Model:
public class SomeViewModel extends AndroidViewModel {
DaoRepository daoRepository;
MutableLiveData<User> userLD;
LiveData<XObject> firstLD;
LiveData<XObject> secondLD;
LiveData<XObject> thirdLD;
LiveData<XObject> fourthLD;
public MutableLiveData<User> getUserLD() {
return userLD;
}
public void setUserLD(User user){
userLD.setValue(user);
}
public LiveData<XObject> getFirstLD(long userId) {
return goalWeeklyLD;
}
public LiveData<XObject> getSecondLD(long userId) {
return goalWeeklyLD;
}
public LiveData<XObject> getThirdLD(long userId) {
return goalWeeklyLD;
}
public LiveData<XObject> getForthLD(long userId) {
return goalWeeklyLD;
}
public SomeViewModel(#NonNull Application application) {
super(application);
daoRepository = new DaoRepository(application);
userLD = new MutableLiveData<>();
//so the first LiveData waits for the user to be populated before getting its LiveData becasue we need the userId for our Room query to run
firstLD = Transformations.map(userLD, user -> daoRepository.getMostRecentGoalWeekly(user.getId()).getValue());
//the remaining live data uses values from the first...
setupOtherTransformMaps(userLD.getValue())
}
public void setupOtherTransformMaps(long userId) {
//the secondLD, thirdLD and fourthLD all depends on values from the first (in runs a query that uses its dateExpired)
secondLD = Transformations.map(firstLD, first ->
daoRepository.getAvgGradeRouteInPeriod(userId, first.getDateCreated(),first.getDateExpires()).getValue());
thirdLD = Transformations.map(firstLD, first ->
daoRepository.getAvgGradeRouteInPeriod(userId, first.getDateCreated(),first.getDateExpires()).getValue());
fourthLD = Transformations.map(firstLD, first ->
daoRepository.getAvgGradeRouteInPeriod(userId, first.getDateCreated(),first.getDateExpires()).getValue());
}}

Thankfully Google was smart and created a component which lets you combine variable number of LiveData into a single LiveData, and only emit events when you choose to do so!
This is called MediatorLiveData.
In your case though, you only need to channel 1 LiveData (userLD) into 1 another LiveData, that will emit each time userLd has a new value.
So you can use a predefined MediatorLiveData that does exactly this, specifically Transformations.switchMap.
firstLD = Transformations.switchMap(userLD, user -> daoRepository.getMostRecentGoalWeekly(user.getId()));
EDIT: Yup, you seem to need to expose these LiveData separately from one another, but they all depend on the first query to execute.
So you need to replace Transformations.map { ...getValue() with Transformations.switchMap and you'll be good to go.
public SomeViewModel(#NonNull Application application) {
super(application);
CustomApplication app = (CustomApplication) application;
daoRepository = app.daoRepository();
userLD = new MutableLiveData<>();
firstLD = Transformations.switchMap(userLD, user -> daoRepository.getMostRecentGoalWeekly(user.getId()));
secondLD = Transformations.switchMap(firstLD, first ->
daoRepository.getAvgGradeRouteInPeriod(userId, first.getDateCreated(),first.getDateExpires()));
thirdLD = Transformations.switchMap(firstLD, first ->
daoRepository.getAvgGradeRouteInPeriod(userId, first.getDateCreated(),first.getDateExpires()));
fourthLD = Transformations.switchMap(firstLD, first ->
daoRepository.getAvgGradeRouteInPeriod(userId, first.getDateCreated(),first.getDateExpires()));
}

Related

Passing query parameters to LiveData/Viewmodel in Android app

Since I'm new to programming Android apps I followed a tutorial on how to use the Android Architecture Components and Firebase for implementing the MVVM (using LiveData, ViewModel, etc.).
The tutorial I followed can be found here:
Part 1
Part 2
Part 3
I'm now left with what I think is a decent implementation of the MVVM, but I can not wrap my head around how I am supposed to pass query parameters to it. Right now I need to hardcode the ID of the document I want to retrieve:
public class AlarmDAO {
private FirebaseFirestore firebaseFirestore = FirebaseFirestore.getInstance();
public AlarmLiveData getFirestoreLiveData() {
DocumentReference documentReference = firebaseFirestore.collection(Collection.ALARMS.name).document("5RxJNuNyhDJlz49wpBkw");
return new AlarmLiveData(documentReference);
}
}
That then gets called by a class extending ViewModel.
public class AlarmViewModel extends ViewModel {
private AlarmDAO DAO = new AlarmDAO();
private AlarmLiveData liveData = null;
public LiveData<Alarm> getAlarmLiveData() {
liveData = DAO.getFirestoreLiveData();
return liveData;
}
public LiveData<Alarm> getAlarm() {
return liveData.alarm;
}
}
And then I observe that data in my activity:
model.getAlarmLiveData().observe(this, Observable -> {});
model.getAlarm().observe(this, alarm -> {
if (alarm != null) {
alarmTextView.setText(alarm.getTest());
else {
Log.d(TAG, "Waiting for data");
}
});
My problem is that I do not see a way of querying for a specific alarm. For instance model.getAlarm("someId"). I am under the impression that it should be done in the DAO and/or the ViewModel, but I can't figure out how. Another thing I do not understand is why I need to observe both model.getAlarmLiveData() and model.getAlarm() in my activity, as using only one does not work. The answer to both of those questions is most likely very simple, but thus far I haven't been able to figure it out.
For completeness: the Alarm class is nothing besides a getter and setter for two strings, and the AlarmLiveData class is below.
public class AlarmLiveData extends LiveData<Alarm> implements EventListener<DocumentSnapshot> {
private static final String TAG = AlarmLiveData.class.getSimpleName();
private Alarm alarmTemp = new Alarm();
private DocumentReference documentReference;
private ListenerRegistration listenerRegistration = () -> {};
public MutableLiveData<Alarm> alarm = new MutableLiveData<>();
public AlarmLiveData(DocumentReference documentReference) {
this.documentReference = documentReference;
}
#Override
protected void onActive() {
listenerRegistration = documentReference.addSnapshotListener(this);
super.onActive();
}
#Override
protected void onInactive() {
listenerRegistration.remove();
super.onInactive();
}
#Override
public void onEvent(#Nullable DocumentSnapshot documentSnapshot, #Nullable FirebaseFirestoreException e) {
if (documentSnapshot != null && documentSnapshot.exists()) {
alarmTemp = new Alarm();
alarmTemp.setId(documentSnapshot.getId());
alarmTemp.setTest(documentSnapshot.get("test").toString());
alarm.setValue(alarmTemp);
} else {
Log.d(TAG, "ERROR");
}
}
}
Thank you for reading, I'm looking forward to the answer(s)!
The reason you have to use both model.getAlarmLiveData() and model.getAlarm() looks to be that your AlarmLiveData class extends LiveData but sets a value for the contained MutableLiveData member variable instead of setting its own class value.
Inside your AlarmLiveData class:
// Comment out/Remove your 'public MutableLiveData<alarm> alarm' member variable from the top.
// You're going to want to set the value of the AlarmLiveData class itself instead.
// ...
// Then inside of your onEvent callback
#Override
public void onEvent(#Nullable DocumentSnapshot documentSnapshot, #Nullable FirebaseFirestoreException e) {
if (documentSnapshot != null && documentSnapshot.exists()) {
alarmTemp = new Alarm();
alarmTemp.setId(documentSnapshot.getId());
alarmTemp.setTest(documentSnapshot.get("test").toString());
// Set the value for the AlarmLiveData class directly
setValue(alarmTemp);
} else {
Log.d(TAG, "ERROR");
}
}
I'm not sure why you're creating a DAO class and I would most likely move that code directly into the AlarmViewModel class.
But, here is how you can alter your current DAO class if you don't want to remove it:
// Pass in the document id you want to create a document reference for
public AlarmLiveData getFirestoreLiveData(String documentId) {
DocumentReference documentReference = firebaseFirestore.collection(Collection.ALARMS.name).document(documentId);
return new AlarmLiveData(documentReference);
}
Your AlarmViewModel class would look something like this:
public class AlarmViewModel extends ViewModel {
private AlarmDAO DAO = new AlarmDAO();
private AlarmLiveData liveData = null;
// Make sure to take in the document id so you can create the corresponding LiveData
public LiveData<Alarm> getAlarmLiveData(String documentId) {
// Only create a new LiveData instance if the current one is null.
// This is helpful if you intend to use this as a Shared ViewModel.
if(liveData == null){
liveData = DAO.getFirestoreLiveData(documentId);
}
return liveData;
}
}
Finally, in your Activity:
// Pass in the document id and observe the ViewModel
model.getAlarmLiveData("MY_DOCUMENT_ID").observe(this, alarm -> {
if (alarm != null) {
alarmTextView.setText(alarm.getTest());
}else{
Log.d(TAG, "Waiting for data");
}
});

MVVM - response List does not contain any values

I'm just trying to learn mvvm, and I faced some issue - My List which should contain response from API are empty. I'm not sure why it is. Here's some code:
MainActivity
mViewModel = new ViewModelProvider(this).get(ViewModel.class);
mViewModel.getData().observe(this, new Observer<List<Model>() {
#Override
public void onChanged(List<Model> list) {
if (data.size() > 0) {
data.clear();
}
if (list != null) {
data.addAll(list);
Log.i(TAG, "onChanged: " + data.size());
}
}
});
ViewModel
private Repository mRepository;
private MutableLiveData<List<Model> liveData;
public ViewModel(#NonNull Application application) {
super(application);
mRepository = Repository.getInstance();
liveData = mRepository.getData();
}
public MutableLiveData<List<Model> getData(){
return liveData;
}
Repository
public MutableLiveData<List<Model>> getData(){
MutableLiveData<List<Model> mLiveData = new MutableLiveData<>();
mApiCall.callApi()
.enqueue(new Callback<List<Model>() {
#Override
public void onResponse(Call<List<Model> call, Response<List<Model> response) {
mLiveData.setValue(response.body());
}
#Override
public void onFailure(Call<List<Model> call, Throwable t) {
t.getMessage();
}
});
return mLiveData;
}
in function getData() you are returning a live data and in this line in your view model :
liveData = mRepository.getData();
you are assigning it to liveDate which is a mutable live data created in your view model and the problem is here.
when this assignment is happened the observer in the liveData variable in view model will be removed and that's why we should use switchMap like this :
private var result : LiveData<List<Response>> = MutableLiveData()
result : LiveData<Response> = Transformations.map(mRepository.getData()){
it
}
and now all you need to do is to observe on repo in your view like this :
viewmodel.result.observe(this, Observer{ list ->
// to do with the result
})
response.body() will provide with class APIResponse. But what you need as response is List.
To get the expected response as List, try response.body().getMetadata().getResults()
It could be either repository initialisation as well. Do try creating
public void init() {
mRepository = Repository.getInstance();
liveData = mRepository.getData();
}
use viewModel.init();
Model class properties should match with response JSON fields to get the model objects from the response.
Debug on the response.body() and see the data coming. if the model is empty then there would be a mismatch on the fields.

RxJava and list of `Single` observable

I'm new in RxJava so I still have many doubts.
I'm creating this:
#Override
public Single<Result> saveUser(final User user) {
return Single.create(new Single.OnSubscribe<Result>() {
#Override
public void call(final SingleSubscriber<? super Result> singleSubscriber) {
if(user.isValid()){
save(user);
//Set result ok
singleSubscriber.onSuccess(result);
} else {
//This sets an error.
singleSubscriber.onError(error);
}
}
});
}
Depending of the success of the operation, the Single emits it's data and who is subscribed receives the data.
The problem now is that at some point I need to store a list of users. Something like:
public void saveUsers(List<User> listOfUsers){
for (User user : listOfUsers) {
saveUser(user);
}
}
How can I create an Single so I can be subscribed to the initial Single
I would rather create flatmap out of observable list.
public void saveUsers(List<User> listOfUsers){
Observable.from(listOfUsers).flatMap((User user)->{
if(user.isValid()){
save(user);
//Set result ok
return Observable.just(result);
}else
return Observable.error(new RuntimeException("..."));
}).retry(2);
}
If you make your saveUsers method blocking, call Observable#toBlocking.

Setting Singleton property value in Firebase Listener

I'm currently testing out Firebase along with a Singleton model I plan to use to access during the lifecycle of the whole app. I'm now stuck with something that seems really trivial but I can't figure it out for the life of me. I have a sample of the model I use: Bookmarks in firebase.
public class BookSingleton {
private static BookSingleton model;
private ArrayList<BookMark> bookmarks = new ArrayList<BookMark>();
public static BookSingleton getModel()
{
if (model == null)
{
throw new IllegalStateException("The model has not been initialised yet.");
}
return model;
}
public ArrayList<Bookmark> theBookmarkList()
{
return this.bookmarks;
}
public void setBookmarks(ArrayList<Bookmark> bookmarks){
this.bookmarks = bookmarks;
}
public void loadModelWithDataFromFirebase(){
Firebase db = new Firebase(//url);
Firebase bookmarksRef = fb.child(//access correct child);
final ArrayList<Bookmark> loadedBookmarks = new ArrayList<Bookmark>();
bookmarksRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
//getting all properties from firebase...
Bookmark bookmark = new Bookmark(//properties here);
loadedBookmarks.add(bookmark);
}
}
//bookmarks still exist here at this point
setBookmarks(loadedBookmarks);
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
//by now loadedBookmarks is empty
//this is probably the issue?
//even without this line bookmarks is still not set in mainactivity
setBookmarks(loadedBookmarks);
}
Now when I start the mainActivity with the instance of the Singleton set I get a null error because clearly the function I wrote to load the model data from firebase sets nothing.
Something like this:
MainActivity
public class MainActivity extends AppCompatActivity {
private BookSingleton theModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the model
theModel = BookSingleton.getModel(this);
//manually setting this works
// ArrayList<Book> bookSamples = new ArrayList<Book>;
// bookSamples.add(aBookSample);
theModel.loadModelWithSampleData(bookSamples);
//should have set the singleton model property Bookmarks to the results from firebase
theModel.loadModelWithDataFromFirebase();
//returns 0
Log.d(TAG, "" + theModel.theBookmarkList().size());
setContentView(R.layout.activity_main);
//......rest of code
How can I make this work?
Firebase loads and synchronizes data asynchronously. So your loadModelWithDataFromFirebase() doesn't wait for the loading to finish, it just starts loading the data from the database. By the time your loadModelWithDataFromFirebase() function returns, the loading hasn't finished yet.
You can easily test this for yourself with some well-placed log statements:
public void loadModelWithDataFromFirebase(){
Firebase db = new Firebase(//url);
Firebase bookmarksRef = fb.child(//access correct child);
Log.v("Async101", "Start loading bookmarks");
final ArrayList<Bookmark> loadedBookmarks = new ArrayList<Bookmark>();
bookmarksRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Log.v("Async101", "Done loading bookmarks");
//getting all properties from firebase...
Bookmark bookmark = new Bookmark(//properties here);
loadedBookmarks.add(bookmark);
}
#Override
public void onCancelled(FirebaseError error) { throw error.toException(); }
});
Log.v("Async101", "Returning loaded bookmarks");
setBookmarks(loadedBookmarks);
}
Contrary to what you likely expect, the order of the log statements will be:
Start loading bookmarks
Returning loaded bookmarks
Done loading bookmarks
You have two choice for dealing with the asynchronous nature of this loading:
squash the asynchronous bug (usually accompanied by muttering of phrases like: "it was a mistake, these people don't know what they're doing")
embrace the asynchronous beast (usually accompanied by quite some hours of cursing, but after a while by peace and better behaved applications)
Take the blue pill - make the asynchronous call behave synchronously
If you feel like picking the first option, a well placed synchronization primitive will do the trick:
public void loadModelWithDataFromFirebase() throws InterruptedException {
Firebase db = new Firebase(//url);
Firebase bookmarksRef = fb.child(//access correct child);
Semaphore semaphore = new Semaphore(0);
final ArrayList<Bookmark> loadedBookmarks = new ArrayList<Bookmark>();
bookmarksRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Bookmark bookmark = new Bookmark(//properties here);
loadedBookmarks.add(bookmark);
semaphore.release();
}
#Override
public void onCancelled(FirebaseError error) { throw error.toException(); }
});
semaphore.acquire();
setBookmarks(loadedBookmarks);
}
Update (20160303): when I just tested this on Android, it blocked my app. It works on a regular JVM fine, but Android is more finicky when it comes to threading. Feel free to try and make it work... or
Take the red pill - deal with the asynchronous nature of data synchronization in Firebase
If you instead choose to embrace asynchronous programming, you should rethink your application's logic.
You currently have "First load the bookmarks. Then load the sample data. And then load even more."
With an asynchronous loading model, you should think like "Whenever the bookmarks have loaded, I want to load the sample data. Whenever the sample data has loaded, I want to load even more."
The bonus of thinking this way is that it also works when the data may be constantly changing and thus synchronized multiple times: "Whenever the bookmarks change, I want to also load the sample data. Whenever the sample data changes, I want to load even more."
In code, this leads to nested calls or event chains:
public void synchronizeBookmarks(){
Firebase db = new Firebase(//url);
Firebase bookmarksRef = fb.child(//access correct child);
final ArrayList<Bookmark> loadedBookmarks = new ArrayList<Bookmark>();
bookmarksRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Bookmark bookmark = new Bookmark(//properties here);
loadedBookmarks.add(bookmark);
setBookmarks(loadedBookmarks);
loadSampleData();
}
#Override
public void onCancelled(FirebaseError error) { throw error.toException(); }
});
}
In the above code we don't just wait for a single value event, we instead deal with all of them. This means that whenever the bookmarks are changed, the onDataChange is executed and we (re)load the sample data (or whatever other action fits your application's needs).
To make the code more reusable, you may want to define your own callback interface, instead of calling the precise code in onDataChange. Have a look at this answer for a good example of that.
TL;DR: Embrace Firebase Asynchronicity
As I mentioned in another post, you can deal with the asynchronous nature of Firebase using promises. It would be like this:
public Task<List<Data>> synchronizeBookmarks(List<Bookmark> bookmarks) {
return Tasks.<Void>forResult(null)
.then(new GetBook())
.then(new AppendBookmark(bookmarks))
.then(new LoadData())
}
public void synchronizeBookmarkWithListener() {
synchronizeBookmarks()
.addOnSuccessListener(this)
.addOnFailureListener(this);
}
com.google.android.gms.tasks
Google API for Android provides a task framework (just like Parse did with Bolts), which is similar to JavaScript promises concept.
First you create a Task for downloading the bookmark from Firebase:
class GetBook implements Continuation<Void, Task<Bookmark>> {
#Override
public Task<Bookmark> then(Task<Void> task) {
TaskCompletionSource<Bookmark> tcs = new TaskCompletionSource();
Firebase db = new Firebase("url");
Firebase bookmarksRef = db.child("//access correct child");
bookmarksRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
tcs.setResult(dataSnapshot.getValue(Bookmark.class));
}
});
tcs.getTask();
}
}
Now that you got the idea, supose that setBookmarks and loadSampleData are also asynchronous. You also can create them as Continuation tasks (just like the previous one) that will run in sequence:
class AppendBookmark(List<Bookmark> bookmarks) implements
Continuation<List<Bookmark>, Task<Bookmark> {
final List<Bookmark> bookmarks;
LoadBookmarks(List<Bookmark> bookmarks) {
this.bookmark = bookmark;
}
#Override
Task<List<Bookmark>> then(Task<Bookmark> task) {
TaskCompletionSource<List<Bookmark>> tcs = new TaskCompletionSource();
bookmarks.add(task.getResult());
tcs.setResult(this.bookmarks);
return tcs.getTask();
}
}
class LoadSampleData implements Continuation<List<Bookmark>, List<Data>> {
#Override
public Task<List<Data>> then(Task<List<Bookmark>> task) {
// ...
}
}
You have to initialize your Singleton when the class is loaded.
Put this on your code:
private static BookSingleton model = new BookSingleton();
private BookSingleton() {
}
public static BookSingleton getModel() { return model == null ? new BookSingleton() : model;
}

RxJava split one Observable to two subObservables

I'm totally new to RxJava and I've spent all day understanding it, I'm tying to think how to solve this problem:
I have one object, fetched by Retrofit, it contains two ArrayLists, I have to process every ArrayList differently. Currently it looks like:
apiService.getUser(token).enqueue(new Callback<User>() {
#Override
public void onResponse(Response<User> response) {
final User user = response.body();
for (Skill s : user.getSkills()) {
// process here first ArrayList
}
for (OrganizerAction o : user.getOrganizerActions()) {
// process here second ArrayList
}
}
#Override
public void onFailure(Throwable t) {
t.printStackTrace();
}
});
UPDATE:
public class User {
// fields
#SerializedName("organizer_actions")
#Expose
private List<OrganizerAction> mOrganizerActions;
#SerializedName("skills")
#Expose
private List<Skill> mSkills;
public List<OrganizerAction> getOrganizerActions() {
return mOrganizerActions;
}
public List<Skill> getSkills() {
return mSkills;
}
}
Thanks,
Anton
This answer is for Retrofit 2.0.0-beta, which is what you appear to be using. Also, you didn't give your POJO or service definitions, so going to use a general GitHub API example as a guide, modify to match your specify data.
First step is to convert your service definition to use Observable instead of Call.
public interface GitHubService {
#GET("/users/{user}")
Observable<User> getUser(#Path("user") String user);
}
Where User is
public class User {
public String login;
public int id;
}
Next, add a custom call adapter with to your retrofit builder with addCallAdapterFactory --
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
Get your service in the usual way --
GitHubService gitHubService = retrofit.create(GitHubService.class);
Next, get your observable and call cache on it to create an Observable that will replay the result. From that Observable, you can subscribe multiple times, in your case, you can subscribe twice. Once for each type of data you are interested in, and use the map function to transform from the User object to your specific fields. map allows you to apply function to the data in the observable. See the docs for more details. In this example, we will make two streams. One each for the id and login fields.
Observable<User> getUserResult = gitHubService.getUser("octocat").cache(1);
getUserResult.map(new Func1<User, Integer>() {
#Override
public Integer call(User user) {
return user.id;
}
}).subscribe(new Action1<Integer>() {
#Override
public void call(Integer id) {
Log.d("Stream 1", "id = " + id);
}
});
getUserResult.map(new Func1<User, String>() {
#Override
public String call(User user) {
return user.login;
}
}).subscribe(new Action1<String>() {
#Override
public void call(String login) {
Log.d("Stream 2", "login = " + login);
}
});
Finally, make sure your gradle file has the needed dependencies,
compile 'io.reactivex:rxjava:1.0.14'
compile 'com.squareup.retrofit:retrofit:2.0.0-beta1'
compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta1'
compile 'com.squareup.retrofit:converter-gson:2.0.0-beta1'
And, not directly related to your question, but if you are going to doing RxJava in Android, I recommend you checkout Retrolambda if you have not already. The above map and subscribe code, and Rx code in general, is more succinct with lambdas.
getUserResult.map(user -> user.id).subscribe(
id -> { Log.d("Stream 1", "id = " + id); }
);
getUserResult.map(user -> user.login).subscribe(
login -> { Log.d("Stream 2", "login = " + login); }
);

Categories

Resources