Introduction
ImgurDner is an experimental Android app that downloaded hot images from imgur.com. It was architected with the popular MVP, or Model View & Presenter pattern.
Background
While employing design patterns is not a prerequisite to developing a mobile app, architectural patterns such as MVC or MVP help achieve a separation of concerns of the code: distinguishing modules responsible for views from modules for business logic; establishing effective communication channels between GUI and backend data. In addition, unit tests become easier and the code’s readability and maintenance are noticeably enhanced.
Using the code
For the sake of a to-the-point demonstration, the author keeps the code simple enough so that readers can focus on the architectural aspect of the code to grasp an understanding about the use of MVP pattern in Android programming.
In MVP pattern, the Presenter acts as a “middle-man” between the View and the Model. It establishes a two way communication with a View module: taking inputs from the View as requested by the Model; at the same time, retrieving results from the Model and directly updating the GUI via the View's methods (this is a distinctive feature of MVP from MVC pattern).
The project consists of five major java files:
- DownloadActivity and DownloadFragment, these two files are the View modules (though it's mostly Fragment's job. Android recommends to use fragment over activity for UI purpose);
- DownloadContract, an interface that hosts View sub interface and Presenter interface;
- DownloadPresenter, the presenter interface's implementation, acting as an liaison between View and Model
- ImgurService, an Inteface class in compliance with the Retrofit HTTP client framework protocol for easy and asynchronous web service access.
DownloadActivity.java is responsible for creating a Presenter's instance and passing the reference to the fragment component; the activity class is also responsible for passing the fragment, which is a View implementation, to the Presenter. By completing these two steps, the view and the presenter now have a two-way communication. Be noted that one difference between MVP and MVC patterns is that in MVP presenter can manipulate UI rendering via the view reference.
@Override
protected void onCreate(Bundle savedInstanceState) {
...
DownloadFragment downloadFragment = (DownloadFragment)getSupportFragmentManager().findFragmentById(R.id.contentFrame);
if(downloadFragment == null){
downloadFragment = DownloadFragment.newInstance();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.add(R.id.contentFrame, downloadFragment);
transaction.commit();
}
downloadPresenter = new DownloadPresenter(downloadFragment);
downloadFragment.setPresenter(downloadPresenter);
}
...
It's a common practice to use a Contract interface in MVP pattern, which is consisted of two sub interfaces.
- View interface. Normal Android Fragment classes would implement this interface.
- Presenter interface, which includes basic presenter's functionalities. Any concrete Presenter classes would at least implement this interface.
public interface DownloadContract {
interface View extends BaseView<presenter> {
}
interface Presenter extends BasePresenter {
}
}
public interface BasePresenter {
void start();
}
public interface BaseView<T> {
void setPresenter(T presenter);
}</presenter>
The concrete Presenter class does the heavy lift of fetching data from a web service. In this case, we use imgur.com's API to access its rich image repository.
In order to access imgur's webservice we use Retrofit HTTP client framework. Brief steps are shown below:
First we define an interface with the web service's URL
public interface ImgurService {
String URL = "https://api.imgur.com/3/gallery/hot/viral/0.json";
@Headers({
"Authorization: Client-ID XXXXXXXXX",
"User-Agent: XXXXXXXXXXXXXXXXXXXXXXXXX"
})
@GET("/")
void getAllImages(Callback<Image> cb);
}
Then we retrieve the data by following Retrofit's coding convention, once succeeded, the presenter calls View's method to update the UI:
ImgurService restInterface = new RestAdapter.Builder().setEndpoint(ImgurService.URL).build().create(ImgurService.class);
restInterface.getAllImages(new retrofit.Callback<Image>() {
@Override
public void success(Image model, retrofit.client.Response response) {
List<Datum> images = model.getData();
for(Datum img : images){
Log.d(TAG, img.getLink());
}
List<Datum> jpgImgs = Stream.of(images).filter(p -> p.getLink().contains(".jpg")||p.getLink().contains(".gif")).collect(Collectors.toList());
ImageRepo.newInstance().setImages(jpgImgs);
((DownloadFragment)mDownloadView).updateUI();
((DownloadFragment)mDownloadView).stopSpin();
}
@Override
public void failure(RetrofitError error) {
Log.e(TAG, error.getMessage());
}
});
Be noted: the classes such as Datum or Image are Java classes transformed from JSON stream (the data format used by imgur's web service). Handling java objects are much comfortable than parsing raw JSON streams for developers. This JSON<->Java objects transformation is known as Marshalling/Unmarshalling and can be achieved via technologies such as Jackson.
Wait a minute, here I have a better idea though, follow these two simple steps:
- Use a HTTP client utility, my favorite is postman to fetch the data in JSON
- Take one unit of the data from the above result and paste it into this online utility so that its schema can be read out to generate a bunch of Java classes, only one click away.
Points of Interest
How to use the code
It's easy. Start Android Studio, load the downloaded source and click "Run"
Reference
History
- 07/07/2017, initial version