iT邦幫忙

2025 iThome 鐵人賽

DAY 24
0

MVP 架構簡介

  • V(View)- 視圖層

    負責顯示介面和處理使用者的互動行為(像是顯示資料、監聽按鈕點擊等)。
    它本身不處理業務邏輯,邏輯會交給 Presenter 來做。

  • P(Presenter)- 邏輯層

    負責所有的邏輯判斷和處理。
    它接收 View 發來的操作指令,去向 Model 取得或修改資料,並把結果回傳給 View,決定下一步畫面要怎麼呈現。
    可以想像成溝通的中介者,控制整個流程和業務規則。

  • M(Model)- 資料層

    負責管理和存取資料,像是用戶資料、帳號密碼或從資料庫撈資料。
    Model 像是資料的倉庫,Presenter 需要資料時就會向它取用。

  • Contract(介面合約)

    是一組用來定義 View、Presenter 和 Model 之間溝通方法的接口(interface)。
    透過 Contract,三者能清楚知道彼此提供和要求哪些功能,降低耦合度,也方便多人協作和維護。


檔案構成:

image


1.MainActivity (view)

ui介面,這頁是登入畫面,用來填寫帳號密碼的,其他的邏輯判斷並不寫在這一頁

public class MainActivity extends AppCompatActivity implements MainContract.view {
    private EditText accountEditText, passwordEditText;
    private Button loginButton;
    private MainPresenter presenter;
    //設定變數
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        accountEditText = findViewById(R.id.main_account_et);
        passwordEditText = findViewById(R.id.main_password_et);
        loginButton = findViewById(R.id.main_login_btn);

        //New出Presenter,並把自己丟入。
        presenter = new MainPresenter(this);

        loginButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //將accountEt和passwordEt裡的內容轉成字串並丟給設定的變數
                String account = accountEditText.getText().toString();
                String password = passwordEditText.getText().toString();

                //再將兩個變數丟給presenter去處理
                presenter.Login(account, password);
            }
        });
    }
    @Override
    public void LoginSuccess() {
        Toast.makeText(this, "登入成功", Toast.LENGTH_SHORT).show();
        Intent intent = new Intent(this, LoginActivity.class);
        startActivity(intent);
    }
    @Override
    public void LoginError() {
        Toast.makeText(this, "登入失敗,請檢查帳號或密碼", Toast.LENGTH_SHORT).show();
    }
}

2.MainPresenter (presenter)

presenter則負責處理邏輯判斷等工作,像這頁是判斷輸入的帳號與密碼是否與 Model 中預設的帳密相符,若相符則呼叫 View 的 LoginSuccess(),不相符則呼叫 View 的 LoginError(),另外,MainPresente、MainActivity、MainModle之間要連接呼叫都必須透過MainContract

public class MainPresenter implements MainContract.presenter {
    //用來與 MainActivity(View)溝通的介面
    //MainPresenter 不能直接呼叫 MainActivity,必須透過 MainContract.view 的介面來操作。
    private MainContract.view view;
    private MainModel model;
    public MainPresenter(MainContract.view view) {
        this.view = view;
        //接收view
        model = new MainModel(this);
        //初始化Model並把自己丟入
    }

    @Override
    public void Login(String username, String password) {
        //從model調正確資料過來
        //判斷MainActivity傳送過來的帳密與MainModel裡設定的帳密是否相同
        boolean isSuccess = model.LoginData(username, password);

        if (isSuccess) {
            view.LoginSuccess();
            //通知view顯示登入成功並執行登入成功後的操作
        } else {
            view.LoginError();
            //通知view顯示登入失敗並執行登入失敗後的操作
        }
    }
}

3.MainModle (modle)

modle它有些類似資料庫,負責管理和存取資料,當接收到MainPresenter查詢正確資料的請求後,它會將正確資料回傳給MainPresenter,供MainPresenter比對

public class MainModel implements MainContract.model {
    private MainContract.presenter presenter;
    public MainModel(MainContract.presenter presenter) {
        this.presenter = presenter;
        //接收presenter
    }

    public boolean LoginData(String username, String password) {
        return username.equals("admin") && password.equals("1234");
        //回傳正確的帳號是admin且正確的密碼為1234
    }
}

4.MainContract (介面合約)

MainContract 是在整個 MVP 架構中,作為View、Presenter、Model 三者溝通的橋樑,並定義介面合約,其他三頁之間的互動及資料往來都需要透過Contract做為橋樑

public interface MainContract {
    interface view{
        //登入成功時由 Presenter 呼叫,讓 View 做相對應的 UI 操作(如跳轉頁面、顯示提示)
        void LoginSuccess();
        void LoginError();
    }
    interface presenter{
        void Login(String username, String password);
    }
    interface model{
        boolean LoginData(String username, String password);
    }
}

下面是這兩個Activity完整的xml

activity_main

<?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:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:orientation="vertical"
        android:layout_weight="1">

        <TextView
            android:id="@+id/main_title_tv"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="0.1"
            android:gravity="center"
            android:text="Login"
            android:textSize="25dp"
            android:textStyle="bold" />

        <EditText
            android:id="@+id/main_account_et"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="0.1"
            android:hint="帳號"
            android:inputType="text"/>

        <EditText
            android:id="@+id/main_password_et"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:hint="密碼"
            android:layout_weight="0.1"
            android:inputType="textPassword" />

        <Button
            android:id="@+id/main_login_btn"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="0.1"
            android:text="登入" />

        <View
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1" />
    </LinearLayout>
</LinearLayout>

activity_login

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

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:layout_editor_absoluteX="1dp"
        tools:layout_editor_absoluteY="1dp">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:orientation="horizontal">

            <View
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1" />

            <View
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1" />

            <View
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1" />
        </LinearLayout>

        <TextView
            android:id="@+id/login_success_tv"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="0.2"
            android:gravity="center"
            android:textSize="50dp"
            android:textStyle="bold"
            android:text="登入成功" />

        <View
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1" />

    </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

執行成果展示:

image alt


上一篇
Day 23.RxJava+Retrofit
系列文
Android 新手的 30 天進化論:從初學者到小專案開發者24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言