Cách cập nhật LiveData của ViewModel từ dịch vụ nền và Cập nhật giao diện người dùng


95

Gần đây tôi đang khám phá Kiến trúc Android, đã được google giới thiệu gần đây. Từ Tài liệu, tôi đã tìm thấy điều này:

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

hoạt động có thể truy cập danh sách này như sau:

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

Câu hỏi của tôi là, tôi sẽ làm điều này:

  1. trong loadUsers()hàm, tôi đang tìm nạp dữ liệu không đồng bộ, nơi đầu tiên tôi sẽ kiểm tra cơ sở dữ liệu (Phòng) cho dữ liệu đó

  2. Nếu tôi không nhận được dữ liệu ở đó, tôi sẽ thực hiện một lệnh gọi API để tìm nạp dữ liệu từ máy chủ web.

  3. Tôi sẽ chèn dữ liệu đã tìm nạp vào cơ sở dữ liệu (Phòng) và cập nhật giao diện người dùng theo dữ liệu.

Cách tiếp cận được khuyến nghị để làm điều này là gì?

Nếu tôi bắt đầu Servicegọi API từ loadUsers()phương thức, làm cách nào để cập nhật MutableLiveData<List<User>> usersbiến từ phương thức đó Service?


8
Trước hết, bạn đang thiếu một Kho lưu trữ. ViewModel của bạn sẽ không thực hiện bất kỳ tác vụ tải dữ liệu nào. Ngoài ra, vì bạn đang sử dụng Room, nên Dịch vụ của bạn không cần phải cập nhật LiveData trong ViewModel trực tiếp. Dịch vụ chỉ có thể chèn dữ liệu vào Room, trong khi ViewModelData của bạn chỉ nên được gắn vào Room và nhận cập nhật từ Room (sau khi Service chèn dữ liệu). Nhưng để có kiến ​​trúc tuyệt đối tốt nhất, hãy xem việc triển khai lớp NetworkBoundResource từ cuối trang này: developer.android.com/topic/libraries/architecture/guide.html
Marko Gajić

cảm ơn bạn đã gợi ý :)
CodeCameo

1
Lớp Repositor không được đề cập trong các tài liệu mô tả offocial ROOM hoặc các thành phần kiến trúc android
Jonathan

2
Kho là một thực tế gợi ý tốt nhất để tách mã và kiến trúc, xem ví dụ này: codelabs.developers.google.com/codelabs/...
glisu

1
Chức năng loadUsers()cơ bản sẽ gọi repo để có được thông tin người dùng
CodeCameo

Câu trả lời:


96

Tôi giả định rằng bạn đang sử dụng các thành phần kiến ​​trúc Android . Thực ra dù bạn đang gọi service, asynctask or handlercập nhật dữ liệu ở đâu cũng không thành vấn đề . Bạn có thể chèn dữ liệu từ dịch vụ hoặc từ asynctask bằng postValue(..)phương pháp. Lớp học của bạn sẽ trông như thế này:

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

Như usershiện tại LiveData,Room cơ sở dữ liệu có trách nhiệm cung cấp dữ liệu người dùng bất cứ nơi nào nó được chèn vào.

Note: Trong kiến ​​trúc MVVM giống như kiến ​​trúc, kho lưu trữ chủ yếu chịu trách nhiệm kiểm tra và kéo dữ liệu cục bộ và dữ liệu từ xa.


3
Tôi nhận được "java.lang.IllegalStateException: Không thể truy cập cơ sở dữ liệu trên luồng chính vì nó" khi gọi các phương thức db của tôi như trên, Bạn có thể cho biết điều gì có thể xảy ra không?
pcj

Tôi đang sử dụng evernote-job đang cập nhật cơ sở dữ liệu ở chế độ nền khi tôi đang sử dụng UI. Nhưng LiveDatakhông cập nhật
Akshay Chordiya

users.postValue(mUsers);-> Nhưng, phương thức postValue của MutableLiveData có thể chấp nhận LiveData không ???
Cheok Yan Cheng

