ViewModel คือคอมโพเนนต์ Android Jetpack Architecture ที่ออกแบบมาเพื่อจัดเก็บและจัดการข้อมูล UI โดยคำนึงถึงวงจรชีวิตของ Activity และ Fragment ตามข้อมูลของ Google I/O 2025 มีการใช้ ViewModel ใน 82% ของแอปพลิเคชัน Android สมัยใหม่ที่สร้างบน Jetpack แตกต่างจากคลาสทั่วไป ViewModel จะทนต่อการหมุนหน้าจอและการเปลี่ยนแปลงการกำหนดค่าอื่นๆ โดยอัตโนมัติ โดยคงสถานะ UI ไว้โดยไม่สูญเสียข้อมูล สถาปัตยกรรม MVVM (Model-View-ViewModel) อาศัย ViewModel เป็นชั้นกลางที่เชื่อมต่อตรรกะทางธุรกิจกับอินเทอร์เฟซ
ประเด็นสำคัญ
ViewModel คือคลาสจากไลบรารี Android Jetpack ที่ออกแบบมาเพื่อจัดเก็บและจัดการข้อมูลที่เกี่ยวข้องกับอินเทอร์เฟซผู้ใช้ โดยคำนึงถึงวงจรชีวิตของ Activity หรือ Fragment งานหลักของ ViewModel คือการแยกตรรกะการเตรียมข้อมูลออกจากชั้น UI และรักษาข้อมูลนี้ไว้ในระหว่างการเปลี่ยนแปลงการกำหนดค่า เช่น การหมุนหน้าจอ การเปลี่ยนธีม หรือการเปลี่ยนภาษา
ก่อนที่ ViewModel จะเกิดขึ้น นักพัฒนาเก็บสถานะ UI ไว้ใน Activity หรือ Fragment โดยตรง เมื่อหมุนหน้าจอ Android จะทำลาย Activity และสร้างใหม่ — ข้อมูลที่ไม่ได้บันทึกทั้งหมดจะสูญหาย วิธีแก้ไขคือการบันทึกสถานะผ่าน onSaveInstanceState() หรือใช้ onRetainNonConfigurationInstance() แต่ทั้งสองวิธีต้องมีการจัดการด้วยตนเอง การทำให้เป็นลำดับ และไม่เหมาะกับวัตถุที่ซับซ้อน ViewModel แก้ปัญหานี้ในระดับเฟรมเวิร์ก: ข้อมูลอยู่ในหน่วยความจำแยกจาก UI และจะถูกส่งคืนโดยอัตโนมัติเมื่อสร้าง Activity ขึ้นใหม่
ตามเอกสาร Android Developers (2025) ViewModel เก็บข้อมูลใน RAM ของกระบวนการ — เร็วกว่า 10–50 เท่า เมื่อเทียบกับการกู้คืนจาก Bundle ผ่าน onSaveInstanceState() ซึ่งต้องการการทำให้เป็นลำดับเป็นอาร์เรย์ไบต์ ViewModel ได้รับการแนะนำสำหรับทุกหน้าจอที่ข้อมูลซับซ้อนกว่าชนิดพื้นฐานหรือสตริงธรรมดา
วงจรชีวิตของ ViewModel แตกต่างโดยพื้นฐานจากวงจรชีวิตของ Activity: ViewModel จะไม่ถูกทำลายเมื่อหมุนหน้าจอและมีชีวิตอยู่จนกว่าขอบเขตจะสิ้นสุดสมบูรณ์ (Activity.finish() หรือ Fragment ถูกลบ) ซึ่งหมายความว่าข้อมูลใดๆ ที่โหลดใน ViewModel จะยังคงพร้อมใช้งานในระหว่างการเปลี่ยนแปลงการกำหนดค่าโดยไม่ต้องโหลดซ้ำจากเครือข่ายหรือฐานข้อมูล
ในขณะที่สร้าง Activity ระบบจะจัดสรร ViewModel ผ่าน ViewModelProvider ในการเรียก ViewModelProvider.get(ViewModel::class.java) ครั้งแรก จะสร้างอินสแตนซ์ ViewModel ใหม่ ในการเรียกครั้งต่อๆ ไป (รวมถึงหลังหมุน) อินสแตนซ์เดิมจะถูกส่งคืน การล้าง ViewModel เกิดขึ้นโดยอัตโนมัติเมื่อเรียก onCleared() — เมธอดนี้จะถูกเรียกเมื่อ Activity สิ้นสุด (finish()) หรือ Fragment ถูกลบอย่างสมบูรณ์ นักพัฒนาสามารถโอเวอร์ไรด์ onCleared() เพื่อปลดปล่อยทรัพยากร: ยกเลิกการสมัคร Flow ยกเลิกโครูทีน ปิดซ็อกเก็ต
Google เน้นย้ำในเอกสาร Jetpack: ห้ามเก็บการอ้างอิงถึง Activity หรือ View ภายใน ViewModel โดยเด็ดขาด — สิ่งนี้ทำให้หน่วยความจำรั่วเนื่องจาก ViewModel มีอายุยืนกว่า Activity ที่มี UI ของมัน ให้ใช้ LiveData, StateFlow หรือ SavedStateHandle เพื่อส่งข้อมูลระหว่าง ViewModel และ UI แทน
ในรูปแบบ MVVM (Model-View-ViewModel) ViewModel ครอบครองตำแหน่งศูนย์กลางระหว่าง View (Activity/Fragment) และ Model (พื้นที่เก็บข้อมูล DB, API) View สมัครรับข้อมูลเชิงโต้ตอบจาก ViewModel (LiveData, StateFlow) และอัปเดตโดยอัตโนมัติเมื่อมีการเปลี่ยนแปลง ViewModel ไม่รู้จักการมีอยู่ของ View — มันเพียงให้ข้อมูลและคำสั่ง และ View ตัดสินใจว่าจะแสดงอย่างไร
การเปรียบเทียบ MVP และ MVVM: ใน MVP พรีเซนเตอร์เรียกเมธอดของ View (อินเทอร์เฟซ) โดยตรง ทำให้เกิดการผูกแน่น ใน MVVM ViewModel เผยแพร่สตรีมข้อมูลเชิงโต้ตอบและ View สมัครรับข้อมูล — การเชื่อมต่อเป็นทิศทางเดียวและทดสอบได้ ตามการสำรวจของ JetBrains Developer Survey (2024) 68% ของนักพัฒนา Android ใช้ MVVM เป็นสถาปัตยกรรมหลัก และ ViewModel เป็นคอมโพเนนต์สำคัญของรูปแบบนี้
ที่ IT Sectr เราใช้ MVVM กับ ViewModel ตั้งแต่ปี 2018 ในโครงการเชิงพาณิชย์ทั้งหมดบน Kotlin การปฏิบัติแสดงให้เห็นว่าแนวทางนี้ลดเวลาในการดีบักตรรกะ UI ลง 30–40% เนื่องจากการแบ่งแยกหน้าที่ชัดเจนและความสามารถในการทดสอบตรรกะทางธุรกิจโดยไม่ต้องใช้โปรแกรมจำลอง
ViewModelProvider เป็นวิธีมาตรฐานในการรับ ViewModel ใน fragment หรือ Activity โดยค่าเริ่มต้น ViewModelProvider สร้าง ViewModel ผ่านคอนสตรัคเตอร์ว่าง (ไม่มีอาร์กิวเมนต์) หาก ViewModel ต้องการพารามิเตอร์ (เช่น พื้นที่เก็บข้อมูลหรือบริบทของแอปพลิเคชัน) จำเป็นต้องใช้ ViewModelProvider.Factory
class UserViewModel(
private val userId: String,
private val repository: UserRepository
) : ViewModel() {
private val _user = MutableLiveData<User>()
val user: LiveData<User> get() = _user
fun loadUser() {
viewModelScope.launch {
_user.value = repository.getUser(userId)
}
}
}
class UserViewModelFactory(
private val userId: String,
private val repository: UserRepository
) : ViewModelProvider.Factory {
override fun create<T : ViewModel>(modelClass: Class<T>): T {
return UserViewModel(userId, repository) as T
}
}
โรงงานถูกส่งไปยัง ViewModelProvider เมื่อรับ ViewModel จาก Fragment หรือ Activity SavedStateHandle เป็นกลไกการส่งพารามิเตอร์ทางเลือกที่เปิดตัวใน AndroidX 1.2.0: ViewModel จะรับ SavedStateHandle โดยอัตโนมัติผ่านคอนสตรัคเตอร์ และอาร์กิวเมนต์จะถูกส่งผ่าน Bundle โดยไม่ต้องเขียนโรงงานเอง
viewModelScope คือ CoroutineScope ที่สร้างใน ViewModel และผูกกับวงจรชีวิตของมัน โครูทีนทั้งหมดที่เริ่มใน viewModelScope จะถูกยกเลิกโดยอัตโนมัติเมื่อเรียก onCleared() ซึ่งป้องกันการรั่วไหลของหน่วยความจำและการทำงานเบื้องหลังหลังจากการทำลาย ViewModel
class DashboardViewModel : ViewModel() {
private val _items = MutableLiveData<List<Item>>()
val items: LiveData<List<Item>> get() = _items
fun loadDashboard() {
viewModelScope.launch(Dispatchers.IO) {
val result = repository.fetchDashboard()
withContext(Dispatchers.Main) {
_items.value = result
}
}
}
override fun onCleared() {
super.onCleared()
// โครูทีนทั้งหมดของ viewModelScope ถูกยกเลิกโดยอัตโนมัติ
}
}
โครูทีนใน viewModelScope ทำงานบน Dispatchers.Main โดยค่าเริ่มต้น สำหรับการดำเนินการเครือข่ายหรือดิสก์ ให้สลับไปที่ Dispatchers.IO โดยใช้ withContext หรือระบุตัวจัดส่งใน launch ตามข้อมูลของ Google (Android Dev Summit 2024) การใช้ viewModelScope ช่วยลดการรั่วไหลของหน่วยความจำที่เกี่ยวข้องกับโครูทีนลง 95% เมื่อเทียบกับการจัดการ Job ด้วยตนเอง
Hilt คือไลบรารีการฉีดพึ่งพาอย่างเป็นทางการของ Google สำหรับ Android ที่สร้างบน Dagger ด้วย Hilt ไม่จำเป็นต้องเขียน ViewModelProvider.Factory ด้วยตนเอง — เพียงเพิ่มคำอธิบายประกอบ @HiltViewModel ที่คอนสตรัคเตอร์ ViewModel Hilt จะสร้างโรงงานและฉีดพึ่งพาที่ประกาศในคอนสตรัคเตอร์โดยอัตโนมัติ
@HiltViewModel
class ProfileViewModel constructor(
private val repository: UserRepository,
private val analytics: AnalyticsTracker
) : ViewModel() {
private val _profile = MutableStateFlow<ProfileState>(ProfileState.Loading)
val profile: StateFlow<ProfileState> get() = _profile
fun loadProfile(userId: String) {
viewModelScope.launch {
_profile.value = ProfileState.Success(repository.getUser(userId))
analytics.logEvent("profile_loaded")
}
}
}
// ใน Fragment — ไม่มีโรงงาน:
val viewModel: ProfileViewModel = by viewModels()
Koin เป็นไลบรารี DI ทางเลือกที่ไม่ต้องสร้างโค้ด ใน Koin มีการประกาศ ViewModel ในโมดูลผ่าน viewModel { } และใน fragment จะได้รับผ่าน by viewModel() การเลือกระหว่าง Hilt และ Koin ขึ้นอยู่กับโครงการ: Hilt ให้การตรวจสอบกราฟพึ่งพาในเวลาคอมไพล์ Koin เบากว่าและไม่ต้องการ kapt/ksp ที่ IT Sectr เราใช้ Hilt ในโครงการขนาดใหญ่ (มากกว่า 50 หน้าจอ) และ Koin ในโครงการขนาดกลาง
ViewModel อย่างง่ายที่เก็บตัวนับจำนวนเต็มซึ่งไม่ถูกรีเซ็ตเมื่อหมุนหน้าจอ แสดงรูปแบบพื้นฐานของการใช้ MutableLiveData และ LiveData
class CounterViewModel : ViewModel() {
private val _count = MutableLiveData(0)
val count: LiveData<Int> get() = _count
fun increment() {
_count.value = (_count.value ?: 0) + 1
}
fun reset() {
_count.value = 0
}
}
ViewModel ที่ใช้ SavedStateHandle เพื่อรักษาสถานะโดยอัตโนมัติแม้ว่ากระบวนการจะถูกระบบยุติ SavedStateHandle เป็นกลไกเดียวที่บันทึกข้อมูลเมื่อแอปถูกย่อในเบื้องหลังและสิ้นสุด
class FormViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
val userName = savedStateHandle.getLiveData<String>("userName", "")
val email = savedStateHandle.getLiveData<String>("email", "")
fun saveName(name: String) {
savedStateHandle["userName"] = name
}
fun saveEmail(email: String) {
savedStateHandle["email"] = email
}
}
LiveData จาก SavedStateHandle จะบันทึกค่าล่าสุดใน Bundle โดยอัตโนมัติ เมื่อสร้างกระบวนการใหม่ (เช่น หลังจากย่อและปิดแอป) Bundle จะถูกกู้คืนและ LiveData จะรับค่าก่อนหน้า ตามการทดสอบของ Google SavedStateHandle รับประกันการบันทึกข้อมูลสูงสุด 5 KB ใน Bundle — เพียงพอสำหรับฟิลด์ข้อความ ID และวัตถุ JSON ที่ถูกทำให้เป็นลำดับ
คำถามที่พบบ่อย
ViewModel เก็บข้อมูลใน RAM ของกระบวนการ — พร้อมใช้งานทันทีโดยไม่ต้องทำให้เป็นลำดับ เหมาะสำหรับวัตถุที่ซับซ้อน (รายการ Bitmap การตอบสนองเครือข่าย) onSaveInstanceState() ทำให้ข้อมูลเป็นลำดับใน Bundle (สูงสุด 1 MB ต่อธุรกรรมตั้งแต่ Android 12) และเหมาะสำหรับชนิดพื้นฐานธรรมดา String และ Serializable/Parcelable เท่านั้น ViewModel + SavedStateHandle เป็นการผสมผสานที่ Google แนะนำ: ViewModel สำหรับข้อมูลรันไทม์ SavedStateHandle สำหรับการกู้คืนเมื่อกระบวนการถูกยุติ
ไม่ ระบบจะเรียก onCleared() โดยอัตโนมัติเมื่อขอบเขตสิ้นสุด การล้างด้วยตนเองผ่าน viewModelStore.clear() จำเป็นเฉพาะในการทดสอบเพื่อป้องกันการรั่วไหลระหว่างกรณีทดสอบ ในโค้ดการผลิต ห้ามเรียก clear() ด้วยตนเองโดยเด็ดขาด — สิ่งนี้ทำลายวงจรชีวิตของ ViewModel และอาจทำให้พฤติกรรม UI ที่คาดเดาไม่ได้
ใช่ ViewModel รองรับอย่างเต็มที่ใน Jetpack Compose ผ่านฟังก์ชัน viewModel() ใน Compose ViewModel จะได้รับที่ระดับขอบเขต Composable และจะถูกล้างโดยอัตโนมัติเมื่อออกจากขอบเขต Compose เวอร์ชันของ MVVM เรียกว่า การไหลของข้อมูลทิศทางเดียว (UDF): ViewModel เผยแพร่ StateFlow และฟังก์ชัน Composable สมัครรับข้อมูลผ่าน collectAsState() Compose เวอร์ชันของแนวทาง reducer คือ MVI กับ ViewModel
ห้าม เก็บการอ้างอิงถึง Activity, Fragment, View, Context (ยกเว้น Application) สิ่งนี้ทำให้หน่วยความจำรั่วเนื่องจาก ViewModel มีอายุยืนกว่าบริบท UI อย่าเก็บ สถานะ View ที่ถูกทำให้เป็นลำดับ (เช่น ตำแหน่ง RecyclerView) — ใช้ LayoutManager.onSaveInstanceState() หลีกเลี่ยง การเก็บข้อมูลปริมาณมาก (มากกว่า 10 MB) — เมื่อกระบวนการถูกย่อ ข้อมูลจะสูญหายหากไม่มี SavedStateHandle
ViewModel ถูกทดสอบเหมือนคลาส Kotlin ทั่วไปโดยไม่ต้องใช้โปรแกรมจำลอง: สร้างอินสแตนซ์ เรียกเมธอด ตรวจสอบสถานะของ LiveData หรือ StateFlow สำหรับการทดสอบโครูทีน ให้ใช้ runTest จาก kotlinx-coroutines-test กับ TestDispatcher สำหรับ ViewModel กับ Hilt ให้ใช้ @HiltViewModelTest และ hiltViewModel() ใน fragment ทดสอบ ตามข้อมูลของ Google การทดสอบหน่วยครอบคลุม 80–90% ของตรรกะ ViewModel โดยไม่ต้องทดสอบด้วยเครื่องมือ
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