Error using Google API to check addresses

18

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.
asked by Maguz 24.03.2017 в 20:48
source

3 answers

2

Solution (more or less)

Because the adapter was getting dizzy when it received text changes so often, try to implement a method that would disconnect the gac when it is written very fast.

    input.setAdapter(pacra); //justo despues de esta linea que esta en la pregunta 
    input.addTextChangedListener(new TextWatcher() {
        private Timer timer = new Timer();
        private Timer timer2 = new Timer();
        private final long DELAY = 1000; // milliseconds
        private final long DELAY2 = 500; // milliseconds
        private Long startTime = null, difference = null;

        public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
        public void onTextChanged(CharSequence s, int start, int before, int count) {}

        @Override
        public void afterTextChanged(Editable s) {
            Logmc.d(TAG, ">> afterTextChanged: ["+s+"]");

            if(startTime == null) {
                startTime = System.currentTimeMillis();
            }else{
                difference = System.currentTimeMillis() - startTime;
                startTime = System.currentTimeMillis();
                Logmc.d(TAG, "difference: "+difference);
                if(difference < 200){
                    Logmc.d(TAG, "MUY RÁPIDO!");

                    gac.disconnect();
                    timer.cancel();
                    timer = new Timer();
                    timer.schedule(
                            new TimerTask() {
                                @Override
                                public void run() {
                                    Logmc.d(TAG, "!!!!!!!!");
                                    if(!gac.isConnected()){
                                        gac.connect();
                                        resetearTexto();
                                    }
                                }
                            },
                            DELAY
                    );
                }else{
                    timer.cancel();
                    if(!gac.isConnected()){
                        gac.connect();
                        resetearTexto();
                    }
                }
            }
        }

        private void resetearTexto(){
            try {
                //esperamos 500 ms para resetear texto y que vuelva a buscar direcciones
                timer2.cancel();
                timer2 = new Timer(); //esperamos que el gac este bien conectado
                timer2.schedule(
                        new TimerTask() {
                            @Override
                            public void run() {
                                runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        if (input.getText().length() != 0) {
                                            input.setText("" + input.getText());
                                            input.setSelection(input.getText().length()); //focus in right of text
                                        }
                                        startTime = null;
                                    }
                                });
                            }
                        },
                        DELAY2
                );
            }catch (Exception e){ Logmc.e(TAG, "ERROR!", e); }
        }
    });

The bad thing about this solution is that you have to implement it in all the editText that shows the suggestions but until now it has been a solution and it has not been dropped again. EDITO: it still falls but much less: (!

    
answered by 30.03.2017 в 17:03
1

The problem specified is:

  

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 thread background, 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.example.lala.adaptors.PlacesAutocompleteRowAdapter)]

and it's because the method:

   protected FilterResults performFiltering(CharSequence constraint) {

runs in the background and from that method you are changing the results of listaResultados that are necessary for the Adapter. Do not modify from a Thread in background.

Reviewing your class can be optimized, in fact you do not need 3 methods to receive a different type of Activity, it can be a single method that receives the context of the Activity no matter what it is. In addition to this you can add the class PlaceAutocomplete within your main class PlacesAutocompleteRowAdapter .

    
answered by 29.03.2017 в 04:07
0

The thread that takes the result through the callback is not the thread of the GUI.

In onResult (), do nothing, just send "places" with a GUI event to the GUI thread. Do not modify either the model or the view from onResult ().

I do not know how to send events to the GUI on Android ...

view.post (); // I think with this ....

view.post(new Runnable() {
   public void run() {
      if (!places.getStatus().isSuccess()) {
      ......
   }
});
    
answered by 03.04.2017 в 14:07