Nested RecyclerViews MVP implementation - android

I want to make an app that has a vertical RecyclerView with nested horizontal RecyclerViews. I don't understand how to use properly an MVP pattern in such case. MVP "rule" says that it should be only one View for a screen.
My View interface:
public interface ViewLayer {
void showProductsInCategory(int categoryId, List<ProductModel> productList, PresenterLayer presenter);
void showCategories(List<CategoryModel> categoryItemList, PresenterLayer presenter);
}
Presenter:
public interface PresenterLayer {
void onViewReady();
}
Model:
public interface InteractorLayer {
void getProducts(int categoryId);
void getCategories();
}
Model listener interface:
public interface InteractorListener {
void onProductsLoaded(int id, List<ProductModel> products);
void onCategoriesLoaded(List<CategoryModel> categories);
}
CategoryModel:
public class CategoryModel {
private String categoryName;
private List<ProductModel> productList;
public String getCategoryName() {
return categoryName;
}
public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}
public List<ProductModel> getProductList() {
return productList;
}
public void setProductList(List<ProductModel> productList) {
this.productList = productList;
}
}
So I have to select each nested RecyclerView by categoryId to add the data to their adapter. Can I create separate Model-View-Presenter interfaces for every horizontal RecyclerView?
UPD:
Step by step
1) MainActivity.onCreate calls presenter.onViewReady()
2) Presenter calls interactorLayer.getCategories()
3) Model calls InteractorListener.onCategoriesLoaded(List<CategoryModel> categories)
4) Presenter calls ViewLayer(MainActivity) showCategories(List<CategoryModel> categoryItemList, PresenterLayer presenter)
5) MainActivity sets that categoryItemList to the outer RecyclerView's adapter. Now each categoryItem has null productList
6) In the method onCategoriesLoaded(...) after ViewLayer.showCategories(...) Presenter calls Model's InteractorLayer.getProducts(i) in the cycle for each Category
7) After any productList loaded Presenter calls ViewLayer's showProductsInCategory(...)
8) MainActivity gets the Adapter of the main RecyclerView, gets a Category item and sets the productList for it.
9) MainActivity calls Adapter's notifyDataSetChanged()
10) The inner RecyclerView sets new productList when onBinding calls
I think its very complicated. What can I do with that?
UPD 03/24/2017
Source code: https://github.com/Lex74/ProductsShop

