Crash on ArrayAdapter creation due to Retrofit enqueue() method incompletion

caesar

New Member
#1
I have and issue with my Android sample application. Here are two variants of Activity class code and one of them crashes and another one does not and I don't understand why. In the following case the program works fine:
Mã:
public class MainActivity extends AppCompatActivity {
@Inject
Retrofit retrofit;

@BindView(R.id.lvMain)
ListView lvMain;

@BindView(R.id.tvWelcome)
TextView tvWelcome;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    ButterKnife.bind(this);
    ((RetrofitlearningApp) getApplication()).getGlobalConfigComponent().inject(this);

    tvWelcome.setGravity(Gravity.CENTER);

    HeroesApi heroesApi = retrofit.create(HeroesApi.class);
    Call<List<Hero>> call = heroesApi.getAll();
    call.enqueue(new HeroesCallback());
}

private List<String> getHeroNames(List<Hero> heroes) {
    return heroes.stream()
            .map(Hero::getName)
            .collect(Collectors.toList());
}

private class HeroesCallback implements Callback<List<Hero>> {

    @Override
    public void onResponse(Call<List<Hero>> call, Response<List<Hero>> response) {
        if (!(response.body() == null || response.body().isEmpty())) {
            List<Hero> heroes = response.body();

            ArrayAdapter<String> adapter = new ArrayAdapter<>(getApplicationContext(), android.R.layout.simple_list_item_1, getHeroNames(heroes));
            lvMain.setAdapter(adapter);
        } else {
            tvWelcome.setText("No heroes found");
            Toast.makeText(getApplicationContext(), "No heroes found", Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    public void onFailure(Call<List<Hero>> call, Throwable t) {
        Toast.makeText(getApplicationContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
        tvWelcome.setText("No access to Valhalla");
    }
}
}
But in this one, although enqueue() method is called and response.body() contains a list, onResponse() seems to be never called, and the field "heroes" remains null and the program crashes:
Mã:
public class MainActivity extends AppCompatActivity {
@Inject
Retrofit retrofit;

@BindView(R.id.lvMain)
ListView lvMain;

@BindView(R.id.tvWelcome)
TextView tvWelcome;

private List<Hero> heroes;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    ButterKnife.bind(this);
    ((RetrofitlearningApp) getApplication()).getGlobalConfigComponent().inject(this);

    tvWelcome.setGravity(Gravity.CENTER);

    HeroesApi heroesApi = retrofit.create(HeroesApi.class);
    Call<List<Hero>> call = heroesApi.getAll();
    call.enqueue(new HeroesCallback());

    ArrayAdapter<String> adapter = new ArrayAdapter<>(getApplicationContext(), android.R.layout.simple_list_item_1, getHeroNames(heroes));
    lvMain.setAdapter(adapter);
}

private List<String> getHeroNames(List<Hero> heroes) {
    return heroes.stream()
            .map(Hero::getName)
            .collect(Collectors.toList());
}

private class HeroesCallback implements Callback<List<Hero>> {

    @Override
    public void onResponse(Call<List<Hero>> call, Response<List<Hero>> response) {
        if (!(response.body() == null || response.body().isEmpty())) {
            heroes = response.body();
        } else {
            tvWelcome.setText("No heroes found");
            Toast.makeText(getApplicationContext(), "No heroes found", Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    public void onFailure(Call<List<Hero>> call, Throwable t) {
        Toast.makeText(getApplicationContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
        tvWelcome.setText("No access to Valhalla");
    }
}
}
Would you, please, explain me this phenomenon?
 

Admin

Administrator
Thành viên BQT
#2
Your callback is called asynchronously. It isn't executed when you call enqueue(), instead it's called at some point in time after that when an answer from server arrives.

You're trying to access heroes right after your enqueue() and of course get an NPE.

To address this you should access your heroes variable only when you got the response. As an option move

Mã:
ArrayAdapter<String> adapter = new ArrayAdapter<>(getApplicationContext(), android.R.layout.simple_list_item_1, getHeroNames(heroes));
lvMain.setAdapter(adapter);
to your callback.
 
Top