Refresh data with RxJava and Retrofit - android

I'm using Retrofit with RxJava2 to obtain some data from a Rest API. I want to use a SwipeRefreshLayout to update the view and I'm using a ViewModel to handle the API call, so I want to implement a method in there to refresh the data programmatically.
I want to obtain something like this https://stackoverflow.com/a/34276564/6787552 but instead of having a periodic trigger, I want to do that programmatically when the user pull to refresh.
That's the ViewModel:
public class DashboardViewModel extends ViewModel {
public final Single<Dashboard> dashboard;
public DashboardViewModel() {
dashboard = Api.getDashboard();
refresh();
}
public void refresh() {
// Refresh data
}
}
And in the DashboardFragment:
#Override
public View onCreateView(...) {
...
viewModel.dashboard
.observeOn(AndroidSchedulers.mainThread())
.subscribe(dashboard -> {
binding.setDashboard(dashboard);
binding.swipeRefreshLayout.setRefreshing(false);
});
binding.swipeRefreshLayout.setOnRefreshListener(() -> viewModel.refresh());
...
}
Thank you in advance!
EDIT:
That's what I ended up doing:
public class DashboardViewModel extends ViewModel {
private final BehaviorSubject<Dashboard> dashboard;
public DashboardViewModel() {
dashboard = BehaviorSubject.createDefault(Api.getDashboard());
}
public void refresh() {
// I use a Object because null values are not supported
dashboard.onNext(Api.getDashboard());
}
public Observable<Dashboard> getDashboard(){
return dashboard;
}
}
And then in the DashboardFragment just subscribe to viewModel.getDashbaord()

I'm not 100% sure that I understood what you want to do but if I got the question right, you can do something like this:
put a subject inside the model (probably a BehaviorSubject?)
expose it as an observable to the
view and subscribe to it (instead of subscribing to the single)
in the model, when you
receive a new call to refresh() from the ui, do something like
subject.onNext(Api.getDashboard())
in this way, each call to refresh will cause the emission of a new dashboard, and that will be properly bound by the subscription in the view.

Related

Callback from viewmodel to ui without data