First, I'd like to state that I don't think of myself as a MVP guru, rather as someone who's striving to understand the pattern,
My favourite MVP reference: The Clean Architecture from Uncle Bob's blog
According to this blog post, there is something called The Dependency Rule:
...source code dependencies can only point inwards. Nothing in an inner circle can know anything at all about something in an outer circle...
For example, the Presenter class does not need to know anything about RecyclerView or RecyclerView.Adapter. It needs some interface to pass information to the outer layer.
The methods of the interface depend on the use case: with a List, one would like to be able to
pass a reference to the whole data List (showCategories())
refresh single list items (showProductsInCategory())
So I think the Dependency Rule says, that the ViewLayer interface has to offer methods which satisfy the needs of the [Model layer and the] Presenter layer. As a Presenter, I simply don't care whether the View out there is a ListView or maybe not a View at all but rather some combination of sound and vibration signals.
On the other hand, it seems to be perfectly ok for a View class to know the name (and methods of) its Presenter class, so maybe the PresenterLayer interface is no must-have.
It's entirely up to the View how the data will be offered to the user. A nested View structure still is just a complicated View. So I dont' think one needs to provide nested interfaces.
In some cases with nested Lists, the Presenter might need a method to update an item of the inner List, something like showSingleProductInCategory(ProductModel product, int categoryPosition, int productPosition).
Another interesting question: who keeps (and may modify) the data? In my opinion, the Presenter is responsible for the data, and it should only pass a reference to the data into the View layer or notify it of changes. An Adapter should not have the right to modify the original data list, a Presenter should never have to ask the Adapter "how many items are there?" and I don't really like the idea of two separate data lists. The names of the various notify... methods seem to indicate that I'm on the right track there.
This means Presenter will always hold on to the original data List. If data changes, the Presenter will update its data (may be clear() and "copy the new items", may also be more fine-grained, depending on what ProductLoader is offering) Afterwards, Presenter will notify the Adapter via the ViewLayer interface.
Link to a zip file with the modified Java classes
EDIT
Somehow I doubt that "one View for one screen" will work well for Android. Imagine the typical Master-Detail situation. If the screen is large, you will want to use the space and show both Fragments at once.
So if you have one View (and one Presenter) per Fragment, everything will work for all types of screens. It's up to the Activity to manage the Fragments depending on the screen size.
I've already explained that I like to have the Adapter of some ListView or RecyclerView implement the interface which is required as a callback for the Presenter. (All the Fragment in its role as callback could do would be to pass the information on to the Adapteranyway)
On the other hand, a Fragment may well contain several groups of data. Some of them may be somehow related (like all the songs by one particular artist), others (all those ads...) rather not. The Presenter needs methods to tell the View what to show to the user: one method for the artist, one for the advertisement etc.
So if I had an app with a handful of Fragments, the interface would contain methods like
void showAdvertisement(AdObject ad);
void showArtistInfo(Artist artist);
... and the Presenter would expect some class implementing this specific interface in its Constructor. (Plus the Adapter for the songs), and I'd have the Fragment implement the interface for all the non-collection data.
In a project with several apps, one might consider using generic interfaces
(one for any kind of detail information, one for collections). Then one would have a method showData(T data), and the Presenter in the example above would expect one callback for the advertisement and one for the artist info:
MyPlaylistPresenter (DetailInterface<AdObject> adCallback, DetailInterface<Artist> artistCallback, CollectionInterface<Song> songsCallback){...}
and then in the Fragment one would write:
MyPlaylistPresenter presenter = new MyPlaylistPresenter(this, this, adapter);
A little bit like Lego :), but all in all less interface classes. And methods which do basically the same thing have the same name all over the project, so I think it contributes to maintainability.
Now about your other question:
If your app has a Model on the client side, then I think you're right.
On the other hand, there are projects where the Model is part of the backend. Then the Presenter would be the logical choice.

Related

How to pass data from parent view's presenter to child view's presenter?

