การสังเกต LiveData จาก ViewModel


95

ฉันมีคลาสแยกต่างหากที่ฉันจัดการการดึงข้อมูล (โดยเฉพาะ Firebase) และฉันมักจะส่งคืนออบเจ็กต์ LiveData จากมันและอัปเดตแบบอะซิงโครนัส ตอนนี้ฉันต้องการเก็บข้อมูลที่ส่งคืนไว้ใน ViewModel แต่ปัญหาคือเพื่อให้ได้ค่าดังกล่าวฉันต้องสังเกตวัตถุ LiveData ที่ส่งคืนจากคลาสการดึงข้อมูลของฉัน วิธีการสังเกตการณ์ต้องการวัตถุ LifecycleOwner เป็นพารามิเตอร์แรก แต่เห็นได้ชัดว่าฉันไม่มีสิ่งนั้นอยู่ใน ViewModel ของฉันและฉันรู้ว่าฉันไม่ควรเก็บการอ้างอิงถึง Activity / Fragment ภายใน ViewModel ฉันควรทำอย่างไรดี?


คำตอบ:


39

ในบล็อกโพสต์นี้โดยนักพัฒนาซอฟต์แวร์ของ Google Jose Alcérrecaขอแนะนำให้ใช้การเปลี่ยนแปลงในกรณีนี้ (ดูย่อหน้า "LiveData ในที่เก็บ") เนื่องจากViewModelไม่ควรมีการอ้างอิงใด ๆ ที่เกี่ยวข้องกับView(กิจกรรมบริบท ฯลฯ ) เนื่องจากทำให้ยาก ทดสอบ.


คุณจัดการให้ Transformation ทำงานให้คุณแล้วหรือยัง? กิจกรรมของฉันไม่ทำงาน
romaneso

26
การแปลงด้วยตัวเองไม่ได้ผลเนื่องจากโค้ดใด ๆ ที่คุณเขียนในการแปลงจะแนบมาเพื่อรันเมื่อเอนทิตีบางส่วนสังเกตการเปลี่ยนแปลงเท่านั้น
orbitbot

9
ฉันไม่รู้ว่าทำไมจึงเป็นคำตอบที่แนะนำไม่มีส่วนเกี่ยวข้องกับคำถามนี้ 2 ปีต่อมาและเรายังไม่รู้วิธีสังเกตการเปลี่ยนแปลงข้อมูลที่เก็บใน viewmodel ของเรา
Andrew

26

ในเอกสารViewModel

อย่างไรก็ตามอ็อบเจ็กต์ ViewModel ต้องไม่สังเกตการเปลี่ยนแปลงของสิ่งที่สังเกตได้ตลอดอายุการใช้งานเช่นอ็อบเจ็กต์ LiveData

อีกวิธีหนึ่งคือให้ข้อมูลใช้ RxJava แทน LiveData จากนั้นจะไม่ได้รับประโยชน์จากการรับรู้วงจรชีวิต

ใน Google ตัวอย่างของtodo-mvvm-live-kotlinจะใช้การโทรกลับโดยไม่มี LiveData ใน ViewModel

ฉันเดาว่าหากคุณต้องการปฏิบัติตามแนวคิดทั้งหมดของการเป็นวงจรชีวิตเราจำเป็นต้องย้ายรหัสการสังเกตในกิจกรรม / ส่วนย่อย มิฉะนั้นเราสามารถใช้การโทรกลับหรือ RxJava ใน ViewModel

การประนีประนอมอีกอย่างคือใช้ MediatorLiveData (หรือ Transformations) และสังเกต (ใส่ตรรกะของคุณที่นี่) ใน ViewModel สังเกตว่าผู้สังเกตการณ์ MediatorLiveData จะไม่ทริกเกอร์ (เหมือนกับการเปลี่ยนแปลง) เว้นแต่จะสังเกตเห็นในกิจกรรม / ส่วนย่อย สิ่งที่เราทำคือเราใส่การสังเกตว่างไว้ใน Activity / Fragment ซึ่งงานจริงจะทำใน ViewModel

// ViewModel
fun start(id : Long) : LiveData<User>? {
    val liveData = MediatorLiveData<User>()
    liveData.addSource(dataSource.getById(id), Observer {
        if (it != null) {
            // put your logic here
        }
    })
}

