iT邦幫忙

2025 iThome 鐵人賽

DAY 8
0

今天會介紹Lifecycle和Data Binding兩個學習MVVM架構也會需要學的知識,最後會有個簡單的範例來實作一下,那就直接開始(・w・)b

Lifecycle

在傳統 Android 開發中,最大的痛點之一就是螢幕旋轉。一旦旋轉,Activity 就會被銷毀再重建,所有儲存在 Activity 中的臨時資料都會消失

  • Jetpack ViewModelLiveData

    1. ViewModel:ViewModel 與 Activity/Fragment 生命週期相關,但存活時間通常超過單次 Activity 實例,只要 Activity 還沒被使用者或系統徹底終結(例如按返回鍵),ViewModel 就會一直存在
      • Activity 建立 → ViewModel 建立
      • 螢幕旋轉,舊 Activity 銷毀 → ViewModel 依然存在
      • 新 Activity 建立 → 取得同一個 ViewModel 實例,資料完美保留
    2. LiveDataLiveData 是個具備生命週期感知能力的資料容器
      • 當你用 liveData.observe(this, ...) 訂閱它時,this(也就是 Activity或Fragment)會把自己的生命週期狀態告訴 LiveData
      • LiveData 只會在 Activity 處於活躍狀態STARTEDRESUMED)時,才會把最新的資料通知給它。如果 Activity 處於背景或已被銷毀,LiveData 不會觸發更新,從而避免了記憶體洩漏和程式崩潰
  • 加入 build.gradle 依賴

    要順利使用 ViewModelLiveData 等 Jetpack 元件,就要加入依賴

    dependencies {
        def lifecycle_version = "2.7.0"  //或最新版
        implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
        implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
    }
    

這邊只有簡單帶過,下面兩篇Google開發人員文章可以看看:D

https://developer.android.com/topic/libraries/architecture/lifecycle?hl=zh-tw

https://developer.android.com/guide/components/activities/activity-lifecycle?hl=zh-tw#java

Data Binding (資料綁定)

  • 用途:它取代了 MVP 中的 Contract,以一種更宣告式、更自動化的方式將兩者連結起來,消除了大量手動更新 UI 的樣板程式碼

  • 實現方式:它讓 ViewModel 的公開屬性與方法,成為一個隱性的合約。View 在 XML 佈局中直接宣告要如何履行這份合約

  • 優點:

    1. 消除樣板程式碼:不再需要 findViewById 和手動的 setTextsetOnClickListener
    2. Type Safe:在編譯時期就會檢查綁定的變數與方法是否存在,減少執行時期的錯誤
    3. 效能更好:Data Binding 會為每個佈局檔案產生一個對應的綁定類別,它會一次性地掃描所有元件並快取起來,避免了每次都遍歷 View 樹的 findViewById
    4. 架構清晰:讓 Activity/Fragment 的職責更單純,只專注於處理複雜的 UI 邏輯,而資料和事件則交由 ViewModel 和 XML 處理
  • 啟用與基本設定

    1. 打開build.gradle,在 android 區塊中輸入
    android {
        buildFeatures {
            dataBinding true
        }
    }
    
    1. 轉換佈局檔案
      • 根節點改為 <layout>
      • 加入 <data> 區塊:在 <layout> 下方、原來的根視圖(例如 LinearLayout)上方,加入 <data> 區塊來宣告要綁定的變數
    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
    
        <data>
            <variable
                name="viewModel"
                type="com.example.yourproject.LoginViewModel" />
        </data>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            </LinearLayout>
    
    </layout>
    
    1. 在 Activity/Fragment 中初始化
    // 假設您的佈局檔案是activity_main.xml
    // DataBinding會自動產生一個名為ActivityMainBinding的類別
    private ActivityMainBinding binding;
    private LoginViewModel viewModel;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    
        //使用DataBindingUtil取代setContentView
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
    
        //取得ViewModel實例
        viewModel = new ViewModelProvider(this).get(LoginViewModel.class);
    
        //將ViewModel設定給XML中宣告的變數
        binding.setViewModel(viewModel);
    
        //為LiveData設定生命週期擁有者,這樣LiveData變化才能自動更新 UI
        binding.setLifecycleOwner(this);
    }
    
  • 核心語法

    1. 單向綁定:@{}
      • 資料流向:ViewModel → View 用於顯示資料
    <TextView 
    		android:text="@{viewModel.userName}" />
    <ProgressBar
        android:visibility="@{viewModel.isLoading ? View.VISIBLE : View.GONE}" />
    
    1. 雙向綁定:@={}
      • 資料流向:ViewModel ↔ View 用於使用者輸入
    <EditText 
    		android:text="@={viewModel.userInput}" />
    
    1. 事件綁定:@{() -> ...}
      • 將 UI 事件直接綁定到 ViewModel 的方法
    <Button 
    		android:onClick="@{() -> viewModel.onLoginClicked()}" />
    <Button 
    		android:onClick="@{viewModel::onLoginClicked}" />
    
    1. 導入類別:<import>
      • 如果需要在 XML 中使用像 View.VISIBLE 這樣的靜態欄位或方法,可以先導入對應的類別
    <data>
        <import type="android.view.View" />
        <variable ... />
    </data>
    
    <ProgressBar
        android:visibility="@{viewModel.isLoading ? View.VISIBLE : View.GONE}" />
    

