Итак, недавно я изучил 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, является асинхронным и выполняет все задачи в фоновом потоке.
Может ли кто-нибудь дать какие-либо намеки на то, как я могу позаботиться об этом? Спасибо!
selectedPokemonModelCall.enqueue
— асинхронный запрос. downloadMainPokemonData.postValue(pokemonHashMap)
вызывается перед обработкой Response<SelectedPokemonModel>
данных
@ArpanSircar, вы можете использовать его внутри public void onResponse
, но это внутри цикла, поэтому вы получите много бесполезных значений. Я бы рекомендовал использовать синхронный вызов selectedPokemonModelCall.execute()
, и когда цикл достигает конца, вы получаете данные одним куском. И не забудьте использовать Thread
. Для получения дополнительной информации посетите howtodoinjava.com/retrofit2/retrofit-sync-async-calls
Это может потребовать многого, но есть ли способ, которым я могу использовать метод enqueue
и получить данные одним куском в конце? Я действительно не научился делать асинхронные вызовы. И я попробовал метод execute
без многопоточности, но он возвращает NetworkOnMainThreadException
.
@ArpanSircar, вот почему я сказал - не забудьте использовать Thread
. Оберните foreach с помощью Thread thread = new Thread(){ public void run(){ for(...){....} downloadMainPokemonData.postValue(pokemonHashMap) } } thread.start();
Где вы рекомендуете разместить код загрузкиMainPokemonData.postValue(...), чтобы данные обрабатывались в нужное время?