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

Kích thước icon cho app Android và công cụ tạo icon của Google

Để hiển thị chuẩn theo các size màn hình thì chúng ta sẽ theo các kích thước sau: 36 × 36 (ldpi) – Low 48 × 48 (mdpi) – Medium 72 × 72 (hdpi) – High 96 × 96 (x-hdpi) – x-high 144 × 144 (xx-hdpi) 192 × 192 (xxx-hdpi) 512 × 512 (Google Play store) -> Kích thước này để làm ảnh demo cho App khi upload lên store. Khi tạo icon launcher cho app nếu tạo bằng các  Launcher icon generator  cửa Google thì khi cài vào điện thoại nó sẽ bé hơn so với các app khác vì google tự động cho thêm padding vào icon. Tránh điều này thì nên tự thiết kế bằng Photoshop sau đó dùng  cái này  để tạo thì sẽ to và đẹp hơn, nó tạo nhanh và đủ các kích thước chuẩn như bên trên kia. Nếu bạn muốn bo góc thì cũng làm bo góc ở trong photoshop trước sau đó mới dùng công cụ bên trên. Kiến thức liên quan đến đơn vị đo trong Android: pixel có thể hiểu là số điểm ảnh có trong 1 dot có hình vuông vì là ảnh bitmap mà. Ảnh đen trắng binary image thì 1 dot = 1 px = 1 bit (chỉ có trạng thá...

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

 

Get Current location using FusedLocationProviderClient in Android

Hello to coders, Previously we have taught you  how you get current location using GPS/Network Provider . Then android has revealed  FusedLocationProviderClient  under  GoogleApi . FusedLocationProviderClient is for interacting with the location using fused location provider. ( NOTE :   To use this feature, GPS must be turned on your device. For manually ask the user to turn on GPS, please check  next article ) So let’s get started for the tutorial for getting the current location. First, add a dependency for location by play services: implementation 'com.google.android.gms:play-services-location:15.0.1' Then define FusedLocationProviderClient: private FusedLocationProviderClient mFusedLocationClient; private TextView txtLocation; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout. activity_main ); this.txtLocation = (TextView) findViewById(R.id. txtLocat...