How can I have my viewmodel call a function in my activity or fragment without using a callback and where no data is actually sent. LiveData is used to send data from the viewmodel to the view but I have no data. I just want to notify the ui about something. Should this be done using RxJava or is that overkill?
LiveData is just fine, here is what I did recently (derived from https://medium.com/androiddevelopers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150)
first create a class
public class OneTimeEvent {
private Boolean received;
public OneTimeEvent() {
received = false;
}
public Boolean receive () {
if (!received) {
received = true;
return true;
}
return false;
}
}
then in your ViewModel expose your event
private MediatorLiveData<OneTimeEvent> eventListener = new MediatorLiveData<>();
public LiveData<OneTimeEvent> onEvent() {
return eventListener;
}
now you have to trigger the event somewhere in your ViewModel (like something else is finished)
eventListener.setValue(new OneTimeEvent()); //if its a background thread or callback use postValue!
that's it, now you can observe onEvent() in any activity or fragment you like
ViewModel.onEvent().observe(this, new Observer<OneTimeEvent>() {
#Override
public void onChanged(OneTimeEvent oneTimeEvent) {
if (oneTimeEvent.receive()){
// do something on event
}
}
});
Hope this helps, it acts just like an EventListener, only that you can Listen from Multiple Locations simultaneously, and each event will only be fired once, eg if the observer is reattached somewhere else.

View Model keeps creating instance of Live Data

I created the instance of View Model in onCreate method of an activity.
ticketViewModel = ViewModelProviders.of(this).get(TicketViewModel.class);
Then i have a method, AddTicket, which uses viewModel to hit a service and on response from viewModel i dismiss loading animation.
public void addTicket(View view){
ticketViewModel.AddTicket(id).observe(this, response ->{
dismissLoadingAnimation();
}
Now after adding a ticket, user can repress the Add Ticket button, and the addTicket() method will be called again.
but this time observer defined in ViewModel gets called 2 times, resulting in 2 network calls, and 2 dismissLoadingAnimation execution.
And if i keep pressing addTicket button, the number of executing observer defined inside ViewModel keep increases.
This is my View Model code.
public class TicketViewModel extends AndroidViewModel implements IServiceResponse {
MutableLiveData<String> mObservableResponse = new MutableLiveData<String>();
public MutableLiveData AddTicket(String id){
JsonObject jsonObject= new JsonObject();
jsonObject.addProperty("id", id);
NetworkUtility networkUtility= new NetworkUtility(this, ADD_TICKET);
networkUtility.hitService(URL, jsonObject, RequestMethods.POST);
return mObservableResponse;
}
#Override
public void onServiceResponse(String response, String callType){
if(serviceTag.equalsIgnoreCase(ADD_TICKET)){
mObservableResponse.setValue("success");
}
}
}
The number of executing observer defined inside ViewModel keep increases becasue with every click You're registering new observers. You're not supposed to register observer with onClick() method.
You should do it in onCreate() method of your Activity or in onViewCreated method of your fragment. If You'll do that, there won't be a need to removeObserver when You'll finish work. Lifecycle mechanism will cover it for you.
But if you really want answer for you question, this is how you can do it
yourViewModel.yourList.removeObservers(this)
Passing this means passing your Activity, or there is a second way:
yourViewModel.yourList.removeObserver(observer)
val observer = object : Observer<YourObject> {
override fun onChanged(t: YourObject?) {
//todo
}
}
The purpose of Viewmodel is to expose observables (Livedata)
The purpose of View(Activity/Fragment) is to get these observables and observe them
Whenever there is a change in these observables(Livedata) the change is automatically posted to the active subscribed owners(Activity/Fragment), so you need not remove them in onPause/onStop as it is not mandatory
I can suggest few changes to your code to solve the problem with the above mentioned pointers
ViewModel
public class TicketViewModel extends AndroidViewModel implements IServiceResponse {
MutableLiveData<String> mObservableResponse = new MutableLiveData<String>();
public LiveData<String> getResponseLiveData(){
return mObservableResponse;
}
public void AddTicket(String id){
JsonObject jsonObject= new JsonObject();
jsonObject.addProperty("id", id);
NetworkUtility networkUtility= new NetworkUtility(this, ADD_TICKET);
networkUtility.hitService(URL, jsonObject, RequestMethods.POST);
}
#Override
public void onServiceResponse(String response, String callType){
if(serviceTag.equalsIgnoreCase(ADD_TICKET)){
mObservableResponse.setValue("success");
}
}
}
View
onCreate(){
ticketViewModel = ViewModelProviders.of(this).get(TicketViewModel.class);
observeForResponse();
}
private void observeForResponse(){
ticketViewModel.getResponseLiveData().observe(this, response ->{
//do what has to be updated in UI
}
}
public void addTicket(View view){
ticketViewModel.AddTicket(id);
}
Hope this is of help :)
You only need to call the observe once, I prefer to do it in onResume and then call removeObserver in onPause:
Adds the given observer to the observers list
You keep adding listeners to the data so you get multiple callbacks.
Edit:
I took an existing code sample of mine for a Fragment and renamed everything (I hope), there's no example here for setting the data into the ViewModel but it should be ticketViewModel.AddTicket(id); in your case.
public class ListFragment extends Fragment {
private MyViewModel viewModel;
private MyRecyclerViewAdapter recyclerViewAdapter;
private Observer<List<DatabaseObject>> dataObserver;
private RecyclerView recyclerView;
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_layout, container, false);
initRecyclerView(rootView, getContext());
initObservers();
return rootView;
}
private void initRecyclerView(View rootView, Context context) {
recyclerViewAdapter = new MyRecyclerViewAdapter(context);
recyclerView = rootView.findViewById(R.id.recycler_view);
recyclerView.setAdapter(recyclerViewAdapter);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(context);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.addItemDecoration(new DividerNoLastItemDecoration());
}
private void initObservers() {
dataObserver = new Observer<List<DatabaseObject>>() {
#Override
public void onChanged(#Nullable final List<DatabaseObject> data) {
recyclerViewAdapter.setData(data);
}
};
}
#Override
public void onResume() {
super.onResume();
initViewModel();
}
private void initViewModel() {
FragmentActivity activity = getActivity();
if (activity != null) {
viewModel = ViewModelProviders.of(activity).get(MyViewModel.class);
viewModel.getData().observe(activity, dataObserver);
}
}
#Override
public void onPause() {
super.onPause();
if (viewModel != null) {
viewModel.getData().removeObserver(dataObserver);
viewModel = null;
}
}
}
I had similar problem. You could try to use SingleLiveEvent
Or, in my, more complicated case, i had to use custom observer. It would looks like this:
public class CustomObserver implements Observer<YourType> {
private MyViewModel mViewModel;
public CustomObserver (){}
public void setViewModel(MyViewModel model) {
mViewModel = model;
}
#Override
public void onChanged(#Nullable YourType object) {
mViewModel.AddTicket(id).removeObserver(this); // removing previous
mmViewModel.refreshTickets(); // refreshing Data/UI
// ... do the job here
// in your case it`s: dismissLoadingAnimation();
}
}
And using it like:
public void addTicket(View view){
ticketViewModel.AddTicket(id).observe(this, myCustomObserver);
}
If you are willing to do some changes, i think we can handle it in much cleaner way
LiveData is meant to be used to contain a property value of a view
In ViewModel
public class TicketViewModel extends AndroidViewModel implements IServiceResponse {
private MutableLiveData<Boolean> showLoadingAnimationLiveData = new MutableLiveData<String>();
public LiveData<Boolean> getShowLoadingAnimationLiveData(){
return showLoadingAnimationLiveData;
}
public void addTicket(String id){
JsonObject jsonObject= new JsonObject();
jsonObject.addProperty("id", id);
NetworkUtility networkUtility= new NetworkUtility(this, ADD_TICKET);
networkUtility.hitService(URL, jsonObject, RequestMethods.POST);
showLoadingAnimationLiveData.setValue(true);
}
#Override
public void onServiceResponse(String response, String callType){
if(serviceTag.equalsIgnoreCase(ADD_TICKET)){
showLoadingAnimationLiveData.setValue(false);
}
}
}
In 'onCreate' of your Activity/Fragment
ticketViewModel.getShowLoadingAnimationLiveData().observe(this,showLoadingAnimation->{
if(showLoadingAnimation != null && showLoadingAnimation){
startLoadingAnimation();
}else{
dismissLoadingAnimation();
}
})
The main concept is to divide the responsibilities,
Activity/Fragment doesn't need to know which process is going on, they only need to know what are the current properties/state of there child views.
We need to maintain a LiveData in ViewModels for each changing property/state depending on Views. ViewModel needs to handle the view states depending on whats happening.
Only responsibility the Activity/Fragment has about a process is to trigger it and forget and ViewModel needs handle everything(like informing Repositories to do the work and changing View Properties).
In your Case,
'addTicket' is a process about which Activity/Fragment doesn't need to know about there status.
The only responsibility of Activity/Fragment about that process is to trigger it.
ViewModel is one who needs to analyze the state of process(in-progress/success/failed) and give appropriate values to the LiveDatas to inform the respective Views

how to be notified in view in Databinding library?

Hi my problem is that my activity listening for viewmodel field changes but callback not get called!
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
binding.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
#Override
public void onPropertyChanged(Observable observable, int i) {
Log.d(getClass().getSimpleName(), "changed");
}
});
User user = new User("user");
binding.setUser(user);
user.setName("newUser");
}
}
and my viewModel:
public class User extends BaseObservable {
public String name;
public User(String name) {
this.name = name;
}
#Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
}
UI updated but callback not get called. I want a callback plus UI update.
I want to know data binding library works like this! there is Libraries like Rxjava for this but I don't like to import it.
When you do this:
ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
binding.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
#Override
public void onPropertyChanged(Observable observable, int i) {
Log.d(getClass().getSimpleName(), "changed");
}
});
User user = new User("user");
binding.setUser(user);
user.setName("newUser");
You should receive one notification on the callback you provided and it should be received when you call binding.setUser(user). Look for ActivityMainBinding.java and you will see the code generated for the class in your app's build folder. In the setUser() method, you'll see the call to notifyPropertyChanged(BR.user) that will call your listener.
The problem you're seeing is that the data change you want (name) is happening on the User and not on the Binding. In order to get notifications on changes to the user, you must add a lister like this:
ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
User user = new User("user");
user.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
#Override
public void onPropertyChanged(Observable observable, int i) {
Log.d(getClass().getSimpleName(), "changed");
}
});
binding.setUser(user);
user.setName("newUser");
Anything implementing the Observable interface will support any number of listeners, so you can feel free to listen for events.
Are you expecting to get the callback when you called setUser ? Because that's not the way it works. setUser explicitly sets the data model views on the bound views. The property change callback would be invoked if you change the value of any of the data model properties beyond this point. Change your code to this and i guess you would get the callback -
User user = new User("sa");
binding.setUser(user);
user.setName("Johny Depp"); //you should receive the callback beyond this and the UI would update too
Edit -
A class implementing the Observable interface will allow the binding to attach a single listener to a bound object to listen for changes of all properties on that object.
The Observable interface has a mechanism to add and remove listeners, but notifying is up to the developer. To make development easier, a base class, BaseObservable, was created to implement the listener registration mechanism. The data class implementer is still responsible for notifying when the properties change. This is done by assigning a Bindable annotation to the getter and notifying in the setter.

