Implementasi MVP (Model-View-Presenter)

This image has an empty alt attribute; its file name is FlowMVP.png
The MVP Flow

Start writing or type / to choose a block

Write preformatted text…

Hi Coder !

Pada artikel ini kita akan coba mengimplementasikan salah satu pattern yang paling populer yang sangat banyak sekali digunakan Developer salam pengembangan aplikasi berbasi Android.

Sebelum kita mulai untuk mengimplementasikan MVP (Model-View-Presenter) ada beberapa point berikut ini :

What should you have ?

  1. Have Experience Java on Android Before
  2. Have Experience OOP Java

What will you learn ?

  1. Implementation MVP (Model-View-Presenter) ?
  2. Understanding how MVP (Model-View-Presenter) works ?
  3. Why MVP (Model-View-Presenter) ?

What will you do ?

  1. Calling REST API (GET) use Retrofit

The Power of MVP

  1. Memudahkan kita untuk handle lifecycle Activity/Fragment di Android
  2. Model (Data Layer) yang memegang peranan penting untuk menyediakan data yang kita butuhkan
  3. View yang menampilkan data atau interaksi dari user melakukan event tertentu
  4. Presenter berkomunikasi dengan Model dan View untuk mengatur presentation logic
  5. Kode nya keren guys !

Pertama kita persiapkan dependencies yang kita butuhkan terlebih dahulu :

// circle images
implementation 'de.hdodenhof:circleimageview:3.0.0'

// loading images
implementation 'com.squareup.picasso:picasso:2.71828'

// koneksi
implementation 'com.squareup.retrofit2:retrofit:2.6.0'
implementation 'com.squareup.retrofit2:converter-gson:2.6.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.0.1'

implementation 'androidx.recyclerview:recyclerview:1.1.0-beta02'
//noinspection GradleCompatible
implementation 'com.android.support:design:28.0.0'

// injection views
implementation 'com.jakewharton:butterknife:8.0.0'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.0.0'

Kedua kita coba definisikan pada gradle URL nya untuk mengambil data dari server :

buildConfigField "String", "SERVER_URL", '\"http://desa.digital/appchat/json/\"'
buildConfigField "String", "IMAGES", '"http://desa.digital/appchat/userfiles/images/"'

Ketiga buat layout pada activity_main.xml dan buat code nya seperti berikut ini :

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".main.MainActivity">

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerview"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:ignore="MissingConstraints">

</androidx.recyclerview.widget.RecyclerView>

</androidx.constraintlayout.widget.ConstraintLayout>

Keempat buat layout list_item_user.xml dan buat code nya seperti ini :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:orientation="horizontal">

<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/iv_item_user"
android:layout_width="65dp"
android:layout_height="70dp"
android:src="@color/colorPrimaryDark"
android:layout_marginLeft="10dp"/>

<RelativeLayout
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginRight="1dp"
android:paddingLeft="16dp">

<TextView
android:id="@+id/tv_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:text="Name"
android:textColor="#444444"
android:textSize="16dp"
android:textStyle="bold"/>

<TextView
android:id="@+id/tv_level"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/tv_name"
android:text="Level"
android:textColor="#888888"/>

<TextView
android:id="@+id/tv_timestamp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:text="12:00 AM"
android:textSize="10dp"/>

</RelativeLayout>

</LinearLayout>

Supaya code kita lebih clean saya membuat 2 buah class contract didalam package base yang nantinya akan digunakan oleh semua masing-masing Presenter & View :

package com.mvp.pattern.base;

public interface BasePresenter<T extends BaseView> {
void onAttach(T view);
void onDettach();
}
package com.mvp.pattern.base;

public interface BaseView {
void onAttachView();
void onDettachView();
}

Selanjutnya kita akan menggunakan postman untuk testing API Development kita dengan URL dan end_point seperti gambar dibawah ini :

This image has an empty alt attribute; its file name is Screen-Shot-2019-08-09-at-16.04.34-1024x640.png

Selanjutnya buat package model untuk menampung response data dari postman yang udah kita test di Postman dan code hasil generate menggunakan pojo akan menjadi seperti ini :

package com.mvp.pattern.main.model;

import com.google.gson.annotations.SerializedName;

public class DataItem{

@SerializedName("password")
private String password;

@SerializedName("nama")
private String nama;

@SerializedName("foto")
private String foto;

@SerializedName("level")
private String level;

@SerializedName("tgl")
private String tgl;

@SerializedName("id")
private String id;

public void setPassword(String password){
this.password = password;
}

public String getPassword(){
return password;
}

public void setNama(String nama){
this.nama = nama;
}

public String getNama(){
return nama;
}

public void setFoto(String foto){
this.foto = foto;
}

public String getFoto(){
return foto;
}

public void setLevel(String level){
this.level = level;
}

public String getLevel(){
return level;
}

public void setTgl(String tgl){
this.tgl = tgl;
}

public String getTgl(){
return tgl;
}

public void setId(String id){
this.id = id;
}

public String getId(){
return id;
}
}
package com.mvp.pattern.main.model;

