Cómo actualizar LiveData de un ViewModel desde el servicio en segundo plano y actualizar la interfaz de usuario

95

Recientemente, estoy explorando la Arquitectura de Android, que ha sido introducida recientemente por Google. De la documentación he encontrado esto:

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

la actividad puede acceder a esta lista de la siguiente manera:

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

Mi pregunta es, voy a hacer esto:

  1. en la loadUsers()función estoy obteniendo los datos de forma asincrónica donde primero verificaré la base de datos (Habitación) para esos datos

  2. Si no obtengo los datos allí, realizaré una llamada a la API para obtener los datos del servidor web.

  3. Insertaré los datos obtenidos en la base de datos (Habitación) y actualizaré la interfaz de usuario de acuerdo con los datos.

¿Cuál es el enfoque recomendado para hacer esto?

Si empiezo Servicea llamar a la API desde el loadUsers()método, ¿cómo puedo actualizar la MutableLiveData<List<User>> usersvariable a partir de eso Service?

CódigoCameo
fuente
8
En primer lugar, le falta un repositorio. Su ViewModel no debería realizar ninguna tarea de carga de datos. Aparte de eso, dado que usa Room, su Servicio no tiene que actualizar LiveData en ViewModel directamente. El servicio solo puede insertar datos en Room, mientras que ViewModelData debe adjuntarse solo a Room y obtener actualizaciones de Room (después de que el servicio inserte los datos). Pero para obtener la mejor arquitectura absoluta, mire la implementación de la clase NetworkBoundResource en la parte inferior de esta página: developer.android.com/topic/libraries/architecture/guide.html
Marko Gajić
gracias por la sugerencia :)
CodeCameo
1
La clase de repositor no se menciona en los documentos oficiales que describen ROOM o los componentes de la arquitectura de Android
Jonathan
2
El repositorio es una práctica recomendada sugerida para la separación de código y la arquitectura, mira este ejemplo: codelabs.developers.google.com/codelabs/…
glisu
1
La función loadUsers()básicamente llamará al repositorio para obtener la información del usuario
CodeCameo

Respuestas:

96

Supongo que está utilizando componentes de arquitectura de Android . En realidad, no importa dónde llame service, asynctask or handlerpara actualizar los datos. Puede insertar los datos del servicio o del asynctask usando el postValue(..)método. Tu clase se vería así:

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

Tal como usersestá LiveData, la Roombase de datos es responsable de proporcionar datos a los usuarios donde sea que se inserte.

Note: En una arquitectura similar a MVVM, el repositorio es principalmente responsable de verificar y extraer datos locales y remotos.

androidcodehunter
fuente
3
Recibo "java.lang.IllegalStateException: No se puede acceder a la base de datos en el hilo principal porque" al llamar a mis métodos db como el anterior, ¿puedes decir qué podría estar mal?
pcj
Estoy usando evernote-job, que actualiza la base de datos en segundo plano mientras estoy en la interfaz de usuario. Pero LiveDatano se actualiza
Akshay Chordiya
users.postValue(mUsers);-> Pero, ¿el método postValue de MutableLiveData puede aceptar LiveData ???
Cheok Yan Cheng
2
Mi error fue usar en valuelugar de postValue. Gracias por tu respuesta.
Hesam
1
@pcj, debe realizar la operación de la sala en un hilo o habilitar las operaciones en el hilo principal: busque más respuestas en Google.
kilokahn
46

Puede usar el MutableLiveData<T>.postValue(T value)método del hilo de fondo.

private void loadUsers() {
    // do async operation to fetch users and use postValue() method
   users.postValue(listOfData)
}
minhazur
fuente
19
Para recordar, postValue () está protegido en LiveData pero es público en MutableLiveData
Long Ranger
este trabajo incluso desde una tarea en segundo plano y en situaciones en las .setValue()que no estaría permitido
davejoem
17

... en la función loadUsers () estoy obteniendo los datos de forma asincrónica ... Si inicio un servicio para llamar a la API desde el método loadUsers (), ¿cómo puedo actualizar la variable MutableLiveData> users de ese servicio?

Si la aplicación está obteniendo datos del usuario en un hilo en segundo plano, será útil postValue (en lugar de setValue ).

En el método loadData hay una referencia al objeto "usuarios" MutableLiveData. El método loadData también obtiene algunos datos nuevos del usuario de algún lugar (por ejemplo, un repositorio).

Ahora, si la ejecución está en un hilo en segundo plano, MutableLiveData.postValue () actualiza a los observadores externos del objeto MutableLiveData.

Quizás algo como esto:

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
fuente
El getUsers()método del repositorio podría llamar a una API para los datos que son asíncronos (podría iniciar un servicio o asíncrono para esto) en ese caso, ¿cómo puede devolver la Lista de su declaración de retorno?
CodeCameo
Quizás podría tomar el objeto LiveData como argumento. (Algo como repository.getUsers (usuarios)). Entonces, el método del repositorio llamaría a users.postValue a sí mismo. Y, en ese caso, el método loadUsers ni siquiera necesitaría un hilo de fondo.
albert c braun
1
Gracias por la respuesta ... sin embargo, estoy usando un Room DB para almacenamiento y el DAO devuelve un LiveData <Object> ... ¿cómo convierto LiveData en un MutableLiveData <Object>?
kilokahn
No creo que el DAO de Room esté realmente destinado a tratar con objetos MutableLiveData. El DAO le está notificando de un cambio en la base de datos subyacente, pero, si desea cambiar el valor en la base de datos, debe llamar a los métodos de DAO. También tal vez la discusión aquí sea útil: stackoverflow.com/questions/50943919/…
albert c braun
3

Eche un vistazo a la guía de arquitectura de Android que acompaña a los nuevos módulos de arquitectura como LiveDatay ViewModel. Discuten este tema exacto en profundidad.

En sus ejemplos, no lo ponen en un servicio. Eche un vistazo a cómo lo resuelven utilizando un módulo de "repositorio" y Retrofit. Los apéndices en la parte inferior incluyen ejemplos más completos que incluyen la comunicación del estado de la red, informes de errores, etc.

user1978019
fuente
2

Si está llamando a su api en el repositorio, entonces,

En repositorio :

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

En ViewModel

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

En actividad / Fragmento

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

Nota: Usando JAVA_1.8 lambda aquí, puede usar sin él

Shubham Agrawal
fuente