MutableLiveData не возвращает объект HashMap после завершения модернизации загрузки моих данных и обновления HashMap

#java #android #retrofit #android-livedata

#java #Android #модернизация #android-livedata

Вопрос:

Итак, я недавно изучил Retrofit и пытался создать приложение, которое использует API для загрузки сведений о покемонах. Вот код для MainActivity.java класс и MainActivityViewModel.java класс. Там есть и другие классы, но в основном это классы моделей, поэтому я их не упомянул:

MainActivity.java

 import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;

import android.os.Bundle;
import android.widget.Toast;

import com.arpansircar.java.pokemonapplication.R;
import com.arpansircar.java.pokemonapplication.viewmodel.MainActivityViewModel;

public class MainActivity extends AppCompatActivity {

    private MainActivityViewModel mainActivityViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mainActivityViewModel = new ViewModelProvider(this).get(MainActivityViewModel.class);
    }

    @Override
    protected void onResume() {
        super.onResume();
        observeRetrofitFailures();

        mainActivityViewModel.downloadMainPokemonData();
        mainActivityViewModel.returnMainDownloadedData().observe(this, resultsPokemonServiceModels ->
                mainActivityViewModel.downloadSelectedPokemonData(resultsPokemonServiceModels));

        mainActivityViewModel.returnDownloadedPokemonData().observe(this, map -> {

        });
    }

    private void observeRetrofitFailures() {
        mainActivityViewModel.returnMainServiceErrorLiveData().observe(this, s ->
                Toast.makeText(MainActivity.this, s, Toast.LENGTH_SHORT).show());

        mainActivityViewModel.returnSelectedPokemonServiceErrorLiveData().observe(this, s ->
                Toast.makeText(this, s, Toast.LENGTH_SHORT).show());
    }
}
 

MainActivityViewModel.java

 import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;

import com.arpansircar.java.pokemonapplication.model.GetPokemonId;
import com.arpansircar.java.pokemonapplication.model.MainPokemonServiceModel;
import com.arpansircar.java.pokemonapplication.model.ResultsPokemonServiceModel;
import com.arpansircar.java.pokemonapplication.model.SelectedPokemonModel;
import com.arpansircar.java.pokemonapplication.retrofit.MainPokemonRetrofitInstance;
import com.arpansircar.java.pokemonapplication.retrofit.PokemonServiceInterface;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.internal.EverythingIsNonNull;

public class MainActivityViewModel extends ViewModel {

    private MainPokemonServiceModel mainPokemonServiceModel;
    private final Map<String, String> pokemonHashMap = new HashMap<>();
    private final PokemonServiceInterface mainPokemonServiceInterface = MainPokemonRetrofitInstance.getInstance();

    private final MutableLiveData<String> mainServiceErrorLiveData = new MutableLiveData<>();
    private final MutableLiveData<String> selectedPokemonServiceErrorLiveData = new MutableLiveData<>();
    private final MutableLiveData<Map<String, String>> downloadMainPokemonData = new MutableLiveData<>();
    private final MutableLiveData<List<ResultsPokemonServiceModel>> resultsPokemonServiceModelListLiveData = new MutableLiveData<>();

    public void downloadMainPokemonData() {
        Call<MainPokemonServiceModel> mainPokemonServiceModelCall = mainPokemonServiceInterface.getResultsDataFromService();
        mainPokemonServiceModelCall.enqueue(new Callback<MainPokemonServiceModel>() {

            @Override
            @EverythingIsNonNull
            public void onResponse(Call<MainPokemonServiceModel> call, Response<MainPokemonServiceModel> response) {
                if (!response.isSuccessful()) {
                    mainServiceErrorLiveData.postValue(String.valueOf(response.code()));
                    return;
                }

                mainPokemonServiceModel = response.body();
                resultsPokemonServiceModelListLiveData.postValue(Objects.requireNonNull(mainPokemonServiceModel).resultsPokemonServiceModel);
            }

            @Override
            @EverythingIsNonNull
            public void onFailure(Call<MainPokemonServiceModel> call, Throwable t) {
                mainServiceErrorLiveData.postValue(t.getMessage());
            }
        });
    }

