Como atualizar o LiveData de um ViewModel do serviço de segundo plano e Atualizar UI

95

Recentemente, estou explorando a arquitetura Android, que foi introduzida recentemente pelo Google. Na documentação , encontrei o seguinte:

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

a atividade pode acessar essa lista da seguinte maneira:

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

Minha pergunta é, vou fazer isso:

  1. na loadUsers()função, estou buscando os dados de forma assíncrona, onde primeiro verificarei o banco de dados (Room) para esses dados

  2. Se eu não conseguir os dados lá, farei uma chamada de API para buscar os dados do servidor web.

  3. Vou inserir os dados buscados no banco de dados (Room) e atualizar a IU de acordo com os dados.

Qual é a abordagem recomendada para fazer isso?

Se eu começar Servicea chamar a API a partir do loadUsers()método, como posso atualizar a MutableLiveData<List<User>> usersvariável a partir disso Service?

CodeCameo
fonte
8
Em primeiro lugar, está faltando um Repositório. Seu ViewModel não deve realizar nenhuma tarefa de carregamento de dados. Fora isso, como você está usando o Room, seu serviço não precisa estar atualizando o LiveData no ViewModel diretamente. O serviço só pode inserir dados na Room, enquanto o ViewModelData deve ser anexado apenas à Room e obter atualizações da Room (depois que o serviço inserir os dados). Mas para obter a melhor arquitetura absoluta, veja a implementação da classe NetworkBoundResource na parte inferior desta página: developer.android.com/topic/libraries/architecture/guide.html
Marko Gajić
obrigado pela sugestão :)
CodeCameo
1
A classe Repositor não é mencionada nos documentos oficiais que descrevem ROOM ou os componentes da arquitetura do Android
Jonathan
2
Repositório é uma prática recomendada sugerida para separação de código e arquitetura, veja este exemplo: codelabs.developers.google.com/codelabs/…
glisu
1
A função loadUsers()basicamente chamará o repo para obter as informações do usuário
CodeCameo

Respostas:

96

Presumo que você esteja usando componentes de arquitetura Android . Na verdade, não importa onde você está ligando service, asynctask or handlerpara atualizar os dados. Você pode inserir os dados do serviço ou da asynctask usando o postValue(..)método. Sua classe ficaria assim:

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

Como usersestá LiveData, o Roombanco de dados é responsável por fornecer os dados dos usuários onde quer que esteja inserido.

Note: Na arquitetura tipo MVVM, o repositório é principalmente responsável por verificar e puxar dados locais e dados remotos.

androidcodehunter
fonte
3
Estou recebendo "java.lang.IllegalStateException: Não é possível acessar o banco de dados no thread principal desde que" ao chamar meus métodos db como acima, você pode dizer o que pode estar errado?
pcj 01 de
Estou usando o evernote-job, que atualiza o banco de dados em segundo plano enquanto estou na interface do usuário. Mas LiveDatanão está atualizando
Akshay Chordiya
users.postValue(mUsers);-> Mas, o método postValue de MutableLiveData é capaz de aceitar LiveData ???
Cheok Yan Cheng,
2
Meu erro foi usar em valuevez de postValue. Obrigado pela sua resposta.
Hesam
1
@pcj, você precisa fazer a operação de sala em um thread ou ativar as operações no thread principal - google para obter mais respostas.
kilokahn
46

Você pode usar o MutableLiveData<T>.postValue(T value)método do thread em segundo plano.

private void loadUsers() {
    // do async operation to fetch users and use postValue() method
   users.postValue(listOfData)
}
minhazur
fonte
19
Para lembrar, postValue () é protegido em LiveData, mas público em MutableLiveData
Long Ranger
este trabalho mesmo a partir de uma tarefa em segundo plano e em situações onde .setValue()não seria permitido
davejoem
17

... na função loadUsers () estou buscando os dados de forma assíncrona ... Se eu iniciar um serviço para chamar a API do método loadUsers (), como posso atualizar a variável MutableLiveData> users desse serviço?

Se o aplicativo estiver buscando dados do usuário em um thread de segundo plano, postValue (em vez de setValue ) será útil.

No método loadData, há uma referência ao objeto "usuários" MutableLiveData. O método loadData também busca alguns dados novos do usuário em algum lugar (por exemplo, um repositório).

Agora, se a execução for em um thread de segundo plano, MutableLiveData.postValue () atualiza observadores externos do objeto MutableLiveData.

Talvez algo assim:

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
fonte
O getUsers()método do repositório pode chamar uma api para os dados que são assíncronos (pode iniciar um serviço ou uma tarefa assíncrona para isso), nesse caso, como ele pode retornar a lista de sua instrução de retorno?
CodeCameo
Talvez pudesse usar o objeto LiveData como argumento. (Algo como repository.getUsers (users)). Então, o método do repositório chamaria o próprio users.postValue. E, nesse caso, o método loadUsers nem precisaria de um thread de segundo plano.
albert c braun
1
Obrigado pela resposta ... no entanto, estou usando um Room DB para armazenamento e o DAO retorna um LiveData <Object> ... como faço para converter o LiveData em um MutableLiveData <Object>?
kilokahn
Não acho que o DAO da Room seja realmente destinado a lidar com objetos MutableLiveData. O DAO está notificando sobre uma alteração no banco de dados subjacente, mas, se você quiser alterar o valor no banco de dados, deverá chamar os métodos do DAO. Além disso, talvez a discussão aqui seja útil: stackoverflow.com/questions/50943919/…
albert c braun
3

Dê uma olhada no guia de arquitetura do Android que acompanha os novos módulos de arquitetura como LiveDatae ViewModel. Eles discutem exatamente esse assunto em profundidade.

Em seus exemplos, eles não o colocam em serviço. Dê uma olhada em como eles resolvem isso usando um módulo de "repositório" e Retrofit. Os adendos na parte inferior incluem exemplos mais completos, incluindo comunicação do estado da rede, relatórios de erros, etc.

user1978019
fonte
2

Se você está chamando sua API no Repositório, então,

No Repositório :

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

Em ViewModel

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

Em atividade / fragmento

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

Observação: usando JAVA_1.8 lambda aqui, você pode usar sem ele

Shubham Agrawal
fonte