androidandroid-recyclerviewandroid-googleapiclient

GoogleAPIClient on recyclerview adapter


This app have a fragment that does CRUD operations on a single table database.

It is done on an class that draws a FAB and a recyclerview.

The FAB open an alertDialog with a insert form.

The recyclerview shows the database rows as cardviews. Each cardview have an edit button and a delete button.

The edit button open a alertdialog with the edit form in it.

The database stores journeys that each have a starting point and an ending point.

When entering or editing the places, user get suggestions.

For this I was using Google Places Web Service API but got to my attention that I must use Google Places API for Android and hence GoogleApiClient

Note that the app is working fine, doing the CRUD successfully and even getting the directions suggestions from Web Services.

I was able to make the insert form use the correct API, but not the edit form.

Here is the fragment code:

public class RecyclerFragment extends Fragment
        implements AlertDialog.OnClickListener,
        GoogleApiClient.OnConnectionFailedListener,
        GoogleApiClient.ConnectionCallbacks {

    private static final String TAG = "RecyclerFragment";
    RunDbHelper runDbHelper; // Database helper class
    RecyclerView recyclerView;
    RecyclerViewAdapter recyclerViewAdapter;

    private OnOkButtonListener mCallback;    

    private GoogleApiClient mGoogleApiClient;
    private PlaceArrayAdapter mPlaceArrayAdapter;

    private static final int GOOGLE_API_CLIENT_ID = 0;

    private static final LatLngBounds BOUNDS_MOUNTAIN_VIEW = new LatLngBounds(
            new LatLng(37.398160, -122.180831), new LatLng(37.430610, -121.972090));

    private static LatLngBounds BOUNDS_AUTOCOMPLETE;

    public RecyclerFragment() {
        this.mCallback = null;
    }

    // main activity calls this method to pass the location so it can calculate bounds for the direction suggestions
    public void setAutocompleteBounds(LatLng centerLatLng){
        if (centerLatLng!= null && !centerLatLng.toString().isEmpty()){
            Double latSW, lngSW, lngNE, latNE;
            latSW = centerLatLng.latitude-.5;
            lngSW = centerLatLng.latitude-.5;
            lngNE = centerLatLng.latitude+.5;
            latNE = centerLatLng.latitude+.5;
            BOUNDS_AUTOCOMPLETE = new LatLngBounds(
                    new LatLng(latSW, lngSW), new LatLng(latNE, lngNE));
        } else {
            BOUNDS_AUTOCOMPLETE = BOUNDS_MOUNTAIN_VIEW;
        }
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        View view = inflater.inflate(R.layout.fragment_recycler, container, false);
        try {
            mGoogleApiClient = new GoogleApiClient.Builder(getContext())
                    .addApi(Places.GEO_DATA_API)
                    .enableAutoManage(getActivity(), GOOGLE_API_CLIENT_ID, this)
                    .addConnectionCallbacks(this)
                    .build();
        } catch ( Exception e) {
            Log.i(TAG, "onCreateView: " + e);
        }

        mPlaceArrayAdapter = new PlaceArrayAdapter(getContext(), android.R.layout.simple_list_item_1,
                BOUNDS_AUTOCOMPLETE, null);

        runDbHelper = RunDbHelper.getInstance(getContext());
        List<RunData> mList = runDbHelper.getAllRuns();
        recyclerViewAdapter = new RecyclerViewAdapter(getContext(), mList);
        recyclerView = (RecyclerView) view.findViewById(R.id.rvRunList);
        recyclerView.setHasFixedSize(true);
        recyclerView.setAdapter(recyclerViewAdapter);
        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));

        FloatingActionButton fab = (FloatingActionButton) view.findViewById(R.id.fabAdd);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // Data entry dialog to insert new runs into database
                dialogInsertRun();
            }
        });
        return view;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
    }

    public void dialogInsertRun() { 

        // Get the Activity for layout inflater as this dialog runs inside a fragment
        LayoutInflater inflater = LayoutInflater.from(getActivity());
        final View view = inflater.inflate(R.layout.dialog_new_run, null);

        // Dialog Builder
        AlertDialog.Builder addRunDialog = new AlertDialog.Builder(getActivity());
        addRunDialog.setTitle(R.string.dialog_insert_run_title)
                .setView(view);

        // Data entry field objects
        final EditText runParcelEditText = (EditText) view.findViewById(R.id.new_run_parcel);
        final AutoCompleteTextView collectAddressACTV = (AutoCompleteTextView) view.findViewById(R.id.actv_new_collect_address);

        final EditText collectPersonEditText = (EditText) view.findViewById(R.id.new_collect_person);
        final AutoCompleteTextView deliveryAddressACTV = (AutoCompleteTextView) view.findViewById(R.id.actv_new_delivery_address);
        final EditText deliveryPersonEditText = (EditText) view.findViewById(R.id.new_delivery_person);

        collectAddressACTV.setThreshold(3);
        collectAddressACTV.setOnItemClickListener(mAutocompleteClickListener);
        collectAddressACTV.setAdapter(mPlaceArrayAdapter);

        deliveryAddressACTV.setThreshold(3);
        deliveryAddressACTV.setOnItemClickListener(mAutocompleteClickListener);
        deliveryAddressACTV.setAdapter(mPlaceArrayAdapter);

        addRunDialog.setPositiveButton(R.string.button_positive, new DialogInterface.OnClickListener() {

            @Override
            public void onClick(DialogInterface dialog, int id) {

                RunData runData = new RunData(); // POJO that holds data from database

                runData.run_parcel = getStringOrEmpty(runParcelEditText);
                runData.collect_person = getStringOrEmpty(collectPersonEditText);
                runData.collect_address = getStringOrEmpty(collectAddressACTV);
                runData.delivery_person = getStringOrEmpty(deliveryPersonEditText);
                runData.delivery_address = getStringOrEmpty(deliveryAddressACTV);

                // must insert at least one of the places (starting or ending point) or both
                if (!(runData.collect_address.isEmpty() && runData.delivery_address.isEmpty())) {

                    runData = runDbHelper.insertRun(runData, getActivity());
                    if (runData != null) {
                        cardViewMessageIsEmpty.setVisibility(View.INVISIBLE);
                        recyclerViewAdapter = new RecyclerViewAdapter(getActivity(), runDbHelper.getAllRuns());
                        recyclerView.setAdapter(recyclerViewAdapter);
                        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
                        mCallback.addMarkersToMap(runData);
                    }
                } else {
                    Toast.makeText(getActivity(), R.string.dialog_insert_run_toast_nowhere, Toast.LENGTH_LONG).show();
                }
            }
        });

        addRunDialog.setNegativeButton(R.string.button_negative, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int whichButton) {
                dialog.cancel();
            }
        });

        addRunDialog.create();
        addRunDialog.show();
    }

    private String getStringOrEmpty(EditText editText) {
        String mString = editText.getText().toString();
        mString = (mString == null || mString.isEmpty() ? "" : mString);
        return mString;
    }

    @Override
    public void onClick(DialogInterface dialogInterface, int i) {
        //Log.d(TAG, "onClick: ");
    }

    @Override
    public void onAttach(Context context) {

        if (context instanceof OnOkButtonListener) {
            mCallback = (OnOkButtonListener) context; // keep a reference to eula activity for interface
        } else {
            throw new ClassCastException(context.toString()
                    + getString(R.string.exception_onokbutton_listener));
        }
        super.onAttach(context);
    }

    public void setCustomObjectListener(OnOkButtonListener listener) {
        this.mCallback = listener;
    }

    @Override
    public void onConnected(@Nullable Bundle bundle) {
        mPlaceArrayAdapter.setGoogleApiClient(mGoogleApiClient);
        Log.i(TAG, "Google Places API connected.");
    }

    @Override
    public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
        Log.e(TAG, "Google Places API connection failed with error code: "
                + connectionResult.getErrorCode());

        Toast.makeText(getContext(),
                "Google Places API connection failed with error code:" +
                        connectionResult.getErrorCode(),
                Toast.LENGTH_LONG).show();
    }

    @Override
    public void onConnectionSuspended(int i) {
        mPlaceArrayAdapter.setGoogleApiClient(null);
        Log.e(TAG, "Google Places API connection suspended.");
    }

    public interface OnOkButtonListener {
        void addMarkersToMap(RunData runData);
    }

    private AdapterView.OnItemClickListener mAutocompleteClickListener
            = new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            final PlaceArrayAdapter.PlaceAutocomplete item = mPlaceArrayAdapter.getItem(position);
            final String placeId = String.valueOf(item.placeId);
            Log.i(TAG, "Selected: " + item.description);
            PendingResult<PlaceBuffer> placeResult = Places.GeoDataApi
                    .getPlaceById(mGoogleApiClient, placeId);
            placeResult.setResultCallback(mUpdatePlaceDetailsCallback);
            Log.i(TAG, "Fetching details for ID: " + item.placeId);
        }
    };

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        if(mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
            mGoogleApiClient.stopAutoManage(getActivity());
            mGoogleApiClient.disconnect();
        }
    }   

    private ResultCallback<PlaceBuffer> mUpdatePlaceDetailsCallback
            = new ResultCallback<PlaceBuffer>() {
        @Override
        public void onResult(PlaceBuffer places) {
            if (!places.getStatus().isSuccess()) {
                Log.e(TAG, "Place query did not complete. Error: " +
                        places.getStatus().toString());
                return;
            }
            // Selecting the first object buffer.
        }
    };
}

