Search code examples
androidlistviewandroid-arrayadapterindexoutofboundsexception

IndexOutOfBounds Exception in custom adapter getView


I'm making an application that takes information from Google Places and displays it in a listview. The problem I'm currently having is that the listview displays the information but shortly after the application crashes and I get an IndexOutOfBounds exception in the array adapter where it gets the Place p from places.get(position).

The Activity class:

public class NearbyLocationsActivity extends BaseActivity implements AsyncDelegate {
private Location mLastLocation;
private GetLocations nearbyLocations;
private PlaceAdapter adapter;

private ArrayList<Place> nearbyPlaces;

private double mLat;
private double mLong;

private ListView locationsList;

public Spinner typesSpinner;

private BroadcastReceiver broadcastReceiver;

private String radius = "10000";

private int selectedSpinnerIndex;

private String [] types = {"everything", "restaurant", "bar", "museum", "night_club", "cafe", "movie_theater"};

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

    locationsList = (ListView) findViewById(R.id.locations_list);
    typesSpinner = (Spinner) findViewById(R.id.type_spinner);

    nearbyPlaces = new ArrayList();

    adapter = new PlaceAdapter(getApplicationContext(), nearbyPlaces);
    locationsList.setAdapter(adapter);

    if(!runtimePermissions()) {
        enableService();
    }
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    super.onCreateOptionsMenu(menu);

    return true;
}

public void enableService() {
    Intent i = new Intent(getApplicationContext(), LocationService.class);
    startService(i);
}

private boolean runtimePermissions() {
    if (Build.VERSION.SDK_INT >= 23 && ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
            != PackageManager.PERMISSION_GRANTED
            && ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)
            != PackageManager.PERMISSION_GRANTED) {

            requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION,
                    Manifest.permission.ACCESS_COARSE_LOCATION}, 100);
            return true;
    }
    return false;
}

@Override
public void onResume() {
    super.onResume();
    if (broadcastReceiver == null) {
        broadcastReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                mLastLocation = (Location) intent.getExtras().get("coordinates");
                mLat = mLastLocation.getLatitude();
                mLong = mLastLocation.getLongitude();

                nearbyLocations = new GetLocations(NearbyLocationsActivity.this);
                nearbyLocations.execute();
            }
        };
        registerReceiver(broadcastReceiver, new IntentFilter("location_updates"));
    }
}

@Override
public void onDestroy() {
    super.onDestroy();
    if (broadcastReceiver != null) {
        unregisterReceiver(broadcastReceiver);
    }
}

@Override
public void onStop() {
    Intent i = new Intent(getApplicationContext(), LocationService.class);
    stopService(i);
    super.onStop();
}

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == 100) {
        if (grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
            enableService();
        } else {
            runtimePermissions();
        }
    }
}

@Override
public void asyncComplete(boolean success) {
    adapter.notifyDataSetChanged();

    typesSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
            selectedSpinnerIndex = typesSpinner.getSelectedItemPosition();
            new GetLocations(NearbyLocationsActivity.this).execute();
        }

        @Override
        public void onNothingSelected(AdapterView<?> parent) {
            selectedSpinnerIndex = 0;
        }
    });

    locationsList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            Place p = adapter.getItem(position);
            Intent intent = new Intent(getApplicationContext(), CreateEventActivity.class);
            intent.putExtra("selectedPlace", p);
            startActivity(intent);
        }
    });
}

public class GetLocations extends AsyncTask<Void, Void, Void> {

    private AsyncDelegate delegate;

    public GetLocations (AsyncDelegate delegate){
        this.delegate = delegate;
    }

