So aktualisieren Sie LiveData eines ViewModel über den Hintergrunddienst und die Update-Benutzeroberfläche

94

Kürzlich beschäftige ich mich mit Android-Architektur, die kürzlich von Google eingeführt wurde. Aus der Dokumentation habe ich folgendes gefunden:

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

Die Aktivität kann wie folgt auf diese Liste zugreifen:

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

Meine Frage ist, ich werde dies tun:

  1. In der loadUsers()Funktion rufe ich die Daten asynchron ab, wobei ich zuerst die Datenbank (Raum) auf diese Daten überprüfe

  2. Wenn ich die Daten dort nicht erhalte, werde ich einen API-Aufruf durchführen, um die Daten vom Webserver abzurufen.

  3. Ich werde die abgerufenen Daten in die Datenbank (Raum) einfügen und die Benutzeroberfläche entsprechend den Daten aktualisieren.

Was ist der empfohlene Ansatz, um dies zu tun?

Wie kann ich die Variable von dort aktualisieren, wenn ich a starte Service, um die API über die loadUsers()Methode aufzurufen ?MutableLiveData<List<User>> usersService

CodeCameo
quelle
8
Zunächst fehlt Ihnen ein Repository. Ihr ViewModel sollte keine Datenladeaufgaben ausführen. Abgesehen davon muss Ihr Dienst die LiveData im ViewModel nicht direkt aktualisieren, da Sie Room verwenden. Der Dienst kann nur Daten in Room einfügen, während Ihre ViewModelData nur an Room angehängt werden sollten, und Updates von Room erhalten (nachdem der Service Daten eingefügt hat). Die absolut beste Architektur finden Sie in der Implementierung der NetworkBoundResource-Klasse am Ende dieser Seite: developer.android.com/topic/libraries/architecture/guide.html
Marko Gajić,
Vielen Dank für den Vorschlag :)
CodeCameo
1
Die Repositor-Klasse wird in den offiziellen Dokumenten, die ROOM oder die Komponenten der Android-Architektur beschreiben, nicht erwähnt
Jonathan,
2
Repository ist eine empfohlene bewährte Methode für die
Codetrennung und
1
Die Funktion loadUsers()ruft im Grunde das Repo auf, um die Benutzerinformationen zu erhalten
CodeCameo

Antworten:

95

Ich gehe davon aus, dass Sie Android-Architekturkomponenten verwenden . Eigentlich spielt es keine Rolle, wo immer Sie anrufen service, asynctask or handler, um die Daten zu aktualisieren. Sie können die Daten aus dem Dienst oder aus der Asynctask mithilfe der postValue(..)Methode einfügen . Ihre Klasse würde so aussehen:

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

Da das usersist LiveData, Roomist Datenbank verantwortlich für die Datennutzer überall dort , wo es eingesetzt wird.

Note: In einer MVVM-ähnlichen Architektur ist das Repository hauptsächlich für das Überprüfen und Abrufen lokaler Daten und entfernter Daten verantwortlich.

androidcodehunter
quelle
3
Beim Aufrufen meiner Datenbankmethoden wie oben wird die Meldung "java.lang.IllegalStateException: Zugriff auf die Datenbank im Hauptthread seitdem" angezeigt. Können Sie feststellen, was möglicherweise falsch ist?
PCJ
Ich verwende einen Evernote-Job, der die Datenbank im Hintergrund aktualisiert, während ich mich auf der Benutzeroberfläche befinde. LiveData
Wird
users.postValue(mUsers);-> Kann die postValue-Methode von MutableLiveData LiveData akzeptieren?
Cheok Yan Cheng
2
Mein Fehler war valueanstelle von postValue. Danke für deine Antwort.
Hesam
1
@pcj Sie müssen entweder die Raumoperation in einem Thread ausführen oder Operationen im Hauptthread aktivieren - Google für weitere Antworten.
Kilokahn
46

Sie können die MutableLiveData<T>.postValue(T value)Methode aus dem Hintergrundthread verwenden.

private void loadUsers() {
    // do async operation to fetch users and use postValue() method
   users.postValue(listOfData)
}
Minhazur
quelle
18
Zur Erinnerung, postValue () ist in LiveData geschützt, aber öffentlich in MutableLiveData
Long Ranger
Diese Arbeit auch von einer Hintergrundaufgabe und in Situationen, in denen .setValue()nicht erlaubt wäre
Davejoem
17

... in der Funktion loadUsers () rufe ich die Daten asynchron ab ... Wenn ich einen Dienst zum Aufrufen der API über die Methode loadUsers () starte, wie kann ich die Variable MutableLiveData> users von diesem Dienst aktualisieren?

Wenn die App Benutzerdaten in einem Hintergrundthread abruft , ist postValue (anstelle von setValue ) hilfreich.

In der loadData-Methode gibt es einen Verweis auf das MutableLiveData-Objekt "users". Die loadData-Methode ruft auch einige neue Benutzerdaten von irgendwoher ab (z. B. aus einem Repository).

Wenn sich die Ausführung in einem Hintergrundthread befindet, wird MutableLiveData.postValue () außerhalb der Beobachter des MutableLiveData-Objekts aktualisiert.

Vielleicht so etwas:

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
quelle
Die Repository- getUsers()Methode ruft möglicherweise eine API für die asynchronen Daten auf (startet möglicherweise einen Dienst oder eine Asynctask dafür). Wie kann sie in diesem Fall die Liste von ihrer return-Anweisung zurückgeben?
CodeCameo
Vielleicht könnte es das LiveData-Objekt als Argument nehmen. (So ​​etwas wie repository.getUsers (Benutzer)). Dann würde die Repository-Methode users.postValue selbst aufrufen. In diesem Fall würde die loadUsers-Methode nicht einmal einen Hintergrundthread benötigen.
Albert C Braun
1
Vielen Dank für die Antwort. Ich verwende jedoch eine Raumdatenbank für die Speicherung und das DAO gibt eine LiveData <Objekt> zurück. Wie konvertiere ich die LiveData in eine MutableLiveData <Objekt>?
Kilokahn
Ich glaube nicht, dass Room's DAO wirklich für MutableLiveData-Objekte gedacht ist. Das DAO benachrichtigt Sie über eine Änderung der zugrunde liegenden Datenbank. Wenn Sie jedoch den Wert in der Datenbank ändern möchten, rufen Sie die Methoden des DAO auf. Vielleicht ist auch die Diskussion hier nützlich: stackoverflow.com/questions/50943919/…
albert c braun
3

Schauen Sie sich den Android-Architekturleitfaden an , der die neuen Architekturmodule wie LiveDataund begleitet ViewModel. Sie diskutieren genau dieses Thema ausführlich.

In ihren Beispielen stellen sie es nicht in einen Dienst. Schauen Sie sich an, wie sie es mit einem "Repository" -Modul und Retrofit lösen. Die Addendums unten enthalten vollständigere Beispiele, einschließlich der Übermittlung des Netzwerkstatus, der Meldung von Fehlern usw.

user1978019
quelle
2

Wenn Sie Ihre API im Repository aufrufen, dann

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

In ViewModel

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

In Aktivität / Fragment

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

Hinweis: Wenn Sie hier JAVA_1.8 Lambda verwenden, können Sie es auch ohne verwenden

Shubham Agrawal
quelle