Подтвердить что ты не робот

Android LiveData Observable не возвращает данные, если вызовы выполняются быстро

Работа с приложением, которое требует отправки нескольких вызовов API на одну и ту же конечную точку за один раз.

Например, сценарии просмотра каталогов, необходимо получить структуру каталогов, отправив запросы на вызовы для всех папок в текущей папке. Проблема заключается в том, что ответ приходит отдельно для всех папок при дооснащении должным образом, но LiveData observable дает мне только один ответ для всего списка.

Структура каталогов: -

test -> temp -> temp1 -> temp2 
                      -> temp3
                      -> temp4

Наблюдается для прослушивания обратного вызова: -

mViewModel.getServerFilesLiveData().observe(this, browseServerDataResource -> {
      if (browseServerDataResource != null) {
        if (browseServerDataResource.status == APIClientStatus.Status.SUCCESS) {
          if (browseServerDataResource.data != null) {
            Timber.i("Got data for path %s in Observable", browseServerDataResource.data.path);
            if (browseServerDataResource.data.folderList != null
              && browseServerDataResource.data.folderList.size() > 0) {
              for (final String name : browseServerDataResource.data.folderList) {
                final ServerDirectoryPathInfo pathInfo = new ServerDirectoryPathInfo();
                pathInfo.completePath = browseServerDataResource.data.path + "/" + name;
                getFolderDownloadPath(pathInfo.completePath);
              }
            }
            mFolderCountToParse--;
            Timber.d("Folders left to parse %d", mFolderCountToParse);
            if (mFolderCountToParse == 0) {
              showToast("Parsed all folders");
            }
          }
        }
      }
    });

Функция для совершения вызовов для получения данных: -

  private void getFolderDownloadPath(@NonNull final String path) {
    mViewModel.getServerFiles(path);
    mFolderCountToParse++;
  }

