GoogleApiClient in fragments

0

The app in question has one main activity and two fragments.

There are two information panels, one contains the address list and another shows a map showing the addresses of the list.

The list is displayed in RecyclerView , composed of cardviews at each address.

Inserting and updating addresses occurs in two%% s of different% s. But in both I'm going to use a fragment and Google Place API for Android, using a AutoCompleteTextView object.

I did not find examples with fragments, only using GoogleApiClient .

I'm following this tutorial: Android Places API: Autocomplete with getPlaceByID

How do I use this within activity ? Where should fragment instantiation be done?

UPDATE

I tried to follow the tips of Ack Lay, and made some progress, but I ended up messing with the mistakes.

It happens that my code is slightly different from the example. The field, alias, fields, which will receive the suggestions, are not present during GoogleApiClient because they are part of a form. which appears when you click on the FAB defined in this event.

Note that it is already working with onCreateView() , which I adapted from another tutorial. Later, I read that instead of using this API, in Android, it should be used Google Places API Web Service and this is the reason for the change.

import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.Fragment;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.CardView;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AutoCompleteTextView;
import android.widget.EditText;
import android.widget.Toast;

import java.util.List;


public class RecyclerFragment extends Fragment implements AlertDialog.OnClickListener {

private static final String TAG = "RecyclerFragment";
RecyclerView recyclerView;
RunDbHelper runDbHelper;
RecyclerViewAdapter recyclerViewAdapter;
private OnOkButtonListener mCallback;
private CardView cardViewMessageIsEmpty;

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

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

    View view = inflater.inflate(R.layout.fragment_recycler, container, false);
    FloatingActionButton fab = (FloatingActionButton) view.findViewById(R.id.fabAdd);
    cardViewMessageIsEmpty = (CardView) view.findViewById(R.id.emptyMessageCardView);
    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()));

    fab.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            // Data entry dialog to add runs
            dialogInsertRun();
        }
    });

    cardViewMessageIsEmpty.setVisibility((mList == null || mList.isEmpty()) ? View.VISIBLE : View.INVISIBLE);
    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 inflaterView = 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(inflaterView);

    // Data entry field objects
    final EditText runParcelEditText = (EditText) inflaterView.findViewById(R.id.new_run_parcel);
    final AutoCompleteTextView collectAddressACTV = (AutoCompleteTextView) inflaterView.findViewById(R.id.actv_new_collect_address);
    final EditText collectPersonEditText = (EditText) inflaterView.findViewById(R.id.new_collect_person);
    final AutoCompleteTextView deliveryAddressACTV = (AutoCompleteTextView) inflaterView.findViewById(R.id.actv_new_delivery_address);
    final EditText deliveryPersonEditText = (EditText) inflaterView.findViewById(R.id.new_delivery_person);

    // Set directions into recyclerViewAdapter for autocomplete
    collectAddressACTV.setAdapter(new GooglePlacesAutocompleteAdapter(getActivity(), R.layout.dialog_new_run_autocomplete));
    deliveryAddressACTV.setAdapter(new GooglePlacesAutocompleteAdapter(getActivity(), R.layout.dialog_new_run_autocomplete));

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

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

            RunData runData = new RunData();

            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);

            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;
}

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

Fields are Google Places API for Android and collectAddressACTV in method deliveryAddressACTV

