https://medium.com/@shri_chaudhari/working-with-mvp-and-retrofit-2-0-in-android-4016253ab3fc
Recently I have observed that many Android developers are talking about the app architecture, usage of new libraries in the project. So I was thinking why not create a post which will cover some of the topics, so I have decided to go with MVP along with Retrofit 2.0.
This post is what I have researched and learnt from various resources. You can even find an example for the same in this blog. I believe that many of you are already aware of MVP and Retrofit, so without going into much details, I will give short descriptions and then start with the example.
Recently I have observed that many Android developers are talking about the app architecture, usage of new libraries in the project. So I was thinking why not create a post which will cover some of the topics, so I have decided to go with MVP along with Retrofit 2.0.
This post is what I have researched and learnt from various resources. You can even find an example for the same in this blog. I believe that many of you are already aware of MVP and Retrofit, so without going into much details, I will give short descriptions and then start with the example.
MVP (Model-View-Presenter)
The MVP stands for Model View Presenter. In this architecture, each layer is defined to do certain tasks.

- Model is responsible for handling all business logic, communication with backend server and database operations.
- The View is responsible for displaying data to a user in the form of UI screens. In Android usually, Activity implements the View.
- The Presenter is acted as a middleman between View and Model. It basically retrieves data from Model and returns it to View for display Purpose.
Retrofit 2.0

A Retrofit is type-safe HTTP client for Android and Java created by Square Inc. It is an open source library which simplifies the HTTP communication. Using this library, developers can do the network stuff much easier.
Project Overview
After going through the basics of MVP and Retrofit, its time to use them in the project. For this project, we will use TMDB API. The internet movie database is a web page similar to IMDB where you can find a list of movies with their description, images, trailers and so on. We are going to show the list of popular movies in recycle view and also details of the selected movie. First of all, we will have to get the API key from this website to access the database.
Here is the final project we are about to build.
Here is the final project we are about to build.

Project structure:
After we will be completed by this application, you can see below project structure for our application.

