+25

MVP Pattern trong Android

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

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

3. 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:

  1. Phức tạp để code cho Unit Testing. Trong khi Unit Testing khuyến khích Developers thay đổi mã nguồn.
  2. Khó theo dõi logic trong dự án.
  3. 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:

  1. 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 đó.
  2. 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.
  3. 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!!!

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

5. 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:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
    android:gravity="center"
    android:orientation="vertical"
    android:padding="16dp">

    <TextView
        android:textStyle="italic"
        android:textSize="30sp"
        android:text="MVP Example"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <ImageView
        android:layout_width="250dp"
        android:layout_height="250dp"
        android:src="@drawable/jet_pack" />

    <EditText
        android:id="@+id/text_username"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Username"
        android:paddingLeft="16dp" />

    <EditText
        android:id="@+id/text_password"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Password"
        android:paddingLeft="16dp" />

    <Button
        android:id="@+id/button_sign_in"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="Sign In" />

    <TextView
        android:id="@+id/button_sign_up"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="Do you have not an account! Sign up now?" />
</LinearLayout>

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.

6. 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!

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

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí