วิธีอัปเดต LiveData ของ ViewModel จากบริการเบื้องหลังและอัปเดต UI


97

เมื่อเร็ว ๆ นี้ฉันกำลังสำรวจสถาปัตยกรรม Android ซึ่งได้รับการแนะนำโดย Google เมื่อเร็ว ๆ นี้ จากเอกสารฉันพบสิ่งนี้:

public class MyViewModel extends ViewModel {
    private MutableLiveData<List<User>> users;
    public LiveData<List<User>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<List<Users>>();
            loadUsers();
        }
        return users;
    }

    private void loadUsers() {
        // do async operation to fetch users
    }
}

กิจกรรมสามารถเข้าถึงรายการนี้ได้ดังนี้:

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
            // update UI
        });
    }
}

คำถามของฉันคือฉันจะทำสิ่งนี้:

  1. ในloadUsers()ฟังก์ชันฉันกำลังดึงข้อมูลแบบอะซิงโครนัสโดยที่ฉันจะตรวจสอบฐานข้อมูล (ห้อง) สำหรับข้อมูลนั้นก่อน

  2. หากฉันไม่ได้รับข้อมูลที่นั่นฉันจะทำการเรียก API เพื่อดึงข้อมูลจากเว็บเซิร์ฟเวอร์

  3. ฉันจะแทรกข้อมูลที่ดึงลงในฐานข้อมูล (ห้อง) และอัปเดต UI ตามข้อมูล

แนวทางที่แนะนำในการดำเนินการนี้คืออะไร?

หากฉันเริ่มต้นServiceเพื่อเรียก API จากloadUsers()เมธอดฉันจะอัปเดตMutableLiveData<List<User>> usersตัวแปรจากสิ่งนั้นได้Serviceอย่างไร


9
ก่อนอื่นคุณไม่มี Repository ViewModel ของคุณไม่ควรทำงานโหลดข้อมูลใด ๆ นอกเหนือจากนั้นเนื่องจากคุณใช้ Room บริการของคุณจึงไม่จำเป็นต้องอัปเดต LiveData ใน ViewModel โดยตรง บริการสามารถแทรกข้อมูลลงใน Room เท่านั้นในขณะที่ ViewModelData ของคุณควรแนบกับ Room เท่านั้นและรับการอัปเดตจาก Room (หลังจาก Service แทรกข้อมูล) แต่สำหรับสถาปัตยกรรมที่ดีที่สุดอย่างแท้จริงให้ดูการใช้งานคลาส NetworkBoundResource จากด้านล่างของหน้านี้: developer.android.com/topic/libraries/architecture/guide.html
Marko Gajić

ขอบคุณสำหรับข้อเสนอแนะ :)
CodeCameo

1
ไม่ได้กล่าวถึงคลาส Repositor ในเอกสารทางการที่อธิบายถึง ROOM หรือส่วนประกอบสถาปัตยกรรม Android
โจนาธาน

2
Repository เป็นแนวทางปฏิบัติที่ดีที่สุดที่แนะนำสำหรับการแยกโค้ดและสถาปัตยกรรมดูตัวอย่างนี้codelabs.developers.google.com/codelabs/…
glisu

1
loadUsers()โดยทั่วไปฟังก์ชันจะเรียก repo เพื่อรับข้อมูลผู้ใช้
CodeCameo

คำตอบ:


97

ฉันสมมติว่าคุณกำลังใช้ส่วนประกอบสถาปัตยกรรมหุ่นยนต์ จริงๆแล้วไม่สำคัญว่าคุณจะโทรservice, asynctask or handlerไปที่ใดเพื่ออัปเดตข้อมูล คุณสามารถแทรกข้อมูลจากบริการหรือจาก asynctask โดยใช้postValue(..)วิธีการ ชั้นเรียนของคุณจะมีลักษณะดังนี้:

private void loadUsers() {
    // do async operation to fetch users and use postValue() method
    users.postValue(listOfData)
}