// Activity/Fragment
viewModel.start(id)?.observe(this, Observer {
    // blank observe here
})

PS: ฉันอ่านViewModels และ LiveData: Patterns + AntiPatternsซึ่งแนะนำว่า Transformations ฉันไม่คิดว่ามันจะได้ผลเว้นแต่จะสังเกต LiveData (ซึ่งอาจต้องทำที่ Activity / Fragment)


2
มีอะไรเปลี่ยนแปลงในเรื่องนี้หรือไม่? หรือ RX การโทรกลับหรือการสังเกตว่างเป็นเพียงวิธีแก้ไข?
qbait

2
วิธีใดในการกำจัดข้อสังเกตที่ว่างเปล่าเหล่านี้?
Ehsan Mashhadi

1
อาจใช้ Flow ( mLiveData.asFlow()) หรือobserveForever.
Machado

โซลูชัน Flow ดูเหมือนจะใช้งานได้หากคุณไม่ต้องการมี / คุณไม่ต้องการตรรกะผู้สังเกตการณ์ใด ๆ ใน Fragment
adek111

14

ฉันคิดว่าคุณสามารถใช้ watchForever ซึ่งไม่ต้องการอินเทอร์เฟซเจ้าของวงจรชีวิตและคุณสามารถสังเกตผลลัพธ์จาก viewmodel


2
นี่เป็นคำตอบที่ถูกต้องสำหรับฉันโดยเฉพาะอย่างยิ่งในเอกสารเกี่ยวกับ ViewModel.onCleared () กล่าวว่า: "มีประโยชน์เมื่อ ViewModel สังเกตข้อมูลบางอย่างและคุณต้องล้างการสมัครสมาชิกนี้เพื่อป้องกันการรั่วไหลของ ViewModel นี้"
Yosef

2
ขออภัยCannot invoke observeForever on a background thread
Boken

1
ดูเหมือนว่าถูกต้องตามกฎหมาย แม้คนหนึ่งจะมีการบันทึกสังเกตการณ์ในสาขา Viewmodel onClearedและยกเลิกการเป็นสมาชิกที่ สำหรับเธรดพื้นหลัง - สังเกตจากเธรดหลักนั่นแหล่ะ
Kirill Starostin

@Boken คุณสามารถบังคับobserveForeverให้เรียกจาก main ผ่านGlobalScope.launch(Dispatchers.Main) { myvm.observeForever() }
rmirabelle

5

ใช้ Kotlin coroutines กับส่วนประกอบสถาปัตยกรรม

คุณสามารถใช้liveDataฟังก์ชันตัวสร้างเพื่อเรียกใช้suspendฟังก์ชันโดยให้ผลลัพธ์เป็นLiveDataวัตถุ

val user: LiveData<User> = liveData {
    val data = database.loadUser() // loadUser is a suspend function.
    emit(data)
}

คุณยังสามารถปล่อยค่าหลายค่าจากบล็อก การemit()เรียกแต่ละครั้งจะระงับการทำงานของบล็อกจนกว่าLiveDataจะมีการตั้งค่าบนเธรดหลัก

val user: LiveData<Result> = liveData {
    emit(Result.loading())
    try {
        emit(Result.success(fetchUser()))
    } catch(ioException: Exception) {
        emit(Result.error(ioException))
    }
}

ในการกำหนดค่า gradle ของคุณให้ใช้androidx.lifecycle:lifecycle-livedata-ktx:2.2.0หรือสูงกว่า

นอกจากนี้ยังมีบทความเกี่ยวกับเรื่องนี้

อัปเดต : นอกจากนี้ยังสามารถเปลี่ยนแปลงได้LiveData<YourData>ในไฟล์Dao interface. คุณต้องเพิ่มsuspendคำสำคัญในฟังก์ชัน:

@Query("SELECT * FROM the_table")
suspend fun getAll(): List<YourData>

และในกรณีที่ViewModelคุณต้องการรับแบบอะซิงโครนัสเช่นนั้น:

viewModelScope.launch(Dispatchers.IO) {
    allData = dao.getAll()
    // It's also possible to sync other data here
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.