今天會介紹Lifecycle和Data Binding兩個學習MVVM架構也會需要學的知識,最後會有個簡單的範例來實作一下,那就直接開始(・w・)b
在傳統 Android 開發中,最大的痛點之一就是螢幕旋轉。一旦旋轉,Activity 就會被銷毀再重建,所有儲存在 Activity 中的臨時資料都會消失
Jetpack ViewModel
和 LiveData
:
LiveData
是個具備生命週期感知能力的資料容器
liveData.observe(this, ...)
訂閱它時,this
(也就是 Activity或Fragment)會把自己的生命週期狀態告訴 LiveDataSTARTED
或 RESUMED
)時,才會把最新的資料通知給它。如果 Activity 處於背景或已被銷毀,LiveData 不會觸發更新,從而避免了記憶體洩漏和程式崩潰加入 build.gradle
依賴
要順利使用 ViewModel
和 LiveData
等 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
用途:它取代了 MVP 中的 Contract
,以一種更宣告式、更自動化的方式將兩者連結起來,消除了大量手動更新 UI 的樣板程式碼
實現方式:它讓 ViewModel 的公開屬性與方法,成為一個隱性的合約。View 在 XML 佈局中直接宣告要如何履行這份合約
優點:
findViewById
和手動的 setText
、setOnClickListener
findViewById
啟用與基本設定
android {
buildFeatures {
dataBinding true
}
}
<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>
// 假設您的佈局檔案是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);
}
核心語法
@{}
<TextView
android:text="@{viewModel.userName}" />
<ProgressBar
android:visibility="@{viewModel.isLoading ? View.VISIBLE : View.GONE}" />
@={}
<EditText
android:text="@={viewModel.userInput}" />
@{() -> ...}
<Button
android:onClick="@{() -> viewModel.onLoginClicked()}" />
<Button
android:onClick="@{viewModel::onLoginClicked}" />
<import>
View.VISIBLE
這樣的靜態欄位或方法,可以先導入對應的類別<data>
<import type="android.view.View" />
<variable ... />
</data>
<ProgressBar
android:visibility="@{viewModel.isLoading ? View.VISIBLE : View.GONE}" />
用簡單的登入畫面,登入後畫面會顯示成功或失敗的範例來了解MVVM
<?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>
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);
}
}
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("登入失敗");
}
});
}
}
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.