iT邦幫忙

2024 iThome 鐵人賽

DAY 26
0

在 Android 應用開發中,架構設計是讓應用更具可擴展性、易於測試和維護的關鍵。而 MVP(Model-View-Presenter)架構是一種常用的設計模式,能夠清晰地將業務邏輯與視圖分離。在這篇文章中,我將介紹 MVP 架構的基本概念,並展示一個使用 MVP 架構開發的 Android 登入範例應用程式。

什麼是 MVP 架構?

MVP 是一種將應用邏輯拆分為三個主要部分的架構模式:

  • Model: 負責數據的處理與管理,例如資料的獲取、儲存等。它不應該依賴於 View 或者 Presenter。
  • view: 負責顯示數據和與用戶互動,包含界面上的元件(UI 元件)及其行為。View 只應該處理 UI 的渲染工作,不應該負責業務邏輯。
  • Presenter: 負責協調 Model 和 View 之間的交互邏輯。它接收來自 View 的用戶輸入,並根據這些輸入操作 Model,最後將結果反饋回 View。
    MVP 架構有助於使應用程式更加模組化,使得我們可以更容易地測試 Presenter 的邏輯,而不必擔心 UI 部分。

MVP 架構的示例:一個簡單的登入應用

這裡我們將實現一個簡單的登入功能,當用戶輸入帳號和密碼後,應用會檢查是否匹配,並根據結果給出不同的提示。該應用基於 MVP 架構進行設計,我們將展示三個主要組件:Model、View 和 Presenter。

1. MainActivity (View 層

MainActivity 是應用的主要界面,實現了 MainContract.view 介面,用於顯示登入的相關信息。
布局檔:

<?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"
    tools:context=".ui.main.MainActivity">

    <TextView
        android:id="@+id/account_tv"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:gravity="center"
        android:text="帳號 :"
        app:layout_constraintBottom_toTopOf="@+id/guideline4"
        app:layout_constraintEnd_toStartOf="@+id/guideline"
        app:layout_constraintHorizontal_bias="0.431"
        app:layout_constraintStart_toStartOf="@+id/guideline2"
        app:layout_constraintTop_toTopOf="@+id/guideline3"
        app:layout_constraintVertical_bias="0.297" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.26" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.1" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.29" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.38" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.47058824" />

    <TextView
        android:id="@+id/password_tv"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:gravity="center"
        android:text="密碼 :"
        app:layout_constraintBottom_toTopOf="@+id/guideline5"
        app:layout_constraintEnd_toStartOf="@+id/guideline"
        app:layout_constraintStart_toStartOf="@+id/guideline2"
        app:layout_constraintTop_toTopOf="@+id/guideline4" />

    <EditText
        android:id="@+id/account_et"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:ems="10"
        android:hint="輸入帳號"
        android:inputType="text"
        app:layout_constraintBottom_toTopOf="@+id/guideline4"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@+id/guideline"
        app:layout_constraintTop_toTopOf="@+id/guideline3" />

    <EditText
        android:id="@+id/password_et"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:ems="10"
        android:hint="輸入密碼"
        android:inputType="text"
        app:layout_constraintBottom_toTopOf="@+id/guideline5"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@+id/guideline"
        app:layout_constraintTop_toTopOf="@+id/guideline4" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:text="Button"
        app:layout_constraintBottom_toTopOf="@+id/guideline9"
        app:layout_constraintEnd_toStartOf="@+id/guideline7"
        app:layout_constraintHorizontal_bias="0.547"
        app:layout_constraintStart_toStartOf="@+id/guideline8"
        app:layout_constraintTop_toTopOf="@+id/guideline6"
        app:layout_constraintVertical_bias="0.531"
        android:onClick="setButton"/>

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline6"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.60328317" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline7"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.6885645" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline8"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.3187348" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline9"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.7564979" />

</androidx.constraintlayout.widget.ConstraintLayout>

java檔:

public class MainActivity extends AppCompatActivity implements MainContract.view {
    private EditText account, password; // 帳號和密碼輸入框
    private Button sent; // 登入按鈕
    private MainPresenter presenter; // Presenter 對象
    private Context context = this; // 上下文對象

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main); // 設定佈局文件

        // 初始化視圖元素
        account = findViewById(R.id.account_et);
        password = findViewById(R.id.password_et);
        sent = findViewById(R.id.button);

        // 創建 Presenter 實例
        presenter = new MainPresenter(this);
    }

    // 按鈕點擊事件,啟動登入流程
    public void setButton(View view) {
        presenter.getLoginData(account.getText().toString(), password.getText().toString());
    }

    // 顯示登入錯誤訊息
    @Override
    public void loginError() {
        Toast.makeText(context, "帳密錯誤", Toast.LENGTH_SHORT).show();
    }

    // 顯示使用者名稱
    @Override
    public void showUserName(String userName) {
        Toast.makeText(context, "歡迎登入 " + userName, Toast.LENGTH_SHORT).show();
    }
}