MVP for Activity with multiple Fragments

I have an Activity with two Fragments in it.
The activity (MainActivity) retrieves data from an open weather api. I have implemented MVP for this in which:
Model contains all the response objects from the API
View is the Activity
Presenter contains MainPresenter, MainPresenterImpl, MainView, GetDataInteractor and GetDataInteractorImpl.
So, the activity gets the data from the web service. Both fragments will display data from the data retrieved in the activity.
What is the best practice using MVP in this situation? I know how to pass data between fragments <-> activity via interface/callbacks, my question is does this behaviour change when implementing MVP?
The activity/fragments should be considered as just the view in the MVP model. This means that they should just show data and receive user interactions.
It is ok to communicate activity and fragments via interface/callbacks.
But, it is not an activity/fragment responsibility to call the API services.
The presenter should be responsible to call the api services.
So, the presenter should expose a method like loadXXX, internally it would make the call to the service. When the response is received, the presenter should call view.showXXX with the results of the service. The activity/fragment should call this loadXXX method and implement the showXXX.
Usually, the presenter is created or injected into the activity/fragment.
The activity/fragment has to implement an interface exposed by the presenter, and the presenter holds a weak reference of this interface, so that it can callback.
When the user interacts with the screen, for example, an onClick on a button, the activity/fragment calls the corresponding method in the presenter, e.g. presenter.loadUserDetails() the presenter tells the view to show as loading, e.g. view.showAsLoading() because it has to do its stuff: maybe validating something or loading data from an api service and finally callback with the results to the view, e.g. view.showUserDetails(userDetails).
To summarize, an example, in code of the various parts of MVP:
Activity/Fragment represents just the View of MVP:
public class MyActivity extends AppCompatActivity implements MyPresenter.View {
private MyPresenter mPresenter;
public onCreate() {
...
mPresenter = new MyPresenter(this); // Or inject it and then set the view.
}
public void onClick(View v) {
mPresenter.loadXXX(param1, param2);
}
// MyPresenter.View methods
public void showAsLoading() {
...
}
public void showUserDetails(UserDetails userDetails) {
...
}
}
Model:
public class UserDetails {
...
}
Presenter:
public class MyPresenter {
private WeakReference<MyPresenter.View> mWeakView;
public MyPresenter(MyPresenter.View view) {
mWeakView = new WeakReference(view);
}
public void loadXXX(String param1, String param2) {
MyPresenter.View view = mWeakView.get();
if (view != null) {
view.showAsLoading();
// Do stuff, e.g. make the Api call and finally call view.showUserDetails(userDetails);
}
}
interface View {
void showAsLoading();
void showUserDetails(UserDetails userDetails);
}
}