    @Override
    protected Void doInBackground(Void... params) {
        nearbyPlaces.clear();
        StringBuilder sb = new StringBuilder();
        String http = "https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=" + mLat + "," + mLong +
                "&radius=10000";

        if (selectedSpinnerIndex != 0) {
            http += "&types=" + types[selectedSpinnerIndex];
        }

        http += "&key=AIzaSyDFb37i6VGPj2EG6L7dLO5H7tDhLCqCW2k";

        HttpURLConnection urlConnection;
        try {
            URL url = new URL(http);
            urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.setRequestMethod("GET");

            if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
                InputStream in = new BufferedInputStream(urlConnection.getInputStream());
                BufferedReader reader = new BufferedReader(new InputStreamReader(in));

                String line;
                while ((line = reader.readLine()) != null) {
                    sb.append(line);
                }
            }

            JSONObject jsonObject = new JSONObject(sb.toString());
            JSONArray array = jsonObject.getJSONArray("results");

            for (int i = 0; i < array.length(); i++) {
                JSONObject placeLine = (JSONObject) array.get(i);
                Place place = new Place();
                JSONObject geometryLine = placeLine.getJSONObject("geometry");
                JSONObject locationLine = geometryLine.getJSONObject("location");
                Place.Location location = new Place.Location();
                location.setLat(locationLine.getDouble("lat"));
                location.setLon(locationLine.getDouble("lng"));
                place.setLocation(location);
                place.setIcon(placeLine.getString("icon"));
                place.setPlaceId(placeLine.getString("place_id"));

                String detailsHttp = "https://maps.googleapis.com/maps/api/place/details/json?key=AIzaSyDFb37i6VGPj2EG6L7dLO5H7tDhLCqCW2k&placeid=" + place.getPlaceId();
                getPlaceDetails(detailsHttp, place);

                place.setName(placeLine.getString("name"));

                /*JSONArray typesJson = new JSONArray("types");
                String [] types = new String[typesJson.length()];
                for (int a = 0; i < typesJson.length(); i++) {
                    types[a] = typesJson.getString(a);
                }
                place.setTypes(types);*/

                place.setVicinity(placeLine.getString("vicinity"));

                try {
                    place.setRating(placeLine.getInt("rating"));
                } catch (JSONException je) {
                    place.setRating(-1);
                }

                try {
                    JSONArray photosJson = placeLine.getJSONArray("photos");
                    //for (int k = 0; i < photosJson.length(); i++) {
                        JSONObject photoLine = (JSONObject) photosJson.get(0);

                        String photoHttp = "https://maps.googleapis.com/maps/api/place/photo?maxwidth=400&" +
                                "photoreference=" + photoLine.getString("photo_reference") + "&key=AIzaSyDFb37i6VGPj2EG6L7dLO5H7tDhLCqCW2k";

                        place.setPhotoHttp(photoHttp);
                        //place.addPhotoHttp(photoHttp);
                    //}
                } catch (JSONException je) {
                    place.setPhotoHttp(null);
                }
                nearbyPlaces.add(place);
            }

        } catch(MalformedURLException mue){
            System.out.println("A malformed URL exception occurred. " + mue.getMessage());
        } catch(IOException ioe){
            System.out.println("A input/output exception occurred. " + ioe.getMessage());
        } catch(JSONException je){
            System.out.println("A JSON error occurred. " + je.getMessage());
        }

        return null;
    }

    public void getPlaceDetails(String http, Place p) {
        StringBuilder sb = new StringBuilder();
        HttpURLConnection urlConnection;
        try {
            URL url = new URL(http);
            urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.setRequestMethod("GET");

            if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
                InputStream in = new BufferedInputStream(urlConnection.getInputStream());
                BufferedReader reader = new BufferedReader(new InputStreamReader(in));

                String line;
                while ((line = reader.readLine()) != null) {
                    sb.append(line);
                }
            }

            JSONObject jsonObject = new JSONObject(sb.toString());
            JSONObject resultsJson = jsonObject.getJSONObject("result");
            String address = resultsJson.getString("formatted_address");
            p.setAddress(address);
        } catch(MalformedURLException mue){
            System.out.println("A malformed URL exception occurred. " + mue.getMessage());
        } catch(IOException ioe){
            System.out.println("A input/output exception occurred. " + ioe.getMessage());
        } catch(JSONException je){
            System.out.println("A JSON error occurred. " + je.getMessage());
        }
    }

    @Override
    protected void onPostExecute(Void aVoid) {
        // Thread is finished downloading and parsing JSON, asyncComplete is
        delegate.asyncComplete(true);
    }
}}

The Custom Adapter Class:

public class PlaceAdapter extends ArrayAdapter<Place> {
private Context mContext;
private ArrayList<Place> places;

public PlaceAdapter(Context context, ArrayList<Place> nearbyPlaces) {
    super(context, R.layout.place_list_item, nearbyPlaces);
    mContext = context;
    this.places = nearbyPlaces;
}

@Override
public View getView(final int position, View convertView, ViewGroup parent) {
    LayoutInflater inflater = LayoutInflater.from(mContext);
    View view = inflater.inflate(R.layout.place_list_item, null);
    final Place p = places.get(position);

    TextView placeName = (TextView) view.findViewById(R.id.place_name);
    placeName.setText(p.getName());

    TextView placeAddress = (TextView) view.findViewById(R.id.place_address);
    placeAddress.setText(p.getAddress());

    ImageView placeImage = (ImageView) view.findViewById(R.id.place_picture);

    if (p.getPhotoHttp() != null) {
        Picasso.with(mContext).load(p.getPhotoHttp()).into(placeImage);
    } else {
        Picasso.with(mContext).load(p.getIcon()).into(placeImage);
    }
    return view;
}}

Solution

  • It look like you have multiple issue.

    1. First, I would recommend you to create a new arraylist in "doInBackground", as if user happen to scroll the listview while you clear the array list "nearbyPlaces" then your app will crash as no element remain in the list "nearbyPlaces".
    2. You seem to missing calling "notifyDataSetChanged()" after you add item to the list.

    So the best solution is

    1. Create a new arraylist in "doInBackground", add all the item you need
    2. Pass the arraylist to "onPostExecute"
    3. Set "nearbyPlaces" to the new arraylist and then call "adapter.notifyDataSetChanged()"

    Also, it can solve crash index out of range but it could produce crash when you call "adapter.notifyDataSetChanged()" in case if user already leaving the activity with such as press home, click on item to move to another activity. So make sure that UI or View is accessible.