And here is the adapter code:

class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.RecyclerViewHolder> {

private static final String TAG = "RecyclerViewAdapter";

private Context context;
private List<RunData> dataList = new ArrayList<>();
private LayoutInflater inflater;
private RunDbHelper runDbHelper;

RecyclerViewAdapter(Context context, List<RunData> dataList1) {

    this.context = context;
    this.dataList = dataList1;
    this.runDbHelper = RunDbHelper.getInstance(this.context);
    inflater = LayoutInflater.from(context);
}

@Override
public RecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View inflateView = inflater.inflate(R.layout.fragment_recycler_row, parent, false);
    return new RecyclerViewHolder(inflateView);
}

@Override
public void onBindViewHolder(final RecyclerViewHolder holder, final int position) {

    holder.runID.setText(dataList.get(position).run_id);
    holder.collectAddress.setText(dataList.get(position).collect_address);
    holder.deliveryAddress.setText(dataList.get(position).delivery_address);     

    holder.ivEdit.setOnClickListener(new View.OnClickListener() {

        @Override
        public void onClick(View v) {               
            RunData runData = new RunData();
            runData.run_id = holder.runID.getText().toString();                
            runData.collect_address = holder.collectAddress.getText().toString();
            runData.delivery_address = holder.deliveryAddress.getText().toString();                
            dialogEditRun(runData, position);
        }
    });
}

