ดูภาพรวมของโมเดล เป็นส่วนหนึ่งของ Android Jetpack
คลาส ViewModel
คือตรรกะทางธุรกิจหรือสถานะระดับหน้าจอ
ตัวยึดตำแหน่ง ซึ่งจะแสดงสถานะให้กับ UI และสรุปตรรกะทางธุรกิจที่เกี่ยวข้องเอาไว้
ข้อดีหลักคือระบบจะแคชสถานะและคงสถานะไว้
การเปลี่ยนแปลงการกำหนดค่า ซึ่งหมายความว่า UI ของคุณไม่จําเป็นต้องดึงข้อมูลอีกครั้ง
เมื่อไปยังกิจกรรมต่างๆ หรือหลังจากการเปลี่ยนแปลงการกำหนดค่า เช่น
เมื่อหมุนหน้าจอ
ดูข้อมูลเพิ่มเติมเกี่ยวกับผู้ถือรัฐได้ที่คำแนะนำสำหรับผู้ถือรัฐ ในทำนองเดียวกัน หากต้องการข้อมูลเพิ่มเติมเกี่ยวกับเลเยอร์ UI โดยทั่วไป ให้ดูที่เลเยอร์ UI คำแนะนำ
ประโยชน์ของ ViewModel
อีกตัวเลือกหนึ่งที่ใช้แทน ViewModel คือคลาสธรรมดาที่เก็บข้อมูลที่คุณแสดง ใน UI ของคุณ ปัญหานี้อาจกลายเป็นปัญหาเมื่อไปยังกิจกรรมต่างๆ หรือ ปลายทางการนำทาง การดำเนินการดังกล่าวจะทำลายข้อมูลดังกล่าวหากคุณไม่จัดเก็บไว้ โดยใช้กลไกการบันทึกสถานะอินสแตนซ์ ViewModel มอบฟังก์ชัน API สำหรับความต่อเนื่องของข้อมูลที่ช่วยแก้ไขปัญหานี้ได้
ประโยชน์หลักๆ ของคลาส ViewModel หลักๆ แล้วมีอยู่ 2 ประการดังนี้
- ซึ่งช่วยให้คุณรักษาสถานะ UI ไว้ได้
- มอบการเข้าถึงตรรกะทางธุรกิจ
ความต่อเนื่อง
ViewModel ช่วยให้สามารถคงอยู่ผ่านทั้งสถานะที่ ViewModel ระงับ และ การดำเนินการที่ ViewModel ทริกเกอร์ การแคชนี้หมายความว่าคุณไม่จำเป็นต้อง เพื่อดึงข้อมูลอีกครั้งผ่านการเปลี่ยนแปลงการกำหนดค่าทั่วไป เช่น หน้าจอ การหมุน
ขอบเขต
เมื่อคุณสร้างอินสแตนซ์ ViewModel แล้วส่งเป็นออบเจ็กต์ที่นำการเรียก
ViewModelStoreOwner
ซึ่งอาจเป็นปลายทางของการนำทาง
กราฟการนำทาง กิจกรรม ส่วนย่อย หรือประเภทอื่นใดที่ใช้
ของ Google จากนั้น ViewModel จะมีขอบเขตเป็นวงจรของ
ViewModelStoreOwner
รูปภาพจะอยู่ในความทรงจำจนถึงเวลา ViewModelStoreOwner
จะหายไปอย่างถาวร
ช่วงคลาสจะเป็นคลาสย่อยทางตรงหรือทางอ้อมของ
อินเทอร์เฟซของ ViewModelStoreOwner
คลาสย่อยโดยตรงได้แก่
ComponentActivity
, Fragment
และ NavBackStackEntry
ดูรายการคลาสย่อยโดยอ้อมทั้งหมดได้ที่
การอ้างอิง ViewModelStoreOwner
รายการ
เมื่อส่วนย่อยหรือกิจกรรมที่มีการกำหนดขอบเขต ViewModel ถูกทำลาย การทำงานแบบไม่พร้อมกันจะดำเนินต่อไปใน ViewModel ที่มีขอบเขตการทำงาน นี่คือ กุญแจสู่ความต่อเนื่อง
ดูข้อมูลเพิ่มเติมได้ที่ส่วนวงจรการใช้งาน ViewModel ด้านล่าง
สถานะแฮนเดิลที่บันทึกไว้
SavedStateHandle ให้คุณคงข้อมูลไว้ได้ ไม่ใช่แค่การกำหนดค่า เปลี่ยนแปลงไป แต่ครอบคลุมกระบวนการทำงานต่างๆ มากขึ้น กล่าวคือคุณสามารถทำให้ UI จะยังคงเดิมแม้ว่าผู้ใช้จะปิดแอปและเปิดแอปในภายหลังก็ตาม
การเข้าถึงตรรกะทางธุรกิจ
แม้ว่าจะมีตรรกะทางธุรกิจส่วนใหญ่อยู่ในข้อมูล เลเยอร์ UI ยังสามารถมีตรรกะทางธุรกิจได้ด้วย ในบางกรณี รวมข้อมูลจากที่เก็บหลายแหล่งเพื่อสร้างสถานะ UI ของหน้าจอ หรือเมื่อ ข้อมูลบางประเภทไม่จำเป็นต้องใช้ชั้นข้อมูล
ViewModel เป็นที่ที่เหมาะสมในการจัดการตรรกะทางธุรกิจในเลเยอร์ UI ViewModel ยังรับผิดชอบการจัดการเหตุการณ์และมอบสิทธิ์ให้ ของลำดับชั้นเมื่อจำเป็นต้องใช้ตรรกะทางธุรกิจเพื่อแก้ไข ข้อมูลแอปพลิเคชัน
Jetpack Compose
เมื่อใช้ Jetpack Compose, ViewModel เป็นวิธีหลักในการแสดง UI ของหน้าจอ เป็น Composable ในแอปแบบผสม กิจกรรมและ Fragment จะโฮสต์ ฟังก์ชันที่ประกอบกันได้ นี่เป็นการเปลี่ยนแปลงจากแนวทางที่ผ่านมา ซึ่งไม่ ง่ายและสะดวกในการสร้าง UI ที่นำกลับมาใช้ใหม่ได้ด้วยกิจกรรมและ แฟรกเมนต์ใหม่ ซึ่งทำให้มีการใช้งานมากขึ้นมากในฐานะตัวควบคุม UI
สิ่งสำคัญที่สุดที่ต้องคำนึงถึงเมื่อใช้ ViewModel กับการเขียนคือ
ว่าคุณไม่สามารถกำหนดขอบเขต ViewModel ให้กับ Composable ได้ นั่นเป็นเพราะ Composable
ไม่ใช่ ViewModelStoreOwner
Composable เดียวกันสองอินสแตนซ์ใน
Composable หรือ Composable ที่ต่างกัน 2 รายการเข้าถึง ViewModel เดียวกัน
ภายใต้ ViewModelStoreOwner
เดียวกันจะได้รับอินสแตนซ์ เดียวกันของ
ViewModel ซึ่งมักไม่ใช่ลักษณะการทำงานตามที่คาดไว้
หากต้องการรับประโยชน์ของ ViewModel ใน Compose ให้โฮสต์แต่ละหน้าจอเป็น Fragment หรือกิจกรรม หรือใช้ Compose Navigation และใช้ ViewModels ใน Composable ให้อยู่ใกล้กับจุดหมายของการนำทางมากที่สุด นั่นเป็นเพราะ คุณสามารถกำหนดขอบเขต ViewModel ไปยังปลายทางการนำทาง กราฟการนำทาง กิจกรรมและส่วนย่อย
ดูข้อมูลเพิ่มเติมได้ในคู่มือเกี่ยวกับการยกรัฐสำหรับ Jetpack Compose
ใช้ ViewModel
ต่อไปนี้เป็นตัวอย่างการใช้งาน ViewModel สำหรับหน้าจอที่ ซึ่งช่วยให้ผู้ใช้ทอยลูกเต๋าได้
Kotlin
data class DiceUiState(
val firstDieValue: Int? = null,
val secondDieValue: Int? = null,
val numberOfRolls: Int = 0,
)
class DiceRollViewModel : ViewModel() {
// Expose screen UI state
private val _uiState = MutableStateFlow(DiceUiState())
val uiState: StateFlow<DiceUiState> = _uiState.asStateFlow()
// Handle business logic
fun rollDice() {
_uiState.update { currentState ->
currentState.copy(
firstDieValue = Random.nextInt(from = 1, until = 7),
secondDieValue = Random.nextInt(from = 1, until = 7),
numberOfRolls = currentState.numberOfRolls + 1,
)
}
}
}
Java
public class DiceUiState {
private final Integer firstDieValue;
private final Integer secondDieValue;
private final int numberOfRolls;
// ...
}
public class DiceRollViewModel extends ViewModel {
private final MutableLiveData<DiceUiState> uiState =
new MutableLiveData(new DiceUiState(null, null, 0));
public LiveData<DiceUiState> getUiState() {
return uiState;
}
public void rollDice() {
Random random = new Random();
uiState.setValue(
new DiceUiState(
random.nextInt(7) + 1,
random.nextInt(7) + 1,
uiState.getValue().getNumberOfRolls() + 1
)
);
}
}
จากนั้นคุณจะเข้าถึง ViewModel จากกิจกรรมได้โดยทำดังนี้
Kotlin
import androidx.activity.viewModels
class DiceRollActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Create a ViewModel the first time the system calls an activity's onCreate() method.
// Re-created activities receive the same DiceRollViewModel instance created by the first activity.
// Use the 'by viewModels()' Kotlin property delegate
// from the activity-ktx artifact
val viewModel: DiceRollViewModel by viewModels()
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect {
// Update UI elements
}
}
}
}
}
Java
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Create a ViewModel the first time the system calls an activity's onCreate() method.
// Re-created activities receive the same MyViewModel instance created by the first activity.
DiceRollViewModel model = new ViewModelProvider(this).get(DiceRollViewModel.class);
model.getUiState().observe(this, uiState -> {
// update UI
});
}
}
Jetpack Compose
import androidx.lifecycle.viewmodel.compose.viewModel
// Use the 'viewModel()' function from the lifecycle-viewmodel-compose artifact
@Composable
fun DiceRollScreen(
viewModel: DiceRollViewModel = viewModel()
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
// Update UI elements
}
ใช้โครูทีนกับ ViewModel
ViewModel
รองรับโครูทีน Kotlin สามารถคงประสิทธิภาพ
การทำงานแบบอะซิงโครนัสในลักษณะเดียวกับที่ยังคงสถานะ UI อยู่
ดูข้อมูลเพิ่มเติมได้ที่ใช้โครูทีน Kotlin กับสถาปัตยกรรม Android คอมโพเนนต์
วงจรชีวิตของ ViewModel
วงจรของ ViewModel
เชื่อมโยงกับขอบเขตโดยตรง ViewModel
จะยังอยู่ในหน่วยความจำจนถึง ViewModelStoreOwner
ซึ่งมีการกำหนดขอบเขต
หายไป ซึ่งอาจเกิดขึ้นในบริบทต่อไปนี้
- ในกรณีที่มีกิจกรรม ให้ถือว่าสิ้นสุด
- ในกรณีที่มีเศษส่วนอยู่ ส่วนที่แยกออกมา
- ในกรณีของรายการการนำทาง เมื่อนำรายการดังกล่าวออกจากสแต็กด้านหลัง
สิ่งนี้ทำให้ ViewModels เป็นโซลูชันที่ยอดเยี่ยมสำหรับการจัดเก็บข้อมูลที่ใช้ได้จริง การเปลี่ยนแปลงการกำหนดค่า
รูปที่ 1 แสดงถึงสถานะในวงจรต่างๆ ของกิจกรรมที่เกิดขึ้น
หมุนเวียนไปเรื่อยๆ จนครบ ภาพประกอบแสดงอายุการใช้งาน
ViewModel
ถัดจากวงจรกิจกรรมที่เกี่ยวข้อง ส่วนนี้
แผนภาพแสดงสถานะของกิจกรรม สถานะพื้นฐานเหมือนกันจะมีผลกับ
วงจรของส่วนย่อย
โดยปกติคุณจะขอViewModel
ในครั้งแรกที่ระบบเรียกใช้
เมธอด onCreate()
ของออบเจ็กต์กิจกรรม ระบบอาจเรียก
onCreate()
หลายครั้งตลอดระยะเวลาของกิจกรรม เช่น
เช่นเดียวกับเมื่อหมุนหน้าจออุปกรณ์ จะมีViewModel
อยู่เมื่อคุณ
ส่งคำขอ ViewModel
ครั้งแรกจนกว่ากิจกรรมจะเสร็จสิ้นและทำลายแล้ว
กำลังล้างทรัพยากร Dependency ของ ViewModel
ViewModel เรียกเมธอด onCleared
เมื่อ ViewModelStoreOwner
จะทำลายผลิตภัณฑ์นั้นไปตลอดวงจรชีวิต ซึ่งจะช่วยให้คุณล้างข้อมูลงานต่างๆ
หรือทรัพยากร Dependency ที่อยู่หลังวงจรของ ViewModel
ตัวอย่างต่อไปนี้แสดงถึงทางเลือกอื่นของ viewModelScope
viewModelScope
เป็น CoroutineScope
ในตัวที่
จะทำตามวงจรของ ViewModel โดยอัตโนมัติ ViewModel ใช้เพื่อ
ทำให้เกิดการดำเนินการที่เกี่ยวข้องกับธุรกิจ หากคุณต้องการใช้ขอบเขตที่กำหนดเองแทน
ของ viewModelScope
เพื่อทดสอบที่ง่ายขึ้น ViewModel สามารถรับค่า
CoroutineScope
เป็นทรัพยากร Dependency ในเครื่องมือสร้าง เมื่อ
ViewModelStoreOwner
จะล้าง ViewModel เมื่อสิ้นสุดวงจรชีวิต
ViewModel ยกเลิก CoroutineScope
ด้วย
class MyViewModel(
private val coroutineScope: CoroutineScope =
CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
) : ViewModel() {
// Other ViewModel logic ...
override fun onCleared() {
coroutineScope.cancel()
}
}
ตั้งแต่อายุการใช้งาน เวอร์ชัน 2.5 ขึ้นไป คุณจะส่ง Closeable
ได้อย่างน้อย 1 รายการ
ไปยังตัวสร้างของ ViewModel ซึ่งจะปิดโดยอัตโนมัติเมื่อ
ล้างอินสแตนซ์ ViewModel แล้ว
class CloseableCoroutineScope(
context: CoroutineContext = SupervisorJob() + Dispatchers.Main.immediate
) : Closeable, CoroutineScope {
override val coroutineContext: CoroutineContext = context
override fun close() {
coroutineContext.cancel()
}
}
class MyViewModel(
private val coroutineScope: CoroutineScope = CloseableCoroutineScope()
) : ViewModel(coroutineScope) {
// Other ViewModel logic ...
}
แนวทางปฏิบัติแนะนำ
ต่อไปนี้คือแนวทางปฏิบัติแนะนำที่สำคัญหลายประการที่คุณควรปฏิบัติตามเมื่อติดตั้งใช้งาน ViewModel:
- เนื่องจากการกำหนดขอบเขต ให้ใช้ ViewModels เป็นรายละเอียดการใช้งาน ตัวยึดสถานะระดับหน้าจอ อย่าใช้ไอคอนเหล่านี้เป็นเจ้าของสถานะของ UI ที่นำมาใช้ใหม่ได้ คอมโพเนนต์ เช่น กลุ่มชิปหรือรูปแบบ มิฉะนั้นคุณจะได้รับ อินสแตนซ์ ViewModel ในการใช้งานคอมโพเนนต์ UI เดียวกันในลักษณะที่แตกต่างกันภายใต้ ViewModelStoreOwner เว้นแต่คุณใช้คีย์โมเดลการดูที่ชัดแจ้งต่อชิป
- ViewModels ไม่ควรทราบเกี่ยวกับรายละเอียดการใช้งาน UI เก็บชื่อไว้ ของเมธอดที่ ViewModel API แสดงและช่องสถานะ UI กว้างที่สุดเท่าที่จะทำได้ ด้วยวิธีนี้ ViewModel ของคุณสามารถรองรับ UI: โทรศัพท์มือถือ อุปกรณ์แบบพับได้ แท็บเล็ต หรือแม้แต่ Chromebook
- เนื่องจากอาจมีอายุนานกว่า
ViewModelStoreOwner
, ViewModels ไม่ควรเก็บข้อมูลอ้างอิงของ API ที่เกี่ยวข้องกับวงจร เช่นContext
หรือResources
เพื่อป้องกันการรั่วไหลของหน่วยความจำ - อย่าส่ง ViewModels ไปยังคลาส ฟังก์ชัน หรือคอมโพเนนต์ UI อื่นๆ และเนื่องจากแพลตฟอร์มจะจัดการสิ่งเหล่านี้ คุณควรทำให้พาร์ทเนอร์เหล่านี้ใกล้ชิดกับคุณมากที่สุด ได้ ใกล้กับฟังก์ชันที่ประกอบกันได้ ได้แก่ กิจกรรม ส่วนย่อย หรือหน้าจอ ซึ่งจะป้องกันไม่ให้คอมโพเนนต์ระดับล่างเข้าถึงข้อมูลและตรรกะมากกว่า ที่จำเป็น
ข้อมูลเพิ่มเติม
เนื่องจากข้อมูลของคุณมีความซับซ้อนมากขึ้น คุณอาจเลือกแยกชั้นเรียนเฉพาะ
เพื่อโหลดข้อมูล วัตถุประสงค์ของ ViewModel
คือการสรุปข้อมูลสำหรับ
ตัวควบคุม UI เพื่อให้ข้อมูลผ่านการเปลี่ยนแปลงการกำหนดค่า สำหรับข้อมูล
เกี่ยวกับวิธีโหลด คงไว้ และจัดการข้อมูลในการเปลี่ยนแปลงการกำหนดค่า โปรดดู
สถานะ UI ที่บันทึกไว้
คู่มือสถาปัตยกรรมแอป Android แนะนำให้สร้างคลาสที่เก็บ ในการจัดการฟังก์ชันเหล่านี้
แหล่งข้อมูลเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับชั้นเรียน ViewModel
ได้ที่
ที่ไม่ซับซ้อน
เอกสารประกอบ
ตัวอย่าง
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- ใช้โครูทีน Kotlin กับคอมโพเนนต์ที่รับรู้วงจรชีวิต
- บันทึกสถานะ UI
- โหลดและแสดงข้อมูลแบบแบ่งหน้า