範例

用簡單的登入畫面,登入後畫面會顯示成功或失敗的範例來了解MVVM

activity_login.xml

<?xml version="1.0" encoding="utf-8"?>
<layout 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">
    <data>
        <variable
            name="viewModel_login_vm"
            type="com.example.mvvm_test.ui.login.LoginViewModel" />
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:gravity="top|center"
        tools:context=".ui.login.LoginActivity">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="#7034DB" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="MVVM"
            android:textSize="24sp"
            android:layout_marginTop="24dp"
            android:layout_marginBottom="24dp"/>
        <EditText
            android:layout_width="300dp"
            android:layout_height="60dp"
            android:hint="帳號"
            android:inputType="numberDecimal"
            android:layout_marginBottom="16dp"
            android:text="@={viewModel_login_vm.username}"/>
            <!--雙向綁定,使用者輸入的文字會自動更新到 ViewModel-->

        <EditText
            android:layout_width="300dp"
            android:layout_height="60dp"
            android:hint="密碼 (12345)"
            android:inputType="textPassword"
            android:layout_marginBottom="30dp"
            android:text="@={viewModel_login_vm.password}"/>

        <Button
            android:layout_width="200dp"
            android:layout_height="wrap_content"
            android:text="登入"
            android:textSize="18dp"
            android:layout_marginBottom="24dp"
            android:onClick="@{() -> viewModel_login_vm.onLoginClicked()}"/>
            <!--事件綁定,當按鈕被點擊時,會去執行viewModel_login_vm裡的onLoginClicked()-->

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="24dp"
            android:textSize="18sp"
            android:text="@{viewModel_login_vm.loginResult}"
            tools:text="登入結果:"/>
            <!--單向綁定,當loginResult的值改變時,TextView會自動更新-->
            <!--tools命名空間的屬性,只在Android Studio設計預覽畫面中有效 -->
    </LinearLayout>
</layout>

LoginActivity.java

package com.example.mvvm_test.ui.login;

import android.os.Bundle;

import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingComponent;
import androidx.databinding.DataBindingUtil;
import androidx.lifecycle.ViewModelProvider;

import com.example.mvvm_test.R;
import com.example.mvvm_test.databinding.ActivityLoginBinding;

public class LoginActivity extends AppCompatActivity {
    private LoginViewModel loginViewModel;
    private ActivityLoginBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        //它會載入activity_login,並回傳一個ActivityLoginBinding的實例,讓你用binding來操作佈局裡的東西
        binding = DataBindingUtil.setContentView(this,R.layout.activity_login);
        //獲取ViewModel,用ViewModelProvider可以確保ViewModel在Activity因為螢幕旋轉等原因重建時,裡面的資料還能存活下來,不會遺失
        loginViewModel = new ViewModelProvider(this).get(LoginViewModel.class);
        //將ViewModel和View連結起來
        binding.setViewModelLoginVm(loginViewModel);
        //設定LifecycleOwner之後,當ViewModel中的LiveData改變時,binding就會自動去更新對應的UI元件。
        binding.setLifecycleOwner(this);
    }
}

LoginViewModel.java

package com.example.mvvm_test.ui.login;

import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;

import com.example.mvvm_test.data.repository.LoginRepository;

public class LoginViewModel extends ViewModel {
    public MutableLiveData<String> username = new MutableLiveData<>();
    public MutableLiveData<String> password = new MutableLiveData<>();
    public MutableLiveData<String> loginResult = new MutableLiveData<>();
    // 使用MutableLiveData讓資料變成可觀察的。View可以觀察這些資料的變化來自動更新UI

    private final LoginRepository loginRepository;
    public LoginViewModel() {
        //在這裡建立LoginRepository的實例
        loginRepository = new LoginRepository();
    }

    //這個方法會被綁定到登入按鈕的點擊事件上,當使用者點擊登入按鈕時,方法會被觸發
    public void onLoginClicked() {

        //從LiveData取得密碼
        String CheckPassword = password.getValue();

        //呼叫Repository的login方法
        loginRepository.login(CheckPassword, new LoginRepository.OnLoginListener() {
            @Override
            public void Success() {
                loginResult.setValue("登入成功");
            }
            @Override
            public void Error() {
                loginResult.setValue("登入失敗");
            }
        });
    }
}

LoginRepository.java

package com.example.mvvm_test.data.repository;

public class LoginRepository {
    public interface OnLoginListener {
        void Success();
        void Error();
    }
    public void login(String password, OnLoginListener listener) {
        //透過listener呼叫方法通知ViewModel
        if (password.equals("12345")) {
            listener.Success();
        } else {
            listener.Error();
        }
    }
}

今天就先介紹到這裡,明天會來介紹簡單介紹Layout部分,明天見 掰 .w.
https://ithelp.ithome.com.tw/upload/images/20250922/20176154JqncVO0Z5m.png


上一篇
Day7 MVVM架構1
下一篇
Day9 各種Layout
系列文
Android 菜鳥30天從0到1的學習紀錄12
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言