    public void downloadSelectedPokemonData(List<ResultsPokemonServiceModel> resultsPokemonServiceModelList) {
        for (ResultsPokemonServiceModel resultsPokemonServiceModel : resultsPokemonServiceModelList) {
            String pokemonId = GetPokemonId.getId(resultsPokemonServiceModel.url);
            Call<SelectedPokemonModel> selectedPokemonModelCall = MainPokemonRetrofitInstance.getInstance().getSelectedPokemonSprite(pokemonId);

            selectedPokemonModelCall.enqueue(new Callback<SelectedPokemonModel>() {
                @Override
                @EverythingIsNonNull
                public void onResponse(Call<SelectedPokemonModel> call, Response<SelectedPokemonModel> response) {
                    if (!response.isSuccessful()) {
                        selectedPokemonServiceErrorLiveData.postValue(String.valueOf(response.code()));
                        return;
                    }

                    SelectedPokemonModel selectedPokemonModel = response.body();
                    String pokemonName = Objects.requireNonNull(selectedPokemonModel).pokemonName;
                    String pokemonSpriteUrl = selectedPokemonModel.pokemonSpriteModel.spriteUrl;
                    pokemonHashMap.put(pokemonName, pokemonSpriteUrl);
                }

                @Override
                @EverythingIsNonNull
                public void onFailure(Call<SelectedPokemonModel> call, Throwable t) {
                    selectedPokemonServiceErrorLiveData.postValue(t.getMessage());
                }
            });
        }

        downloadMainPokemonData.postValue(pokemonHashMap);
    }

    public LiveData<Map<String, String>> returnDownloadedPokemonData() {
        return downloadMainPokemonData;
    }

    public LiveData<List<ResultsPokemonServiceModel>> returnMainDownloadedData() {
        return resultsPokemonServiceModelListLiveData;
    }

    public LiveData<String> returnMainServiceErrorLiveData() {
        return mainServiceErrorLiveData;
    }

    public LiveData<String> returnSelectedPokemonServiceErrorLiveData() {
        return selectedPokemonServiceErrorLiveData;
    }

}
 

Моя основная проблема заключается в строке downloadMainPokemonData.postValue(pokemonHashMap); . Когда загрузка завершается, объект MutableLiveData (по какой-то причине) не уведомляется об этом, т. Е. HashMap не возвращается обратно в MainActivity.

Я провел несколько проверок журнала и обнаружил, что метод действительно запускается. Однако система завершает работу с методом еще до завершения загрузки. Загрузка действительно завершена, да. Но по какой-то причине он не возвращает загруженную HashMap обратно в MainActivity. Я думаю, что это может быть связано с тем, что метод постановки в очередь, предоставляемый Retrofit, является асинхронным и выполняет все задачи в фоновом потоке.

Может ли кто-нибудь дать какие-либо подсказки относительно того, как я могу позаботиться об этом? Спасибо!

Ответ №1:

selectedPokemonModelCall.enqueue это асинхронный запрос. downloadMainPokemonData.postValue(pokemonHashMap) вызывается до Response<SelectedPokemonModel> обработки данных

Комментарии:

1. Куда вы рекомендуете поместить код downloadMainPokemonData.postValue(…), чтобы данные обрабатывались в нужное время?

2. @ArpanSircar вы можете использовать его внутри public void onResponse , но это внутренний цикл, поэтому вы получите много бесполезных значений. Я бы рекомендовал использовать синхронный вызов selectedPokemonModelCall.execute() , и когда цикл достигает конца, вы получаете данные в одном фрагменте. И не забудьте использовать Thread . Для получения дополнительной информации проверьте howtodoinjava.com/retrofit2/retrofit-sync-async-calls

3. Это может потребовать многого, но есть ли какой-либо способ, которым я могу использовать enqueue метод и получить данные в одном фрагменте в конце? Я действительно не научился выполнять асинхронные вызовы. И я опробовал execute метод без многопоточности, но он возвращает NetworkOnMainThreadException .

4. @ArpanSircar вот почему я сказал — не забудьте использовать Thread . Оберните foreach с помощью Thread thread = new Thread(){ public void run(){ for(...){....} downloadMainPokemonData.postValue(pokemonHashMap) } } thread.start();