import com.google.gson.annotations.SerializedName;

import java.util.List;

public class ResponseUser{

@SerializedName("data")
private List<DataItem> data;

@SerializedName("isSuccess")
private boolean isSuccess;

public void setData(List<DataItem> data){
this.data = data;
}

public List<DataItem> getData(){
return data;
}

public void setIsSuccess(boolean isSuccess){
this.isSuccess = isSuccess;
}

public boolean isIsSuccess(){
return isSuccess;
}
}

Selanjutnya buat package utils dan buat class MyFunction dan buat code nya seperti ini :

package com.mvp.pattern.main.utils;

import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.Toast;
import androidx.annotation.LayoutRes;
import com.squareup.picasso.Picasso;

import java.io.File;

public class MyFunction {

public static void toast(Context ctx, String message) {
Toast.makeText(ctx, message, Toast.LENGTH_SHORT).show();
}

public static void longToast(Context ctx, String message) {
Toast.makeText(ctx, message, Toast.LENGTH_SHORT).show();
}

public static void intent(Context ctx, Class intent) {
ctx.startActivity(new Intent(ctx, intent));
}

public static void displayImagePreview(ImageView iv, File file) {
Picasso.get().load(file).into(iv);
}

public static void displayImagesOriginal( ImageView iv, String url) {
Picasso.get().load(url).into(iv);
}

public static void displayImagesThumbnail(Context ctx, ImageView iv, String url, float thumbnail) {

}

public static View inflate(Context context, @LayoutRes int layout, ViewGroup vg, boolean attachToRoot) {
return LayoutInflater.from(context).inflate(layout, vg, attachToRoot);
}
}

Selanjutnya buat package networking. Package ini digunakan untuk membuat 1 buah class dan 1 buah class interface sebagai koneksi kita ke url dan end_point dari url server kita dan buat code nya seperti ini :

package com.mvp.pattern.networking;

import com.mvp.pattern.BuildConfig;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

import java.util.concurrent.TimeUnit;

public class Network {

/**
* this function return logging your request & response headers & body
* */
private static HttpLoggingInterceptor provideLoggingInterceptor() {
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
if (BuildConfig.DEBUG) {
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
} else {
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.NONE);
}
return loggingInterceptor;
}

/**
* this function return sets the default for new connections
* the default value is 10 seconds
* */
private static OkHttpClient provideOkHttpClient() {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(provideLoggingInterceptor())
.retryOnConnectionFailure(true)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build();
return okHttpClient;
}

/**
* this function for request & response connections to base url
* */
private static Retrofit provideRetrofit() {
return new Retrofit.Builder()
.baseUrl(BuildConfig.SERVER_URL)
.client(provideOkHttpClient())
.addConverterFactory(GsonConverterFactory.create())
.build();
}

public static Routes provideApiService() {
return provideRetrofit().create(Routes.class);
}
}
package com.mvp.pattern.networking;

import com.mvp.pattern.main.model.ResponseUser;
import retrofit2.Call;
import retrofit2.http.*;

public interface Routes {

@GET("get-pengguna-view.php")
Call<ResponseUser> getListUser();
}

Selanjutnya buat class adapter dan buat code nya menjadi seperti ini :

package com.mvp.pattern.main;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindView;
import butterknife.ButterKnife;
import com.mvp.pattern.BuildConfig;
import com.mvp.pattern.R;
import com.mvp.pattern.main.model.DataItem;
import com.mvp.pattern.main.utils.MyFunction;
import de.hdodenhof.circleimageview.CircleImageView;

import java.util.ArrayList;
import java.util.List;

public class UserAdapter extends RecyclerView.Adapter<UserAdapter.ViewHolder> {

Context context;
List<DataItem> dataItems = new ArrayList<>();

public UserAdapter(Context context) {
this.context = context;
}

public void setData(List<DataItem> data) {
dataItems.clear();
dataItems.addAll(data);
notifyDataSetChanged();
}

@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup vg, int viewType) {
View view = MyFunction.inflate(context, R.layout.list_item_user, vg, false);
return new ViewHolder(view);
}

@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.bind(dataItems.get(position));
}

@Override
public int getItemCount() {
if (dataItems == null) return 0;
else return dataItems.size();
}

public class ViewHolder extends RecyclerView.ViewHolder {

@BindView(R.id.iv_item_user)
CircleImageView ivItemUser;
@BindView(R.id.tv_name)
TextView tvName;
@BindView(R.id.tv_timestamp)
TextView tvTimestamp;
@BindView(R.id.tv_level)
TextView tvLevel;

public ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}

private void bind(DataItem data) {
MyFunction.displayImagesOriginal(ivItemUser, BuildConfig.IMAGES + data.getFoto());
tvName.setText(data.getNama());
tvLevel.setText(data.getLevel());
tvTimestamp.setText(data.getTgl());
}
}
}

