5

Meine Frage bezieht sich auf ViewModel second time returns null wobei ich keinen Rückruf in observe Funktion bekomme, wenn ich einen wiederholten Anruf zum Server mache. Im Anschluss ist der Code ich verwende -AndroidViewModel - Doppelte Aufrufe gibt keine Daten in der Funktion beobachten zurück

@Singleton 
public class NetworkInformationViewModel extends AndroidViewModel { 
    private LiveData<Resource<NetworkInformation>> networkInfoObservable; 
    private final APIClient apiClient; 

    @Inject 
    NetworkInformationViewModel(@NonNull APIClient apiClient, @NonNull Application application) { 
    super(application); 
    this.apiClient = apiClient; 
    getNetworkInformation(); 
    } 

    public LiveData<Resource<NetworkInformation>> getNetworkInfoObservable() { 
    return networkInfoObservable; 
    } 

    // making API calls and adding it to Observable 
    public void getNetworkInformation() { 
    networkInfoObservable = apiClient.getNetworkInformation(); 
    } 
} 

In Aktivität wird das Ansichtsmodell wie folgt definiert -

final NetworkInformationViewModel networkInformationViewModel = 
     ViewModelProviders.of(this, viewModelFactory).get(NetworkInformationViewModel.class); 
    observeViewModel(networkInformationViewModel); 

Die observeViewModel Funktion verwendet wird, auf ViewModel beobachtbaren hinzuzufügen.

public void observeViewModel(final NetworkInformationViewModel networkInformationViewModel) { 
    networkInformationViewModel.getNetworkInfoObservable() 
     .observe(this, networkInformationResource -> { 
     if (networkInformationResource != null) { 
      if (networkInformationResource.status == APIClientStatus.Status.SUCCESS) { 
      Timber.d("Got network information data"); 
      } else { 
      final Throwable throwable = networkInformationResource.throwable; 
      if (throwable instanceof SocketTimeoutException) { 
       final NetworkInformation networkInformation = networkInformationResource.data; 
       String error = null; 
       if (networkInformation != null) { 
       error = TextUtils.isEmpty(networkInformation.error) ? networkInformation.reply : networkInformation.error; 
       } 
       Timber.e("Timeout error occurred %s %s", networkInformationResource.message, error); 

      } else { 
       Timber.e("Error occurred %s", networkInformationResource.message); 
      } 
      if (count != 4) { 
       networkInformationViewModel.getNetworkInformation(); 
       count++; 
       // Uncommenting following line enables callback to be received every time 
       //observeViewModel(networkInformationViewModel); 
      } 
      } 
     } 
     }); 
    } 

die folgende Zeile in obiger Funktion uncommenting ermöglicht es der Rückruf jedes Mal zu kommen, aber es muss eine richtige Art und Weise, dies zu tun sein.

//observeViewModel(networkInformationViewModel); 

Bitte beachten Sie: - Ich brauche nicht RxJava Umsetzung dieses für die Umsetzung.

+0

Haben Sie die Antwort überprüft: https://stackoverflow.com/questions/45889604/livedata-is-not-updating-its-value-after-first-call? – NiVeR

+0

@NiVeR Versuchte das, hilft nicht. –

+0

können Sie Code hinzufügen, wie Sie es beobachtbar hinzufügen? – Gautam

Antwort

1

Gerade jetzt in getNetworkInformation() Sie sind:

  1. Erstellen eines neuen LiveData
  2. die Statt der LiveData mit setValue

aktualisieren, sollten Sie ein einzelnes LiveData für APIClient als Mitglied erstellt haben Variable, dann in getNetworkInformation() nur dieses Mitglied aktualisieren .

Allgemeiner ist Ihre APIClient eine Datenquelle. Für Datenquellen können Sie sie LiveData-Mitgliedsobjekte enthalten, die aktualisiert werden, wenn sich die Daten ändern. Sie können Getter für diese LiveData-Objekte bereitstellen, um sie in ViewModels zugänglich zu machen und sie in Ihren Aktivitäten/Fragmenten zu hören. Dies ist ähnlich, wenn Sie eine andere Datenquelle, z. B. "Room", verwenden und ein LiveData-Ereignis abhören, das von Room zurückgegeben wird.

So ist der Code würde in diesem Fall wie folgt aussehen:

