Why use MVP?
In Android, we have a problem arising from the fact that Android activities are closely coupled to both UI and data access mechanisms. We can find extreme examples such as CursorAdapter, which mixes adapters, which are part of the view, with cursors, something that should be relegated to the depths of data access layer.
For an application to be easily extensible and maintainable, we need to define well-separated layers. What do we do tomorrow if, instead of retrieving the same data from a database, we need to do it from a web service? We would have to redo our entire view.
MVP makes views independent from our data source. We divide the application into at least three different layers, which lets us test them independently. With MVP we take most of the logic out from the activities so that we can test it without using instrumentation tests.
Triển khai MVP trên Android
Có rất nhiều biến thể cũng như phương pháp triển khai MVP, tất cả mọi người có thể điều chỉnh mô hình này tùy theo nhu cầu và cách họ cảm thấy thoải mái hơn. Các mô hình này, về cơ bản khác nhau ở số lượng chức năng mà tầng
Presenter
đảm nhận.
Một view nhận tương tách từ người dùng disable hoặc enable progress bar, liệu có nên giao nhiệm vụ này cho một
presenter
? Một Activity sẽ nhận sự kiện click vào nút settings trên ActionBar hay sự kiện này sẽ được một đối tượng
presenter
trong Activity đảm nhận?
Những câu hỏi như vậy tạo ra nhiều cách để chúng ta triển khai mô hình MVP, và trên thực tế chưa có một tiêu chuẩn chính xác nào được đưa ra. Vì vậy dưới đây tôi xin trình bày một trong những phương pháp đó.
Tầng trình diễn – Presenter
Tầng trình diễn có trách nhiệm như một middle-man giữa
View
và
Model
. Nó lấy dữ liệu từ
Model
, định dạng và trả về cho
View
. Nhưng không giống như MVC, nó cũng quyết định những gì sẽ xảy ra khi người dùng tương tác với
View
, hay nói cách khác nó hàm chứa logic ứng dụng.
Tầng logic dữ liệu – Model
Trong một ứng dụng với thiết kế kiến trúc tốt, mô hình này sẽ chỉ là một
gateway
giữa tầng
domain
và tầng
business logic
. Trong mô hình Clean Architecture của Uncle Bob, Model sẽ là một interactor thự thi một use case. Để đơn giản, ở đây Model đơn thuần được nhìn nhận như một data source – cung cấp dữ liệu chúng ta muốn hiển thị trong giao diện ứng dụng.
Tầng giao diện – View
View, thường được
implement
bởi một Activity (hoặc có thể là một Fragment, một View … tùy thuộc vào cấu trúc ứng dụng), Activity này sẽ chứa một thuộc tính là một lớp
Presenter
. Lý tưởng nhất
Presenter
nên được cung cấp bởi một Dependency Injection framewok như Dagger, nhưng trong trường hợp ứng dụng không sử dụng một thư viện hay framework như vậy ta hoàn toàn có thể tạo ra các đối tượng
Presenter
này.
Sau đây là ví dụ triển khai một màn hình Login trong Android:
- Lớp LoginActivity implements LoginView interface để hiển thị kết quả tới người dùng, đồng thời một thuộc tính LoginPresenter chịu trách nhiệm tiếp nhận và thực thi logic ứng dụng (ở đây là xác thực người dùng) và trả
public class LoginActivity extends Activity implements LoginView, View.OnClickListener { private ProgressBar progressBar; private EditText username; private EditText password; private LoginPresenter presenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); progressBar = (ProgressBar) findViewById(R.id.progress); username = (EditText) findViewById(R.id.username); password = (EditText) findViewById(R.id.password); findViewById(R.id.button).setOnClickListener(this); presenter = new LoginPresenterImpl(this); } @Override public void showProgress() { progressBar.setVisibility(View.VISIBLE); } @Override public void hideProgress() { progressBar.setVisibility(View.GONE); } @Override public void setUsernameError() { username.setError(getString(R.string.username_error)); } @Override public void setPasswordError() { password.setError(getString(R.string.password_error)); } @Override public void navigateToHome() { startActivity(new Intent(this, MainActivity.class)); finish(); } @Override public void onClick(View v) { presenter.validateCredentials(username.getText().toString(), password.getText().toString()); } }
- LoginView là một lớp giao diện cho phép phương thức hiển thị có thể đuợc thực thi hay hoán đổi dễ dàng trên nhiều Activity khác nhau.
public interface LoginView { public void showProgress(); public void hideProgress(); public void setUsernameError(); public void setPasswordError(); public void navigateToHome(); }
- Tương tự như LoginView, LoginPresenter cũng giúp cho việc triển khai nhiều logic trên cùng một Activity hay hoán đổi các logic này cho nhau thông qua việc khởi tạo một lớp Implement khác.
public interface LoginPresenter { public void validateCredentials(String username, String password); }
- Lớp OnLoginFinishedListener ở đây được sử dụng như một custom listener cho phép đa dạng hóa các phương thức hiển thị kết quả tới người dùng.
public interface OnLoginFinishedListener { public void onUsernameError(); public void onPasswordError(); public void onSuccess(); }
- Giống như với OnLoginFinishedListener, LoginInteractor cũng cho phép custom các phương thức tương tác của người dùng với ứng dụng.
public interface LoginInteractor { public void login(String username, String password, OnLoginFinishedListener listener); }
- Và cuối cùng là các lớp implement, thực thi logic ứng dụng, phương thức hiển thị kết quả cũng như phương thức tiếp nhận tương tác người dùng.
public class LoginPresenterImpl implements LoginPresenter, OnLoginFinishedListener { private LoginView loginView; private LoginInteractor loginInteractor; public LoginPresenterImpl(LoginView loginView) { this.loginView = loginView; this.loginInteractor = new LoginInteractorImpl(); } @Override public void validateCredentials(String username, String password) { loginView.showProgress(); loginInteractor.login(username, password, this); } @Override public void onUsernameError() { loginView.setUsernameError(); loginView.hideProgress(); } @Override public void onPasswordError() { loginView.setPasswordError(); loginView.hideProgress(); } @Override public void onSuccess() { loginView.navigateToHome(); } }
public class LoginInteractorImpl implements LoginInteractor { @Override public void login(final String username, final String password, final OnLoginFinishedListener listener) { // Mock login. I'm creating a handler to delay the answer a couple of seconds new Handler().postDelayed(new Runnable() { @Override public void run() { boolean error = false; if (TextUtils.isEmpty(username)){ listener.onUsernameError(); error = true; } if (TextUtils.isEmpty(password)){ listener.onPasswordError(); error = true; } if (!error){ listener.onSuccess(); } } }, 2000); } }
Dễ thấy ở đây, việc thay đổi logic ứng dụng, phương thức hiển thị hay cách thức tương tác là hết sức dễ dàng bằng việc thêm các lớp Interface, Implement khác hay viết các lớp wrapper các lớp sẵn có. Điều này càng dễ dàng hơn trong Java 8 khi một dạng phương thức mới – phương thức
default
được áp dụng. Tôi sẽ nói tới dạng phương thức này trong một bài viết sớm nhất.
Lý do nên dùng
-
Điều đầu tiên mà chúng ta có thể dễ dàng nhận thấy là MVP phân chia các lớp một cách rành mạch so với MVC. Trong mô hình MVC, View và Model có thể tương tác lẫn nhau tức là chúng ta thường có xu hướng đưa nhiều các xử lý bên ngoài vào View. Hệ quả là View sẽ chứa rất nhiều các xử lý liên quan đến business logic và data logic. Và như vậy chúng ta cần phải phân tách 2 lớp xử lý này một cách rành mạch giống như MVP đã làm.
-
Trong quá trình phát triển phần mềm, View là lớp mà có khả năng sẽ thay đổi nhiều nhất nên việc phân tách sẽ giúp thay đổi View một cách độc lập mà không ảnh hướng đến các thành phần khác.
-
Với mô hình MVP, việc viết Unit Tests sẽ đơn giản hơn rất nhiều. Bởi vì giờ đây Presenter và Model layer sẽ thuần là code java xử lý logic
-
Có thể giúp giảm thiểu lượng code trong ứng dụng cũng như gộp các xử lý, logic trong ứng dụng vào cùng một nơi.
Conclusion
Separating interface from logic in Android is not easy, but the MVP pattern makes it easier to prevent our activities end up degrading into very coupled classes consisting of hundreds or even thousands of lines. In large Apps, it is essential to organize our code well. Otherwise, it becomes impossible to maintain and extend.
Nowadays, there are other alternatives like MVVM, and I will create new articles comparing them and helping with the migration. So keep tuned!
Remember you have the repository, where you can take a look at the code in both Kotlin and Java
And if you want to learn more about Kotlin, check the sample App of my Kotlin for Android Developers book, or take a look at the online course.[/et_pb_text][/et_pb_column][/et_pb_row][/et_pb_section]
MVP Architecture with Example in Android
In this blog, we will take an example of a login screen in Android and cover up MVP very simply.
As shown in the above screen we have the Username and Password field and a Login button.
If the user enters the username and password and tries to log in we will show a success or error message in a text view below the button.
Here we will have 3 parts as MVP says
- Model part-> Contains logic
- View Part-> Contains UI part
- Presenter Part-> Responsible for communication between Model and View
Here is the project structure of our application
Let’s Discuss 1 By 1 in Detail
The View part is responsible for getting the user’s input and sending to the Model
Code of MainActivity
class MainActivity : AppCompatActivity(), LoginResultListener { //declaration of the views private lateinit var etUserName: EditText private lateinit var etPassword: EditText private lateinit var btnLogin: Button private lateinit var tvResult: TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) initUI() } private fun initUI() { //Binding the xml with kotlin code etUserName = findViewById(R.id.etUserName) etPassword = findViewById(R.id.etPassword) btnLogin = findViewById(R.id.btnLogin) tvResult = findViewById(R.id.tvResult) //btn click listener btnLogin.setOnClickListener { //Creating the object of presenter and passing reference of login result listener var presenter = LoginModel(this) // passing user name and password to login model by do login method presenter.doLogin(etUserName.text.toString(), etPassword.text.toString()) } } override fun onSuccess(result: String) { tvResult.text = result } override fun onFail(errorMessage: String) { tvResult.text = errorMessage } }
LoginResultListener is an interface which is responsible for listening to the result status we will send the result from the model to view by using this interface.
interface LoginResultListener { fun onSuccess(result: String) fun onFail(errorMessage: String) }
The view part is over now
The presenter part is responsible for presenting the data to model
The presenter has a doLogin method which will be implemented by Login Model and will have the logic for valid user login,In do login method we are sending the username and password entered by the user.
@FunctionalInterface interface LoginPresenter { fun doLogin(userName: String, password: String) }
Model Part is having the logic of valid-user
In the Login model we are passing the instance of LoginResultListener which will help us to send the data back to the UI again if the user has entered the correct username and password we will send success else we will send the fail message.
class LoginModel(var loginResult: LoginResultListener) : LoginPresenter { override fun doLogin(userName: String, password: String) { if (userName == “Aalishan” && password == “12345”) { loginResult.onSuccess(“Login Success Welcome $userName”) } else { loginResult.onFail(“Provided credentials are wrong”) } } }
We have completed all the coding parts here and if you want to have the source code you can download it from here and play around.
https://github.com/Aalishan565/MVP-Pattern-Kotlin-Android
MVP Pattern trong Android
Bài đăng này đã không được cập nhật trong 5 năm
Mở đầu.
Hello mọi người ^^. Hôm nay mình sẽ trình bày một chút hiểu biết của mình về kiến trúc MVP. Nó là một kiến trúc thường dùng trong lập trình, cũng không còn mới nữa nhưng hiện đang được các Developers rất thích dùng, đặc biệt là trong Android như chính bản thân mình. Let’s go!
MVP là gì?
2.1 Nguồn gốc.
MVP là viết tắt của model-view-presenter. Là một kiến trúc phần mềm ra đời từ những năm 1990 tại công ty Taligent ở mỹ. Taligent là một công ty bao gồm sự hợp tác của 3 đại gia công nghệ lớn là Apple, IBM và Hewlett-Packard. MVP ban đầu là kiến trúc cơ bản để phát triển ứng dụng trong môi trường CommonPoint dựa trên C++ và sau đó chuyển sang Java. Đến năm 1998 thì Taligent giải thể.
Năm 2006, Microsoft bắt đầu kết hợp vào documentation và examples cho lập giao diện người dùng (User Interface – UI) trong .NET framwork
Từ khi xuất hiện cho đến nay MVP pattern có rất nhiều biến thể. Vậy nó là gì?
2.2 MVP là gì?
MVP là một User Interface Architectural Pattern (kiến trúc giao diện người dùng) được thiết kế để tạo điều kiện cho Automated Unit Testing và cải tiến Separation of Concerns trong việc trình bày logic (presentation logic).
Thế thì Automated Unit Testing là gì? Hiểu một cách đơn giản là:
Thứ nhất Unit Testing là một quá trình phát triển phần mềm, trong đó có các phần nhỏ nhất có thể test, kiểm tra của ứng dụng gọi là Unit, được xem xét riêng rẽ, độc lập để hoạt động tốt.
Thứ hai: Unit Testing có thể được thực hiện thủ công nhưng thường là tự động nên được gọi là Automated Unit Testing.
Còn Separation of Concerns bạn có thể hiểu là nguyên tắc để tách một chương trình phần mềm thành các thành phần riêng biệt, phần này thay đổi thì không phục thuộc vào phần kia và ngược lại.
Vậy MVP cấu tạo bao gồm những gì để thỏa mãn 2 yếu tố trên:
Mode: là một interface xác định dữ liệu được hiển thị, hoặc dữ liệu này được thực hiện trong giao diện người dùng (UI)
View: là một interface thụ động dùng để hiện thị dữ liệu (là Model) và định hướng các lệnh người dùng (events) tới Presenter để Presenter hành động dựa trên các dữ liệu đó.
Presenter: hành động theo Model và View. Presenter lấy dữ liệu từ repositories (Model), sau đó định dạng dữ liệu và hiển thị lên View.
Tại sao phải là MVP?
Mọi công việc thật ra là đơn giản với dễ dàng hơn khi có một kế hoạch thích hợp đúng không nào! Nếu không lên kế hoạch thì sẽ mang lại nhiều khó khăn và rủi ro hơn, tốn công sức hơn. Trong lập trình phần mềm cũng vậy, cụ thể là trong Android. Để bắt đầu một dự án Android nào, thì việc đầu tiên là phải chọn được một kiến trúc. Nếu bạn không chọn đúng bạn có thể phải đối mặt với các vấn đề sau trong quá trình phát triển ứng dụng của mình:
- Phức tạp để code cho Unit Testing. Trong khi Unit Testing khuyến khích Developers thay đổi mã nguồn.
- Khó theo dõi logic trong dự án.
- Khó để truy trì (maintain) và thêm các tính năng (feature) mới cho quá trình sử dụng và phát triển phần mềm.
Vậy một ứng dụng Android chất lượng cao thì đầu tiên nó phải có một kiến trúc lý tưởng đúng không nào? Vậy kiến trúc đó phải như thế nào:
- Simplicity: Sự đơn giản! Kiến trúc phải tách ứng dụng Android thành các module nhỏ, và phải xác định một vai trò duy nhất và rõ ràng cho mỗi module đó.
- Focus on Business Logic: Một kiến trúc lý tưởng phải phân tách mã để cho các Developers có thể tập trung vào business logic thay vì cải tiến các đoạn code khác.
- Low-cost Maintenance: Chi phí bảo trì thấp! Một kiến trúc lý tưởng thì không mất nhiều thời gian để thay đổi logic. Nó cũng dễ dàng thêm (add) các tính năng mới hay gỡ bỏ (remove) những tính năng không còn phù hợp nữa.
Với MVP nó đã tách ứng dụng Android thành 3 tầng là model-view-presenter. Và trong mỗi tầng đều được chia thành module nhỏ và mỗi module lại có một vai trò riêng biệt. Vậy nó đã thỏa mãn Simplicity.
Tầng presenter là nơi tập trung xử lý logic, là cầu nối giữa model và view. Đây là nơi mà các Developers có thể focus để theo theo dõi logic của ứng dụng và thay đổi chúng. Thỏa mãn Focus on Business Logic.
Cũng vì tách thành 3 tầng nên MVP là cho ứng dụng Android dễ dàng Testing. Dễ dàng gỡ bỏ những tính năng không còn phù hợp và thêm các tính năng mới -> maintain ứng dụng dễ dàng, chi phí thấp
Vậy MVP đúng là một một pattern lý tưởng đúng chưa các bạn!!!
So sánh MVC với MVP
MVC (model-view-controller) có thể xem là một pattern được tiếp cận đầu tiên. Nó ra đời từ những năm 1970 và rất được thịnh hành trong lập trình Web. MVC cũng được áp dụng nhiều trong Android. Đó là trước đây, nhưng hiện tại thì không phù hợp nữa. Để hiểu vì sao thì ta có thể đem so sánh sự khác nhau với MVP.
MVC
. View có thể gọi đến Model
. Unit Testing không tốt bằng MVP
. Tất cả các View cùng dùng chung một Controller
MVP
. View có thể xem là không có liên kết gì với Model, Presener có trách nhiệm liên kết Model tới View.
. Dễ dàng sử dụng Unit Testing hơn vì tương tác với View thông qua interface.
. Thường thì một View sẽ tương ứng với một Presenter. Những View phức tạp có thể có nhiều presenter cùng đảm nhiệm.
Sử dụng MVP trong Android như thế nào?
Mục này mình sẽ làm một example hướng dẫn các bạn chưa thực thi MVP trong Android bao giờ. Exmaple này theo follow của Google và chỉ ra cách tương tác giữa Presenter với View là chính. Model coi như đã có rồi.
Chúng ta sẽ tạo một project đơn giản: màn hình Sign In sử dụng MVP.
1.Màn hình Sign In:
với code của file activity_sign_in.xml có thể được viết như sau:
2.Package signin:
Sẽ gồm 3 class là SignInActivity đại diện cho View, SignInPresenter đại diện cho Presenter và interface SignInContract là nơi khai báo các interface chứa phương thức để tương tác qua lại giữa Presenter và View.
SignInContract: sẽ có 2 interface con là View và Presenter.
public interface SignInContract { interface View { void signInSuccess(); void signInFailure(String error); } interface Presenter { void handleSignIn(String username, String password); } }
SignInPresenter: sẽ implement Presenter interface trong SignInContract. Và trong nó có chứa mộ View Interface.
public class SignInPresenter implements SignInContract.Presenter { private SignInContract.View mView; public void setView(SignInContract.View view) { mView = view; } @Override public void handleSignIn(String username, String password) { // giả sữ đây là quá trình làm việc với model // và chỉ có account với username là mvpexample vs password // là 1234 login được chẳng hạn. if (username.equals("mvpexample") && password.equals("1234")) { mView.signInSuccess(); return; } mView.signInFailure("Username or Password not true!"); } }
Khi quá trình kiểm tra Sign In thành công thì sẽ gọi đến phương thức signInSucess() ngược lại thì gọi signInFailure().
SignInActivity: Bạn phải implement View interface và khai báo và khởi tạo một SignInPresenter. Khi ấn SignIn Button thì sẽ gọi đến phương thức void handleSignIn(String username, String password) với username và password lấy từ các edittext.
public class SignInActivity extends AppCompatActivity implements SignInContract.View, View.OnClickListener { private EditText mTextUsername; private EditText mTextPassword; private Button mButtonSignIn; private TextView mButtonSignUp; private SignInPresenter mSignInPresenter; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_sign_in); initView(); registerListener(); initPresenter(); } private void initView() { mTextUsername = findViewById(R.id.text_username); mTextPassword = findViewById(R.id.text_password); mButtonSignIn = findViewById(R.id.button_sign_in); mButtonSignUp = findViewById(R.id.button_sign_up); } private void registerListener() { mButtonSignIn.setOnClickListener(this); mButtonSignUp.setOnClickListener(this); } private void initPresenter() { mSignInPresenter = new SignInPresenter(); mSignInPresenter.setView(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.button_sign_in: login(); break; case R.id.button_sign_up: startActivity(new Intent(this, SignUpActivity.class)); break; default: break; } } private void login() { String username = mTextUsername.getText().toString(); String password = mTextPassword.getText().toString(); if (username.isEmpty() || password.isEmpty()) { Toast.makeText(this, "Username or Password is Empty!", Toast.LENGTH_SHORT).show(); return; } mSignInPresenter.handleSignIn(username, password); } @Override public void signInSuccess() { Toast.makeText(this, "Sign In Success!", Toast.LENGTH_SHORT).show(); startActivity(new Intent(this, MainActivity.class)); } @Override public void signInFailure(String error) { Toast.makeText(this, error, Toast.LENGTH_SHORT).show(); }
3.Các màn hình như SignUpActivity, MainActivity: Bạn có thể làm tương tự để xây dựng nên giao diện và chức năng của màn hình Sign Up nhé. Để hiểu rõ hơn bạn có thể clone code từ: https://github.com/tuannguyen38/mvp-example. Và để tìm hiểu sâu hơn bạn có thể xem ở: https://github.com/googlesamples/android-architecture/tree/todo-mvp.
Tổng kết.
Qua bài viết này mình đã trình bày một chút hiểu biết của mình về MVP Pattern. Mong sẽ giúp được các bạn mới học Android hiểu thêm về kiến trúc này. Cám ơn vì đã theo dõi bài viết!
Tài liệu tham khảo
https://en.wikipedia.org/wiki/Model–view–presenter https://www.tutorialspoint.com/design_pattern/mvc_pattern.htm https://www.spaceotechnologies.com/mvp-android-architectural-pattern/ https://stackoverflow.com/
All rights reserved
Mô hình MVP trong Android viết tắt của Model – View – Presenter, đây là mô hình kiến trúc hiện đang được nhiều lập trình viên lựa chọn sử dụng.
Mô hình MVP là gì?
Mô hình MVP trong Android giúp tách tầng trình diễn ra khỏi tầng dữ liệu, việc sử dụng MVP Pattern sẽ giúp dễ dàng mở rộng một cách nhanh chóng, dễ dàng bảo trì và dễ dàng kiểm thử ứng dụng.
Mô hình MVP bao gồm?
- Model: phụ trách xử lý tầng dữ liệu có thể là các thực thể (Entities), API Services, SQLite, SharedPreferences, Realm Database, helpers…
- View: phụ trách trình bày cách dữ liệu hiển thị, tiếp nhận tương tác từ người dùng sau đó gọi đến Presenter xử lý tương tác.
- Presenter: phụ trách tiếp nhận các yêu cầu từ View sau gọi sự kiện xử lý tương ứng, có thể sẽ lấy dữ liệu từ Model và cuối cùng đẩy dữ liệu cho View hiển thị.
Thiết kế mô hình MVP trong Android
Trong ứng dụng tra cứu thị trường chúng ta sẽ thiết kế mô hình MVP trong Android cụ thể như sau:
Bước 1: Đầu tiên bạn tạo lớp TyGiaView.java nhằm để hiển thị dữ liệu, lớp này sẽ được implement tại TyGiaActivity.
package com.teamvietdev.tracuuthitruong.listener; import com.teamvietdev.tracuuthitruong.model.TyGia; public interface TyGiaView { public void onComplete(TyGia tyGia); public void onError(String msg); }
Bước 2: Tiếp theo bạn tao lớp TyGiaPresenter.java và TyGiaPresenterImpl.java sử dụng để xử lý các yêu cầu từ phía View, truy vấn dữ liệu từ Model và sau đó đẩy dữ liệu cho View hiển thị.
Lớp TyGiaPresenter.java như sau:
package com.teamvietdev.tracuuthitruong.presenter; public interface TyGiaPresenter { public void loadData(String token); }
Lớp TyGiaPresenterImpl.java như sau:
package com.teamvietdev.tracuuthitruong.presenter; import com.teamvietdev.tracuuthitruong.listener.TyGiaView; import com.teamvietdev.tracuuthitruong.model.TyGia; import com.teamvietdev.tracuuthitruong.retrofit.ApiClient; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; public class TyGiaPresenterImpl implements TyGiaPresenter{ private TyGiaView tyGiaView; private ApiClient apiClient; public TyGiaPresenterImpl(TyGiaView tyGiaView) { this.tyGiaView = tyGiaView; this.apiClient = new ApiClient(); } @Override public void loadData(String token) { Call
call = apiClient.getClient().getListTyGiaNgoaiTe(token); call.enqueue(new Callback
() { @Override public void onResponse(Call
call, Response
response) { if (response.body() != null) { tyGiaView.onComplete(response.body()); } else { tyGiaView.onError(“null”); } } @Override public void onFailure(Call
call, Throwable throwable) { tyGiaView.onError(throwable.getMessage().toString()); } }); } }
Bước 3: Cuối cùng tại lớp TyGiaActivity.java bạn sẽ khởi tạo Presenter, gọi sự kiện tải dữ liệu và trình bày hiển thị dữ liệu lên màn hình.
package com.teamvietdev.tracuuthitruong.view.tygia; import android.os.Bundle; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.view.View; import android.widget.Toast; import com.teamvietdev.tracuuthitruong.R; import com.teamvietdev.tracuuthitruong.adapter.TyGiaAdapter; import com.teamvietdev.tracuuthitruong.listener.DividerItemDecoration; import com.teamvietdev.tracuuthitruong.listener.TyGiaView; import com.teamvietdev.tracuuthitruong.model.NgoaiTe; import com.teamvietdev.tracuuthitruong.model.TyGia; import com.teamvietdev.tracuuthitruong.presenter.TyGiaPresenter; import com.teamvietdev.tracuuthitruong.presenter.TyGiaPresenterImpl; import java.util.ArrayList; import java.util.List; public class TyGiaActivity extends AppCompatActivity implements TyGiaView { private Toolbar toolbar; private RecyclerView recyclerView; private SwipeRefreshLayout swipeRefreshLayout; private TyGia tyGia = null; private List
listNgoaiTe = null; private TyGiaPresenter tyGiaPresenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_ty_gia); toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Snackbar.make(view, “Replace with your own action”, Snackbar.LENGTH_LONG) .setAction(“Action”, null).show(); } }); tyGiaPresenter = new TyGiaPresenterImpl(this); swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipeRefresh); swipeRefreshLayout.setColorSchemeResources(R.color.colorAccent); swipeRefreshLayout.setRefreshing(true); recyclerView = (RecyclerView) findViewById(R.id.recyclerView); recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.addItemDecoration(new DividerItemDecoration(getApplicationContext(), LinearLayoutManager.VERTICAL)); swipeRefreshLayout.setOnRefreshListener( new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { swipeRefreshLayout.setRefreshing(true); tyGiaPresenter.loadData(“teamvietdev.com”); } } ); swipeRefreshLayout.setRefreshing(true); tyGiaPresenter.loadData(“teamvietdev.com”); } @Override public void onComplete(TyGia tyGia) { listNgoaiTe = tyGia.getDanh_sach_ngoai_te(); toolbar.setTitle(“NGOẠI TỆ (” + tyGia.getThoi_gian_tra_cuu() + “)”); setSupportActionBar(toolbar); recyclerView.setAdapter(new TyGiaAdapter(listNgoaiTe)); swipeRefreshLayout.setRefreshing(false); } @Override public void onError(String msg) { toolbar.setTitle(“NGOẠI TỆ”); setSupportActionBar(toolbar); listNgoaiTe = new ArrayList<>(); recyclerView.setAdapter(new TyGiaAdapter(listNgoaiTe)); swipeRefreshLayout.setRefreshing(false); Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show(); } }
Cấu trúc thư mục mô hình MVP trong Android như sau:
Bạn có thể tải mã nguồn hướng dẫn thiết kế mô hình MVP trong Android thông qua ứng dụng Android tra cứu thị trường tại Github:
Lời kết: MVC, MVP, MVVM là những Design Pattern tốt nhất thường được các lập trình viên sử dụng khi xây dưng ứng dụng Android, còn bạn nghĩ đâu là Design Pattern tốt nhất?. Hẹn gặp lại các bạn trong các bài viết tiếp theo trong chuyên mục lập trình Android phần hướng dẫn xây dựng ứng dụng tra cứu thị trường.
(Tác giả: Team Việt Dev)
xml
version
"1.0"
encoding
"utf-8"
?>
androidx.constraintlayout.widget.ConstraintLayout
android:layout_width
"match_parent"
android:layout_height
"match_parent"
android:background
"#168BC34A"
tools:context
".MainActivity"
TextView
android:id
"@+id/textView3"
android:layout_width
"wrap_content"
android:layout_height
"wrap_content"
android:fontFamily
"@font/roboto"
android:text
"@string/heading"
android:textAlignment
"center"
android:textColor
"@android:color/holo_green_dark"
android:textSize
"30sp"
android:textStyle
"bold"
app:layout_constraintBottom_toBottomOf
"parent"
app:layout_constraintEnd_toEndOf
"parent"
app:layout_constraintHorizontal_bias
"0.498"
app:layout_constraintStart_toStartOf
"parent"
app:layout_constraintTop_toTopOf
"parent"
app:layout_constraintVertical_bias
"0.060000002"
/>
TextView
android:id
"@+id/textView2"
android:layout_width
"wrap_content"
android:layout_height
"wrap_content"
android:fontFamily
"@font/roboto"
android:text
"@string/subHeading"
android:textAlignment
"center"
android:textColor
"@android:color/holo_green_dark"
android:textSize
"24sp"
android:textStyle
"bold"
app:layout_constraintBottom_toTopOf
"@+id/button"
app:layout_constraintEnd_toEndOf
"parent"
app:layout_constraintHorizontal_bias
"1.0"
app:layout_constraintStart_toStartOf
"parent"
app:layout_constraintTop_toTopOf
"parent"
app:layout_constraintVertical_bias
"0.356"
/>
TextView
android:id
"@+id/textView"
android:layout_width
"411dp"
android:layout_height
"wrap_content"
android:fontFamily
"@font/roboto"
android:gravity
"center"
android:padding
"8dp"
android:text
"@string/description"
android:textAlignment
"center"
android:textAppearance
"?android:attr/textAppearanceSearchResultTitle"
app:layout_constraintBottom_toTopOf
"@+id/button"
app:layout_constraintEnd_toEndOf
"parent"
app:layout_constraintStart_toStartOf
"parent"
app:layout_constraintTop_toBottomOf
"@+id/textView2"
app:layout_constraintVertical_bias
"0.508"
/>
Button
android:id
"@+id/button"
android:layout_width
"0dp"
android:layout_height
"wrap_content"
android:layout_margin
"@android:dimen/notification_large_icon_height"
android:background
"#4CAF50"
android:text
"@string/buttonText"
android:textAllCaps
"true"
android:textColor
"@android:color/background_light"
android:textSize
"20sp"
android:textStyle
"bold"
app:layout_constraintBottom_toBottomOf
"parent"
app:layout_constraintLeft_toLeftOf
"parent"
app:layout_constraintRight_toRightOf
"parent"
app:layout_constraintTop_toTopOf
"parent"
app:layout_constraintVertical_bias
"0.79"
/>
ProgressBar
android:id
"@+id/progressBar"
style
"?android:attr/progressBarStyleLarge"
android:layout_width
"wrap_content"
android:layout_height
"wrap_content"
android:visibility
"gone"
app:layout_constraintBottom_toBottomOf
"parent"
app:layout_constraintLeft_toLeftOf
"parent"
app:layout_constraintRight_toRightOf
"parent"
app:layout_constraintTop_toTopOf
"parent"
/>
ImageView
android:id
"@+id/imageView"
android:layout_width
"wrap_content"
android:layout_height
"wrap_content"
app:layout_constraintBottom_toBottomOf
"parent"
app:layout_constraintEnd_toEndOf
"parent"
app:layout_constraintStart_toStartOf
"parent"
app:layout_constraintTop_toBottomOf
"@+id/button"
app:layout_constraintVertical_bias
"1.0"
app:srcCompat
"@drawable/banner"
/>
androidx.constraintlayout.widget.ConstraintLayout
MVP Pattern for Android
Bài đăng này đã không được cập nhật trong 2 năm
MVP Pattern for Android
Mô hình MVP (Model View Presenter) là một dẫn xuất từ mô hình MVC (Model View Controller), hiện tại đang được áp dụng trong nhiều ứng dụng Android.
Bài viết này giới thiệu khái quát về mô hình MVP cũng như đưa ra một trong số rất nhiều cách triển khai mô hình này trên Android.
Mô hình MVP là gì?
MVP là một mô hình kiến trúc hướng giao diện người dùng, được thiết kế để tạo thuận lợi cho việc kiểm thử đơn vị (unit testing) và tăng tính tách biệt giữa tầng dữ liệu và tầng hiển thị dữ liệu trong mô hình MVC.
Mô hình MVP cho phép tách tầng trình diễn (
Presenter
) ra khỏi tầng dữ liệu (
Model
), vì vậy tương tác với giao diện được tách biệt với cách chúng ta biểu diễn nó trên màn hình (
View
), hay nói cách khác, tất cả logic khi người dùng tương tác được tách ra và đưa vào tầng trình diễn. Thiết kế lý tưởng nhất là với cùng một logic được áp dụng cho nhiều View khác nhau và hoán đổi được cho nhau.
Trong đó:
-
Model
là một interface xác định cách mà dữ liệu được hiển thị trong giao diện người dùng. -
View
là một giao diện người dùng thụ động hiển thị dữ liệu (
Model
) và tiếp nhận tương tác người dùng và truyền tới để
Presenter
xử lý tương tác. -
Presenter
được ví như middle-man. Khi người dùng tương tác với
View
,
Presenter
tiếp nhận tương tác người dùng và update
Model
. Khi
Model
được update hay có thay đổi,
Presenter
lấy dữ liệu từ
Model
, định dạng và đưa tới
View
để hiển thị.
Luồng dữ liệu trong mô hình MVP
Điểm khác biệt dễ thấy nhất ở đây khi so sánh mô hình MVP với mô hình MVC chính là vị trí cũng như chức năng của tầng
Presenter
và
View
so với tầng
Controller
:
Trong mô hình MVP, tầng
View
là tầng duy nhất tiếp nhận tương tác người dùng thay vì cả 2 tầng
View
và tầng
Controller
trong mô hình MVC. Logic xử lý tương tác cũng như logic xử lý dữ liệu hiển thị được tách ra trong tầng
Presenter
thay vì được gộp chung với tiếp nhận tương tác trong
Controller
.
Tại sao sử dụng MVP?
Trong Android, có một vấn đề phát sinh từ thực tế là các cơ chế xử lý tương tác trong Android được kết hợp chặt chẽ giữa giao diện người dùng và xử lý, truy cập dữ liệu.
-
Một ví dụ điển hình là
CursorAdapter
, đây là sự kết hợp của việc định dạng dữ liệu với xử lý tương tác giao diện. Đôi khi trong đó còn bao gồm cả xử lý dữ liệu ở mức sâu hơn (như tương tác với CSDL) thông qua
Cursor
.
Việc kết hợp này có thể giúp giảm thiểu lượng code trong ứng dụng cũng như gộp các xử lý, logic trong ứng dụng vào cùng một nơi. Nhưng đối với một ứng dụng liên tục phát triển, hay một ứng dụng lớn, việc này khiến cho lượng code trên mỗi logic trở nên rất lớn, các logic xen lần, chồng chéo lên nhau, rất khó cho việc đọc hiểu, bảo trì cũng như mở rộng.
Phân tầng, phân lớp ứng dụng có thể giảm đi hiệu năng do ứng dụng cần thêm nhiều tài nguyên, nhưng tính khả chuyển của ứng dụng cũng tăng gấp nhiều lần., không những thế còn mở rộng khả năng cho phép kiểm thử từng phần của ứng dụng trở nên dễ dàng hơn. Chính vì thế cân nhắc giữa hiệu năng và tính dễ dàng mở rộng, bảo trì là hết sức quan trọng.
Trong Android, kiểm thử là một vấn đề khó khăn vì mối liên kết chặt chẽ giữa giao diện, logic và dữ liệu. MVP tách biệt giao diện khỏi dữ liệu, chia ứng dụng ra thành ít nhất ba lớp khác nhau, từ đó có thể kiểm thử một các độc lập. Với MVP ta có thể kiểm thử đo đạc một cách tối đa các logic của ứng dụng.
Reference
- Antonio Leiva Gordillo – MVP for Android: how to organize the presentation layer
- Uncle Bob – The Clean Architecture
- Wikipedia – Model–view–presenter pattern
- Fernando Cejas – Architecting Android…The clean way?
- Jeff Angellini – AN MVP PATTERN FOR ANDROID
- Konstantin Mikheev – Introduction to Model-View-Presenter on Android
All rights reserved
Tại sao sử dụng MVP?
Trong Android, có một vấn đề phát sinh từ thực tế là các cơ chế xử lý tương tác trong Android được kết hợp chặt chẽ giữa giao diện người dùng và xử lý, truy cập dữ liệu.
-
Một ví dụ điển hình là
CursorAdapter
, đây là sự kết hợp của việc định dạng dữ liệu với xử lý tương tác giao diện. Đôi khi trong đó còn bao gồm cả xử lý dữ liệu ở mức sâu hơn (như tương tác với CSDL) thông qua
Cursor
.
Việc kết hợp này có thể giúp giảm thiểu lượng code trong ứng dụng cũng như gộp các xử lý, logic trong ứng dụng vào cùng một nơi. Nhưng đối với một ứng dụng liên tục phát triển, hay một ứng dụng lớn, việc này khiến cho lượng code trên mỗi logic trở nên rất lớn, các logic xen lần, chồng chéo lên nhau, rất khó cho việc đọc hiểu, bảo trì cũng như mở rộng.
Phân tầng, phân lớp ứng dụng có thể giảm đi hiệu năng do ứng dụng cần thêm nhiều tài nguyên, nhưng tính khả chuyển của ứng dụng cũng tăng gấp nhiều lần., không những thế còn mở rộng khả năng cho phép kiểm thử từng phần của ứng dụng trở nên dễ dàng hơn. Chính vì thế cân nhắc giữa hiệu năng và tính dễ dàng mở rộng, bảo trì là hết sức quan trọng.
Trong Android, kiểm thử là một vấn đề khó khăn vì mối liên kết chặt chẽ giữa giao diện, logic và dữ liệu. MVP tách biệt giao diện khỏi dữ liệu, chia ứng dụng ra thành ít nhất ba lớp khác nhau, từ đó có thể kiểm thử một các độc lập. Với MVP ta có thể kiểm thử đo đạc một cách tối đa các logic của ứng dụng.
Triển khai MVP trên Android
Có rất nhiều biến thể cũng như phương pháp triển khai MVP, tất cả mọi người có thể điều chỉnh mô hình này tùy theo nhu cầu và cách họ cảm thấy thoải mái hơn. Các mô hình này, về cơ bản khác nhau ở số lượng chức năng mà tầng
Presenter
đảm nhận.
Một view nhận tương tách từ người dùng disable hoặc enable progress bar, liệu có nên giao nhiệm vụ này cho một
presenter
? Một Activity sẽ nhận sự kiện click vào nút settings trên ActionBar hay sự kiện này sẽ được một đối tượng
presenter
trong Activity đảm nhận?
Những câu hỏi như vậy tạo ra nhiều cách để chúng ta triển khai mô hình MVP, và trên thực tế chưa có một tiêu chuẩn chính xác nào được đưa ra. Vì vậy dưới đây tôi xin trình bày một trong những phương pháp đó.
Tầng trình diễn – Presenter
Tầng trình diễn có trách nhiệm như một middle-man giữa
View
và
Model
. Nó lấy dữ liệu từ
Model
, định dạng và trả về cho
View
. Nhưng không giống như MVC, nó cũng quyết định những gì sẽ xảy ra khi người dùng tương tác với
View
, hay nói cách khác nó hàm chứa logic ứng dụng.
Tầng logic dữ liệu – Model
Trong một ứng dụng với thiết kế kiến trúc tốt, mô hình này sẽ chỉ là một
gateway
giữa tầng
domain
và tầng
business logic
. Trong mô hình Clean Architecture của Uncle Bob, Model sẽ là một interactor thự thi một use case. Để đơn giản, ở đây Model đơn thuần được nhìn nhận như một data source – cung cấp dữ liệu chúng ta muốn hiển thị trong giao diện ứng dụng.
Tầng giao diện – View
View, thường được
implement
bởi một Activity (hoặc có thể là một Fragment, một View … tùy thuộc vào cấu trúc ứng dụng), Activity này sẽ chứa một thuộc tính là một lớp
Presenter
. Lý tưởng nhất
Presenter
nên được cung cấp bởi một Dependency Injection framewok như Dagger, nhưng trong trường hợp ứng dụng không sử dụng một thư viện hay framework như vậy ta hoàn toàn có thể tạo ra các đối tượng
Presenter
này.
Sau đây là ví dụ triển khai một màn hình Login trong Android:
- Lớp LoginActivity implements LoginView interface để hiển thị kết quả tới người dùng, đồng thời một thuộc tính LoginPresenter chịu trách nhiệm tiếp nhận và thực thi logic ứng dụng (ở đây là xác thực người dùng) và trả
public class LoginActivity extends Activity implements LoginView, View.OnClickListener { private ProgressBar progressBar; private EditText username; private EditText password; private LoginPresenter presenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); progressBar = (ProgressBar) findViewById(R.id.progress); username = (EditText) findViewById(R.id.username); password = (EditText) findViewById(R.id.password); findViewById(R.id.button).setOnClickListener(this); presenter = new LoginPresenterImpl(this); } @Override public void showProgress() { progressBar.setVisibility(View.VISIBLE); } @Override public void hideProgress() { progressBar.setVisibility(View.GONE); } @Override public void setUsernameError() { username.setError(getString(R.string.username_error)); } @Override public void setPasswordError() { password.setError(getString(R.string.password_error)); } @Override public void navigateToHome() { startActivity(new Intent(this, MainActivity.class)); finish(); } @Override public void onClick(View v) { presenter.validateCredentials(username.getText().toString(), password.getText().toString()); } }
- LoginView là một lớp giao diện cho phép phương thức hiển thị có thể đuợc thực thi hay hoán đổi dễ dàng trên nhiều Activity khác nhau.
public interface LoginView { public void showProgress(); public void hideProgress(); public void setUsernameError(); public void setPasswordError(); public void navigateToHome(); }
- Tương tự như LoginView, LoginPresenter cũng giúp cho việc triển khai nhiều logic trên cùng một Activity hay hoán đổi các logic này cho nhau thông qua việc khởi tạo một lớp Implement khác.
public interface LoginPresenter { public void validateCredentials(String username, String password); }
- Lớp OnLoginFinishedListener ở đây được sử dụng như một custom listener cho phép đa dạng hóa các phương thức hiển thị kết quả tới người dùng.
public interface OnLoginFinishedListener { public void onUsernameError(); public void onPasswordError(); public void onSuccess(); }
- Giống như với OnLoginFinishedListener, LoginInteractor cũng cho phép custom các phương thức tương tác của người dùng với ứng dụng.
public interface LoginInteractor { public void login(String username, String password, OnLoginFinishedListener listener); }
- Và cuối cùng là các lớp implement, thực thi logic ứng dụng, phương thức hiển thị kết quả cũng như phương thức tiếp nhận tương tác người dùng.
public class LoginPresenterImpl implements LoginPresenter, OnLoginFinishedListener { private LoginView loginView; private LoginInteractor loginInteractor; public LoginPresenterImpl(LoginView loginView) { this.loginView = loginView; this.loginInteractor = new LoginInteractorImpl(); } @Override public void validateCredentials(String username, String password) { loginView.showProgress(); loginInteractor.login(username, password, this); } @Override public void onUsernameError() { loginView.setUsernameError(); loginView.hideProgress(); } @Override public void onPasswordError() { loginView.setPasswordError(); loginView.hideProgress(); } @Override public void onSuccess() { loginView.navigateToHome(); } }
public class LoginInteractorImpl implements LoginInteractor { @Override public void login(final String username, final String password, final OnLoginFinishedListener listener) { // Mock login. I'm creating a handler to delay the answer a couple of seconds new Handler().postDelayed(new Runnable() { @Override public void run() { boolean error = false; if (TextUtils.isEmpty(username)){ listener.onUsernameError(); error = true; } if (TextUtils.isEmpty(password)){ listener.onPasswordError(); error = true; } if (!error){ listener.onSuccess(); } } }, 2000); } }
Dễ thấy ở đây, việc thay đổi logic ứng dụng, phương thức hiển thị hay cách thức tương tác là hết sức dễ dàng bằng việc thêm các lớp Interface, Implement khác hay viết các lớp wrapper các lớp sẵn có. Điều này càng dễ dàng hơn trong Java 8 khi một dạng phương thức mới – phương thức
default
được áp dụng. Tôi sẽ nói tới dạng phương thức này trong một bài viết sớm nhất.
Bạn đang muốn tìm kiếm 1 công việc với mức thu nhập cao.✅ Hoặc là bạn đang muốn chuyển đổi công việc mà chưa biết theo học ngành nghề gì cho tốt.✅ Giới thiệu với bạn Chương trình đào tạo nhân sự dài hạn trong 12 tháng với những điều đặc biệt mà chỉ có tại IMIC và đây cũng chính là sự lựa chọn phù hợp nhất dành cho bạn:👉 Thứ nhất: Học viên được đào tạo bài bản kỹ năng, kiến thức chuyên môn lý thuyết, thực hành, thực chiến nhiều dự án và chia sẻ những kinh nghiệm thực tế từ Chuyên gia có nhiều năm kinh nghiệm dự án cũng như tâm huyết truyền nghề.👉 Thứ hai: Được ký hợp đồng cam kết chất lượng đào tạo cũng như mức lương sau tốt nghiệp và đi làm tại các đối tác tuyển dụng của IMIC. Trả lại học phí nếu không đúng những gì đã ký kết.👉 Thứ ba: Cam kết hỗ trợ giới thiệu công việc sang đối tác tuyển dụng trong vòng 10 năm liên tục.👉 Thứ tư: Được hỗ trợ tài chính với mức lãi suất 0 đồng qua ngân hàng VIB Bank.👉 Có 4 Chương trình đào tạo nhân sự dài hạn dành cho bạn lựa chọn theo học. Gồm có:1) Data Scientist full-stack2) Embedded System & IoT development full-stack3) Game development full-stack4) Web development full-stack✅ Cảm ơn bạn đã dành thời gian lắng nghe những chia sẻ của mình. Và tuyệt vời hơn nữa nếu IMIC được góp phần vào sự thành công của bạn.✅ Hãy liên hệ ngay với Phòng tư vấn tuyển sinh để được hỗ trợ về thủ tục nhập học.✅ Chúc bạn luôn có nhiều sức khỏe và thành công!
MVP là một mô hình kiến trúc hướng giao diện người dùng, được thiết kế để tạo thuận lợi cho việc kiểm thử đơn vị (unit testing) và tăng tính tách biệt giữa tầng dữ liệu và tầng hiển thị dữ liệu trong mô hình MVC. Đây là mô hình đang được áp dụng khá nhiều trong ứng dụng Android.
Mô hình MVP cho phép tách tầng trình diễn (Presenter) ra khỏi tầng dữ liệu (Model), vì vậy tương tác với giao diện được tách biệt với cách chúng ta biểu diễn nó trên màn hình (View), hay nói cách khác, tất cả logic sẽ được tách ra và đưa vào tầng trình diễn (Presenter). Thiết kế lý tưởng nhất là với cùng một logic được áp dụng cho nhiều View khác nhau và hoán đổi được cho nhau.
Tìm hiểu chi tiết
Có rất nhiều biến thể cũng như phương pháp triển khai MVP, tất cả mọi người có thể điều chỉnh mô hình này tùy theo nhu cầu và cách họ cảm thấy thoải mái hơn. Nhưng tất cả vẫn phải dựa vào các quy tắc chung trên Model – View – Prensenter.
-
Model: Cái này thì các bạn rất quen thuộc rồi, nó không có gì khác với các mô hình khác, dùng để mô tả, chứa dữ liệu . Ví dụ ta có model Animal, Bike …
-
View: Thường được implement bởi một Activity , Fragment, View … tùy thuộc vào cấu trúc ứng dụng, View này sẽ chứa một thuộc tính là một lớp Presenter.
-
Presenter: Tầng trình diễn có trách nhiệm như một cầu nối giữa View và Model. Nó lấy dữ liệu từ Model, định dạng và trả về cho View. Nhưng không giống như MVC, nó cũng quyết định những gì sẽ xảy ra khi người dùng tương tác với View, hay nói cách khác nó hàm chứa logic ứng dụng. Lý tưởng nhất Presenter nên được cung cấp bởi một Dependency Injection framewok như Dagger, nhưng trong trường hợp ứng dụng không sử dụng một thư viện hay framework như vậy ta hoàn toàn có thể tạo ra các đối tượng Presenter.
Demo thử màn Login
Mình sẽ cùng các bạn làm một demo cơ bản về xử lý màn Login theo mô hình MVP
Bước 1: Tạo Model
User.class
public class User { public String username; public String password; public User(String username, String password) { this.username = username; this.password = password; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
Bước 2: Tạo 1 interface tên là LoginView chứa các phương thức tác động tới giao diện
LoginView.class
public interface LoginView { void loginFail(); void loginSuccessful(); void goHome(); }
Bước 3: Tạo các phương thức xử lý dữ liệu trong class LoginPresenter
LoginPresenter.class
public class LoginPresenter { LoginView loginView; public LoginPresenter(LoginView loginView) { this.loginView = loginView; } public void login(String username, String password) { if (username.equalsIgnoreCase(“admin”) && password.equalsIgnoreCase(“admin”)) { loginView.loginSuccessful(); loginView.goHome(); } else { loginView.loginFail(); } } }
Bước 4: Cuối cùng, ta bắt các sự kiện và xử lý ở class LoginActivity
LoginActivity.class
public class LoginActivity extends AppCompatActivity implements LoginView, View.OnClickListener { private EditText edtUsername; private EditText edtPassword; private Button btnLogin; LoginPresenter loginPresenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); loginPresenter = new LoginPresenter(this); initView(); initClick(); } public void initView() { edtUsername = findViewById(R.id.edtUsername); edtPassword = findViewById(R.id.edtPassword); btnLogin = findViewById(R.id.btnLogin); } public void initClick() { btnLogin.setOnClickListener(this); } @Override public void loginSuccessful() { Toast.makeText(this, “Login Thanh Cong!!”, Toast.LENGTH_SHORT).show(); } @Override public void loginFail() { Toast.makeText(this, “Login Fail!!”, Toast.LENGTH_SHORT).show(); } @Override public void goHome() { startActivity(new Intent(this, HomeActivity.class)); } @Override public void onClick(Viewview) { if (view.getId() == R.id.btnLogin) { String username = edtUsername.getText().toString(); String password = edtPassword.getText().toString(); loginPresenter.login(username, password); } } }
Kết luận
Mô hình MVP đến nay vẫn đang được dùng khá phổ biến. Vấn đề quan trọng bạn cần nhớ là Presenter sẽ làm công việc logic. Ở một số hệ thống người ta còn dùng thêm các interactor, tuy nhiên bản chất vẫn thế. Mối liên hệ giữa view là presenter là 1-1. Nhưng có một lưu ý bạn cần chú ý là
Nhược điểm lớn nhất của mô hình MVP là theo thời gian, Presenter sẽ dần lớn lên do bị thêm các business logic rải rác. Người dùng sẽ rất khó để kiểm soát và chia nhỏ code khi Presenter đã quá lớn.
[et_pb_section admin_label=”section”][et_pb_row admin_label=”row”][et_pb_column type=”4_4″][et_pb_text admin_label=”Text”]MVP (Model View Presenter) pattern is a derivative from the well known MVC (Model View Controller), and one of the most popular patterns to organize the presentation layer in Android Applications.
This article was first published in April 2014, and been the most popular since then. So I’ve decided to update it solving most of the doubts people had, and also convert the code to Kotlin.
There have been breaking changes about architectural patterns since then, such as MVVM with architecture components, but MVP is still valid and an option to take into account.
What is MVP?
The MVP pattern allows separating the presentation layer from the logic so that everything about how the UI works is agnostic from how we represent it on screen. Ideally, the MVP pattern would achieve that the same logic might have completely different and interchangeable views.
The first thing to clarify is that MVP is not an architecture by itself, it’s only responsible for the presentation layer. This has been a controversial assessment, so I want to explain it a bit deeper.
You may find that MVP is defined as an architectural pattern because it can become part of the architecture of your App, but don’t consider that just because you are using MVP, your architecture is complete. MVP only models the presentation layer, but the rest of layers will still require a good architecture if you want a flexible and scalable App.
An example of a complete architecture could be Clean Architecture, though there are many other options.
In any case, it is always better to use it for your architecture that not using it at all.
Mô hình MVP là gì?
MVP là một mô hình kiến trúc hướng giao diện người dùng, được thiết kế để tạo thuận lợi cho việc kiểm thử đơn vị (unit testing) và tăng tính tách biệt giữa tầng dữ liệu và tầng hiển thị dữ liệu trong mô hình MVC.
Mô hình MVP cho phép tách tầng trình diễn (
Presenter
) ra khỏi tầng dữ liệu (
Model
), vì vậy tương tác với giao diện được tách biệt với cách chúng ta biểu diễn nó trên màn hình (
View
), hay nói cách khác, tất cả logic khi người dùng tương tác được tách ra và đưa vào tầng trình diễn. Thiết kế lý tưởng nhất là với cùng một logic được áp dụng cho nhiều View khác nhau và hoán đổi được cho nhau.
Trong đó:
-
Model
là một interface xác định cách mà dữ liệu được hiển thị trong giao diện người dùng. -
View
là một giao diện người dùng thụ động hiển thị dữ liệu (
Model
) và tiếp nhận tương tác người dùng và truyền tới để
Presenter
xử lý tương tác. -
Presenter
được ví như middle-man. Khi người dùng tương tác với
View
,
Presenter
tiếp nhận tương tác người dùng và update
Model
. Khi
Model
được update hay có thay đổi,
Presenter
lấy dữ liệu từ
Model
, định dạng và đưa tới
View
để hiển thị.
Luồng dữ liệu trong mô hình MVP
Điểm khác biệt dễ thấy nhất ở đây khi so sánh mô hình MVP với mô hình MVC chính là vị trí cũng như chức năng của tầng
Presenter
và
View
so với tầng
Controller
:
Trong mô hình MVP, tầng
View
là tầng duy nhất tiếp nhận tương tác người dùng thay vì cả 2 tầng
View
và tầng
Controller
trong mô hình MVC. Logic xử lý tương tác cũng như logic xử lý dữ liệu hiển thị được tách ra trong tầng
Presenter
thay vì được gộp chung với tiếp nhận tương tác trong
Controller
.
How to implement MVP for Android
Well, this is where it all starts to become more diffuse. There are many variations of MVP and everyone can adjust the pattern to their needs and the way they feel more comfortable. It varies depending basically on the number of responsibilities that we delegate to the presenter.
Is the view responsible to enable or disable a progress bar, or should it be done by the presenter? And who decides which actions should be shown in the Action Bar? That’s where the tough decisions begin. I will show how I usually work, but I want this article to be more a place for discussion rather than strict guidelines on how to apply MVP, because up to there is no “standard” way to implement it.
For this article, I’ve implemented a very simple example that you may find on my Github with a login screen and a main screen. For simplicity purposes, the code in the article is in Kotlin, but you can also check the code in Java 8 in the repository.
The model
In an application with a complete layered architecture, this model would only be the gateway to the domain layer or business logic. If we were using Uncle Bob’s clean architecture, the model would probably be an interactor that implements a use case. But for the purpose of this article, it is enough to see it as the provider of the data we want to display in the view.
If you check the code, you will see that I’ve created two mock interactors with artificial delays to simulate requests to a server. The structure of one of this interactors:
class LoginInteractor { … fun login(username: String, password: String, listener: OnLoginFinishedListener) { // Mock login. I’m creating a handler to delay the answer a couple of seconds postDelayed(2000) { when { username.isEmpty() -> listener.onUsernameError() password.isEmpty() -> listener.onPasswordError() else -> listener.onSuccess() } } } }
It’s a simple function that receives the username and the password, and does some validation.
The View
The view, usually implemented by an Activity (it may be a Fragment, a View… depending on how the app is structured), will contain a reference to the presenter. The presenter will be ideally provided by a dependency injector such as Dagger, but in case you don’t use something like this, it will be responsible for creating the presenter object. The only thing that the view will do is calling a presenter method every time there is a user action (a button click for example).
As the presenter must be view agnostic, it uses an interface that needs to be implemented. Here’s the interface that the example uses:
interface LoginView { fun showProgress() fun hideProgress() fun setUsernameError() fun setPasswordError() fun navigateToHome() }
It has some utility methods to show and hide progress, show errors, navigate to the next screen… As mentioned above, there are many ways to do this, but I prefer to show the simplest one.
Then, the activity can implement those methods. Here I show you some, so that you get an idea:
class LoginActivity : AppCompatActivity(), LoginView { … override fun showProgress() { progress.visibility = View.VISIBLE } override fun hideProgress() { progress.visibility = View.GONE } override fun setUsernameError() { username.error = getString(R.string.username_error) } }
But if you remember, I also told you that the view uses the presenter to notify about user interactions. This is how it’s used:
class LoginActivity : AppCompatActivity(), LoginView { private val presenter = LoginPresenter(this, LoginInteractor()) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_login) button.setOnClickListener { validateCredentials() } } private fun validateCredentials() { presenter.validateCredentials(username.text.toString(), password.text.toString()) } override fun onDestroy() { presenter.onDestroy() super.onDestroy() } … }
The presenter is defined as a property for the activity, and when the button is clicked, it calls
validateCredentials()
, which will notify the presenter.
Same happens with
onDestroy()
. We’ll see later why it needs to notify in that case.
The presenter
The presenter is responsible to act as the middleman between view and model. It retrieves data from the model and returns it formatted to the view
Also, unlike the typical MVC, it decides what happens when you interact with the view. So it will have a method for each possible action the user can do. We saw it in the view, but here’s the implementation:
class LoginPresenter(var loginView: LoginView?, val loginInteractor: LoginInteractor) : LoginInteractor.OnLoginFinishedListener { fun validateCredentials(username: String, password: String) { loginView?.showProgress() loginInteractor.login(username, password, this) } … }
MVP has some risks, and the most important we use to forget is that the presenter is attached to the view forever. And the view is an activity, which means that:
- We can leak the activity with long-running tasks
- We can try to update activities that have already died
For the first point, if you can ensure that your background tasks finish in a reasonable amount of time, I wouldn’t worry much. Leaking an activity 5-10 seconds won’t make your App much worse, and the solutions to this are usually complex.
The second point is more worrying. Imagine you send a request to a server that takes 10 seconds, but the user closes the activity after 5 seconds. By the time the callback is called and the UI is updated, it will crash because the activity is finishing.
To solve this, we call the
onDestroy()
method that cleans the view:
fun onDestroy() { loginView = null }
That way we avoid calling the activity in an inconsistent state.
Keywords searched by users: mvp design pattern android
Categories: Có được 78 Mvp Design Pattern Android
See more here: kientrucannam.vn
See more: https://kientrucannam.vn/vn/