I am using MVP patterns in Android. And structure looks like below.
Activity - Presenter
|
Fragment
|
CustomView
|
views
So when the presenter gets data from the network, it directly passes data to fragment, and fragment pass data to a custom view and custom view pass data to views.
I am not sure how I can pass data used in views from activity with MVP patterns. If I make presenters for each fragments, custom views, and views, then how can I pass data from activity's presenter to other presenters?
Anyone can help me out with examples?
In order to give a more specific answer to your question you need to give a concrete example. Every solution is valid in a context. I'll give couple of ways you can do this. Choose the one that suits your problem.
Very important part of the MVP is the Model. As far as I'm aware the term Model became popular in programing with the release of the paper Thing Model View Editor which was later refined and renamed to MVC.
The definition of the concept of a Model from this paper is:
A Model is an active representation of an abstraction in the form of
data in a computing system
The Models are represented in the computer as a collection of data
together with the methods necessary to process these data.
With time and experience, people have discovered and specified different types of models.
Here are some of them:
Domain Model
Application Model (read this article for more information)
Presentation Model
MVP, since it derives from MVC, makes two major divisions of responsibilities: Model (the abstraction that represent concepts) and Presentation (View and Presenter to visualize the Model).
Because we have divided the Model from the Presentation, we can have multipe Views that show the same Model different ways. An example of that is a Model that represents Statistical Data that can be shown different ways: a Pie chart, a Bar chart etc. In this example the Statistical Data Model is a Domain Model.
In the example above, the Model will probably be shared between the two View-Presenter pairs , The PieChart and the BarChart. If you use the Observer pattern, when one of the View-Presenter pairs update the StatisticalModel, it will raise changed events, and both View-Presenter pairs will receive notifications for this change and update.
Sometimes an application needs an ApplicationModel. This model can be shared between different View-Presentation pairs. Let's take a look at a verfy simplified example.
Let's say we have a File Browser application like Windows Explorer. This GUI of the application has two major parts: The left panel that shows a Tree of the folders and the middle File-Folder panel. When a folder is selected in the left folders tree panel, files and folders from the selected folder must be shown in the middle panel. We can do this by defining an ApplicationModel that will capture and represent the above logic and be shared between both View-Presentation pairs for the left and middle panels.
Note: I'll omit details to simply the example and write less code
public class ApplicationState {
// singleton, it's evil I know,
// but it's the simplest way without DI or ServiceLocator
private static ApplicationState mInstance = new ApplicationState();
public static ApplicationState getInstance() { return mInstance; }
private Folder mSelectedFolder;
public bool hasSelectedFolder() { return mSelectedFolder != null; }
public Folder getSelectedFolder() { return mSelectedFolder; }
public Folder setSelectedFolder(Folder f) {
mSelectedFolder = f;
RaiseSelectedFolderChangedEvent();
}
// method for registering listeners, raising events etc.
}
public class FoldersTreeViewPresenter {
private ApplicationState mApplicationState;
public void onSelectFolder(FolderView view) {
// select the folder in the view
mApplicationState.setSelectedFolder(view.Folder);
}
}
public class FilesFoldersViewPresenter : ApplicationStateListener {
private ApplicationState mApplicationState;
public FilesFoldersViewPresenter() {
// you can use service locator, dependency injection, whatever
mApplicationState = ApplicationState.getInstance();
mApplicationState.addEventListener(this);
}
private void getFilesAndFoldersFromFileSystem(Folder folder) {
// get from fs
// fill views with them etc.
}
private void clearView() {
// clear the panel
}
public void onApplicationStateChanged() {
if(mApplicationState.hasSelectedFolder()){
getFilesAndFoldersFromFileSystem(mApplicationState.getSelectedFolder());
}
else {
clearView();
}
}
}
In this example we created a shared object that represent the application state and the logic, that our application has a selection that can be changed. In this case the ApplicationState class is part of the Model and is an Application Model. Because it is shared and it's life time is the same as the application (it exists as long as the application is running) it will hold the state. Views and Presenters are created and destroyed, but this class will exist and hold the state, so that when a new View and/or Presenter is created it can check this state and do something.
In my experince people do concentrate on Views and Presenters more, while they should work on their Models. Peronally I use Models alot as it makes things cleaner and the application easier to understand.
Of course, using Models doesn't always work, so when they don't you can use messaging, having one Presenter sending messages to others. Here's an example with the same File Browser app.
public class MessageBus {
// static this time, you can use DI or ServiceLocator with interface
public static void sendMessage(object m) { }
public static void registerListener(MessageListener listener) { }
}
public class FoldersTreeViewPresenter {
public void onSelectFolder(FolderView view) {
// select the folder in the view
MessageBus.sendMessage(new FolderSelected(view.Folder));
}
}
public class FilesFoldersViewPresenter : MessageListener {
public FilesFoldersViewPresenter() {
MessageBus.addListener(this);
}
private void getFilesAndFoldersFromFileSystem(Folder folder) {
// get from fs
// fill views with them etc.
}
public void onMessage(object m) {
if(m instanceof FolderSelected) {
FolderSelected folderSelectedMessage = (FolderSelected)m;
getFilesAndFoldersFromFileSystem(folderSelectedMessage.Folder);
}
}
}
Depending on your specific case, if you can create a nice Model, either a Domain, Application or Presentation, do it. Share this Model thus creating a dependency on the Model from the Presenters instead of creating a dependency between Presenters. This way you have loose coupling between Presenters and you can change them much easier
If you can't use a Model, use a Messages. It's a nice way to decouple Presenters by creating a protocol of messages that are used for communication.
Check this article on using messages for collaboration between components.
Also here are some good articles on GUI architectures:
https://martinfowler.com/eaaDev/uiArchs.html
http://aspiringcraftsman.com/2007/08/25/interactive-application-architecture/

In the MVP pattern, should adapters hold models or should the presenter hold models and have the adapter reference it?