ในฐานะที่usersเป็นLiveData, Roomฐานข้อมูลเป็นผู้รับผิดชอบในการให้ข้อมูลของผู้ใช้ใดก็ตามที่มันจะถูกแทรก

Note: ในสถาปัตยกรรม MVVM เช่นเดียวกับที่เก็บข้อมูลส่วนใหญ่มีหน้าที่ในการตรวจสอบและดึงข้อมูลในเครื่องและข้อมูลระยะไกล


3
ฉันได้รับ "java.lang.IllegalStateException: ไม่สามารถเข้าถึงฐานข้อมูลบนเธรดหลักได้เนื่องจาก" เมื่อเรียกใช้เมธอด db ของฉันเช่นด้านบนคุณสามารถบอกได้ว่ามีอะไรผิดปกติหรือไม่?
Prashant

ฉันใช้ evernote-job ซึ่งกำลังอัปเดตฐานข้อมูลในเบื้องหลังขณะที่ฉันใช้ UI แต่LiveDataไม่ได้อัปเดต
Akshay Chordiya

users.postValue(mUsers);-> แต่วิธี postValue ของ MutableLiveData สามารถยอมรับ LiveData ได้หรือไม่ ???
Cheok Yan Cheng

2
ความผิดพลาดของฉันถูกใช้valueแทนpostValue. ขอบคุณสำหรับคำตอบ.
Hesam

1
@pcj คุณต้องดำเนินการห้องในเธรดหรือเปิดใช้งานการดำเนินการในเธรดหลัก - Google สำหรับคำตอบเพิ่มเติม
kilokahn

46

คุณสามารถใช้MutableLiveData<T>.postValue(T value)วิธีการจากเธรดพื้นหลัง

private void loadUsers() {
    // do async operation to fetch users and use postValue() method
   users.postValue(listOfData)
}

19
เพื่อเตือนความจำ postValue () ได้รับการปกป้องใน LiveData แต่เป็นสาธารณะใน MutableLiveData
Long Ranger

งานนี้แม้จะเป็นงานเบื้องหลังและในสถานการณ์ที่.setValue()ไม่ได้รับอนุญาต
davejoem

18

... ในฟังก์ชัน loadUsers () ฉันกำลังดึงข้อมูลแบบอะซิงโครนัส ... ถ้าฉันเริ่มบริการเพื่อเรียก API จากเมธอด loadUsers () ฉันจะอัปเดตตัวแปรผู้ใช้ MutableLiveData> จากบริการนั้นได้อย่างไร

หากแอปกำลังดึงข้อมูลผู้ใช้ในเธรดพื้นหลังpostValue (แทนที่จะเป็นsetValue ) จะมีประโยชน์

ในเมธอด loadData มีการอ้างอิงถึงอ็อบเจ็กต์ "users" ของ MutableLiveData เมธอด loadData ยังดึงข้อมูลผู้ใช้ใหม่จากที่ไหนสักแห่ง (ตัวอย่างเช่นที่เก็บ)

ตอนนี้ถ้าการดำเนินการอยู่บนเธรดพื้นหลัง MutableLiveData.postValue () จะอัพเดตผู้สังเกตการณ์ภายนอกของอ็อบเจ็กต์ MutableLiveData

อาจจะเป็นดังนี้:

private MutableLiveData<List<User>> users;

.
.
.

private void loadUsers() {
    // do async operation to fetch users
    ExecutorService service =  Executors.newSingleThreadExecutor();
    service.submit(new Runnable() {
        @Override
        public void run() {
            // on background thread, obtain a fresh list of users
            List<String> freshUserList = aRepositorySomewhere.getUsers();

            // now that you have the fresh user data in freshUserList, 
            // make it available to outside observers of the "users" 
            // MutableLiveData object
            users.postValue(freshUserList);        
        }
    });

}

