Chuyển đến nội dung chính

Working with MVP and Retrofit 2.0 in Android

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.

MVP (Model-View-Presenter)

The MVP stands for Model View Presenter. In this architecture, each layer is defined to do certain tasks.







Fig 1. MVP Architecture

  • 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








Fig 2. Retrofit

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.







Fig 3. Movie App Screenshot

Project structure:

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







Fig 4. Project Structure

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

Bài đăng phổ biến từ blog này

NHỮNG WIDGET THƯỜNG DÙNG TRONG FLUTTER

 https://baoflutter.com/nhung-widget-thuong-dung-trong-flutter/ Trong bài viết trước về  cách xây dựng màn hình ứng dụng Flutter , các bạn biết rằng các màn hình ứng dụng được tạo ra bởi các widget ghép lại với nhau. Vì vậy việc hiểu và sử dụng các Widget là rất quan trọng. Vì vậy trong bài viết này, tôi sẽ giới thiệu cho các bạn về những widget quan trọng trong Flutter. Hầu hết các Widget đều có các phần sau đây: + Đặc tính của Widget như : color, theme, height, weight, decoration, onTap, onPressed + Liên kết với các Widget khác với từ khoá: child, children, home hoặc body Ví dụ : 1 2 3 4 5 6 Container ( color : Colors . blue , height : 300 , weight : 300 , child : Text ( "Widget con" ) , ) Khi làm một số App cơ bản, bạn sẽ nắm chắc được cách sử dụng các Widget hay dùng. MaterialApp – Là widget rất liện lợi, cung cấp các widget cho việc xây dựng ứng dụng sử dụng thư viện Material Design UI của google. – Widget này được sử dụng trong hàm build đầu tiên của hầu hết các ứn...

Các bước cơ bản sử dụng Retrofit để thao tác với API và MVP

 Cài đặt  Retrofit //Retrofit implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' Cài đặt Gson implementation 'com.google.code.gson:gson:2.8.9' Cài thư viện Okhttp implementation 'com.squareup.okhttp3:okhttp:3.12.0' Sử dụng Gson ở trong project: 1. Tạo class App import android.app.Application ; import com.google.gson.Gson ; public class App extends Application { private static App mSelf ; private Gson mGSon ; public static App self () { return mSelf ; } @Override public void onCreate () { super .onCreate() ; mSelf = this; mGSon = new Gson() ; } public Gson getGSon () { return mGSon ; } } 2. Chỉnh file AndroidManifest: <? xml version ="1.0" encoding ="utf-8" ?> <manifest xmlns: android ="http://schemas.android.com/apk/res/android" xmlns: tools ="http://schemas.android....

Cấu trúc cơ bản layout trong Flutter