@Singleton 
public class APIClient { 
    private final MutableLiveData<Resource<NetworkInformation>> mNetworkData = new MutableLiveData<>(); // Note this needs to be MutableLiveData so that you can call setValue 

    // This is basically the same code as the original getNetworkInformation, instead this returns nothing and just updates the LiveData 
    public void fetchNetworkInformation() { 
     apiInterface.getNetworkInformation().enqueue(new Callback<NetworkInformation>() { 
      @Override 
      public void onResponse(
      @NonNull Call<NetworkInformation> call, @NonNull Response<NetworkInformation> response 
     ) { 
      if (response.body() != null && response.isSuccessful()) { 
       mNetworkData.setValue(new Resource<>(APIClientStatus.Status.SUCCESS, response.body(), null)); 
      } else { 
       mNetworkData.setValue(new Resource<>(APIClientStatus.Status.ERROR, null, response.message())); 
      } 
      } 

      @Override 
      public void onFailure(@NonNull Call<NetworkInformation> call, @NonNull Throwable throwable) { 
      mNetworkData.setValue(
       new Resource<>(APIClientStatus.Status.ERROR, null, throwable.getMessage(), throwable)); 
      } 
     }); 
    } 

    // Use a getter method so that you can return immutable LiveData since nothing outside of this class will change the value in mNetworkData 
    public LiveData<Resource<NetworkInformation>> getNetworkData(){ 
     return mNetworkData; 
    } 

} 

Dann in Ihrem Ansichtsmodell ...

// I don't think this should be a Singleton; ViewModelProviders will keep more than one from being instantiate for the same Activity/Fragment lifecycle 
public class SplashScreenViewModel extends AndroidViewModel { 

private LiveData<Resource<NetworkInformation>> networkInformationLiveData; 

    @Inject 
    SplashScreenViewModel(@NonNull APIClient apiClient, @NonNull Application application) { 
    super(application); 
    this.apiClient = apiClient; 

    // Initializing the observable with empty data 
    networkInfoObservable = apiClient.getNetworkData() 

    } 

    public LiveData<Resource<NetworkInformation>> getNetworkInfoObservable() { 
    return networkInformationLiveData; 
    } 

} 

Ihre Aktivität die gleiche sein kann, wie Sie es ursprünglich codiert; Es wird nur die LiveData aus dem ViewModel sehen und beobachten.

Wofür ist Transformations.switchMap?

switchMap ist hier nicht erforderlich, da Sie die zugrunde liegende LiveData-Instanz in APIClient nicht ändern müssen. Dies liegt daran, dass es wirklich nur einen Teil der sich ändernden Daten gibt. Lassen Sie uns sagen, anstatt Ihre APIClient 4 verschiedene Livedata aus irgendeinem Grund nötig, und man wollte sich ändern, welche LiveData Sie beobachtet:

public class APIClient { 
    private MutableLiveData<Resource<NetworkInformation>> mNetData1, mNetData2, mNetData3, mNetData4; 

    ... 
} 

des Dann lassen Sie sagen, dass Ihre fetchNetworkInformation auf verschiedene Livedata beziehen würde abhängig von der Situation zu beobachten. Es könnte wie folgt aussehen:

public LiveData<Resource<NetworkInformation>> getNetworkInformation(int keyRepresentingWhichLiveDataToObserve) { 
    LiveData<Resource<NetworkInformation>> currentLiveData = null; 
    switch (keyRepresentingWhichLiveDataToObserve) { 
     case 1: 
      currentLiveData = mNetData1; 
      break; 
     case 2: 
      currentLiveData = mNetData2; 
      break; 
     //.. so on 
    } 

    // Code that actually changes the LiveData value if needed here 

    return currentLiveData; 
} 

In diesem Fall wird die tatsächliche LiveData von getNetworkInformation kommenden Veränderungen ist, und Sie sind auch eine Art von Parameter verwendet wird, um zu bestimmen, welche Livedata Sie wollen. In diesem Fall würden Sie switchMap verwenden, da Sie sicherstellen möchten, dass die in Ihrer Aktivität/Fragment aufgerufene Beobachtungsanweisung die von Ihrer APIClient zurückgegebenen LiveData beobachtet, selbst wenn Sie die zugrunde liegende LiveData-Instanz ändern. Und du willst nicht noch einmal beobachten.

