8

我的问题与ViewModel 第二次返回 nullobserve有关,如果我重复调用服务器,我不会在函数中获得回调。以下是我正在使用的代码 -

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

在 Activity 中,ViewModel 定义如下 -

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

observeViewModel函数用于添加 observable on ViewModel

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

在上面的函数中取消注释以下行允许回调每次都来,但必须有一个正确的方法来做到这一点。

//observeViewModel(networkInformationViewModel);

请注意:- 我不需要 RxJava 实现来实现它。

4

3 回答 3

1

现在getNetworkInformation()你是:

  1. 创建一个新的LiveData
  2. 更新LiveData使用setValue

相反,您应该将一个LiveDataforAPIClient创建为成员变量,然后在 getNetworkInformation() 中更新该成员LiveData

更一般地说,你APIClient是一个数据源。对于数据源,您可以让它们包含在数据更改时更新的成员 LiveData 对象。您可以为这些 LiveData 对象提供 getter 以使它们在 ViewModel 中可访问,并最终在您的活动/片段中收听它们。这与您获取另一个数据源(例如 Room)并收听 Room 返回的 LiveData 的方式类似。

因此,这种情况下的代码如下所示:

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

}

然后在您的视图模型中...

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

}

您的活动可以与您最初编写的活动相同;它只会从 ViewModel 获取并观察 LiveData。

那么 Transformations.switchMap 有什么用呢?

switchMap这里没有必要,因为您不需要LiveData更改APIClient. 这是因为实际上只有一个变化的数据。假设您的 APIClient 出于某种原因需要 4 个不同的 LiveData,并且您想更改LiveData观察到的内容:

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

    ...
}

然后假设您的 fetchNetworkInformation 将根据情况引用不同的 LiveData 进行观察。它可能看起来像这样:

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

在这种情况下,实际LiveData来自的getNetworkInformation是变化,并且您还使用某种参数来确定您想要的 LiveData。在这种情况下,您将使用 a switchMap,因为您想确保您在 Activity/Fragment 中调用的观察语句观察从您返回的 LiveData APIClient,即使您更改了基础 LiveData 实例。而且您不想再次调用观察。

现在这是一个抽象的例子,但它基本上就是你调用Room Dao所做的——如果你有一个基于 idDao查询你的方法并返回 a ,它将根据 id 返回一个不同的实例。RoomDatabaseLiveDataLiveData

于 2017-12-07T21:59:41.650 回答
0

我已经更新了链接问题的答案。在这里重新发布,因为我已经对这个问题进行了赏金,希望有人会验证这是处理问题的正确方法。

以下是更新的工作解决方案 -

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

活动的代码将更新为 -

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

观看她的droidCon NYC 视频,了解有关 LiveData 的更多信息。LiveData 的官方 Google 存储库是https://github.com/googlesamples/android-architecture-components/查找GithubBrowserSample项目。

apiClient.getNetworkInformation()调用不需要任何参数来获取其他信息。因此,添加了“虚空” MutableLiveData

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;
  }
于 2017-12-05T06:39:42.193 回答
0

我没有遇到同样的问题,但是我遇到了类似的事情,每次我将数据保存在数据库中时,观察者的数量都会增加。我调试的方式是调用了多少个实例或不同的观察者实例,我开始知道当您从视图模型中获取实时数据时,需要检查它是否为非空,或者您可以说只返回 1 个实例-

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

在我只是简单地返回data对象之前,然后在检查源之后,我得出的结论是 livedata 会自动更新您的对象,并且每次没有空检查时都会创建新实例并附加新的观察者。如果我对 livedata 的理解是错误的,有人可以纠正我。

于 2017-12-05T07:04:25.547 回答