2
Sai lầm của tôi là sử dụng valuethay vì postValue. Cảm ơn câu trả lời của bạn.
Hesam,

1
@pcj bạn cần thực hiện hoạt động phòng trong một chuỗi hoặc bật các hoạt động trên chuỗi chính - hãy google để biết thêm câu trả lời.
kilokahn

46

Bạn có thể sử dụng MutableLiveData<T>.postValue(T value)phương pháp từ luồng nền.

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

19
Để nhắc nhở, postValue () được bảo hộ tại LiveData nhưng công trong MutableLiveData
Dài Ranger

công việc này ngay cả từ một nhiệm vụ nền và trong những tình huống .setValue()không được phép
davejoem

17

... trong hàm loadUsers (), tôi đang tìm nạp dữ liệu không đồng bộ ... Nếu tôi bắt đầu một Dịch vụ để gọi API từ phương thức loadUsers (), làm cách nào để cập nhật biến MutableLiveData> người dùng từ Dịch vụ đó?

Nếu ứng dụng đang tìm nạp dữ liệu người dùng trên một chuỗi nền, thì postValue (chứ không phải setValue ) sẽ hữu ích.

Trong phương thức loadData có một tham chiếu đến đối tượng "người dùng" MutableLiveData. Phương thức loadData cũng tìm nạp một số dữ liệu người dùng mới từ một nơi nào đó (ví dụ: một kho lưu trữ).

Bây giờ, nếu việc thực thi nằm trên một chuỗi nền, MutableLiveData.postValue () sẽ cập nhật các trình quan sát bên ngoài của đối tượng MutableLiveData.

Có thể như thế này:

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()Phương thức của Repository có thể gọi một api cho dữ liệu không đồng bộ (có thể bắt đầu một dịch vụ hoặc asynctask cho việc này) trong trường hợp đó, làm thế nào nó có thể trả về Danh sách từ câu lệnh trả về của nó?
CodeCameo

Có lẽ nó có thể lấy đối tượng LiveData làm đối số. (Một cái gì đó giống như repository.getUsers (người dùng)). Sau đó, phương thức kho lưu trữ sẽ gọi chính users.postValue. Và, trong trường hợp đó, phương thức loadUsers thậm chí sẽ không cần một luồng nền.
albert c braun

1
Cảm ơn bạn đã trả lời .... tuy nhiên, tôi đang sử dụng Room DB để lưu trữ và DAO trả về LiveData <Object> ... làm cách nào để chuyển LiveData thành MutableLiveData <Object>?
kilokahn

Tôi không nghĩ rằng DAO của Room thực sự dành để xử lý các đối tượng MutableLiveData. DAO đang thông báo cho bạn về sự thay đổi đối với db cơ bản, nhưng nếu bạn muốn thay đổi giá trị trong DB, bạn sẽ gọi các phương thức của DAO. Cũng có thể các cuộc thảo luận ở đây là hữu ích: stackoverflow.com/questions/50943919/...
albert c braun

3

Hãy xem hướng dẫn kiến ​​trúc Android đi kèm với các mô-đun kiến ​​trúc mới như LiveDataViewModel. Họ thảo luận sâu về vấn đề chính xác này.

Trong các ví dụ của họ, họ không đưa nó vào một dịch vụ. Hãy xem cách họ giải quyết nó bằng cách sử dụng mô-đun "kho lưu trữ" và Trang bị thêm. Các addendums ở dưới cùng bao gồm các ví dụ đầy đủ hơn bao gồm trạng thái mạng giao tiếp, báo cáo lỗi, v.v.


2

Nếu bạn đang gọi api của mình trong Kho lưu trữ thì

Trong kho lưu trữ :

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

Trong ViewModel

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

Trong Hoạt động / Phân đoạn

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

Lưu ý: Sử dụng lambda JAVA_1.8 tại đây, bạn có thể sử dụng mà không cần nó

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.