Currently I have it so that an adapter has a reference to all the models in it. But is it better to let the presenter just hold the models and the adapter can simply reference them?
So for example:
public class Adapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
private Presenter presenter;
public Adapter(Presenter presenter){
this. presenter = presenter;
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
Model m = presenter.getModels().get(position);
// bind model to view holder
}
#Override
public int getItemCount() {
return presenter.getModels().size();
}
}
This way when a Presenter fetches more models, it just simply calls getAdapter().notfiyDataSetChanged(); after the fetch.
You can really go either way with it. Some would say treat the adapter as part of your view and make it as dumb as possible, but there's definitely a benefit to letting the adapter hold the data if you do it right.
For example, I use an abstract base adapter with generics that holds a list of data objects to drive the recyclerview. It provides all the standard CRUD operations for the list (add, update, delete, move, etc). These methods also handle notifying the adapter of the change, so my client code doesn't have to worry about it. It just hands an object to the adapter or tells it to delete/change one, and the adapter handles the rest.
The big benefit here is a huge reduction in the amount of repeated boilerplate code for CRUD operations and dataset change notifications across the various actors interacting with recyclerviews. If you have more than a screen or two with recyclerviews, this savings adds up quick to make it more beneficial than blindly adhering to a mantra.
Normally Adapter considered to be an implementation detail of View.
Presenter should not know View implementation details.
The job of adapter is to hold an array of items and to publish it to views. Adapter should not know about Presenter, models, other views, etc.
Data flow for Adapter, as I understand it:
Model -> Presenter -> View -> Adapter-> ItemView
Control flow is opposite, preferably skipping adapter.
Feel free to ask questions in the project's issues.

What is good implementation on updating items that shift around a lot in RecyclerView (swapping), and immediate user feedback?

Goal(s):
1: Effortless updating for dynamic items.
Example:
I have a List<T> returned from an API, I use that list in my RecyclerView.Adapter. User swipes to refresh and a new list is returned from the API containing some new items and some updated old items. Now the older list needs to remove duplicate items.
Note: assume all items have an updated attribute that might change if a user interacts with it.
2: Immediate user feedback (this might tie in with goal #1).
Example:
To insert a new item into the RecyclerView.Adapter it needs to be created in an API first. Implementation creates object in the RecyclerView.Adapter and in the API simultaneously. When the new object is returned from the API the immediate object that was previously injected right away into the RecyclerView.Adapter "syncs" with the API response. This way the user sees immediate feedback.
Code Example:
I don't really have anything in mind for Goal #1 BUT for Goal 2 I was thinking something like this maybe inside my ViewHolder? (I have heard that updating / syncing models in Viewholders is not a good practice in general because viewholders recycle):
// JAVA 7
private void createNewObjectToBeInsertedIntoRecyclerView(String data) {
// Pass callback to API and at the same time insert object into adapter
mAdapter.addNewObject(data);
mPresenter.createObject(new SyncRequestCallback() {
#Override
public void onSuccessFromAPI(ModelObject model) {
mAdapter.updateObject(model);
}
});
}
// JAVA 8
private void createNewObjectToBeInsertedIntoRecyclerView(String data) {
// Pass callback to API and at the same time insert object into adapter
mAdapter.addNewObject(data);
mPresenter.createObject((sync) -> { mAdapter.updateObject(model); });
}
This is just off the top of my head and it is definitely bug prone.
How Can I Achieve This?:
Looking for a robust solution here, but something that doesn't involve content providers (if possible).
You should not do anything like that in the ViewHolder, just bind the data you got from the API to the UI.
What you should do is operate on the Adapter
when the new List<T> returns from the API, just make the old list in the adapter to point to this new one (oldList = newList) and call mAdapter.notifyDataSetChanged()
You can do like point 1) but that way updates the whole Adapter. If you know where in the Adapter you have inserted that item (and I assume you know), just call mAdapter.notifyItemInserted(position) or alternatively, if you have already created it the Adapter, call mAdapter.notifyItemChanged(position)

How to control ListView with MVP Pattern for Android