getUsers()เมธอดของ Repository อาจเรียก api สำหรับข้อมูลที่เป็น async (อาจเริ่มบริการหรือ asynctask สำหรับสิ่งนี้) ในกรณีนั้นจะส่งคืน List จากคำสั่ง return ได้อย่างไร
CodeCameo

บางทีอาจใช้วัตถุ LiveData เป็นอาร์กิวเมนต์ (บางอย่างเช่น repository.getUsers (ผู้ใช้)) จากนั้นเมธอดที่เก็บจะเรียกใช้ users.postValue เอง และในกรณีนั้นเมธอด loadUsers ไม่จำเป็นต้องมีเธรดพื้นหลังด้วยซ้ำ
albert c braun

1
ขอบคุณสำหรับคำตอบ .... อย่างไรก็ตามฉันใช้ Room DB สำหรับจัดเก็บข้อมูลและ DAO ส่งคืน LiveData <Object> ... ฉันจะแปลง LiveData เป็น MutableLiveData <Object> ได้อย่างไร
กิโลคาห์น

ฉันไม่คิดว่า DAO ของ Room มีจุดประสงค์เพื่อจัดการกับวัตถุ MutableLiveData จริงๆ DAO กำลังแจ้งให้คุณทราบถึงการเปลี่ยนแปลงฐานข้อมูลพื้นฐาน แต่ถ้าคุณต้องการเปลี่ยนค่าในฐานข้อมูลคุณจะเรียกใช้เมธอดของ DAO นอกจากนี้การอภิปรายที่นี่อาจเป็นประโยชน์: stackoverflow.com/questions/50943919/…
albert c braun

3

ลองดูที่เป็นคู่มือสถาปัตยกรรมของ Androidที่มาพร้อมกับโมดูลสถาปัตยกรรมใหม่เหมือนและLiveData ViewModelพวกเขาพูดถึงประเด็นนี้ในเชิงลึก

ในตัวอย่างของพวกเขาพวกเขาไม่ได้ใส่ไว้ในบริการ ดูวิธีแก้ปัญหาโดยใช้โมดูล "ที่เก็บ" และชุดติดตั้งเพิ่มเติม ภาคผนวกที่ด้านล่างประกอบด้วยตัวอย่างที่สมบูรณ์มากขึ้นรวมถึงสถานะเครือข่ายการสื่อสารการรายงานข้อผิดพลาด ฯลฯ


2

หากคุณกำลังเรียก api ของคุณใน Repository แล้ว

ในRepository :

public MutableLiveData<LoginResponseModel> checkLogin(LoginRequestModel loginRequestModel) {
    final MutableLiveData<LoginResponseModel> data = new MutableLiveData<>();
    apiService.checkLogin(loginRequestModel)
            .enqueue(new Callback<LoginResponseModel>() {
                @Override
                public void onResponse(@NonNull Call<LoginResponseModel> call, @Nullable Response<LoginResponseModel> response) {
                    if (response != null && response.isSuccessful()) {
                        data.postValue(response.body());
                        Log.i("Response ", response.body().getMessage());
                    }
                }

                @Override
                public void onFailure(@NonNull Call<LoginResponseModel> call, Throwable t) {
                    data.postValue(null);
                }
            });
    return data;
}

ในViewModel

public LiveData<LoginResponseModel> getUser() {
    loginResponseModelMutableLiveData = repository.checkLogin(loginRequestModel);
    return loginResponseModelMutableLiveData;
}

ในกิจกรรม / ส่วนย่อย

loginViewModel.getUser().observe(LoginActivity.this, loginResponseModel -> {
        if (loginResponseModel != null) {
            Toast.makeText(LoginActivity.this, loginResponseModel.getUser().getType(), Toast.LENGTH_SHORT).show();
        }
    });

หมายเหตุ: การใช้ JAVA_1.8 lambda ที่นี่คุณสามารถใช้ได้โดยไม่ต้องใช้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.