Повторная установка на сервер: -

  public LiveData<Resource<BrowseServerData>> getServerFiles(@NonNull final String additionalUrl) {
    final MutableLiveData<Resource<BrowseServerData>> data = new MutableLiveData<>();
    final String url = mMySharedPreferences.getCurrentUrl()
      + AppConstants.DIRECTORY_END_POINT
      + AppConstants.PATH_END_POINT
      + (TextUtils.isEmpty(additionalUrl) ? "" : additionalUrl);
    Timber.i("Requesting data for - api %s", url);
    mAPI.getServerFiles(url, mMySharedPreferences.getNetworkName())
      .enqueue(new Callback<BrowseServerData>() {
        @Override
        public void onResponse(
          @NonNull Call<BrowseServerData> call, @NonNull Response<BrowseServerData> response
        ) {
          if (response.body() != null && response.isSuccessful()) {
            if (!TextUtils.isEmpty(response.body().path)) {
              Timber.i("Got response for = %s in Retrofit", response.body().path);
            }
            data.setValue(
              new Resource<>(APIClientStatus.Status.SUCCESS, response.body(), null, null));
          } else {
            ErrorMessage errorMessage = null;
            try {
              errorMessage = Utility.getApiError(response, mRetrofit);
            } catch (IOException e) {
              e.printStackTrace();
            }
            if (errorMessage != null) {
              data.setValue(
                new Resource<>(APIClientStatus.Status.ERROR, null, errorMessage.message(), call));
            } else {
              data.setValue(
                new Resource<>(APIClientStatus.Status.ERROR, null, response.message(), call));
            }
          }
        }

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

Данные поступают как: -

I: Got response for = ./test in Retrofit
I: Got data for path ./test in Observable
I: Got response for = ./test/temp in Retrofit
I: Got data for path ./test/temp in Observable
I: Got response for = ./test/temp/temp1 in Retrofit
I: Got data for path ./test/temp/temp1 in Observable
I: Got response for = ./test/temp/temp1/temp2 in Retrofit
I: Got response for = ./test/temp/temp1/temp4 in Retrofit
I: Got response for = ./test/temp/temp1/temp3 in Retrofit
I: Got data for path ./test/temp/temp1/temp3 in Observable

Как вы можете видеть, данные поступают в Observable только для одной папки temp3.

При добавлении случайной задержки при совершении вызовов данные поступают правильно: -

new Handler().postDelayed(new Runnable() {
                  @Override
                  public void run() {
                    getFolderDownloadPath(pathInfo.completePath);
                  }
                }, new Random().nextInt(10000 - 1000) + 1000);

Теперь по крайней мере данные поступают для 2-х папок из 3: -

I: Got response for = . in Retrofit
I: Got data for path . in Observable
I: Got data for the current directory, don't need it, skipping
I: Got response for = ./test in Retrofit
I: Got data for path ./test in Observable
I: Got response for = ./test/temp in Retrofit
I: Got data for path ./test/temp in Observable
I: Got response for = ./test/temp/temp1 in Retrofit
I: Got data for path ./test/temp/temp1 in Observable
I: Got response for = ./test/temp/temp1/temp3 in Retrofit
I: Got response for = ./test/temp/temp1/temp2 in Retrofit
I: Got data for path ./test/temp/temp1/temp2 in Observable
I: Got response for = ./test/temp/temp1/temp4 in Retrofit
I: Got data for path ./test/temp/temp1/temp4 in Observable

Любые идеи, почему это происходит, и способ его исправить?

Обновление: Добавление конструктора ViewModel, который помогает при вызове сервера

@Inject
  BrowseHubMediaViewModel(@NonNull Application application, @NonNull APIClient mAPIClient) {
    super(application);
    mGetServerFilesMutable = new MutableLiveData<>();
    mGetServerFilesLiveData =
      Transformations.switchMap(mGetServerFilesMutable, mAPIClient::getServerFiles);
}

Получение наблюдаемого из ViewModel

  /**
   * Observer to listen for file listing in server
   *
   * @return LiveData<Resource<BrowseServerData>>
   */
  public LiveData<Resource<BrowseServerData>> getServerFilesLiveData() {
    return mGetServerFilesLiveData;
  }
4b9b3361

Ответ 1

Подсказка @niketshah09 подсказывала мне решение. Исходя из этой проблемы, как описано в @niketshah09, проблема заключалась в том, что Transformations.switchMap() удалял последний возвращенный вызов, когда быстро возвращался множественный обратный вызов. Решение заключалось в использовании MediatorLiveData, который объединит все вызовы и убедитесь, что мы получим все обратные вызовы. Например -

final LiveData<Resource<BrowseServerData>> newParsingFolderLiveData = mAPIClient.getServerFiles(completePath);
folderBrowsingMediator.addSource(newParsingFolderLiveData, folderBrowsingMediator::setValue);

Далее, мы должны наблюдать на MediatorLiveData вместо LiveData. Хотя функциональность MediatorLiveData заключается в том, чтобы убедиться, что мы фильтруем и используем правильный поток, в зависимости от логики кодирования, но в этом случае мы хотим получить все обратные вызовы, поэтому при обратных вызовах не применяется фильтрация.

Таким образом, я получаю все обратные вызовы, дайте мне знать, если кто-то ничего не понимает.

Ответ 2

switchmap отбрасывает все предыдущие элементы и принимает только последний.

I: Получил ответ для =./test/temp/temp1/temp2 в Модернизации
I: Получил ответ для =./test/temp/temp1/temp4 в Дополнении

I: Получил ответ для =./test/temp/temp1/temp3 в Retrofit
I: Получены данные для пути. /test/temp/temp 1/temp3 в Observable

вы вызывали temp2 temp4 и temp3 в последовательности, и в то время как данные temp2 и temp4 приходят, вы вызываете temp3. Таким образом, наблюдаемые для temp2 и temp3 будут отброшены и будут возвращены только наблюдаемые для temp4.

Я думаю, что это может решить вашу проблему. вы можете узнать больше о switchMap. то было бы более понятно.