RxJava as event bus?

I'm start learning RxJava and I like it so far. I have a fragment that communicate with an activity on button click (to replace the current fragment with a new fragment). Google recommends interface for fragments to communicate up to the activity but it's too verbose, I tried to use broadcast receiver which works generally but it had drawbacks.
Since I'm learning RxJava I wonder if it's a good option to communicate from fragments to activities (or fragment to fragment)?. If so, whats the best way to use RxJava for this type of communication?. Do I need to make event bus like this one and if that's the case should I make a single instance of the bus and use it globally (with subjects)?
Yes and it's pretty amazing after you learn how to do it. Consider the following singleton class:
public class UsernameModel {
private static UsernameModel instance;
private PublishSubject<String> subject = PublishSubject.create();
public static UsernameModel instanceOf() {
if (instance == null) {
instance = new UsernameModel();
}
return instance;
}
/**
* Pass a String down to event listeners.
*/
public void setString(String string) {
subject.onNext(string);
}
/**
* Subscribe to this Observable. On event, do something e.g. replace a fragment
*/
public Observable<String> getStringObservable() {
return subject;
}
}
In your Activity be ready to receive events (e.g. have it in the onCreate):
UsernameModel usernameModel = UsernameModel.instanceOf();
//be sure to unsubscribe somewhere when activity is "dying" e.g. onDestroy
subscription = usernameModel.getStringObservable()
.subscribe(s -> {
// Do on new string event e.g. replace fragment here
}, throwable -> {
// Normally no error will happen here based on this example.
});
In you Fragment pass down the event when it occurs:
UsernameModel.instanceOf().setString("Nick");
Your activity then will do something.
Tip 1: Change the String with any object type you like.
Tip 2: It works also great if you have Dependency injection.
Update:
I wrote a more lengthy article
Currently I think my preferred approach to this question is this to:
1.) Instead of one global bus that handles everything throughout the app (and consequently gets quite unwieldy) use "local" buses for clearly defined purposes and only plug them in where you need them.
For example you might have:
One bus for sending data between your Activitys and your ApiService.
One bus for communicating between several Fragments in an Activity.
One bus that sends the currently selected app theme color to all Activitys so that they can tint all icons accordingly.
2.) Use Dagger (or maybe AndroidAnnotations if you prefer that) to make the wiring-everything-together a bit less painful (and to also avoid lots of static instances). This also makes it easier to, e. g. have a single component that deals only with storing and reading the login status in the SharedPreferences - this component could then also be wired directly to your ApiService to provide the session token for all requests.
3.) Feel free to use Subjects internally but "cast" them to Observable before handing them out to the public by calling return subject.asObservable(). This prevents other classes from pushing values into the Subject where they shouldn't be allowed to.
Define events
public class Trigger {
public Trigger() {
}
public static class Increment {
}
public static class Decrement {
}
public static class Reset {
}
}
Event controller
public class RxTrigger {
private PublishSubject<Object> mRxTrigger = PublishSubject.create();
public RxTrigger() {
// required
}
public void send(Object o) {
mRxTrigger.onNext(o);
}
public Observable<Object> toObservable() {
return mRxTrigger;
}
// check for available events
public boolean hasObservers() {
return mRxTrigger.hasObservers();
}
}
Application.class
public class App extends Application {
private RxTrigger rxTrigger;
public App getApp() {
return (App) getApplicationContext();
}
#Override
public void onCreate() {
super.onCreate();
rxTrigger = new RxTrigger();
}
public RxTrigger reactiveTrigger() {
return rxTrigger;
}
}
Register event listener wherever required
MyApplication mApp = (App) getApplicationContext();
mApp
.reactiveTrigger() // singleton object of trigger
.toObservable()
.subscribeOn(Schedulers.io()) // push to io thread
.observeOn(AndroidSchedulers.mainThread()) // listen calls on main thread
.subscribe(object -> { //receive events here
if (object instanceof Trigger.Increment) {
fabCounter.setText(String.valueOf(Integer.parseInt(fabCounter.getText().toString()) + 1));
} else if (object instanceof Trigger.Decrement) {
if (Integer.parseInt(fabCounter.getText().toString()) != 0)
fabCounter.setText(String.valueOf(Integer.parseInt(fabCounter.getText().toString()) - 1));
} else if (object instanceof Trigger.Reset) {
fabCounter.setText("0");
}
});
Send/Fire event
MyApplication mApp = (App) getApplicationContext();
//increment
mApp
.reactiveTrigger()
.send(new Trigger.Increment());
//decrement
mApp
.reactiveTrigger()
.send(new Trigger.Decrement());
Full implementation for above library with example -> RxTrigger

Categories

Resources