Selanjutnya buat 1 buah class interface dengan nama User Contract dan buat code nya seperti ini :

package com.mvp.pattern.main;

import com.mvp.pattern.base.BaseView;
import com.mvp.pattern.main.model.DataItem;
import java.util.List;

public interface UserContract {

// akan di implements di class presenter
interface PresenterInterface {
// method ini digunakan di presenter
// untuk request data ke API
void onFetchListUser();
}

// akan di implements di activity atau fragment
// akan di invoke (deklarasi & inisialisasi) di presenter
// supaya bisa dipanggil dalam class presenter
interface ViewInterface extends BaseView {
// method ini digunakan untuk melempar data
// ke aktivity atau fragment untuk menyampaikan data nya
// dan akan ditampilkan ke View
void onShowListUser(List<DataItem> data);
void onShowListEmpty();
void isError(String message);
}
}

Selannjutnya buat class UserPresenter dan buat code nya seperti ini :

package com.mvp.pattern.main;

import android.util.Log;
import com.mvp.pattern.base.BasePresenter;
import com.mvp.pattern.main.model.DataItem;
import com.mvp.pattern.main.model.ResponseUser;
import com.mvp.pattern.networking.Network;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

import java.util.List;

public class UserPresenter implements BasePresenter<UserContract.ViewInterface>, UserContract.PresenterInterface {

UserContract.ViewInterface chatView;

public UserPresenter(UserContract.ViewInterface chatView) {
this.chatView = chatView;
}

@Override
public void onAttach(UserContract.ViewInterface view) {
chatView = view;
}

@Override
public void onDettach() {
chatView = null;
}

@Override
public void onFetchListUser() {
Network.provideApiService().getListUser().enqueue(new Callback<ResponseUser>() {
@Override
public void onResponse(Call<ResponseUser> call, Response<ResponseUser> response) {

// cek if response success
if (response != null && response.isSuccessful()) {

// if success something to do
boolean isSuccess = response.body().isIsSuccess();
List<DataItem> data = response.body().getData();

if (isSuccess) {
// sampaikan data nya ke view melalui class contract interface
chatView.onShowListUser(data);
} else {
// sampaikan data nya ke view melalui class contract interface
chatView.onShowListEmpty();
}

} else {
// sampaikan data nya ke view melalui class contract interface
chatView.isError(response.errorBody().toString());
}
}

@Override
public void onFailure(Call<ResponseUser> call, Throwable t) {
// sampaikan data nya ke view melalui class contract interface
Log.d("TAG", t.getMessage());
chatView.isError(t.getMessage());
}
});
}
}

Selanjutnya pada class MainActivity kita buat code nya seperti ini :

package com.mvp.pattern.main;

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindView;
import butterknife.ButterKnife;
import com.mvp.pattern.R;
import com.mvp.pattern.main.model.DataItem;
import com.mvp.pattern.main.utils.MyFunction;

import java.util.List;

public class MainActivity extends AppCompatActivity implements UserContract.ViewInterface {

@BindView(R.id.recyclerview)
RecyclerView recyclerview;

UserPresenter presenter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
initPresenter();
onAttachView();
presenter.onFetchListUser();
}

private void initPresenter() {
presenter = new UserPresenter(this);
}

@Override
public void onShowListUser(List<DataItem> data) {
UserAdapter adapter = new UserAdapter(this);
adapter.setData(data);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recyclerview.setHasFixedSize(true);
recyclerview.setLayoutManager(layoutManager);
recyclerview.setAdapter(adapter);
}

@Override
public void onShowListEmpty() {
MyFunction.toast(this, getString(R.string.isEmptyListUser));
}

@Override
public void isError(String message) {
MyFunction.toast(this, message);
}

@Override
public void onAttachView() {
presenter.onAttach(this);
}

@Override
public void onDettachView() {
presenter.onDettach();

}

@Override
protected void onStart() {
onAttachView();
super.onStart();
}

@Override
protected void onDestroy() {
onDettachView();
super.onDestroy();
}
}

Sebelum dijalankan ada baiknya kita debug dulu request data ke API nya supaya untuk memastikan request data nya berhasil dan tidak mengalami null pada data yang kita request. Pada tahapan debugging ini akan mengurangi kemungkinan null data sebelum disampaikan kedalam sebuah View melalui class Contract Interface kita yaitu UserContract.ViewInterface {} dan hasil debugging nya akan terlihat seperti ini :

This image has an empty alt attribute; its file name is Screen-Shot-2019-08-09-at-16.32.27-1024x640.png

Hasil debugging untuk request data ke API kita mengindikasikan success seperti pada gambar diatas :

Selanjutnya silahkan run pada device kalian masing-masing ya semoga berhasil. Dan jika ada pertanyaan silahkan tinggalkan pada kolom komentar ya !

Happy Coding !

Open chat
1
Hello, welcome to IMA Studio 🙌
Chat with us ! We are ready to help you :-)
Powered by