@Override
public int getItemCount() {
    return dataList.size();
}

public void dialogEditRun(RunData runData, int position) {

    // Get the Activity for layout inflater as this dialog runs inside a fragment
    LayoutInflater inflater = LayoutInflater.from(context);
    final View inflaterView = inflater.inflate(R.layout.dialog_edit_run, null);
    // Data entry field objects
    final String mRunID;

    final AutoCompleteTextView collectAddressACTV = (AutoCompleteTextView) inflaterView.findViewById(R.id.actv_edit_collect_address);
    final AutoCompleteTextView deliveryAddressACTV = (AutoCompleteTextView) inflaterView.findViewById(R.id.actv_edit_delivery_address);

    mRunID = runData.run_id;
    collectAddressACTV.setText(runData.collect_address);
    deliveryAddressACTV.setText(runData.delivery_address);

    // HERE THE FIELDS AND THE SUGGESTIONS ARE LINKED
    // Set directions into recyclerViewAdapter for autocomplete
    // here is still using G. Places WebService API. Should be G. Places API for Android, using GoogleApiClient
    collectAddressACTV.setAdapter(new GooglePlacesAutocompleteAdapter(context, R.layout.dialog_new_run_autocomplete));
    deliveryAddressACTV.setAdapter(new GooglePlacesAutocompleteAdapter(context, R.layout.dialog_new_run_autocomplete));

    // Dialog Builder
    AlertDialog.Builder editRunDialog = new AlertDialog.Builder(context);
    editRunDialog.setTitle(R.string.dialog_update_run_title).setView(inflaterView);

    editRunDialog.setPositiveButton(R.string.button_positive, new DialogInterface.OnClickListener() {

        @Override
        public void onClick(DialogInterface dialog, int id) {

            RunData runData = new RunData();

            int position = (int) run_date.getTag();
            runData.run_id = mRunID;
            runData.collect_address = getStringOrEmpty(collectAddressACTV);
            runData.delivery_address = getStringOrEmpty(deliveryAddressACTV);

            if (!(runData.collect_address.isEmpty() && runData.delivery_address.isEmpty())){
                // try to update, if success update recycler.
                if (runDbHelper.updateRun(runData, context)){
                    // atualiza o recyclerview
                    dataList.remove(position);
                    notifyItemRemoved(position);
                    dataList.add(position,runData);
                    notifyItemRangeChanged(position, dataList.size());
                    notifyItemInserted(position);

                } else {
                    Toast.makeText(context, "No record updated", Toast.LENGTH_SHORT).show();
                }
            } else {
                Toast.makeText(context, "Inform at least one direction", Toast.LENGTH_SHORT).show();
            }
        }

    });

    editRunDialog.setNegativeButton(R.string.button_negative, new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int whichButton) {
            dialog.cancel();
        }
    });

    editRunDialog.create();
    editRunDialog.show();
}