So let’s gear up and start the project.
1. build.gradle
Add following dependencies to the project.
dependencies { | |
implementation fileTree(dir: 'libs', include: ['*.jar']) | |
// View support library | |
implementation 'com.android.support:appcompat-v7:27.1.1' | |
implementation 'com.android.support.constraint:constraint-layout:1.1.0' | |
implementation 'com.android.support:cardview-v7:27.1.1' | |
implementation 'com.android.support:recyclerview-v7:27.1.1' | |
implementation 'com.android.support:design:27.1.1' | |
// Glide - image processing library | |
implementation 'com.github.bumptech.glide:glide:4.7.1' | |
// Network library | |
implementation 'com.squareup.retrofit2:retrofit:2.4.0' | |
implementation 'com.squareup.retrofit2:converter-gson:2.4.0' | |
//Calendar view | |
implementation ('com.wdullaer:materialdatetimepicker:3.5.2') { | |
exclude group: 'com.android.support' | |
} | |
} |
2. Implementing Retrofit
Before we can start designing model class, first we need to know the structure of JSON data coming from TMDb API. For this example, I am going to use most popular movies API. Using this (http://api.themoviedb.org/3/movie/popular?api_key=YOUR_API_KEY) API we will get the list of 50 most popular movies in the following JSON format.
{
"page":1,
"total_results":19862,
"total_pages":994,
"results":[
{
"vote_count":2023,
"id":299536,
"video":false,
"vote_average":8.7,
"title":"Avengers: Infinity War",
"popularity":725.420399,
"poster_path":"\/7WsyChQLEftFiDOVTGkv3hFpyyt.jpg",
"original_language":"en",
"original_title":"Avengers: Infinity War",
"genre_ids":[
12,
878,
14,
28
],
"backdrop_path":"\/bOGkgRGdhrBYJSLpXaxhXVstddV.jpg",
"adult":false,
"overview":"As the Avengers and their allies have continued to protect the world from threats too large for any one hero to handle, a new danger has emerged from the cosmic shadows: Thanos.",
"release_date":"2018-04-25"
},
...
]
}
a. Now create a Model class Movie.Java
public class Movie { | |
private int id; | |
private String title; | |
@SerializedName("release_date") | |
private String releaseDate; | |
@SerializedName("vote_average") | |
private float rating; | |
@SerializedName("poster_path") | |
private String thumbPath; | |
@SerializedName("overview") | |
private String overview; | |
@SerializedName("backdrop_path") | |
private String backdropPath; | |
@SerializedName("credits") | |
private Credits credits; | |
@SerializedName("runtime") | |
private String runTime; | |
@SerializedName("tagline") | |
private String tagline; | |
@SerializedName("homepage") | |
private String homepage; | |
public Movie(int id, String title, String releaseDate, float rating, String thumbPath, String overview, String backdropPath, Credits credits, String runTime, String tagline, String homepage) { | |
this.id = id; | |
this.title = title; | |
this.releaseDate = releaseDate; | |
this.rating = rating; | |
this.thumbPath = thumbPath; | |
this.overview = overview; | |
this.backdropPath = backdropPath; | |
this.credits = credits; | |
this.runTime = runTime; | |
this.tagline = tagline; | |
this.homepage = homepage; | |
} | |
public int getId() { | |
return id; | |
} | |
public void setId(int id) { | |
this.id = id; | |
} | |
public String getTitle() { | |
return title; | |
} | |
public void setTitle(String title) { | |
this.title = title; | |
} | |
public String getReleaseDate() { | |
return releaseDate; | |
} | |
public void setReleaseDate(String releaseDate) { | |
this.releaseDate = releaseDate; | |
} | |
public float getRating() { | |
return rating; | |
} | |
public void setRating(float rating) { | |
this.rating = rating; | |
} | |
public String getThumbPath() { | |
return thumbPath; | |
} | |
public void setThumbPath(String thumbPath) { | |
this.thumbPath = thumbPath; | |
} | |
public String getOverview() { | |
return overview; | |
} | |
public void setOverview(String overview) { | |
this.overview = overview; | |
} | |
public String getBackdropPath() { | |
return backdropPath; | |
} | |
public void setBackdropPath(String backdropPath) { | |
this.backdropPath = backdropPath; | |
} | |
public Credits getCredits() { | |
return credits; | |
} | |
public void setCredits(Credits credits) { | |
this.credits = credits; | |
} | |
public String getRunTime() { | |
return runTime; | |
} | |
public void setRunTime(String runTime) { | |
this.runTime = runTime; | |
} | |
public String getTagline() { | |
return tagline; | |
} | |
public void setTagline(String tagline) { | |
this.tagline = tagline; | |
} | |
public String getHomepage() { | |
return homepage; | |
} | |
public void setHomepage(String homepage) { | |
this.homepage = homepage; | |
} | |
@Override | |
public String toString() { | |
return "Movie{" + | |
"id=" + id + | |
", title='" + title + '\'' + | |
", releaseDate='" + releaseDate + '\'' + | |
", rating=" + rating + | |
", thumbPath='" + thumbPath + '\'' + | |
", overview='" + overview + '\'' + | |
", backdropPath='" + backdropPath + '\'' + | |
", credits=" + credits + | |
", runTime='" + runTime + '\'' + | |
", tagline='" + tagline + '\'' + | |
", homepage='" + homepage + '\'' + | |
'}'; | |
} | |
} |
b. Create a MovieListResponse.Java as there are some more fields like page no, total pages etc.
public class MovieListResponse { | |
@SerializedName("page") | |
private int page; | |
@SerializedName("results") | |
private List<Movie> results; | |
@SerializedName("total_results") | |
private int totalResults; | |
@SerializedName("total_pages") | |
private int totalPages; | |
public int getPage() { | |
return page; | |
} | |
public void setPage(int page) { | |
this.page = page; | |
} | |
public List<Movie> getResults() { | |
return results; | |
} | |
public void setResults(List<Movie> results) { | |
this.results = results; | |
} | |
public int getTotalResults() { | |
return totalResults; | |
} | |
public void setTotalResults(int totalResults) { | |
this.totalResults = totalResults; | |
} | |
public int getTotalPages() { | |
return totalPages; | |
} | |
public void setTotalPages(int totalPages) { | |
this.totalPages = totalPages; | |
} | |
} |
. Now lets create instance class for Retrofit. Creating a class called ApiClient.Java
public class ApiClient { | |
public static final String BASE_URL = "http://api.themoviedb.org/3/"; | |
private static Retrofit retrofit = null; | |
public static final String IMAGE_BASE_URL = "https://image.tmdb.org/t/p/w200/"; | |
public static final String BACKDROP_BASE_URL = "https://image.tmdb.org/t/p/w780/"; | |
/** | |
* This method returns retrofit client instance | |
* | |
* @return Retrofit object | |
*/ | |
public static Retrofit getClient() { | |
if (retrofit == null) { | |
retrofit = new Retrofit.Builder() | |
.baseUrl(BASE_URL) | |
.addConverterFactory(GsonConverterFactory.create()) | |
.build(); | |
} | |
return retrofit; | |
} | |
} |
d. Creating interface ApiInterfac.Java for defining the end points.
public interface ApiInterface { | |
@GET("movie/popular") | |
Call<MovieListResponse> getPopularMovies(@Query("api_key") String apiKey, @Query("page") int PageNo); | |
@GET("movie/{movie_id}") | |
Call<Movie> getMovieDetails(@Path("movie_id") int movieId, @Query("api_key") String apiKey, @Query("append_to_response") String credits); | |
} |
3. Implementing MVP
We are ready with our API call, now let’s check how we can use this api in MVP model. For this blog, I am going to explain you an example for movies list activity.
a. Create a contract interface MovieListContract.Java. This interface will contain all the methods that our View, Model and Presenter going to implements.
public interface MovieListContract { | |
interface Model { | |
interface OnFinishedListener { | |
void onFinished(List<Movie> movieArrayList); | |
void onFailure(Throwable t); | |
} | |
void getMovieList(OnFinishedListener onFinishedListener, int pageNo); | |
} | |
interface View { | |
void showProgress(); | |
void hideProgress(); | |
void setDataToRecyclerView(List<Movie> movieArrayList); | |
void onResponseFailure(Throwable throwable); | |
} | |
interface Presenter { | |
void onDestroy(); | |
void getMoreData(int pageNo); | |
void requestDataFromServer(); | |
} | |
} |
b. Now let’s create Model class MovieListModel.Java. This class will have actual business logic for fetching data from a server. This class will implement Model Interface from Contract Interface given above.
public class MovieListModel implements MovieListContract.Model { | |
private final String TAG = "MovieListModel"; | |
/** | |
* This function will fetch movies data | |
* @param onFinishedListener | |
* @param pageNo : Which page to load. | |
*/ | |
@Override | |
public void getMovieList(final OnFinishedListener onFinishedListener, int pageNo) { | |
ApiInterface apiService = | |
ApiClient.getClient().create(ApiInterface.class); | |
Call<MovieListResponse> call = apiService.getPopularMovies(API_KEY, pageNo); | |
call.enqueue(new Callback<MovieListResponse>() { | |
@Override | |
public void onResponse(Call<MovieListResponse> call, Response<MovieListResponse> response) { | |
List<Movie> movies = response.body().getResults(); | |
Log.d(TAG, "Number of movies received: " + movies.size()); | |
onFinishedListener.onFinished(movies); | |
} | |
@Override | |
public void onFailure(Call<MovieListResponse> call, Throwable t) { | |
// Log error here since request failed | |
Log.e(TAG, t.toString()); | |
onFinishedListener.onFailure(t); | |
} | |
}); | |
} | |
} |
If you have observed in getMovieList() method we are fetching data with the help of retrofit.
c. Let’s create an Activity MovieListActivity.Java which will act as View in our MVP. This class will handle all UI rendering and UI listeners. This class will implement View interface from our contract interface.
public class MovieListActivity extends AppCompatActivity implements MovieListContract.View, MovieItemClickListener, | |
ShowEmptyView { | |
private static final String TAG = "MovieListActivity"; | |
private MovieListPresenter movieListPresenter; | |
private RecyclerView rvMovieList; | |
private List<Movie> moviesList; | |
private MoviesAdapter moviesAdapter; | |
private ProgressBar pbLoading; | |
private FloatingActionButton fabFilter; | |
private TextView tvEmptyView; | |
private int pageNo = 1; | |
//Constants for load more | |
private int previousTotal = 0; | |
private boolean loading = true; | |
private int visibleThreshold = 5; | |
int firstVisibleItem, visibleItemCount, totalItemCount; | |
private GridLayoutManager mLayoutManager; | |
// Constants for filter functionality | |
private String fromReleaseFilter = ""; | |
private String toReleaseFilter = ""; | |
@Override | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.activity_movie_list); | |
getSupportActionBar().setTitle(getString(R.string.most_popular_movies)); | |
initUI(); | |
setListeners(); | |
//Initializing presenter | |
movieListPresenter = new MovieListPresenter(this); | |
movieListPresenter.requestDataFromServer(); | |
} | |
/** | |
* This method will initialize the UI components | |
*/ | |
private void initUI() { | |
rvMovieList = findViewById(R.id.rv_movie_list); | |
moviesList = new ArrayList<>(); | |
moviesAdapter = new MoviesAdapter(this, moviesList); | |
mLayoutManager = new GridLayoutManager(this, 2); | |
rvMovieList.setLayoutManager(mLayoutManager); | |
rvMovieList.addItemDecoration(new GridSpacingItemDecoration(2, dpToPx(this, 10), true)); | |
rvMovieList.setItemAnimator(new DefaultItemAnimator()); | |
rvMovieList.setAdapter(moviesAdapter); | |
pbLoading = findViewById(R.id.pb_loading); | |
fabFilter = findViewById(R.id.fab_filter); | |
tvEmptyView = findViewById(R.id.tv_empty_view); | |
} | |
/** | |
* This function will contain listeners for all views. | |
*/ | |
private void setListeners() { | |
rvMovieList.addOnScrollListener(new RecyclerView.OnScrollListener() { | |
@Override | |
public void onScrolled(RecyclerView recyclerView, int dx, int dy) { | |
super.onScrolled(recyclerView, dx, dy); | |
visibleItemCount = rvMovieList.getChildCount(); | |
totalItemCount = mLayoutManager.getItemCount(); | |
firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition(); | |
// Handling the infinite scroll | |
if (loading) { | |
if (totalItemCount > previousTotal) { | |
loading = false; | |
previousTotal = totalItemCount; | |
} | |
} | |
if (!loading && (totalItemCount - visibleItemCount) | |
<= (firstVisibleItem + visibleThreshold)) { | |
movieListPresenter.getMoreData(pageNo); | |
loading = true; | |
} | |
// Hide and show Filter button | |
if (dy > 0 && fabFilter.getVisibility() == View.VISIBLE) { | |
fabFilter.hide(); | |
} else if (dy < 0 && fabFilter.getVisibility() != View.VISIBLE) { | |
fabFilter.show(); | |
} | |
} | |
}); | |
fabFilter.setOnClickListener(new View.OnClickListener() { | |
@Override | |
public void onClick(View view) { | |
// Going to filter screen | |
Intent movieFilterIntent = new Intent(MovieListActivity.this, MovieFilterActivity.class); | |
movieFilterIntent.putExtra(KEY_RELEASE_FROM, fromReleaseFilter); | |
movieFilterIntent.putExtra(KEY_RELEASE_TO, toReleaseFilter); | |
startActivityForResult(movieFilterIntent, ACTION_MOVIE_FILTER); | |
} | |
}); | |
} | |
@Override | |
protected void onActivityResult(int requestCode, int resultCode, Intent data) { | |
if (requestCode == ACTION_MOVIE_FILTER) { | |
if (resultCode == Activity.RESULT_OK) { | |
// Checking if there is any data to filter | |
fromReleaseFilter = data.getStringExtra(KEY_RELEASE_FROM); | |
toReleaseFilter = data.getStringExtra(KEY_RELEASE_TO); | |
moviesAdapter.setFilterParameter(fromReleaseFilter, toReleaseFilter); | |
moviesAdapter.getFilter().filter(""); | |
} | |
} | |
} | |
@Override | |
public void showProgress() { | |
pbLoading.setVisibility(View.VISIBLE); | |
} | |
@Override | |
public void hideProgress() { | |
pbLoading.setVisibility(View.GONE); | |
} | |
@Override | |
public void setDataToRecyclerView(List<Movie> movieArrayList) { | |
moviesList.addAll(movieArrayList); | |
moviesAdapter.notifyDataSetChanged(); | |
// This will help us to fetch data from next page no. | |
pageNo++; | |
} | |
@Override | |
public void onResponseFailure(Throwable throwable) { | |
Log.e(TAG, throwable.getMessage()); | |
Toast.makeText(this, getString(R.string.communication_error), Toast.LENGTH_LONG).show(); | |
} | |
@Override | |
protected void onDestroy() { | |
super.onDestroy(); | |
movieListPresenter.onDestroy(); | |
} | |
@Override | |
public void onMovieItemClick(int position) { | |
if (position == -1) { | |
return; | |
} | |
Intent detailIntent = new Intent(this, MovieDetailsActivity.class); | |
detailIntent.putExtra(KEY_MOVIE_ID, moviesList.get(position).getId()); | |
startActivity(detailIntent); | |
} | |
@Override | |
public void showEmptyView() { | |
rvMovieList.setVisibility(View.GONE); | |
tvEmptyView.setVisibility(View.VISIBLE); | |
} | |
@Override | |
public void hideEmptyView() { | |
rvMovieList.setVisibility(View.VISIBLE); | |
tvEmptyView.setVisibility(View.GONE); | |
} | |
} |
d. Now here comes our Presenter, create a class MovieListPresenter.Java. This class will act as the mediator between View and Model. It will have a reference to both View and Model. This class will implement Presenter interface from our contract interface.
public class MovieListPresenter implements MovieListContract.Presenter, MovieListContract.Model.OnFinishedListener { | |
private MovieListContract.View movieListView; | |
private MovieListContract.Model movieListModel; | |
public MovieListPresenter(MovieListContract.View movieListView) { | |
this.movieListView = movieListView; | |
movieListModel = new MovieListModel(); | |
} | |
@Override | |
public void onDestroy() { | |
this.movieListView = null; | |
} | |
@Override | |
public void getMoreData(int pageNo) { | |
if (movieListView != null) { | |
movieListView.showProgress(); | |
} | |
movieListModel.getMovieList(this, pageNo); | |
} | |
@Override | |
public void requestDataFromServer() { | |
if (movieListView != null) { | |
movieListView.showProgress(); | |
} | |
movieListModel.getMovieList(this, 1); | |
} | |
@Override | |
public void onFinished(List<Movie> movieArrayList) { | |
movieListView.setDataToRecyclerView(movieArrayList); | |
if (movieListView != null) { | |
movieListView.hideProgress(); | |
} | |
} | |
@Override | |
public void onFailure(Throwable t) { | |
movieListView.onResponseFailure(t); | |
if (movieListView != null) { | |
movieListView.hideProgress(); | |
} | |
} | |
} |
f you take a look at our presenter it taking request from view for getting the list of movies and sending that request to model for processing.
Wooo! Now we know how we can use Retrofit and MVP in our project.
Demo Video
Run the app now you will see the following app running on your phone.
Nhận xét
Đăng nhận xét