markdown Android MVP一般指南
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了markdown Android MVP一般指南相关的知识,希望对你有一定的参考价值。
*Source:* [Francesco Cervone](https://medium.com/@cervonefrancesco/model-view-presenter-android-guidelines-94970b430ddf), [Florina Muntenescu](https://medium.com/upday-devs/android-architecture-patterns-part-2-model-view-presenter-8a6faaae14a5), [Android Architecture Blueprint](https://github.com/googlesamples/android-architecture/tree/master)
# Android MVP General Guidelines
```
-------- ------------- ---------
| View | <----> | Presenter | ----> | Model |
-------- ------------- ---------
```
The main differences between MVP and MVC is MVP breaks the connection between View and Model land let Presenter effectively become the middle-man to do all the talking.
**- Model:** it is an interface responsible for managing data. Model’s responsibilities include using APIs, caching data, managing databases and so on. The model can also be an interface that communicates with other modules in charge of these responsibilities. For example, if you are using the [Repository pattern](https://martinfowler.com/eaaCatalog/repository.html) the model could be a Repository. If you are using the [Clean architecture](https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html), instead, the Model could be an Interactor. The Android Architecture Blueprint uses Repository (see the [Repository Pattern Gist](https://gist.github.com/vxhviet/ca1451d677d4e77336b98c1607545422) for the implicit difference of Repository pattern used in Android project).
**- Presenter:** the presenter is the middle-man between model and view. All your presentation logic belongs to it. The presenter is responsible for **querying the model and updating the view, reacting to user interactions updating the model**.
**- View:** it is only responsible for **presenting data in a way decided by the presenter**. The view can be implemented by `Activities`, `Fragments`, any Android widget or anything that can do operations like showing a `ProgressBar`, updating a `TextView`, populating a `RecyclerView` and so on.
#### Android MVP Best Practices:
**1. Make View dumb and passive:**
One of the biggest problems of Android is that views (Activities, Fragments,…) aren’t easy to test because of the framework complexity. To solve this problem, you should implement the [Passive View](https://martinfowler.com/eaaDev/PassiveScreen.html) pattern. The implementation of this pattern reduces the behavior of the view to the absolute minimum by using a controller, in our case, the presenter. This choice dramatically improves testability.
For example, if you have a username/password form and a “submit” button, you don’t write the validation logic inside the view but inside the presenter. Your view should just collect the username and password and send them to the presenter.
**2. Make presenter framework-independent:**
In order to make the previous principle really effective (improving testability), **make sure that presenter doesn’t depend on Android classes**. Write the presenter using just Java dependencies for two reasons: firstly you are abstracting presenter from implementation details (Android framework) and consequently, you can write non-instrumented tests for the presenter (even without Robolectric), running tests faster on your local JVM and without an emulator.
> What if I need the Context?
Well, get rid of it. In cases like this, you should ask yourself why you need the context. **You may need the context to access shared preferences or resources, for example. But you shouldn’t do that in the presenter: you should access to resources in the view and to preferences in the model**. These are just two simple examples, but I can bet that the most of the times it is just a problem of wrong responsibilities.
By the way, **the dependency inversion principle** helps a lot in cases like this, when you need to decouple an object.
**3. Write a contract to describe the interaction between View and Presenter:**
When you are going to write a new feature, it is a good practice to write a contract at first step. ***The contract describes the communication between view and presenter**, it helps you to design in a cleaner way the interaction.
I like to use the solution proposed by Google in the [Android Architecture](https://github.com/googlesamples/android-architecture/tree/master) repository: it consists of an interface with two inner interfaces, one for the view and one for the presenter.
Let’s make an example.
```java
public interface SearchRepositoriesContract {
interface View {
void addResults(List<Repository> repos);
void clearResults();
void showContentLoading();
void hideContentLoading();
void showListLoading();
void hideListLoading();
void showContentError();
void hideContentError();
void showListError();
void showEmptyResultsView();
void hideEmptyResultsView();
}
interface Presenter extends BasePresenter<View> {
void load();
void loadMore();
void queryChanged(String query);
void repositoryClick(Repository repo);
}
}
```
Just reading the method names, you should be able to understand the use case I’m describing with this contract.
As you can see from the example, view methods are so simple that suggest there isn’t any logic except UI.
**The View contract:**
Like I said before, the view is implemented by an Activity (or a Fragment). **The presenter must depend on the View interface and not directly on the Activity**: in this way, you decouple the presenter from the view implementation (and then from the Android platform) respecting the D of the SOLID principles: “*Depend upon Abstractions. Do not depend upon concretions*”.
We can replace the concrete view without changing a line of code of the presenter. Furthermore, we can easily unit-test presenter by creating a mock view.
**The Presenter contract:**
> Wait. Do we really need a Presenter interface?
Actually **no**, but I would say **yes**.
There are two different schools of thought about this topic.
Some people think you should write the Presenter interface because you are decoupling the concrete view from the concrete presenter.
However, some developers think you are abstracting something that is already an abstraction (of the view) and you don’t need to write an interface. Moreover, you will likely never write an alternative presenter, then it would be a waste of time and lines of code.
Anyway, having an interface could help you to write a mock presenter, but if you use tools like Mockito you don’t need any interface.
Personally, **I prefer to write the Presenter interface** for two simple reasons (besides those I listed before):
- **I’m not writing an interface for the presenter. I’m writing a Contract that describes the interactions between View and Presenter**. And having the rules of both “contractual parties” in the same file sounds very clean to me.
- **It isn’t a real effort**.
**4. Define a naming convention to separate responsibilities:**
The presenter may generally have two categories of methods:
- **Actions** (`load()` for example): they describes what the presenter does.
- User events (`queryChanged(...)` for example): they are actions triggered by the user like “*typing in a search view*” or “*clicking on a list item*”.
More actions you have, more logic will be inside the view. User events, instead, suggest that they leave to the presenter the decision of what to do. For instance, a search can be launched only when at least a fixed number of characters are typed by the user. In this case, the view just calls the `queryChanged(...)` method and the presenter will decide when to launch a new search applying this logic.
`loadMore()` method, instead, is called when a user scrolls to the end of the list, then presenter loads another page of results. This choice means that when a user scrolls to the end, view knows that a new page has to be loaded. To “reverse” this logic, I could have named the method `onScrolledToEnd()` letting concrete presenter decide what to do.
What I’m saying is that during the “contract design” phase, **you must decide for each user event, what is the corresponding action and who the logic should belong to**.
**5. Do not create Activity-lifecycle-style callbacks in the Presenter interface:**
With this title I mean the presenter shouldn’t have methods like `onCreate(...)`, `onStart()`, `onResume()` and their dual methods for several reasons:
- In this way, the presenter would be coupled in particular with the Activity lifecycle. What if I want to replace the Activity with a Fragment? When should I call the `presenter.onCreate(state)` method? In fragment’s `onCreate(...)`, `onCreateView(...)` or `onViewCreated(...)`? What if I’m using a custom view?
- **The presenter shouldn’t have a so complex lifecycle**. The fact that the main Android components are designed in this way, doesn’t mean that you have to reflect this behavior everywhere. If you have the chance to simplify, just do it.
Instead of calling a method of the same name, **in an Activity lifecycle callback, you can call a presenter’s action**. For example, you could call `load()` at the end of `Activity.onCreate(...)`.
**6. Presenter has a 1-to-1 relation with the view:**
The presenter doesn’t make sense without a view. It comes with the view and goes when the view is destroyed. It manages one view at a time.
You can handle the view dependency in the presenter in multiple ways. One solution is to provide some methods like `attach(View view)` and `detach()` in the presenter interface, like the example shown before. The problem of this implementation is that the view is *nullable*, then you have to add a null-check every time presenter needs it. This could be a bit boring…
I said that there is a 1-to-1 relation between view and presenter. We can take advantage of this. **The concrete presenter, indeed, can take the view instance as a constructor parameter** (This is the preferred method. Android Architecture Blueprint use this too). By the way, you may need anyway a method to subscribe presenter to some events. So, I recommend to define a method `start()` (or something similar) to run presenter’s business.
> What about `detach()`?
If you have a method `start()`, you may need at least a method to release dependencies. Since we called the method that lets presenter subscribe some events `start()`, I’d call this one `stop()`.
```Java
public interface BasePresenter<V> {
void attach(V view);
void detach();
}
BasePresenterAttach.java
```
```Java
public interface BasePresesnter {
void start();
void stop();
}
BasePresenterStart.java
```
**7. Do not save the state inside the presenter:**
I mean using a `Bundle`. You can’t do this if you want to respect the point 2. You can’t serialize data into a `Bundle` because presenter would be coupled with an Android class.
I’m not saying that the presenter should be stateless because I’d be lying. In the use case I described before, for instance, the presenter should at least have the page number/offset somewhere.
> So, you must retain the presenter, right?
**8. No. Do not retain the presenter:**
I don’t like this solution mainly because I think that presenter is not something we should persist, it is not a data class, to be clear.
Some proposals provide a way to retain presenter during configuration changes using retained fragments or `Loaders`. Apart from personal considerations, I don’t think that this is the best solution. With this trick, the presenter survives to orientation changes, but when Android kills the process and destroys the Activity, the latter will be recreated together with a new presenter. For this reason, **this solution solves only half of the problem**.
> So...?
**9. Provide a cache for the Model to restore the View state:**
In my opinion, solving the “*restore state*” problem requires adapting a bit the app architecture. A great solution in line with this thoughts was proposed in [this article](https://hackernoon.com/presenters-are-not-for-persisting-f537a2cc7962). Basically, the author suggests **caching network results** using an interface like a Repository or anything with the aim to manage data, scoped to the application and not to the Activity (so that it can survive to orientation changes).
This interface is just a smarter **Model**. The latter should provide at least a disk-cache strategy and possibly an in-memory cache. Therefore, even if the process is destroyed, the presenter can restore the view state using the disk cache.
The view should concern only about any necessary request parameters to restore the state. For instance, in our example, we just need to save the query.
Now, you have two choices:
- **You abstract this behavior in the model layer** so that when presenter calls `repository.get(params)`, if the page is already in cache, the data source just returns it, otherwise the APIs are called.
- **You manage this inside the presenter** adding just another method in the contract to restore the view state. `restore(params)`, `loadFromCache(params)` or `reload(params)` are different names that describe the same action, you choose.
#### Android MVP Q&A:
> Q: Should we create a method `onActivityResult()` in Presenter to help Activity to handle those result?
I wouldn’t create a method onActivityResult, in the presenter because, like you said, it’s view responsibility.
Sometimes you need to do something in the presenter right after the onActivityResult. In that case, you handle the result in the view and call some presenter method to run the action you need.
---
>Let’s say that i have a button. When i clik it, it calls `getLastKnownLocation()` from presenter. To get location i need an activity, becouse i have to check permissions, also i need activity/context to create GoogleApiClient. Is there any better aproach than passing an activity to the presenter?
You can do that by hiding this behavior behind an interface to get the location, let’s say `LocationService`. The latter, would depend on the `Activity/Context`. Through dependency injection, you could inject this service into presenter decoupling the latter from the `Activity`.
View calls `presenter.onButtonClick()``, presenter calls `locationService.getLocation()`.
---
>how do you deal with RecyclerView’s adapters, especially if the items displayed need some additional preprocessing? I saw both the solution where each ViewHolder have its own presenter and the one where the main presenter feeds the view with the list of preprocessed items. Do you follow any of them, or you have some way of your own?
I prefer not to create a presenter for each `ViewHolder`. I think we should use MVP only if there is a significant logic to handle.
Most of the time the “main presenter” just feeds the view with the list of items. In the example I provided, you can see the method `addResults(List<Repository> repos)`. That `Repository` class should be a convenient model class to be used by the Adapter. Did you mean this with “additional preprocessing”?
---
>- is there consensus on the naming conventions used of the functions in the presenter and view interface within the contract? I feel like readability will go up ALOT once you know which name corresponds to a presenter or not (for example refresh/fetch/update are all along the same lines but could be unique for either the presenter or the view.
>- Suppose I want to start refreshing multiple graphs in a single view from the presenter, what is the easiest way to do this ?
- Actually you should distinguish view methods from presenter ones just because the view methods should be verbs about something to **show/hide** (showProgressBar, showGraph,…). The presenter methods, as I explained in the article, should be **actions** (load, fetch,..) or **user interactions** (queryChanged, submit,…).
- If you have to add some graphs to the view, you could define a method in the view named `addGraph(Graph graph)`. The presenter should have the responsibility to create the `Graph` objects. `RxJava` could be useful but it is not essential. You can do this also with `AsyncTasks`.
---
>What about RecyclerView ViewHolders with complex logic like comments for video, which can be deleted/editted/created/quoted e.t.c.? I’ve read your answers for Marcin Jedynak about it, but the question is:
Where to store `List<CommentPresenter>`? And who needs to manage them?
>Let’s say, order of actions is:
>
>`View` calls `presenter.onLoadMore();`
>
>`Presenter` calls `commentsRepository.loadNew(lastLoadTimestamp);`
>
>`Presenter` calls `view.updateComments(commentsChanges);` // `commentsChanges` has info about changes (edition/creation/deletion) of all comments from `lastLoadTimestamp`
>So what happening now? Does `View` manages list of presenters? (then it will be `Active View`)
Or does presenter store presenters for every `ViewHolder` and use `DiffUtil` to handle changes in it?
I don’t think that the “main” presenter should have a reference to the ViewHolders’ presenters.
If your `ViewHolders` are so complex, what do you think about creating a custom view for each one of them with its own presenter? This way, each presenter is handled by the related `ViewHolder/CustomView`.
```
class ViewHolder1 -> class CustomView1 -> class Presenter1
class ViewHolder2 -> class CustomView2 -> class Presenter2
…
class ViewHolderN -> class CustomViewN -> class PresenterN
```
Of course, an instance of `ViewHolderX` has an instance of the `CustomViewX` with its own instance of `PresenterX` with `1 ≤ X ≤ N`.
What do you think?
> It would be a good idea, will it not cause state loss while scrolling due to reusing old `View` for new chunk of data? Seems like either `Presenter` must be written with pretty strict data-change policy, either it must completely drop its old `Presenter` or new data arrival.
Yes… Presenter could survive and “repopulate” itself with a new bind. Seems that the `attach()` and `detach()` fit better in this case.
No, I’m wrong. Actually the `View` is the same so `attach()` and `detach()` are useless again in this case. You should provide just a way to pass new data to `Presenter`.
I don’t particularly like this solution, but since creating a `Presenter` for each `onBindViewHolder` is not recommended for performance reasons, I’d prefer the “efficient” solution to the “clean” one :)
---
>What about if the presenter need to choose string from `strings.xml`? The string chosen would depends on the state and it may have some parameters in it. It would need context to access the `getString` method.
It isn’t presenter’s responsibility. The view should access to resources, instead. You should move this kind of logic inside the view.
>For most of the cases, yes. But sometimes the string to be displayed is determined by some value of other variables. Wouldn’t this be the business logic?
I think that choosing what string to show is presentation/view logic.
The fact that a string depends on something else, doesn’t mean that you can’t move the string resolution in the view maintaining business logic in the presenter.
---
>My problem is decoupling the `Presenter` from Android Platform. How can I deal with frameworks like `Loaders`, `Cursors`, `Content Provider` without `Presenters` knows about them?
There is already a debate about this topic [here](https://medium.com/@kingsleyadio/hi-francesco-two-questions-for-you-36860fe3eb20). I believe that there are such things we can’t abstract, or at least, abstracting them is an excessive and vain effort. MVP doesn’t solve every problem.
If you think it isn’t worth it, don’t do it.
---
> How the model will get the CONTEXT , If required?
You could provide the Context in the model constructor, for example.
---
> I want to add swipe functionality to the items in RecyclerView to delete items.
>Can you please tell me where should I put the abstract method for it? In the Presenter or the View interface of the Contract?
I think that the view should listen to the swipe event and then call a method on the presenter like `itemSwiped`. This method should delete the item and notify the view. So, I would define this `itemSwiped` method in the `Presenter` contract and a method to remove the item in the `View` contract.
---
>My question is about framework independence for presenters. What if I need to run some simple validation in my presenter, like check if the email is valid an the password is not empty and I want to use handy `TextUtils` class? It would instantly make my presenter framework-aware and break the entire idea, but at the same time I hardly imagine implementing `TextUtils` myself just to not break the pattern. The only way I see is using some 3rd-party text utils library, like apache-commons which is Android-independent but the questions remains open. What do you use for text validations in your presenters?
Yeah, it’s really frustrating, it’s true. Using apache commons or creating your utils class can be both solutions. Or if you use Kotlin, you can solve this problem with extension functions, I think… Personally, I use my `TextUtils` class.
It’s your decision: you can run JVM tests if you use one of these solutions, otherwise you have to run instrumented tests.
---
> I was writing an app component where I had implemented two screens where I am implementing View using `Fragments` (say `FragmentA` and `FragmentB`). Now, I start my application using an `Activity` which first loads `FragmentA`. Now, based on a user action in this view, I want to replace the current fragment to `FragmentB` in the current activity. Which component should talk to the `Activity` in this case; the `Presenter` or the `View` (i.e. the `Fragment`)?
If you have corresponding `PresenterA` and `PresenterB` for `FragmentA` and `FragmentB`, the `PresenterA` shouldn’t talk directly to the `Activity`, but the `Fragment` should do that. In my opinion, the fact that the `View` is a `Fragment` inside an `Activity` and it has to be replaced with another `Fragment`, isn’t `Presenter`’s responsibility.
---
> If the presenter shouldn’t know anything about the Android framework, how can it take the view instance as a constructor parameter? Shouldn’t it take the contract interface instance as a parameter, like `SearchRepositoriesContract.View`?
I meant exactly what you said, indeed.
---
>What if the `Model` uses, for example, the `Context` class, to open a SQLite Database? I should have the `Context` somewhere, and in my case, I think that `Context` could be in the `Presenter`.
>If I use my `Model` too much and I need to open the SQLite DB too much, I think that’s not correct to ask the `View` ‘hey, give me your `Context`’ every time I want to get and show some info.
>What do you think?
The presenter should never ask the `Context` to anyone. The context should never appear in the presenter code in order to make it non-instrumented unit-testable.
If you need the `Context` to interact with a database, you should hide this behavior behind an interface.
Let’s say your current class that deals with the db is `UsersDbHelper`. You should just create an interface `UsersRepository` (`UserModel`, or whatever you want) that uses `UsersDbHelper` to fetch users. Injecting a `UsersRepository` object into the presenter, removes the context dependency from the latter.
>But, If you do what you say, the `Context` has to be in `UsersRepository` (or in the `Model`), right? So, how the `UsersRepository` gets the `Context`? The `UsersRepository`, in some way, knows the `View`? Or the `Presenter` gives the `UserRepository` the `Context`?
>I mean, someone has to know the `Context`, and the `Model` cannot have communication with the `View`, so…
**Dependency injection** is the answer. `UsersRepository` should take `Context` as constructor parameter.
---
>Say I have an `Activity` that binds and unbinds a `Service` in `onStart`/`onStop` respectively. Input comes from the user and needs to be processed by the bound service. What would be your approach in this case to do it with MVP?
>What I had in mind is having a setter method on the `Presenter` for the service interface and call it once the service is connected/disconnected. This way I can still test the `Presenter` without any framework dependency since the `Presenter` will be working with an interface of the service.
>What do you think about that? Is there a better way?
>I wrote [an article](https://medium.com/@nimroddayan/android-bound-services-and-mvp-12ca9f70c7c7#.xxdxg5nt3) about my expierence with Android Bound Services and MVP.
I think `Services` are an Android framework detail really difficult to abstract. To be honest, I’ve never faced this problem, so I wouldn’t give you a probably wrong advice.
I can just say that your solution looks quite good to me from the “MVP perspective”.
---
>My thoughts related to MVP are similar, but i’m not sure about approach to cache data in app scoped mechanism (anyway, i have to try that Store stuff from article you linked to).. What about cache invalidation? For example —user enters activity for the second time and got cached data (cuz previously request was saved). I suppose that there are plenty ways to achieve that but I didn’t find any simple and elegant solution ;)
>Could you give some advices about that problem, please? Or maybe it isn’t a problem at all and I don’t understand something.. :)
Well, it depends on whether your network service allows you to have at least a short-term cache. In that case you could just define a cache expiration time. In this way, when user comes back to the activity and the cache is still valid, gets old data and presenter continues its work like user never left the app.
If the cache is expired, instead, depends on where you decide to put the logic:
- If it is in the model, the latter should automatically call the APIs with the same request params returning fresh data.
- If the logic is inside the presenter, means that there should be a method `model.cache(params)`. When the cache is expired, this method could launch an exception or return any result that describes there is a cache miss/expiration. Then presenter should call model again with the same parameters, but this time using the method that submits the network call.
In a case like the example I provided, you can also invalidate the cache user submits a new search.
---
>"If you are using the Clean architecture, instead, the Model could be an Interactor.""
>
>Why Model could be an Interactor? I understand that Interactor executes Use Cases in the Domain layer; and then it should be run by the Presenter. I guess Model is updated with the info got from the Repository via Presenter.
I meant that sometimes the presenter doesn’t depend directly on the real model, but on an interface (the interactor) that communicates with other modules in charge of that.
It is just a way to separate the three responsibilities of MVP. The activity/fragment is the view, the concrete presenter is … the presenter… and the model is everything that allows presenter to query and update the data.
Of course, Repository manages the model, but the direct dependency of presenter is the interactor.
以上是关于markdown Android MVP一般指南的主要内容,如果未能解决你的问题,请参考以下文章