private String getStringOrEmpty(EditText editText) {
    String mString = editText.getText().toString();
    mString = (mString.isEmpty() ? "" : mString);
    return mString;
}

class RecyclerViewHolder extends RecyclerView.ViewHolder {
    TextView runID, collectAddress, deliveryAddress;
    ImageView ivEdit;

    RecyclerViewHolder(View rowView) {
        super(rowView);
        runID = (TextView) rowView.findViewById(R.id.runId);
        collectAddress = (TextView) rowView.findViewById(R.id.collectAddress);
        deliveryAddress = (TextView) rowView.findViewById(R.id.deliveryAddress);
        ivEdit = (ImageView) rowView.findViewById(R.id.ivEdit);
    }
}
}

When I try to add the code to instantiate the GoogleApiClient inside the reclycler adapter, at onCreateViewHolder(ViewGroup parent, int viewType), Android Studio wont let me. I cannot find the right reference for enableAutoManage

            mGoogleApiClient = new GoogleApiClient.Builder(context)
                    .addApi(Places.GEO_DATA_API)
                    .enableAutoManage(HEREISTHEERROR, GOOGLE_API_CLIENT_ID, this) // nada do que tentei colocar aqui no lugar do XXXXXXX funcionou!
                    .addConnectionCallbacks(this)
                    .build();

In summary, what I want accomplished is to receive suggestions when adding or editing directions data into forms that are shown inside alertDialog.

Must use Google Places API for android, hence GoogleApiClient.

Not sure that this is the right approach, so... Any suggestions or workarounds are welcome.


Solution

  • I have solved the question.

    There can be only one GoogleApiClient with enableAutoManage(). The other calls to the API have to deal with life cicle.

    Accessing Google APIs