I'm using the Google API that suggests street addresses as you type in the address. It works well 99% of the time, but I've run into the following error that I can not control:
VIDEO OF THE EXCEPTION: link
E/AndroidRuntime: FATAL EXCEPTION: main
Process: cl.multicaja.checkinmc, PID: 28926
java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. Make sure your adapter calls notifyDataSetChanged() when its content changes. [in ListView(-1, class android.widget.ListPopupWindow$DropDownListView) with Adapter(class cl.ejemplo.lalala.adaptadores.PlacesAutocompleteRowAdapter)]
at android.widget.ListView.layoutChildren(ListView.java:1573)
at android.widget.AbsListView.onLayout(AbsListView.java:2148)
at android.view.View.layout(View.java:16651)
at android.view.ViewGroup.layout(ViewGroup.java:5440)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336)
at android.widget.FrameLayout.onLayout(FrameLayout.java:273)
at android.view.View.layout(View.java:16651)
at android.view.ViewGroup.layout(ViewGroup.java:5440)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336)
at android.widget.FrameLayout.onLayout(FrameLayout.java:273)
at android.view.View.layout(View.java:16651)
at android.view.ViewGroup.layout(ViewGroup.java:5440)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2183)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1943)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1119)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6060)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:858)
at android.view.Choreographer.doCallbacks(Choreographer.java:670)
at android.view.Choreographer.doFrame(Choreographer.java:606)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:844)
at android.os.Handler.handleCallback(Handler.java:746)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5443)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:728)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:618)
This is my adapter:
import android.support.annotation.NonNull;
import android.widget.ArrayAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.location.places.AutocompleteFilter;
import com.google.android.gms.location.places.AutocompletePrediction;
import com.google.android.gms.location.places.AutocompletePredictionBuffer;
import com.google.android.gms.location.places.Place;
import com.google.android.gms.location.places.Places;
import com.google.android.gms.maps.model.LatLngBounds;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.TimeUnit;
import cl.ejemplo.lalala.actividades.Actividad1;
import cl.ejemplo.lalala.actividades.Actividad2;
import cl.ejemplo.lalala.componentes.PlaceAutocomplete;
import cl.ejemplo.lalala.fragmentos.Actividad3;
import cl.ejemplo.lalala.utilidades.Logmc;
public class PlacesAutocompleteRowAdapter extends ArrayAdapter<PlaceAutocomplete> implements Filterable {
private static final String TAG = ">>> " + PlacesAutocompleteRowAdapter.class.getSimpleName();
private ArrayList<PlaceAutocomplete> listaResultados;
private Actividad1 actividad1;
private Actividad2 actividad2;
private Actividad3 actividad3;
private GoogleApiClient gac;
private AutocompleteFilter acf;
public PlacesAutocompleteRowAdapter(Actividad1 act, int resource, GoogleApiClient gac) {
super(act.getApplicationContext(), resource);
this.gac = gac;
this.actividad1= act;
Collection<Integer> tiposDeFiltro = new ArrayList<>();
tiposDeFiltro.add(Place.TYPE_GEOCODE);
acf = AutocompleteFilter.create(tiposDeFiltro);
}
public PlacesAutocompleteRowAdapter(Actividad2 act, int resource, GoogleApiClient gac) {
super(act.getApplicationContext(), resource);
this.gac = gac;
this.actividad2= act;
Collection<Integer> tiposDeFiltro = new ArrayList<>();
tiposDeFiltro.add(Place.TYPE_GEOCODE);
acf = AutocompleteFilter.create(tiposDeFiltro);
}
public PlacesAutocompleteRowAdapter(Actividad3 act, int resource, GoogleApiClient gac) {
super(mapaComercios.getActivity(), resource);
this.gac = gac;
this.actividad3= act;
Collection<Integer> tiposDeFiltro = new ArrayList<>();
tiposDeFiltro.add(Place.TYPE_GEOCODE);
acf = AutocompleteFilter.create(tiposDeFiltro);
}
@Override
public int getCount() {
if (listaResultados == null)
return 0;
else
return listaResultados.size();
}
@Override
public PlaceAutocomplete getItem(int position) {
Logmc.d(TAG, "getItem");
return listaResultados.get(position);
}
@NonNull
@Override
public Filter getFilter() {
return new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
try {
FilterResults resultados = new FilterResults();
if (nuevoCheck != null) {
if (constraint != null && nuevoCheck.posicionActual != null) {
listaResultados = autocompletar(constraint);
if (listaResultados != null) {
resultados.values = listaResultados;
resultados.count = listaResultados.size();
}
}
} else if (nuevaAdquirencia != null) {
if (constraint != null && nuevaAdquirencia.posicionActual != null) {
listaResultados = autocompletar(constraint);
if (listaResultados != null) {
resultados.values = listaResultados;
resultados.count = listaResultados.size();
}
}
} else if (mapaComercios != null) {
if (constraint != null && mapaComercios.posicionActual != null) {
listaResultados = autocompletar(constraint);
if (listaResultados != null) {
resultados.values = listaResultados;
resultados.count = listaResultados.size();
}
}
}
return resultados;
}catch(Exception e){
e.printStackTrace();
return null;
}
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
try {
if (results != null && results.count > 0) {
notifyDataSetChanged();
} else {
notifyDataSetInvalidated();
}
}catch(Exception e){e.printStackTrace();}
}
};
}
private ArrayList<PlaceAutocomplete> autocompletar(CharSequence constraint){
try {
if (gac.isConnected()) {
LatLngBounds cuadradoBusqueda = null;
if (nuevoCheck != null) cuadradoBusqueda = nuevoCheck.obtenerCuadradoDeBusqueda();
else if (nuevaAdquirencia != null) cuadradoBusqueda = nuevaAdquirencia.obtenerCuadradoDeBusqueda();
else if (mapaComercios != null) cuadradoBusqueda = mapaComercios.obtenerCuadradoDeBusqueda();
//Logmc.d(TAG, "Iniciando consulta de autocompletado para: " + constraint.toString());
//Logmc.d(TAG, "Cuadrado de búsqueda: " + cuadradoBusqueda.toString());
PendingResult<AutocompletePredictionBuffer> resultados =
Places.GeoDataApi.getAutocompletePredictions(
gac,
constraint.toString(),
cuadradoBusqueda,
acf
);
AutocompletePredictionBuffer acpb = resultados.await(60, TimeUnit.SECONDS);
final Status estado = acpb.getStatus();
if (!estado.isSuccess()) {
Logmc.e(TAG, "Error al contactar API: " + estado.toString());
acpb.release();
return null;
}
//Logmc.d(TAG, "Consulta completada. Se recibieron " + acpb.getCount() + " predicciones");
Iterator<AutocompletePrediction> iterador = acpb.iterator();
ArrayList<PlaceAutocomplete> listaResultados = new ArrayList<>(acpb.getCount());
while (iterador.hasNext()) {
AutocompletePrediction prediccion = iterador.next();
PlaceAutocomplete pac = new PlaceAutocomplete(
prediccion.getPlaceId(),
prediccion.getDescription()
);
if(pac.toString().contains(", Chile")) // TO/DO: 08-03-2017 SOLO ACEPTAMOS DIRECCIONES DE CHILE
listaResultados.add(pac);
}
acpb.release();
return listaResultados;
}
Logmc.e(TAG, "Cliente Google API no está conectado para consultas de autocompletado.");
return null;
}catch(Exception e){
e.printStackTrace();
return null;
}
}
}
and this is the PlaceAutocomplete
public class PlaceAutocomplete {
public CharSequence placeId;
public CharSequence description;
public PlaceAutocomplete(CharSequence placeId, CharSequence description) {
this.placeId = placeId;
this.description = description;
}
@Override
public String toString() {
return description.toString();
}
}
and so I call her in the activities:
public class Actividad3 extends Fragment implements InterfazConsulta, GoogleApiClient.OnConnectionFailedListener {
protected GoogleApiClient gac; //Autocompletado de direcciones
private PlacesAutocompleteRowAdapter pacra; //Rows de direcciones que aparecen al escribir la direccion
/*blabla*/
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
/*blabla*/
// AUTOCOMPLETADO DE DIRECCION
gac = new GoogleApiClient.Builder(fragmentActivity)
.enableAutoManage(fragmentActivity, 0 /* clientId */, this)
.enableAutoManage(fragmentActivity, 0 /* clientId */, this)
.addApi(Places.GEO_DATA_API)
.build();
gac.connect();
/*blabla*/
}
//Buscador
private void buscaDireccion(){
//buscar en el mapa
final MaterialAutoCompleteTextView input = new MaterialAutoCompleteTextView(fragmentActivity);
input.setPaddings(20, 0, 10, 0);
//SUGIERE LA DIRECCIÓN, PERO SOLO FUNCIONA CON INTERNET
input.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
try {
final PlaceAutocomplete item = pacra.getItem(position);
if(item != null) {
final String placeId = String.valueOf(item.placeId);
//Log.i(TAG, "placeId: " + placeId);
//Log.i(TAG, "item.description: " + item.description);
PendingResult<PlaceBuffer> placeResult = Places.GeoDataApi.getPlaceById(gac, placeId);
placeResult.setResultCallback(new ResultCallback<PlaceBuffer>() {
@Override
public void onResult(PlaceBuffer places) {
if (!places.getStatus().isSuccess()) {
Log.e(TAG, "Consulta no fue completada. Error: " + places.getStatus().toString());
places.release();
return;
}
final Place place = places.get(0);
if (place.getLatLng() != null && lat != null && lon != null) {
Location posicionLugar = new Location("");
posicionLugar.setLatitude(place.getLatLng().latitude);
posicionLugar.setLongitude(place.getLatLng().longitude);
Location posicionActual = new Location("");
posicionActual.setLatitude(Double.parseDouble(lat));
posicionActual.setLongitude(Double.parseDouble(lon));
}
places.release();
}
});
}
}catch(Exception e){ e.printStackTrace();}
}
});
pacra = new PlacesAutocompleteRowAdapter(Actividad3.this, R.layout.row_place, gac);
input.setAdapter(pacra);
final AlertDialog builder = new AlertDialog.Builder(fragmentActivity)
.setTitle("Ingresa una dirección")
//.setMessage("Recuerde incluir el número luego de la calle.")
.setIcon(R.mipmap.buscar_warning)
.setView(input)
.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(input.getWindowToken(), 0);
Editable value = input.getText();
if (!value.toString().equals("")) {
Estadisticas.upMapaBuscarLupa(fragmentActivity);
new BuscaDireccionTask().execute(fragmentActivity, value.toString(), -56.16696465022672, -77.156982421875, -17.177530993362254, -67.049560546875);
}
}
})
.setNegativeButton("Cancelar", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(input.getWindowToken(), 0);
}
})
.show();
//Sube el builder para que se vean las sugerencias
input.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(builder.getWindow() != null) {
builder.getWindow().setGravity(Gravity.TOP);
}
}
});
}
I need to control the error ... the way in which I reply the error is entering a text for example: "Fitrono" and then delete the last space and re-enter it, delete it and re-enter it until it dies.
The dependence I have on the gradle is:
compile 'com.google.android.gms:play-services-location:8.3.0'
EDITED (03/29/2017) OBSERVATIONS:
- The exception only occurs when I type with both hands. It's strange, but if I replicate the same video test with a finger, the application never falls down. That said I think a possible solution would be to modify the behavior of the editText (in my case it's called input) to allow you to type one key at a time (?) .
- I added Logs in the whole adapter and the last method that comes in is to getCount () , but even adding a try-catch I managed to capture the exception
EDITED (03/04/2017)
- At the moment I am left with my solution, but I will continue waiting for a better answer.