Nun ist dies ein bisschen ein abstraktes Beispiel, aber es ist im Grunde, was Anrufe zu einem Room Dao tun - wenn Sie eine Dao Methode, die Ihre RoomDatabase abfragt basierend auf einer ID und gibt ein LiveData, wird es eine andere Rückkehr LiveData Instanz basierend auf der ID.

+0

Endgültige Frage, Sie haben 'mNetworkData' zu einer privaten globalen Variable in der' APIInterface-Klasse' gemacht, die irgendwie Sinn macht, da ich sie an anderen Stellen benutzen werde. Die App könnte jedoch 30 weitere Observables für verschiedene APIs in verschiedenen Bildschirmen haben, in solchen Fällen macht es alles keinen Sinn, wie sie ein solches Szenario angehen. –

+1

Sie können verschiedene Repositorys für die verschiedenen Datentypen in Ihrer App verwenden, wie in der Abbildung [hier im Github-Beispiel] (https://github.com/googlesamples/android-architecture-components/tree/master/GithubBrowserSample/app/src/) dargestellt. main/java/com/android/beispiel/github/repository) - das kann Ihnen helfen, die Daten zu trennen und nur die Repositories zu verwenden, die die Daten repräsentieren, die für ein bestimmtes ViewModel benötigt werden. – Lyla

+1

Wenn Sie sich fragen, warum das GitHub-Beispiel LiveData zurückzuliefern scheint, liegt es daran, dass es ein [MediatorLiveData-Objekt namens NetworkResource] (https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample) zurückgibt /app/src/main/java/com/android/example/github/repository/NetworkBoundResource.java). Die Verwendung von MediatorLiveData auf diese Weise ist [hier beschrieben] (https://developer.android.com/topic/libraries/architecture/guide.html#addendum). – Lyla

0

Ich habe bereits die linked question's answer aktualisiert. Posten Sie hier erneut, da ich ein Kopfgeld auf die Frage gelegt habe und hoffentlich wird jemand bestätigen, dass dies der richtige Weg ist, um das Problem zu lösen.

Es folgt die aktualisierte Arbeitslösung -

@Singleton 
public class SplashScreenViewModel extends AndroidViewModel { 
    private final APIClient apiClient; 
    // This is the observable which listens for the changes 
    // Using 'Void' since the get method doesn't need any parameters. If you need to pass any String, or class 
    // you can add that here 
    private MutableLiveData<Void> networkInfoObservable; 
    // This LiveData contains the information required to populate the UI 
    private LiveData<Resource<NetworkInformation>> networkInformationLiveData; 

    @Inject 
    SplashScreenViewModel(@NonNull APIClient apiClient, @NonNull Application application) { 
    super(application); 
    this.apiClient = apiClient; 

    // Initializing the observable with empty data 
    networkInfoObservable = new MutableLiveData<Void>(); 
    // Using the Transformation switchMap to listen when the data changes happen, whenever data 
    // changes happen, we update the LiveData object which we are observing in the MainActivity. 
    networkInformationLiveData = Transformations.switchMap(networkInfoObservable, input -> apiClient.getNetworkInformation()); 
    } 

    /** 
    * Function to get LiveData Observable for NetworkInformation class 
    * @return LiveData<Resource<NetworkInformation>> 
    */ 
    public LiveData<Resource<NetworkInformation>> getNetworkInfoObservable() { 
    return networkInformationLiveData; 
    } 

    /** 
    * Whenever we want to reload the networkInformationLiveData, we update the mutable LiveData's value 
    * which in turn calls the `Transformations.switchMap()` function and updates the data and we get 
    * call back 
    */ 
    public void setNetworkInformation() { 
    networkInfoObservable.setValue(null); 
    } 
} 

Die Code der Aktivität wird als aktualisiert werden -

final SplashScreenViewModel splashScreenViewModel = 
    ViewModelProviders.of(this, viewModelFactory).get(SplashScreenViewModel.class); 
observeViewModel(splashScreenViewModel); 
// This function will ensure that Transformation.switchMap() function is called 
splashScreenViewModel.setNetworkInformation(); 

sie für weitere Informationen über Livedata droidCon NYC video beobachten. Das offizielle Google-Repository für LiveData ist https://github.com/googlesamples/android-architecture-components/ Suchen Sie nach GithubBrowserSample Projekt.

Der Aufruf apiClient.getNetworkInformation() benötigt keine Parameter, um zusätzliche Informationen zu erhalten. Daher wurde die "Leere" in MutableLiveData hinzugefügt.

public LiveData<Resource<NetworkInformation>> getNetworkInformation() { 
    final MutableLiveData<Resource<NetworkInformation>> data = new MutableLiveData<>(); 

    apiInterface.getNetworkInformation().enqueue(new Callback<NetworkInformation>() { 
     @Override 
     public void onResponse(
     @NonNull Call<NetworkInformation> call, @NonNull Response<NetworkInformation> response 
    ) { 
     if (response.body() != null && response.isSuccessful()) { 
      data.setValue(new Resource<>(APIClientStatus.Status.SUCCESS, response.body(), null)); 
     } else { 
      data.setValue(new Resource<>(APIClientStatus.Status.ERROR, null, response.message())); 
     } 
     } 

     @Override 
     public void onFailure(@NonNull Call<NetworkInformation> call, @NonNull Throwable throwable) { 
     data.setValue(
      new Resource<>(APIClientStatus.Status.ERROR, null, throwable.getMessage(), throwable)); 
     } 
    }); 
    return data; 
    } 
+0

Normalerweise verwenden Sie die 'input' als ID oder etwas in der 'getNetworkInformation'Funktion, was die Rückgabe eines anderen LiveData mit dieser ID erfordert. Ist es möglich zu teilen, was die Funktion 'getNetworkInformation' macht? Das könnte sein, wo dein Grundproblem liegt. Ich schätze, dass es ein neues LiveData baut - es wäre hilfreich festzustellen, ob es ein neues LiveData erstellen muss oder ob es einfach ein existierendes LiveData mit 'setValue' /' postValue' aktualisieren kann. – Lyla

+0

Ein Beispiel dafür, wie das Aktualisieren eines LiveData, das Netzwerkdaten abruft, aussieht (im Gegensatz zum Erstellen neuer LiveData), finden Sie unter [diese Klasse] (https://github.com/googlecodelabs/android-build-an-app-architecture -components/blob/arch-trainingsschritte/app/src/main/java/com/beispiel/android/sunshine/daten/netzwerk/WeatherNetworkDataSource.java # L180). – Lyla

+0

Hier wird ein [LiveData erstellt] (https://github.com/googlecodelabs/android-build-an-app-architecture-components/blob/arch-training-steps/app/src/main/java/com/example /android/sunshine/data/network/WeatherNetworkDataSource.java#L64) und dann [aktualisiert, wenn die Netzwerkanforderung abgeschlossen ist] (https://github.com/googlecodelabs/android-build-an-app-architecture-components/blob/ arch-training-steps/app/src/main/java/com/beispiel/android/sunshine/data/netzwerk/WeatherNetworkDataSource.java # L180). – Lyla

0

Ich habe das gleiche Problem nicht erfüllt, aber ich stieß auf eine ähnliche Sache, wo die Zahl der Beobachter wurden jedes Mal zu erhöhen war ich die Daten in db speichern. Die Art und Weise, wie ich debuggte, war, wie viele Instanzen oder verschiedene Instanzen von Beobachtern aufgerufen wurden, und ich erfuhr, dass beim Abrufen der Live-Daten vom Ansichtsmodell nach nicht null gesucht werden muss oder dass nur 1 Instanz zurückgegeben wird -

private LiveData<T> data; 
    public LiveData<T> getLiveData(){ 
     if(data ==null){ 
      data = //api call or fetch from db 
     } 
     return data; 
    } 

Bevor ich einfach war das data Objekt zurückkehrt und dann nach der Quelle Überprüfung kam ich zu dem Schluss, dass automatisch live~~POS=TRUNC aktualisiert Ihr Objekt und jedes Mal, ohne die null-Check neue Instanz wurde immer erstellt und neue Beobachter wurden angebracht zu werden . Jemand kann mich korrigieren, wenn mein Verständnis bezüglich der gelebten Dinge falsch ist.

+0

Haben Sie das Video von droidCon gesehen? Das hat geholfen. –

+0

noch nicht, aber ich werde sicherlich sehen. Froh, dass es dir geholfen hat – Gautam

+0

Treffen mit ihr geholfen! Sie hat es empfohlen. –

Verwandte Themen