在這裡,MainActivity 充當 View 層,負責與用戶的交互。當按下登入按鈕時,會將用戶的帳號和密碼傳遞給 Presenter,由 Presenter 進行邏輯處理。

2.MainContract (Contract 層)

MainContract 定義了 View 和 Presenter 之間的契約,這裡我們定義了 View 和 Presenter 的互動方法。

public interface MainContract {
    // View 層的接口
    public interface view {
        void showUserName(String userName); // 顯示用戶名稱
        void loginError(); // 顯示登入錯誤訊息
    }

    // Presenter 層的接口
    interface presenter {
        void getLoginData(String account, String password); // 獲取登入數據
    }

    // Model 層的接口
    interface modle {
        String getUserName(); // 獲取用戶名稱
    }
}

這是 MVP 架構中的一個重要部分,MainContract 確保了 View 和 Presenter 之間的互動是通過定義好的方法進行的,讓整個架構更加清晰。

3. MainPresenter (Presenter 層)

MainPresenter 負責接收來自 View 的用戶輸入,並將其交給 Model 處理。處理完後再將結果返回給 View 進行展示。

public class MainPresenter implements MainContract.presenter {
    private String Username;
    private MainContract.view callBack; // View 層的引用
    private MainModle modle; // Model 層的引用

    public MainPresenter(MainContract.view view) {
        this.callBack = view;
        modle = new MainModle(this); // 建立 Model
    }

    // 檢查帳號和密碼的邏輯
    @Override
    public void getLoginData(String account, String password) {
        if (account.equals("1111") && password.equals("0000")) {
            Username = modle.getUserName(); // 從 Model 獲取用戶名
            callBack.showUserName(Username); // 回傳給 View
        } else {
            callBack.loginError(); // 登入失敗
        }
    }
}

MainPresenter 實現了 MainContract.presenter 介面,它負責帳號和密碼的驗證邏輯,並與 MainModleMainActivity 進行交互。

4. MainModle (Model 層)

MainModle 負責處理數據邏輯,這裡簡單地返回一個用戶名稱。
public class MainModle implements MainContract.modle {
private MainContract.presenter callBack;

public MainModle(MainContract.presenter present) {
    this.callBack = present;
}

// 返回使用者名稱
@Override
public String getUserName() {
    return "Damn";
}

}
MainModle 負責業務數據的處理,將處理結果返回給 Presenter

總結

MVP 架構提供了將應用邏輯與 UI 分離的解決方案,這不僅讓代碼更加模組化,也有助於提升應用程式的可測試性。在這篇文章中,我們以一個簡單的登入功能為例,演示了如何實現 MVP 架構。透過這樣的設計,開發者可以更容易地進行維護和擴展。


上一篇
# DAY25 GitHub 與 IDE 結合使用
下一篇
# DAY27 輕鬆保存應用設定——SharedPreferences
系列文
「淺入 Android Studio 開發環境」—— 工具與插件的高效使用29
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言