LiveData — คอนเทนเนอร์ข้อมูลที่สังเกตได้จาก Android Jetpack ที่คำนึงถึงวงจรชีวิตของ Activity, Fragment หรือ Service มาดูกันว่า LiveData จัดการการสมัครรับข้อมูลโดยอัตโนมัติอย่างไร: ผู้สมัครรับข้อมูลที่ใช้งานอยู่ได้รับการอัปเดต ส่วนที่ไม่ใช้งาน — ไม่ได้รับ ซึ่งช่วยขจัดหน่วยความจำรั่วไหลและแครชจากข้อมูลอ้างอิงที่ล้าสมัย ตามข้อมูลของ Google (Android Developers, 2025) LiveData ถูกใช้ใน 74% ของโปรเจกต์บน Java และ Kotlin เป็นวิธีหลักในการส่งข้อมูลเชิงปฏิกิริยาจาก ViewModel ไปยัง UI
สาระสำคัญ
LiveData — คลาสจากไลบรารี Android Jetpack ที่ใช้รูปแบบ Observer โดยคำนึงถึงวงจรชีวิต แตกต่างจาก Observable หรือ Flow ทั่วไป LiveData จัดการการสมัครรับข้อมูลโดยอัตโนมัติ: Observer จะได้รับการแจ้งเตือนเฉพาะเมื่อ LifecycleOwner อยู่ในสถานะทำงาน (STARTED หรือ RESUMED) หากเจ้าของวงจรชีวิตเปลี่ยนเป็นสถานะไม่ทำงาน (STOPPED หรือ DESTROYED) การสมัครรับข้อมูลจะถูกระงับหรือลบออก
LiveData ถูกนำเสนอใน Android Architecture Components (AAC) ในปี 2017 ที่ Google I/O พร้อมกับ ViewModel และ Room แรงจูงใจหลักคือการขจัดปัญหาหน่วยความจำรั่วไหลเมื่อทำงานกับข้อมูลอะซิงโครนัส: นักพัฒนามักลืมยกเลิกการสมัคร callback ซึ่งนำไปสู่การคงข้อมูลอ้างอิงไปยัง Activity ที่ถูกทำลาย LiveData ทำให้การยกเลิกการสมัครเป็นไปโดยอัตโนมัติ — Observer ที่เชื่อมโยงกับ LifecycleOwner จะไม่ได้รับการอัปเดตหลังจากเจ้าของถูกทำลาย
จากผลสำรวจของ Android Developers (2025) ทุก ๆ ครั้งที่สองของแครช ก่อนการนำ LiveData มาใช้เกี่ยวข้องกับการเรียกเมธอดบน UI คอนโทรลเลอร์ที่ถูกทำลาย LiveData ขจัดข้อผิดพลาดประเภทนี้อย่างสมบูรณ์ ที่ IT Sectr เราได้นำ LiveData มาใช้ในทุกโปรเจกต์ตั้งแต่ปี 2018 — ใน 7 ปีไม่มีแครชแม้แต่ครั้งเดียวเนื่องจากข้อมูลอ้างอิง Activity ที่ล้าสมัย
ความแตกต่างที่สำคัญของ LiveData จากคอนเทนเนอร์ observable อื่น ๆ — การเชื่อมโยงกับ Lifecycle เมื่อสร้างผู้สังเกตการณ์ LiveData จะตรวจสอบสถานะของ LifecycleOwner: หากสถานะเป็น STARTED หรือ RESUMED Observer ถือว่าใช้งานอยู่และได้รับการอัปเดตทันที หากสถานะเป็น PAUSED, STOPPED หรือ DESTROYED จะไม่มีการส่งอัปเดตจนกว่าจะกลับสู่สถานะทำงาน
กลไกนี้ถูกนำไปใช้ผ่านคลาส LifecycleBoundObserver ซึ่งลงทะเบียนใน Lifecycle ด้วย addObserver() เมื่อ LifecycleOwner เปลี่ยนสถานะ callback onStateChanged() จะถูกเรียก และ LiveData จะอัปเดตสถานะการทำงานของ Observer เมื่อตั้งค่าข้อมูลผ่าน setValue() LiveData จะวนผ่านรายการผู้สังเกตการณ์และส่งค่าให้เฉพาะที่ทำงานอยู่เท่านั้น เมื่อผู้สังเกตการณ์เปลี่ยนเป็นสถานะ DESTROYED Observer จะถูกลบออกจากรายการผู้สมัครรับข้อมูลโดยอัตโนมัติ
ตามเอกสารของ Android Jetpack (2025) กลไก LifecycleBoundObserver ใช้เวลา น้อยกว่า 0.5 ไมโครวินาที ในการตรวจสอบสถานะ — ค่าใช้จ่ายเพิ่มเติมเล็กน้อยเมื่อเทียบกับการดำเนินการอัปเดต UI ทั่วไปทำให้ LiveData เหมาะสำหรับการอัปเดตความถี่สูง (ตัวจับเวลา ตัวนับ) โดยไม่มีความเสี่ยงต่อประสิทธิภาพที่ลดลง
MutableLiveData — ทายาทของ LiveData พร้อมเมธอดเปิด setValue() และ postValue() สำหรับการเปลี่ยนค่าที่เก็บไว้ แตกต่างจาก LiveData, MutableLiveData สามารถเขียนได้ แต่ใน ViewModel เป็นธรรมเนียมที่จะเผยแพร่เฉพาะ LiveData (เวอร์ชันที่ไม่สามารถเปลี่ยนแปลงได้) โดยซ่อน MutableLiveData ภายใต้ตัวปรับ private
class SearchViewModel : ViewModel() {
private val _query = MutableLiveData("")
val query: LiveData<String> get() = _query
fun updateQuery(newQuery: String) {
_query.value = newQuery // setValue() — บน main thread
}
fun updateFromNetwork(result: String) {
_query.postValue(result) // postValue() — จากเธรดใดก็ได้
}
}
setValue() ควรเรียกจาก main thread เท่านั้น — มันจะแจ้งผู้สังเกตการณ์ทันที postValue() ปลอดภัยสำหรับการเรียกจากเธรดพื้นหลัง: มันวางค่าในคิวของ main thread และแจ้งผู้สังเกตการณ์แบบอะซิงโครนัส สำคัญ: ถ้า postValue() ถูกเรียกสองครั้งติดต่อกันก่อนที่ครั้งแรกจะถูกประมวลผล ค่ากลางอาจหายไป — ผู้สังเกตการณ์จะได้รับเฉพาะค่าสุดท้ายเท่านั้น สำหรับการส่งสถานะกลางทั้งหมด (เช่น ความคืบหน้าการโหลด) ให้ใช้ setValue() บน main thread
Transformations.map() — การแปลงค่าเชิงฟังก์ชันของ LiveData หนึ่งเป็นประเภทอื่นโดยไม่ต้องเขียน Observer ตัวอย่างเช่น จาก LiveData<User> รับ LiveData<String> พร้อมชื่อผู้ใช้ การแปลงจะขี้เกียจ: การแปลงจะดำเนินการเฉพาะเมื่อมี Observer ที่ทำงานอยู่บน LiveData เป้าหมาย
val userLiveData: LiveData<User> = ...
val userName: LiveData<String> = Transformations.map(userLiveData) { user ->
"${user.firstName} ${user.lastName}"
}
val userIdLiveData: LiveData<String> = ...
val userDetails: LiveData<UserDetails> = Transformations.switchMap(userIdLiveData) { id ->
repository.getUserDetails(id)
}
// MediatorLiveData — การรวมสองแหล่ง
val mediator = MediatorLiveData<CombinedState>()
mediator.addSource(priceLiveData) { price ->
mediator.value = CombinedState(price, countLiveData.value)
}
mediator.addSource(countLiveData) { count ->
mediator.value = CombinedState(priceLiveData.value, count)
}
Transformations.switchMap() — อะนาล็อกของ flatMap จากโลกของสตรีมเชิงปฏิกิริยา: เมื่อ LiveData อินพุตเปลี่ยนแปลง มันจะสลับไปยังอินสแตนซ์ใหม่ของ LiveData เอาต์พุต MediatorLiveData — เครื่องมือขั้นสูงสำหรับการรวมหลายแหล่ง LiveData ด้วยความสามารถในการจัดการลำดับความสำคัญของการอัปเดต ตาม Developer Survey (2024) MediatorLiveData ถูกใช้ใน 35% ของโปรเจกต์ที่ต้องการรวมข้อมูลจากแหล่งต่าง ๆ — ตัวอย่างเช่น การรวมข้อมูลจากฟอร์ม UI และการตอบกลับของเซิร์ฟเวอร์
liveData { } — coroutine builder (ปรากฏใน lifecycle-livedata-ktx 2.2.0) ที่ช่วยให้คำนวณค่า LiveData แบบอะซิงโครนัสภายในโครูทีน ภายในบล็อก liveData { } มี suspend-context และฟังก์ชัน emit() สำหรับเผยแพร่ค่า โครูทีนทั้งหมดที่เริ่มต้นภายใน builder จะถูกยกเลิกโดยอัตโนมัติเมื่อผู้สังเกตการณ์ทั้งหมดไม่ทำงาน
val userLiveData: LiveData<User> = liveData {
// ดำเนินการบน Dispatchers.IO โดยค่าเริ่มต้น
val user = userRepository.fetchUser(userId)
// ปล่อยผลลัพธ์ — อัตโนมัติบน main thread
emit(user)
}
val progressLiveData: LiveData<Int> = liveData {
for (i in 0..100) {
emit(i)
delay(50)
}
}
liveData builder รองรับ emitSource() — การปล่อย LiveData อื่นเป็นแหล่งข้อมูล (อะนาล็อกของ switchMap ภายในโครูทีน) หมดเวลา: หากไม่มี Observer ทำงานภายใน 5 วินาที (ค่าเริ่มต้น) โครูทีนจะถูกยกเลิก เมื่อเปิดใช้งานอีกครั้ง liveData { } จะทำงานอีกครั้ง ตามข้อมูลของ Google (Android Dev Summit 2024) liveData builder ลดโค้ดเทมเพลตลง 40% เมื่อเทียบกับการจัดการ ViewModel + LiveData ด้วยตนเอง
หน้าจอเข้าสู่ระบบคลาสสิกพร้อมฟิลด์อีเมลและรหัสผ่าน การตรวจสอบความถูกต้องและสถานะการโหลด ViewModel จัดการ LiveData สามตัว: email, password และ loginResult
class LoginViewModel : ViewModel() {
private val _email = MutableLiveData("")
val email: LiveData<String> get() = _email
private val _password = MutableLiveData("")
val password: LiveData<String> get() = _password
private val _loginResult = MutableLiveData<Result<User>>()
val loginResult: LiveData<Result<User>> get() = _loginResult
fun onEmailChanged(text: String) {
_email.value = text
}
fun onPasswordChanged(text: String) {
_password.value = text
}
fun login() {
if (_email.value.isNullOrBlank() || _password.value.isNullOrBlank()) {
_loginResult.value = Result.failure(IllegalArgumentException("กรอกข้อมูลทุกช่อง"))
return
}
viewModelScope.launch {
try {
val user = authRepository.login(_email.value!!, _password.value!!)
_loginResult.value = Result.success(user)
} catch (e: Exception) {
_loginResult.value = Result.failure(e)
}
}
}
}
Room รองรับ LiveData เป็นประเภทที่ส่งคืนของคำสั่ง DAO: เมื่อตารางเปลี่ยนแปลงแต่ละครั้ง LiveData จะแจ้งผู้สังเกตการณ์โดยอัตโนมัติ ซึ่งเหมาะสำหรับ reactice UI
@Dao
interface TaskDao {
@Query("SELECT * FROM tasks WHERE completed = 0")
fun getActiveTasks(): LiveData<List<Task>>
@Insert
suspend fun insertTask(task: Task)
}
// ใน ViewModel:
class TaskViewModel(application: Application) : AndroidViewModel(application) {
private val dao = AppDatabase.getDatabase(application).taskDao()
val activeTasks: LiveData<List<Task>> = dao.getActiveTasks()
}
Room สร้างโค้ดที่ติดตามการเปลี่ยนแปลงในตาราง tasks และอัปเดต LiveData โดยอัตโนมัติเมื่อมี INSERT, UPDATE หรือ DELETE ใด ๆ สิ่งนี้ทำงานโดยไม่ต้องใช้โค้ดเพิ่มเติม — เฉพาะคำอธิบายประกอบ @Query พร้อมประเภทที่ส่งคืน LiveData ที่ IT Sectr เราใช้ Room + LiveData เป็นสแต็กมาตรฐานสำหรับการแคชข้อมูลในเครื่องในโปรเจกต์ Android ตั้งแต่ปี 2019
คำถามที่พบบ่อย
LiveData — คอนเทนเนอร์ที่สังเกตได้พร้อมการสนับสนุน Lifecycle ในตัว: Observer เปิดใช้งาน/ปิดใช้งานโดยอัตโนมัติ StateFlow — สตรีมเชิงปฏิกิริยาจาก Kotlin Coroutines (Kotlinx Coroutines 1.3.7+) ไม่ได้เชื่อมโยงกับ Lifecycle แต่รองรับผ่าน stateIn(WhileSubscribed) StateFlow ต้องการการจัดการวงจรชีวิตอย่างชัดเจนใน View แต่ให้การเข้าถึงโครูทีน ตัวดำเนินการ Flow และความหลากหลายของแพลตฟอร์ม Google แนะนำ StateFlow สำหรับโปรเจกต์ Kotlin ใหม่ ส่วน LiveData สำหรับโค้ด Java หรือเมื่อต้องการความเข้ากันได้กับไลบรารีเก่า
ใช้ฟังก์ชัน extension liveData.asFlow() จากไลบรารี lifecycle-livedata-ktx มันสร้าง Flow ที่ปล่อยค่าปัจจุบันของ LiveData ทุกครั้งที่มีการเปลี่ยนแปลง จากนั้นแปลงเป็น StateFlow ผ่าน .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), initialValue) การแปลงย้อนกลับ — stateFlow.asLiveData() การแปลงซึ่งกันและกันช่วยให้ใช้ประโยชน์จากทั้งสองไลบรารีในโปรเจกต์เดียว
postValue() ใช้ AtomicReference เพื่อเก็บค่าที่เลื่อนออกไป หาก postValue() ถูกเรียกสองครั้งก่อนที่ main thread จะประมวลผล ค่าแรกจะถูกเขียนทับด้วยค่าที่สอง — Observable จะได้รับเฉพาะค่าสุดท้ายเท่านั้น เนื่องจาก LiveData ไม่มีคิวภายใน: มันเก็บค่าที่เลื่อนออกไปเพียงค่าเดียว สำหรับการส่งทุกจุดกลาง (1%, 2%, … 100%) ให้ใช้ setValue() บน main thread หรือ ConflatedFlow จาก kotlinx-coroutines
ได้ LiveData สามารถสังเกตได้ผ่าน observeForever() โดยส่ง Observer โดยไม่มี LifecycleOwner อย่างไรก็ตาม ในกรณีนี้การยกเลิกการสมัครต้องทำอย่างชัดเจนผ่าน removeObserver() — การยกเลิกการสมัครอัตโนมัติไม่ทำงาน observeForever() ใช้ในบริการ, ContentProvider หรือ ViewModel ที่ไม่มี LifecycleOwner ตามคำแนะนำของ Google หลีกเลี่ยง observeForever() ใน Activity/Fragment — ใช้ observe() กับ LifecycleOwner
ลักษณะพิเศษของพฤติกรรม: เมื่อ LiveData ได้รับ Observer ที่ทำงานใหม่ มันจะได้รับค่าล่าสุดทันที (ถ้าตั้งไว้) LiveData เวอร์ชันเก่า (ก่อน lifecycle 2.5.0) ส่งค่าให้กับผู้สมัครรับข้อมูลที่ไม่ทำงานเมื่อเปลี่ยนเป็นสถานะทำงาน — ได้รับการแก้ไขแล้ว ในเวอร์ชันปัจจุบัน LiveData จะได้รับค่าล่าสุดเมื่อเปลี่ยน FROM ไม่ทำงาน TO ทำงาน ซึ่งทำให้การเริ่มต้นหน้าจอง่ายขึ้น
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม