Arsitektur Model-View-Presenter (MVP) pada Aplikasi Android
Penggunaan architectural pattern pada sebuah perangkat lunak sangat penting untuk maintainability dan testability perangkat lunak tersebut. Pada kesempatan ini, saya akan membagikan ilmu mengenai arsitektur Model-View-Presenter (MVP) pada aplikasi Android.
Pertama-tama, saya akan menjelaskan alasan kami memilih MVP sebagai arsitektur aplikasi kami. Arsitektur yang sering diimplementasikan dalam pengembangan perangkat lunak cukup banyak. Pertama ada arsitektur MVC, view dapat diakses baik oleh presenter maupun model. Hal ini membuat tanggung jawab kelas view terlalu besar. Masalah ini dipecahkan pada arsitektur MVP, hubungan model dengan view diputus. Kelas presenter digunakan untuk menghubungkan antara view dengan model. Arsitektur MVP sangat terkenal untuk mengembangkan aplikasi Android. Aplikasi open source milik Google pun menggunakan arsitektur MVP.
Yang terakhir adalah arsitektur MVVM. Meskipun arsitektur ini sudah sangat terkenal di platform Windows. Pada platform Android, arsitektur ini masih baru. Perbedaan mencolok dari arsitektur ini adalah adanya data binding. Perubahan pada model menyebabkan sebuah event dikirim ke view-model dan view-model akan langsung memanipulasi view yang ter-bind dengannya. Google mulai mendukung arsitektur MVVM dengan merilis library Google Architecture Components. Sayangnya tutorial pengimplementasian arsitektur MVVM pada aplikasi Android masih sangat sedikit. Contoh resmi yang dirilis oleh Google pun belum clean dan masih buggy. Karena hal ini, kami memilih arsitektur MVP yang memiliki banyak tutorial dan contoh resmi dari Google yang sudah sangat clean.
Pada tutorial kali ini, kita akan mencoba mengimplementasikan arsitektur MVP untuk membuat sebuah fragment yang dapat menampilkan resep masakan dan mengeditnya. Untuk mengimplementasikannya, pertama-tama buat model dari sebuah resep masakan
public class Recipe {
private String mDishName;
private String mIngredients;
private String mDirections;
public Recipe(String dishName,
String ingredients,
String directions) {
mDishName = dishName;
mIngredients = ingredients;
mDirections = directions;
}
public String getDishName() {
return mDishName;
}
public void setDishName(String dishName) {
mDishName = dishName;
}
public String getIngredients() {
return mIngredients;
}
public void setIngredients(String ingredients) {
mIngredients = ingredients;
}
public String getDirections() {
return mDirections;
}
public void setDirections(String directions) {
mDirections = directions;
}
}
Lalu buat interface kontrak view dan presenter fragment tersebut.
public interface RecipeContract {
interface View {
void setPresenter(Presenter presenter);
void showDishName(String dishName);
void showIngredients(String ingredients);
void showDirections(String directions);
void showEmptyRecipeError();
}
interface Presenter {
void start();
void setDishName(String dishName);
void setIngredients(String ingredients);
void setDirections(String directions);
}
}
Lalu buat kontrak data source resep-resep. Karena ada kemungkinan data source tersebut akan diakses oleh presenter lain, maka kita pisahkan kontrak data source dari kontrak view dan presenter.
public interface RecipeDataSource {
interface GetRecipeCallback {
void onRecipeLoaded(Recipe recipe);
void onDataIsNotAvailable();
}
void getRecipe(String recipeId, GetRecipeCallback callback);
void setDishName(String recipeId, String dishName);
void setIngredients(String recipeId, String ingredients);
void setDirections(String recipeId, String directions);
}
Pada tutorial ini, kita akan menyimpan resep-resep di dalam sebuah HashMap
yang hanya akan disimpan dalam RAM. Untuk itu, implementasi kelas RecipeDataSource
pada kelas RecipeInMemoryDataSource
seperti berikut
public class RecipeInMemoryDataSource
implements RecipeDataSource { private static RecipeInMemoryDataSource sInstance; private Map<String, Recipe> mRecipeMap = new HashMap<>(); public static RecipeInMemoryDataSource getInstance() {
if (sInstance == null) {
sInstance = new RecipeInMemoryDataSource();
}
return sInstance;
} /**
* Prevent direct instantiation.
*/
private RecipesInMemoryDataSource() {
} @Override
public void getRecipe(String recipeId,
GetRecipeCallback callback) {
Recipe recipe = mRecipeMap.get(recipeId); if (recipe != null) {
callback.onRecipeLoaded(recipe);
} else {
callback.onDataIsNotAvailable();
}
} @Override
public void setDishName(String recipeId, String dishName) {
Recipe recipe = mRecipeMap.get(recipeId); if (recipe != null) {
recipe.setDishName(dishName);
} else {
recipe = new Recipe(dishName, null, null);
mRecipeMap.put(recipeId, recipe);
}
} @Override
public void setIngredients(String recipeId,
String ingredients) {
Recipe recipe = mRecipeMap.get(recipeId); if (recipe != null) {
recipe.setIngredients(ingredients);
} else {
recipe = new Recipe(null, ingredients, null);
mRecipeMap.put(recipeId, recipe);
}
} @Override
public void setDirections(String recipeId, String directions) {
Recipe recipe = mRecipeMap.get(recipeId); if (recipe != null) {
recipe.setDirections(directions);
} else {
recipe = new Recipe(null, null, directions);
mRecipeMap.put(recipeId, recipe);
}
}
}
Lalu implementasi kontrak RecipeContract.Presenter
pada kelas RecipePresenter
public class RecipePresenter implements RecipeContract.Presenter { private String mRecipeId;
private RecipeDataSource mRecipeDataSource;
private RecipeContract.View mView; public RecipePresenter(String recipeId,
RecipeDataSource recipeDataSource,
RecipeContract.View view) {
mRecipeId = recipeId;
mRecipeDataSource = recipeDataSource;
mView = view;
} @Override
public void start() {
mRecipeDataSource.getRecipe(mRecipeId,
new RecipeDataSource.GetRecipeCallback() {
@Override
public void onRecipeLoaded(Recipe recipe) {
mView.showDishName(recipe.getDishName());
mView.showIngredients(recipe.getIngredients());
mView.showDirections(recipe.getDirections());
} @Override
public void onDataIsNotAvailable() {
mView.showEmptyRecipeError();
}
});
} @Override
public void setDishName(String dishName) {
mRecipeDataSource.setDishName(mRecipeId, dishName);
} @Override
public void setIngredients(String ingredients) {
mRecipeDataSource.setIngredients(mRecipeId, ingredients);
} @Override
public void setDirections(String directions) {
mRecipeDataSource.setDirections(mRecipeId, directions);
}
}
Lalu implementasi kontrak RecipeContract.View
pada kelas RecipeFragment
public class RecipeFragment extends Fragment
implements RecipeContract.View {
private RecipeContract.Presenter mPresenter;
private ProgressBar mLoadingIndicator;
private EditText mDishName;
private EditText mIngredients;
private EditText mDirections;
public static RecipeFragment newInstance() {
return new RecipeFragment();
}
public RecipeFragment() {
// Required empty public constructor
}
@Override
public void onResume() {
super.onResume();
mPresenter.start();
}
@Override
public void setPresenter(Presenter presenter) {
mPresenter = presenter;
}
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_recipe,
container, false); mLoadingIndicator = root.findViewById(R.id
.loading_indicator);
mDishName = root.findViewById(R.id.dish_name);
mIngredients = root.findViewById(R.id.ingredients);
mDirections = root.findViewById(R.id.directions); Button saveButton = root.findViewById(R.id.button_save);
saveButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mPresenter.setDishName(mDishName.getText()
.toString());
mPresenter.setIngredients(mIngredients.getText()
.toString());
mPresenter.setDirections(mDirections.getText()
.toString());
}
}); return root;
}
@Override
public void showDishName(String dishName) {
mDishName.setText(dishName);
}
@Override
public void showIngredients(String ingredients) {
mIngredients.setText(ingredients);
}
@Override
public void showDirections(String directions) {
mDirections.setText(directions);
}
@Override
public void showEmptyRecipeError() {
Snackbar.make(getString(R.string.empty_recipe_message),
Snackbar.LENGTH_LONG).show();
}
}
Itulah cara mengimplementasikan arsitektur MVP pada aplikasi Android. Untuk belajar lebih lanjut, silahkan eksplorasi repositori-repositori berikut
- https://github.com/googlesamples/android-architecture/tree/todo-mvp
- https://github.com/MindorksOpenSource/android-mvp-architecture
Selamat mencoba dan sampai jumpa!