Dialog XML

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="@dimen/activity_minimal_distance">

    <android.support.design.widget.TextInputLayout
        android:id="@+id/input_new_run_parcel"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <EditText
            android:id="@+id/new_run_parcel"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:ems="10"
            android:hint="@string/collect_what"
            android:imeOptions="flagNoExtractUi"
            android:inputType="text" />
    </android.support.design.widget.TextInputLayout>

    <android.support.design.widget.TextInputLayout
        android:id="@+id/input_actv_new_collect_address"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <AutoCompleteTextView
            android:id="@+id/actv_new_collect_address"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:ems="10"
            android:hint="@string/collect_where"
            android:imeOptions="actionNext|flagNoExtractUi"
            android:inputType="textPostalAddress"
            android:maxLines="2"></AutoCompleteTextView>
    </android.support.design.widget.TextInputLayout>

    <android.support.design.widget.TextInputLayout
        android:id="@+id/input_new_collect_person"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <EditText
            android:id="@+id/new_collect_person"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:ems="10"
            android:hint="@string/collect_who"
            android:imeOptions="flagNoExtractUi"
            android:inputType="textPersonName" />

    </android.support.design.widget.TextInputLayout>

    <android.support.design.widget.TextInputLayout
        android:id="@+id/input_actv_new_delivery_address"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <AutoCompleteTextView
            android:id="@+id/actv_new_delivery_address"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:ems="10"
            android:hint="@string/delivery_where"
            android:imeOptions="actionNext|flagNoExtractUi"
            android:inputType="text"
            android:maxLines="2">

        </AutoCompleteTextView>
    </android.support.design.widget.TextInputLayout>

    <android.support.design.widget.TextInputLayout
        android:id="@+id/input_new_delivery_person"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <EditText
            android:id="@+id/new_delivery_person"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:ems="10"
            android:hint="@string/delivery_who"
            android:imeOptions="flagNoExtractUi"
            android:inputType="textPersonName" />
    </android.support.design.widget.TextInputLayout>

</LinearLayout>

And this class sets the adapter to autocomplete dialogInsertRun() . It is similar to the link adapter class of the tutorial I mentioned.

public class GooglePlacesAutocompleteAdapter extends ArrayAdapter<String> implements Filterable {

    // autocomplete code
    private static final String PLACES_API_BASE = "https://maps.googleapis.com/maps/api/place";
    private static final String TYPE_AUTOCOMPLETE = "/autocomplete";
    private static final String OUT_JSON = "/json";
    //------------ make your specific key ------------
    private static final String API_KEY = "AIza................................";

    private ArrayList<String> resultList;

    public GooglePlacesAutocompleteAdapter(Context context, int textViewResourceId) {
        super(context, textViewResourceId);
    }

    @Override
    public int getCount() {
        return resultList.size();
    }

    @Override
    public String getItem(int index) {
        return resultList.get(index);
    }

    @Override
    public Filter getFilter() {

        Filter filter = new Filter() {
            @Override
            protected FilterResults performFiltering(CharSequence constraint) {
                FilterResults filterResults = new FilterResults();
                if (constraint != null) {
                    // Retrieve the autocomplete results.
                    resultList = autocomplete(constraint.toString(), getUserCountry(getContext()));

                    // Assign the data to the FilterResults
                    filterResults.values = resultList;
                    filterResults.count = resultList.size();
                }
                return filterResults;
            }

            @Override
            protected void publishResults(CharSequence constraint, FilterResults results) {
                if (results != null && results.count > 0) {
                    notifyDataSetChanged();
                } else {
                    notifyDataSetInvalidated();
                }
            }
        };
        return filter;
    }

    public static ArrayList<String> autocomplete(String input, String countryCode) {
        ArrayList<String> resultList = null;

        HttpURLConnection conn = null;
        StringBuilder jsonResults = new StringBuilder();

        try {
            StringBuilder stringBuilder = new StringBuilder(PLACES_API_BASE + TYPE_AUTOCOMPLETE + OUT_JSON);
            stringBuilder
                    .append("?key=" + API_KEY)
                    .append("&components=country:" + countryCode)
                    .append("&input=" + URLEncoder.encode(input, "utf8"));

            URL url = new URL(stringBuilder.toString());

            System.out.println("URL: " + url);
            conn = (HttpURLConnection) url.openConnection();
            InputStreamReader in = new InputStreamReader(conn.getInputStream());

            // Load the results into a StringBuilder
            int read;
            char[] buff = new char[1024];
            while ((read = in.read(buff)) != -1) {
                jsonResults.append(buff, 0, read);
            }
        } catch (MalformedURLException e) {
//            Log.e(TAG, "Error processing Places API URL", e);
            return resultList;
        } catch (IOException e) {
//            Log.e(TAG, "Error connecting to Places API", e);
            return resultList;
        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }

        try {
            // Create a JSON object hierarchy from the results
            JSONObject jsonObj = new JSONObject(jsonResults.toString());
            JSONArray predsJsonArray = jsonObj.getJSONArray("predictions");

            // Extract the Place descriptions from the results
            resultList = new ArrayList<>(predsJsonArray.length());
            for (int i = 0; i < predsJsonArray.length(); i++) {
                System.out.println(predsJsonArray.getJSONObject(i).getString("description"));
                System.out.println("============================================================");
                resultList.add(predsJsonArray.getJSONObject(i).getString("description"));
            }
        } catch (JSONException e) {
            //Log.e(TAG, "Cannot process JSON results", e);
        }
        return resultList;
    }

