Cara memperbarui LiveData ViewModel dari layanan latar belakang dan Perbarui UI

97

Baru-baru ini saya menjelajahi Arsitektur Android, yang baru-baru ini diperkenalkan oleh google. Dari Dokumentasi saya menemukan ini:

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
    }
}

aktivitas dapat mengakses daftar ini sebagai berikut:

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
        });
    }
}

Pertanyaan saya adalah, saya akan melakukan ini:

  1. dalam loadUsers()fungsi saya mengambil data secara asynchronous di mana saya pertama-tama akan memeriksa database (Room) untuk data itu

  2. Jika saya tidak mendapatkan data di sana, saya akan membuat panggilan API untuk mengambil data dari server web.

  3. Saya akan memasukkan data yang diambil ke dalam database (Room) dan memperbarui UI sesuai datanya.

Apa pendekatan yang direkomendasikan untuk melakukan ini?

Jika saya memulai Serviceuntuk memanggil API dari loadUsers()metode, bagaimana saya bisa memperbarui MutableLiveData<List<User>> usersvariabel dari itu Service?

CodeCameo
sumber
9
Pertama-tama, Anda kehilangan Repositori. ViewModel Anda seharusnya tidak melakukan tugas pemuatan data apa pun. Selain itu, karena Anda menggunakan Room, Layanan Anda tidak harus memperbarui LiveData di ViewModel secara langsung. Layanan hanya dapat memasukkan data ke dalam Room, sedangkan ViewModelData Anda harus dilampirkan hanya ke Room, dan mendapatkan pembaruan dari Room (setelah Layanan memasukkan data). Tetapi untuk arsitektur terbaik mutlak, lihat implementasi kelas NetworkBoundResource dari bagian bawah halaman ini: developer.android.com/topic/libraries/architecture/guide.html
Marko Gajić
terima kasih atas sarannya :)
CodeCameo
1
Kelas repositor tidak disebutkan dalam dokumen offocial yang menjelaskan ROOM atau komponen arsitektur android
Jonathan
2
Repositori adalah praktik terbaik yang disarankan untuk pemisahan kode dan arsitektur, lihat contoh ini: codelabs.developers.google.com/codelabs/…
glisu
1
Fungsi loadUsers()pada dasarnya akan memanggil repo untuk mendapatkan informasi pengguna
CodeCameo

Jawaban:

97

Saya berasumsi bahwa Anda menggunakan komponen arsitektur android . Sebenarnya tidak masalah ke mana pun Anda menelepon service, asynctask or handleruntuk memperbarui data. Anda dapat memasukkan data dari layanan atau dari asynctask menggunakan postValue(..)metode. Kelas Anda akan terlihat seperti ini:

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

Sebagai usersadalah LiveData, Roombasis data bertanggung jawab untuk menyediakan data pengguna di mana pun dimasukkan.

Note: Dalam arsitektur seperti MVVM, repositori sebagian besar bertanggung jawab untuk memeriksa dan menarik data lokal dan data jarak jauh.

androidcodehunter
sumber
3
Saya mendapatkan "java.lang.IllegalStateException: Tidak dapat mengakses database di thread utama karena" saat memanggil metode db saya seperti di atas, Dapatkah Anda mengetahui apa yang salah?
Prashant
Saya menggunakan evernote-job yang memperbarui database di latar belakang saat saya menggunakan UI. Tetapi LiveDatatidak memperbarui
Akshay Chordiya
users.postValue(mUsers);-> Tapi, apakah metode postValue MutableLiveData dapat menerima LiveData ???
Cheok Yan Cheng
2
Kesalahan saya menggunakan valuebukannya postValue. Terima kasih atas jawaban anda.
Hesam
1
@pcj Anda perlu melakukan operasi ruang di utas atau mengaktifkan operasi di utas utama - google untuk jawaban lebih lanjut.
kilokahn
46

Anda dapat menggunakan MutableLiveData<T>.postValue(T value)metode dari thread latar belakang.

private void loadUsers() {
    // do async operation to fetch users and use postValue() method
   users.postValue(listOfData)
}
minhazur.dll
sumber
19
Untuk mengingatkan, postValue () dilindungi di LiveData tetapi untuk publik di MutableLiveData
Long Ranger
ini bekerja bahkan dari tugas latar belakang dan dalam situasi di mana .setValue()tidak akan diizinkan
davejoem
18

... dalam fungsi loadUsers () saya mengambil data secara asynchronous ... Jika saya memulai Layanan untuk memanggil API dari metode loadUsers (), bagaimana cara memperbarui variabel pengguna MutableLiveData> dari Layanan itu?

Jika aplikasi mengambil data pengguna di thread latar belakang, postValue (bukan setValue ) akan berguna.

Dalam metode loadData ada referensi ke objek "users" MutableLiveData. Metode loadData juga mengambil beberapa data pengguna baru dari suatu tempat (misalnya, repositori).

Sekarang, jika eksekusi berada di thread latar belakang, MutableLiveData.postValue () memperbarui pengamat luar dari objek MutableLiveData.

Mungkin sesuatu seperti ini:

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);        
        }
    });

}
albert c braun
sumber
getUsers()Metode repositori mungkin memanggil api untuk data yang async (mungkin memulai layanan atau asynctask untuk ini) dalam hal ini bagaimana cara mengembalikan List dari pernyataan kembaliannya?
CodeCameo
Mungkin itu bisa mengambil objek LiveData sebagai argumen. (Sesuatu seperti repository.getUsers (pengguna)). Kemudian metode repositori akan memanggil users.postValue itu sendiri. Dan, dalam kasus ini, metode loadUsers bahkan tidak memerlukan thread latar belakang.
albert c braun
1
Terima kasih atas jawabannya .... namun, saya menggunakan DB Ruang untuk penyimpanan dan DAO mengembalikan LiveData <Object> ... bagaimana cara mengonversi LiveData ke MutableLiveData <Object>?
kilokahn
Saya tidak berpikir DAO Room benar-benar dimaksudkan untuk menangani objek MutableLiveData. DAO memberi tahu Anda tentang perubahan pada db yang mendasari, tetapi, jika Anda ingin mengubah nilai dalam DB, Anda akan memanggil metode DAO. Mungkin juga pembahasan di sini bermanfaat: stackoverflow.com/questions/50943919/…
albert c braun
3

Lihat panduan arsitektur Android yang menyertai modul arsitektur baru seperti LiveDatadan ViewModel. Mereka membahas masalah ini secara mendalam.

Dalam contoh mereka, mereka tidak memasukkannya ke dalam layanan. Lihatlah bagaimana mereka menyelesaikannya dengan menggunakan modul "repositori" dan Retrofit. Adendum di bagian bawah mencakup contoh yang lebih lengkap termasuk status jaringan komunikasi, kesalahan pelaporan, dll.

pengguna1978019
sumber
2

Jika Anda memanggil api Anda di Repository,

Dalam Repositori :

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;
}

Di ViewModel

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

Dalam Aktivitas / Fragmen

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

Catatan: Menggunakan JAVA_1.8 lambda di sini, Anda dapat menggunakannya tanpa itu

Shubham Agrawal
sumber