I'm currently developing an android app using MVP Pattern.
When I try to develop an Activity, I should use a ListView. So I'm using Adapter for ListView. But I heard Adapter is similar to Presenter on MVP Pattern.
I think if Apdater is smiliar to Presenter, then I should make Presenter for updating ListView instead of Adapter.
When this situation, how to develop ListView? Just use Adapter and keep using MVP Pattern?
Thanks for your reading.
Adapter is part of the view. In fact, all Android dependencies should be a part of the view.
To keep the adapter isolated from your model and your presenter use to be a hard task.
I have released a library called PaperKnife for this purpose.
You can use PaperKnife to decouple the adapter from the model and the presenter layer. Follow the next steps:
Abstract the model layer using CellElement interface. Your view layer does't need to know your model.
Create a class to provide the information for your row view. You can use your presenter. Implements the class CellDataProvider and create methods to provide all the information. Annotate your provider methods with #DataSource("DataId") to perform the mapping. Your data methods receive the instance of your model class. For example:
public class SamplePresenterImpl implements SamplePresenter, CellDataProvider {
#DataSource("Title")
public String getTitle(Item item) {
return item.getTitle();
}
// etc.
}
Create a ViewHolder in your adapter and implements the CellViewHolder interface. Create methods to manage the views and use DataTarget("DataId")
static class ViewHolder extends CellViewHolder {
#DataTarget("Title")
public String setTitle(String title) {
mTextViewTitle.setText(title);
}
}
Execute the mapping in your adapter getView method:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// etc.
PaperKnife.map(mList.get(position))
.dataProvider(mCellDataProvider)
.into(viewHolder);
return convertView;
}
In this way your view layer just know the CellElement interface, and your presenter is responsible to provide data to your adapter.
Yes, the Adapter should be the P component in an MVP pattern. In fact ListViews are pretty much written as MVP- the getView() function needs to set all the values of the view each time its called, that's almost the definition of what a presenter must do. Although it's also easy to use it in an MVC type way- simply have getView call functions on the View that pass it the model and do that work in the Views. So really either way will work, just pick your preference.
If you do use an MVP model with complex list rows, I like to make the rows a custom compound View and put more descriptive function names on it- so rather than going listRow.findViewById(R.id.textView).setText(filename) I'll go listRow.setFilename(filename) and let the view know what to do with that. That kind of blurs the bounds of MVP and MVC a bit, but I find it a good balance of readability of your adapter and avoiding some of the awkwardness pure MVC sometimes brings.
If there is only a listview in that activity then there is no need to write a separate presenter because Adapter is actually working as Presenter for ListView. But if you have other UI components than ListView that need to be updated then you must need to write a separate Presenter for those UI components.

Adding arguments to AndroidAnnotation injected beans

I have a piece of code using AndroidAnnotations which is very similar to the one found at:
https://github.com/excilys/androidannotations/wiki/Adapters-and-lists
However - I want to pass an argument to the List adapter to specify which list - i.e.
#AfterInject
void initAdapter() {
persons = personFinder.findAll(companyName);
}
What is the best way to associate companyName with the Adapter? I can't use the constructor with AnroidAnnotations - and #AfterViews is called before the #AfterViews of the parent fragment, so I can't call setters then.
I have currently hacked in a call to set the params manually then refresh the view and removed the #AfterViews - but its nasty and unreliable as I duplicate the pattern down the hierarchy.
EDIT
Just calling the setter works in the most simple case - and is what I currently have.
But doesn't work well in the more complicated case. i.e
EFragment->EViewGroup->EBean ListAdapter
Since I can't use the constructor, I have to wait until the full hierarchy is rendered and laid out before the fragment tells the ViewGroup which Company to show company info, which in turn tells the ListAdapter which company so I can get which people, etc.
It doesn't take much effort for it to get very messy and if my data was on the web - the UI would probably render like a webpage from the 90s.
I was hoping to use something like #Extras - or have a way to pass arguments for #AfterInject to use, or even just put the companyId in the Fragment Context without tying my ListAdapter to only work with one type of Fragment...
Try this
#EFragment
public class MyFragment extends Fragment {
#FragmentArg("myStringArgument")
String myMessage;
#FragmentArg
String anotherStringArgument;
#FragmentArg("myDateExtra")
Date myDateArgumentWithDefaultValue = new Date();
}
Source:
https://github.com/excilys/androidannotations/wiki/FragmentArg

Categories

Resources