If MVVM is all about data binding and cannot do something like view.doThis(), otherwise it's MVP, then how to invoke actions on views?
Suppose I have view that has a snackbar. View is controlled by its ViewModel. How is this ViewModel supposed to show snackbar without going snackbar.show()?
In MVVM, ViewModel captures the state of the view. View observes the ViewModel for changes and updates itself. Thus, the communication between View & ViewModel happens through change of values (as against method calls in MVP).
Since Snackbar is like a global behaviour (like Toast), it can be implemented at the Activity/Fragment level. So, you can make a MessageHelper interface and pass it to the ViewModel as a dependency. Activity will implement it and display Snackbar.
Example:
ItemViewModel that consumes the interface
Activity base class that implements the interface
However, its possible that there is some view specific behaviour which cannot be implemented at the Activity level. For such cases, you can make use of databinding.Observable to trigger an event. For example, lets say we want to animate a particular view. We can create a BindingAdapter
#BindingAdapter({"shakeTrigger"})
public static void showSnackbar(View view, Void trigger) {
// Do the animation here. You could add meaningful argument types to control the animation
}
In XML, we can apply this using
<TextView
bind:shakeTrigger="#{vm.shakeTrigger}"/>
Then, in the viewModel, you can trigger the shake using Data Binding apis. One way using BaseObservable can be:
public class ConfigurationViewModel extends BaseObservable implements ViewModel {
#Bindable
public final Void shakeTrigger = null;
public void shake() {
notifyPropertyChanged(BR.shakeTrigger);
}
}
If you use RxJava, the trigger could be implemented from rx.Observable. You can checkout my library to use RxJava with Data Binding.
https://github.com/manas-chaudhari/android-mvvm
The short answer is you don't and that's actually a good thing. In MVVM ViewModel is responsible for preparing and storing data for the view. So it gets the data from the model and makes it ready to be set on the view but it doesn't set the value, Setting the value and updating view states are responsibilities of the view itself, View in MVVM watches for changes in data and updates itself.
An example of this would be showing an empty list page when your list is empty. To do this in MVVM, you define a state for the view visibility in your ViewModel lets call it emptyPageVisibility and then update this value appropriately.
public class PlaylistDetailViewModel extends ViewModel {
private MutableLiveData<Integer> emptyPageVisibility = new MutableLiveData<>();
private void someMethodInYourViewModel(){
emptyPageVisibility.setValue(View.VISIBLE);
}
}
Then inside your view, you observe this and update the view when this data is changed like this
viewModel.getEmptyPageVisibility().observe(this,
visibility -> emptyPageView.setVisibility(visibility));
Related
I trying to refactor one of my activity class to implement mvp(using mvp mosby library) . I have a RecyclerView and in this view there is some items that some changes apply to them during the run time. for example I do some I/O operation and change one row.
I think it's better to keep my items in presenter class; what is the best practice for this? keep this in 1)presenter or 2)activity or 3)only keep view related item in adapter and all other item in presenter.
the activity now keep items directly and change item row in activity and then notify adapter. isn't better to move all this line in adapter and notify adapter in the adapter class? for example i want change icon of some row.where and which class is responsible for that? adapter? activity? now I want to implement it like this in adapter:
changeItemIcon(int position, int iconRes){
mImages.get(position).setICon(iconRes);
notifyItemChanged(position);
}
I invoke this method on activity and invoke activity method from presenter.
is it good? what is the best practice to do this?
UPDATE
also I find this question ( Best way to update data with a RecyclerView adapter ) that using adapter method for changing items. but what about modify? Need I keep reference to items in my activity?
for example i want change icon of some row.where and which class is responsible for that? adapter? activity?
I know it sounds a little bit strange, but changing an element is always the responsibility of your "business logic", even just for "icons".
The workflow should be as follows (unidirectional data flow):
View appeares, tells presenter to load a list of items
Presenter loads items form "business logic" and registers himself as an
observer / listener / callback (whatever you want to call it)
Presenter receives result and tells the view to display the list of
items (through RecyclerView and corresponding adapter).
so far is what you have implemented I guess, now it comes to the point where you want to change an item.
User clicks on an item in your RecyclerView which then should trigger to change the icon of this item. Therefore View should call: presenter.changeItem()
Presenter is just the man in the middle in this case and will invoke the "business logic layer" to tell that the item should be changed to new state (icon has changed).
"Business logic layer" will change the models state (change the items icon) and then will notify its observer / listeners that the model has been changed.
Since Presenter is still observing / listening to the business logic layer (see point 2.) the Presenter will be notified (see point 6.) with a new (updated) list of items containing the updated item which icon has been changed.
Similar to point 3. Presenter will tell the view to display the new (updated) list of items (through RecyclerView and corresponding adapter).
Do you see the unidirectional data flow? That is very important. Immutability FTW.
MVP has two different variants: Passive View and Supervising Controller. Depending on your taste, you can stick to one or mix both of them in your app.
If you choose Passive View, you need to hide Model from View and let Presenter format data then set to the View. In this case, you need to keep Model reference in Presenter. View should only hold view-data (adapter) for its displaying purpose.
If you stick to Supervising Controller, you can allow View to directly bind data from Model and ask Model to perform some simple logic. Presenter should only care complex logic, i.e some operations which need to involve Services. In this case, you can give Model (your items) to View (activity) and let it interact with Model in some simple manner.
PS: Please also check out our new MVP framework: Robo MVP at http://robo-creative.github.io/mvp.
I've never used mosby, but I've just read their docs (good reading btw) and here's my understanding:
A recycler view usually consists of the view (android term) and an adapter. Both are connected inside a fragment or activity. In terms of MVP/mosby this is all view layer. The presenter should only retrieve and pass the to-be-shown data from your service (model layer in mosby, "service layer" or "business logic" in other concepts), which in turn gets it from a DAO or repository (model layer).
The docs say that the presenter only handles the view state, not the actual contents. Your state is "showing list".
Before some time, i started looking for a pattern to decouple UI from a logic of my app. I decided to use MVP, but there is one significant problem which i cant solve.
How can i inject a instance of presenter into view, if classes that implements Application, are launched from static method. There is also no choice to launch specific instance of class implementing Application, so parameters in constructor are useless.
Also i do not use FXML, my view class is coded in java.
PS: Sorry for my english, as it's not my native language
You can pass a reference from say Main.java to a Presenter. In Main do this:
Presenter p = new Presenter(); // This is your Presenter class
p.setReference(this); // Call a method in the presenter
// and here is a method in Main.java just as an example
public StackPane getRootView(){
return this.rootView;
}
Then in Presenter you have:
private Main main;
public void setReference (Main main) {
this.main = main;
}
Your presenter can now call methods in Main e.g.
StackPane sp = main.getRootView();
You could also do this in the constructor of Presenter.
I have written a sample code to answer to this problematic.
https://github.com/oterrien/JavaFX_Presenter.git
The view interface provides the intention of the view.
For example:
getting and setting text
getting and setting result1
getting and setting result2
getting and setting event handler for the Add button
The concret view is created from FXML file. Each field of the control is defined with #FXML. The action to be triggered when the button is clicked is also a method and is prefixed by #FXML.
The concret view implements the interface by providing a mapping between #FXML fields and getting/setting methods. And the triggered method does just call the event handler.
The concret view is also responsible of creating the presenter (which refers itself as view).
That is the important point. The presenter acts upon the model and the view. It retrieves data from repositories (the model), and formats it for display in the view.
For that purpose, the presenter should be able to call the view in order to set data and retrieve data once updated by user. That is why, the presenter contains a reference of the view. But it should also provide action to be done when view's event handlers are called.
When a user clicks on button "Add", the method which is bound with FXML is called. This method call the EventHandler which has been set by the presenter. In other words, the presenter is responsible of registering its own method to the view's EventHandler.
Finally, testing the presenter just consists in creating a mock of the view.
I have a custom View class that extends Spinner. I'm trying to figure out what the correct way to talk to the Activity that it's embedded in is, when the user makes a selection. I see that the OnItemSelected listener gets a reference to the Adapter, but I'm not clear on whether or not I should be using this adapter and walking up its parent chain somehow, or if I should just talk directly to the context (for some reason that doesn't feel safe, even though I can't think of a way in which it might fail, offhand).
the right way to do that, is to "listen" to your custom view by exposing an interface which your view holding a reference to instance of him, and you hosting activity should implement. exactly like the OnItemSelected interface and any events which android views are exposing is been implemented. this is the observer design pattern.
for example:
public class MyCustomSpinner extends Spinner {
public MyCustomSpinner(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
public interface IMyEventListener {
public void onEventOccurred();
}
private IMyEventListener mEventListener;
public void setEventListener(IMyEventListener mEventListener) {
this.mEventListener = mEventListener;
}
protected void someMethodWhichDoingSomthingAndShouldRaiseAlsoTheEvent() {
/*
* Some Code which the function doing //more code...
*/
if (mEventListener != null) {
mEventListener.onEventOccurred();
}
}
}
this is how you will use it from your activity:
mMyCustomSpinner.setEventListener(new IMyEventListener() {
#Override
public void onEventOccurred() {
// TODO Auto-generated method stub
}
});
I'm trying to figure out what the correct way to talk to the Activity that it's embedded in is, when the user makes a selection.
You don't want to "talk to the Activity that it's embedded in". You want to talk to the controller responsible for the View. Today, that might be an Activity. Tomorrow, that might be a Fragment.
I see that the OnItemSelected listener gets a reference to the Adapter, but I'm not clear on whether or not I should be using this adapter and walking up its parent chain somehow
That implies that the View knows the specific type of Adapter, since the Adapter interface does not have any sort of getContext() method. Moreover, it ties you to talking to the Activity, which is not a good plan at this point, as mentioned above.
Personally, I'm a bit dubious about having a custom Spinner subclass in the first place. But, assuming there's a good reason for it, you should follow Tal Kanel's advice (posted while I was writing this) and design a custom listener interface for this custom event that is being declared by your custom View. Have the controller (Activity or Fragment) supply an implementation of that interface -- this could be directly implemented on the controller, or implemented as an anonymous inner class (as in Tal Kanel's answer), etc. Have your custom View call method(s) on the listener interface as needed.
The correct way is using a listener of some sort. I think you can make direct reference, your code would just not be reusable for another project then...
A simple solution -
((ParentClass) context).functionToRun();
Where ParentClass is the class name of the activity.
I'm basically trying to disable scroll on the list view when the user touches the List Header and MotionEvent.ACTION_MOVE is performed.
If you are wondering on why this crazy implementation? - I'm basically building a custom seekbar on my listView Header which works on MotionEvent.ACTION_MOVE and since the view is a header of list view it should not scroll until user takes off touch from this custom header view.
So my question/problem is.
For now i have 2 java classes. Activity (which has this list view) and the custom Header (which is a View implementing OnTouchListener).
How do I pass value when my header view is touched to this list view (activity) to disable scrolling?
It seems that list view scroll can be disabled by overriding dispatchTouchEvent as said here. Since mine is not a custom listView is it possible to disable in activity class itself?
Sorry i'm just learning, so please dont mind my newbiness.
Ok, I have gone little quiet far on the question I asked, So I'm answering the first half of my question.
How do I pass value when my header view is touched to this list view (activity) to disable scrolling?
I got my solution in creating an interface. So I created a public static interface which has a public void headerTouched(Boolean touchEvent); method. My class which has the code for the listView is the implementer so it automatically overrides this method from the interface class. I this class I create a private variable (in my case: boolean) which listens for the boolean value returned from the driver class. My driver class is the HeaderView which has this interface object and through it I set the value. To help newbies on interface, I have posted my code below.
Interface Class
public static interface HeaderTouchListener{
public void headerTouched(Boolean touchEvent);
}
Implementer Class
private boolean headerTouched = false;
public void headerTouched(Boolean touchEvent) {
headerTouched = touchEvent;
}
Driver Class
HeaderTouchListener touchListner = new ImplementerClassName();
touchListner.headerTouched(true);
I'm still having trouble figuring out how to disable scroll on my list view. More information on it, please follow this question Set dispatchTouchEvent for List View without creating custom List View class. (for disabling scroll)
I need help. I am struggling to get my Observers working in java. Can someone explain to me using MODEL-VIEW-CONTROLLER Architecture how to create and observer from View To Controller.
This is because if i press a button on the view the action event has to call notify the controller of that button being pressed.
For that I'm implementing observers to minimize class coupling.
I have a class Controller, View (Swing using JFrame), and an Application Class that holds the main method.
I tried implementing it so that Controller implements Observer and the View extends Observable.
After triggering the event of clicking the button all code except the notifyObservers("OBJECT") gets called. It disappears somewhere in the java library.
Any Help Will be much appreciated.
the model should extend observable and the view should implement observer (you want the view to depend on the model). you will need to call setChanged to after you change the state of the model to force the observers to be notified.
Double check, that your controller is really observing/listening to the (correct) button instance. Use a debugger and set some breakpoints to check whether notifyObservers is called and who is receiving the notification.