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