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

Giới thiệu về mô hình MVP trên Android

 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.


1. 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.


2. 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. 


3. 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);        
         }    
     }
     
}

4. 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.

  

GitHub

Nhận xét

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

Camera2 - Android

Ở bài viết này mình xin giới thiệu về cách sử dụng Camera2 trong android SDK 21. Với các lập trình viên android việc sử dụng Camera có rất nhiều trong ứng dụng: Camera Capture Images, Barcode - QR Code Reader, AR, Video Record,.... Nhiều ứng dụng chỉ ở tầng ứng dụng sử dụng thông qua  Intent  như vậy hệ thống tự động được gọi ở mức tối ưu nhất, nhưng cũng không ít ứng dụng cần can thiệp vào tầng  native  để xử lý Với  Camera   developer.android.com  đã  deprecate  nó đã không còn được sử dụng cơ bản trong các ứng dụng nữa vì rất nhiều nguyên nhân trong đó phải kể tới: tốn tài nguyên, thời gian capture khá chậm và đặc biệt phục vụ nhu cầu ngày càng cao của người dùng như 'Chụp ảnh liên tục, chụp nhiều ảnh và tự động lấy nét' thì  Camera không đáp ứng được Với  Camera2  đã đáp ứng được những thiếu xót trên ngoài ra việc customize cho ảnh là rất dễ dàng mang lại chất lượng cao ngoài ra việc sử dụng cũng không có nhiều thay ...

Sự khác nhau giữa let, apply, with, run và also trong Kotlin

Với những ai đã sử dụng Kotlin để phát triển ứng dụng, chắc hẳn đã không ít lần sử dụng các standard functions run, with, let, also và apply. Để hiểu và sử dụng thành thục chúng không phải là dễ. Và dưới đây là những điều đúc kết lại được. https://viblo.asia/p/su-khac-nhau-giua-let-apply-with-run-va-also-trong-kotlin-RQqKLNdml7z Scoping functions Có thể hiểu đơn giản, scoping function là phạm vi ảnh hưởng nhất định của một hàm. Nó là điều cốt lõi để phân biệt giữa các scoping functions: run, with, T.run, T.let, T.also và T.apply. Dưới đây là minh hoạ phạm vi của hàm run: fun test ( ) { var mood = "I am sad" run { val mood = "I am happy" println ( mood ) // I am happy } println ( mood ) // I am sad } Ở trên, ta có thể thấy rõ ràng trong phạm vi của hàm run, biến mood đã được định nghĩa lại trước khi in ra mà không làm ảnh hưởng tới phần khác của chương trình 3 attributes of scoping functions 1.Normal vs. extension fun...

TỰ ĐỘNG HUỶ ACTIVITY SAU KHI STARTACTIVITY

Trước giờ để huỷ một Activity khi bạn thường dùng hàm  finish()  đúng không nào? Không đi đâu xa là khi bạn Intent từ một Activity này sang Activity khác mà muốn huỷ luôn Activity đầu tiên luôn thì bạn sẽ dùng đoạn code y chang bên dưới chứ? Cơ chế của Activity là khi bạn chuyển từ một Activity này sáng Activity khác thì nó sẽ Activity đó vào stack, và khi back về thì Activity sẽ được hiện lên lại và chạy vào onResume(), nếu bạn chưa hiểu về  vòng đời của Activity  thì xem lại bài viết kèm video tại blog mình nhé, mình ví dụ khá chi tiết. Intent intent = new Intent(MainActivity.this, LoginActivity.class); startActivity(intent); finish(); Đoạn code trên nghĩa là chuyển từ MainActivity sang LoginActivity và sau đó huỷ luôn MainActivity đúng không nào? Tuy nhiên đó không phải là cách duy nhất mà chúng ta làm đâu bởi Android hỗ trợ chúng ta một số thuộc tính mà bạn không phải dùng code Java để làm. Không lưu Activity vào stack Cũng logic như bài toán phí...