    /**
     * Get ISO 3166-1 alpha-2 country code for this device (or null if not available)
     *
     * @param context Context reference to get the TelephonyManager instance from
     * @return country code or null
     */
    @Nullable
    public static String getUserCountry(Context context) {

        try {
            final TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
            final String simCountry = tm.getSimCountryIso();
            if (simCountry != null && simCountry.length() == 2) { // SIM country code is available
                return simCountry.toLowerCase(Locale.US);
            } else if (tm.getPhoneType() != TelephonyManager.PHONE_TYPE_CDMA) { // device is not 3G (would be unreliable)
                String networkCountry = tm.getNetworkCountryIso();
                if (networkCountry != null && networkCountry.length() == 2) { // network country code is available
                    return networkCountry.toLowerCase(Locale.US);
                }
            }
            String localeCountry = context.getResources().getConfiguration().locale.getCountry();
            if (localeCountry != null && localeCountry.length() == 2) {
                return localeCountry.toLowerCase(Locale.US);
            }
        } catch (Exception e) {
            return null;
        }
        return null;
    }
}
    
asked by anonymous 30.01.2017 / 22:42

2 answers

0

It turns out that everything was wrong from the question. The problem is not because the form that receives the GoogleApiClient suggestions is in a fragment, but rather because it is in a dialog.

It was necessary to adjust where objects are declared and instantiated to work.

Then, following the tips of @ Ack-Lay, turning into a fragment was easy.

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

    private static final String TAG = "RecyclerFragment";
    RecyclerView recyclerView;
    RunDbHelper runDbHelper;
    RecyclerViewAdapter recyclerViewAdapter;
    private OnOkButtonListener mCallback;
    private CardView cardViewMessageIsEmpty;

    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));

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

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

        View view = inflater.inflate(R.layout.fragment_recycler, container, false);

        mGoogleApiClient = new GoogleApiClient.Builder(getActivity())
                .addApi(Places.GEO_DATA_API)
                .enableAutoManage(getActivity(), GOOGLE_API_CLIENT_ID, this)
                .addConnectionCallbacks(this)
                .build();

        mPlaceArrayAdapter = new PlaceArrayAdapter(getContext(), android.R.layout.simple_list_item_1,
                BOUNDS_MOUNTAIN_VIEW, 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 add runs
                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();

                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);

                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);
        }
    };

    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;
            }

        }
    };
}
    
01.02.2017 / 13:16
0
  

How do I use this within a fragment?

Just follow the changes below.

In Fragment you will only change where you have this to getActivity() . Another change will be in your views, because instead of using findViewById(R.id...) will add view thus getting view.findViewById(R.id...) . Example:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, 
    Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.layout_fragment, container);

    ....
    mPhoneTextView = (TextView) view.findViewById(R.id.phone);
    ....

    return view;
}

So in your MainActivity , you have to call your Fragment :

 getSupportFragmentManager().beginTransaction()
                .add(R.id.container, new MeuFragment())
                .commit();
  

Where should GoogleApiClient instantiation be done?

Like Activity , Fragment has a life is a state store handling, which is started at the time Fragment is bound to Activity , and is not necessarily visible on the screen. Therefore, the same process is used as in an activity .

See how you got the change in the IDEONE .

    
30.01.2017 / 23:02