ZOFTINO.COM android and web dev tutorials

Google Places API Place Picker Android Example

Using Google place API, you can find places near a location and get place details such as name, address, phone, etc. Google place API contains PlacePicker and Autocomplete UI widgets and GeoDataClient and PlaceDetectionClient APIs. In this post, you can learn how to use PlacePicker UI widget with place notes example Android app. PlacePicker UI widget displays places near a location on Google map and allows user to select a place.

To know how to get list of places near current location of device, please see nearby places PlaceDetectionClient API example.

To use Google places API, you need to create a project in Google developer console, enable API, create Key, add key to android manifest xml file and add libraries to build.gradle files, for detailed instructions on how to setup your for project to use Places API, please see nearby places Android example.

Place Picker Android Example

The example app first lets user pick a location by using place picker. Then it allows user to enter notes for the selected place and save it. View button in menu lets user to view list of save places and notes.

To use place picker, instantiate PlacePicker.IntentBuilder object optionally passing LatLngBounds to set location for the map to display. If LatLngBounds is not passed to the builder, place picker will show current device locations. Then start place picker activity by calling startActivityForResult passing the intent create using PlacePicker.IntentBuilder.

 LatLngBounds latLngBounds = new LatLngBounds(new LatLng(47.64299816, -122.14351988),
        new LatLng(47.64299816, -122.14351988));
PlacePicker.IntentBuilder builder = new PlacePicker.IntentBuilder();
builder.setLatLngBounds(latLngBounds);

try {
    startActivityForResult(builder.build(this), PLACE_PICKER_REQ_CODE);
} catch (Exception e) {
    Log.e(TAG, e.getStackTrace().toString());
} 

After the above step, place picker will show Google map as shown below.

android place picker place note app pick place

You can drag up the nearby places list to view complete places list as shown below.

 android place picker place note app pick place nearby places list

You can select a place either on the map or from the list. After selecting a place, place picker will show a confirmation screen as shown below

android place picker place note app pick place confirm selection

After you click select on the above screen, the selected place can be captured in onActivityResult method in the activity.

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == LOC_REQ_CODE) {
        if (resultCode == RESULT_OK) {
            showPlacePicker();
        }
    }else if(requestCode == PLACE_PICKER_REQ_CODE){
        if (resultCode == RESULT_OK) {
            place = PlacePicker.getPlace(this, data);
            name.setText(place.getName());
        }
    }
}

After place picker flow is complete, screen will display activity layout. Our example layout displays the selected place name and allows user to enter notes. On submitting save, the place details and notes will be saved in Firestore database.

place picker place notes android app add notes to place

There is a menu action that will start an activity to view place notes in recycler view.

place picker place notes android app view saved place list

Activity

import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.gms.location.places.Place;
import com.google.android.gms.location.places.ui.PlacePicker;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.firebase.firestore.DocumentReference;
import com.google.firebase.firestore.FirebaseFirestore;

import java.text.SimpleDateFormat;


public class PlacePickerActivity extends AppCompatActivity {

    public static final String TAG = "PlacePickerActivity";
    private static final int LOC_REQ_CODE = 1;
    private static final int PLACE_PICKER_REQ_CODE = 2;

    private FirebaseFirestore firestoreDB;
    private Place place;

    private TextView name;
    private EditText notes;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.place_notes);

        Toolbar tb = findViewById(R.id.toolbar);
        setSupportActionBar(tb);
        tb.setSubtitle("Place Picker");

        name = findViewById(R.id.place_name);
        notes = findViewById(R.id.notes);

        findViewById(R.id.save_notes_b).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                savePlaceNotes();
            }
        });

        firestoreDB = FirebaseFirestore.getInstance();
        getCurrentPlaceItems();

    }

    private void getCurrentPlaceItems() {
        if (isLocationAccessPermitted()) {
            showPlacePicker();
        } else {
            requestLocationAccessPermission();
        }
    }

    @SuppressLint("MissingPermission")
    private void showPlacePicker() {        
        PlacePicker.IntentBuilder builder = new PlacePicker.IntentBuilder();
        try {
            startActivityForResult(builder.build(this), PLACE_PICKER_REQ_CODE);
        } catch (Exception e) {
            Log.e(TAG, e.getStackTrace().toString());
        }
    }

    private boolean isLocationAccessPermitted() {
        if (ContextCompat.checkSelfPermission(this,
                Manifest.permission.ACCESS_FINE_LOCATION)
                != PackageManager.PERMISSION_GRANTED) {
            return false;
        } else {
            return true;
        }
    }

    private void requestLocationAccessPermission() {
        ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                LOC_REQ_CODE);
    }

    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == LOC_REQ_CODE) {
            if (resultCode == RESULT_OK) {
                showPlacePicker();
            }
        }else if(requestCode == PLACE_PICKER_REQ_CODE){
            if (resultCode == RESULT_OK) {
                place = PlacePicker.getPlace(this, data);
                name.setText(place.getName());
            }
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.place_notes_menu, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.add_place_notes_m:
                showPlacePicker();
                return true;
            case R.id.view_place_notes_m:
                viewPlaceNotes();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }
    private void savePlaceNotes(){
        PlaceNotes placeNotes = new PlaceNotes();
        placeNotes.setPlaceName(place.getName().toString());
        placeNotes.setAddress(place.getAddress().toString());
        placeNotes.setNotes(notes.getText().toString());
        placeNotes.setLatitude(place.getLatLng().latitude);
        placeNotes.setLongitude(place.getLatLng().longitude);

        SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy");
        String date = formatter.format(System.currentTimeMillis());
        placeNotes.setDate(date);
        addPlaceNotesToCollection(placeNotes);
    }
    private void addPlaceNotesToCollection(PlaceNotes placeNotes){
        firestoreDB.collection("placeNotes")
                .add(placeNotes)
                .addOnSuccessListener(new OnSuccessListener<DocumentReference>() {
                    @Override
                    public void onSuccess(DocumentReference documentReference) {
                        Log.d(TAG, "document added - id "
                                + documentReference.getId());
                        Toast.makeText(PlacePickerActivity.this,
                                "Place notes added to db",
                                Toast.LENGTH_SHORT).show();
                        viewPlaceNotes();
                    }
                })
                .addOnFailureListener(new OnFailureListener() {
                    @Override
                    public void onFailure(@NonNull Exception e) {
                        Log.w(TAG, "Error adding place notes to db", e);
                        Toast.makeText(PlacePickerActivity.this,
                                "Error adding place notes to db",
                                Toast.LENGTH_SHORT).show();
                    }
                });
    }
    public void viewPlaceNotes(){
        Intent i = new Intent();
        i.setClass(this, ViewPlaceNotesActivity.class);
        startActivity(i);
    }
}

Activity Layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".PlacePickerActivity">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary" />

    <android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp">

        <TextView
            android:id="@+id/place_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:textAppearance="@style/TextAppearance.AppCompat.Large"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <EditText
            android:id="@+id/notes"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="top|left"
            android:hint="Enter notes"
            android:inputType="text|textMultiLine"
            android:lines="8"
            android:scrollHorizontally="false"
            android:scrollbars="vertical"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/place_name" />

        <Button
            android:id="@+id/save_notes_b"
            style="@style/Widget.AppCompat.Button.Colored"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Save Place Notes"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/notes" />
    </android.support.constraint.ConstraintLayout>
</LinearLayout>

View Activity

import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;

import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.QuerySnapshot;

import java.util.ArrayList;
import java.util.List;

public class ViewPlaceNotesActivity extends AppCompatActivity {

    public static final String TAG = "ViewPlaceNotesActivity";
    private FirebaseFirestore firestoreDB;
    private RecyclerView recyclerView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.view_place_notes);

        Toolbar tb = findViewById(R.id.toolbar);
        setSupportActionBar(tb);
        tb.setSubtitle("View Place Notes");

        firestoreDB = FirebaseFirestore.getInstance();

        recyclerView = findViewById(R.id.place_notes_lst);

        LinearLayoutManager recyclerLayoutManager =
                new LinearLayoutManager(this);
        recyclerView.setLayoutManager(recyclerLayoutManager);

        DividerItemDecoration dividerItemDecoration =
                new DividerItemDecoration(recyclerView.getContext(),
                        recyclerLayoutManager.getOrientation());
        recyclerView.addItemDecoration(dividerItemDecoration);

        getPlaceNotesFromDb();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.place_notes_menu, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.add_place_notes_m:
                showPlacePicker();
                return true;
            case R.id.view_place_notes_m:
                getPlaceNotesFromDb();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }
    private void getPlaceNotesFromDb() {
        firestoreDB.collection("placeNotes")
                .get()
                .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
                    @Override
                    public void onComplete(@NonNull Task<QuerySnapshot> task) {
                        if (task.isSuccessful()) {
                            List<PlaceNotes> placeNotesList = new ArrayList<PlaceNotes>();
                            Log.d(TAG, "results size "+task.getResult().size());
                            for(DocumentSnapshot doc : task.getResult()){
                                placeNotesList.add(doc.toObject(PlaceNotes.class));
                            }
                            PlaceNotesRecyclerViewAdapter recyclerViewAdapter = new
                                    PlaceNotesRecyclerViewAdapter(placeNotesList,
                                    ViewPlaceNotesActivity.this);
                            recyclerView.setAdapter(recyclerViewAdapter);

                        } else {
                            Log.d(TAG, "Error getting notes ", task.getException());
                        }
                    }
                });
    }
    public void showPlacePicker(){
        Intent i = new Intent();
        i.setClass(this, PlacePickerActivity.class);
        startActivity(i);
    }
}

View Activity Layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".ViewPlaceNotesActivity">
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"/>
    <android.support.v7.widget.RecyclerView
        android:id="@+id/place_notes_lst"
        android:scrollbars="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"/>
</LinearLayout>

Recycler View Adapter

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;

import java.util.List;

public class PlaceNotesRecyclerViewAdapter extends
        RecyclerView.Adapter<PlaceNotesRecyclerViewAdapter.ViewHolder> {

    private List<PlaceNotes> placeNotesList;
    private Context context;

    public PlaceNotesRecyclerViewAdapter(List<PlaceNotes> list, Context ctx) {
        placeNotesList = list;
        context = ctx;
    }
    @Override
    public int getItemCount() {
        return placeNotesList.size();
    }

    @Override
    public PlaceNotesRecyclerViewAdapter.ViewHolder
    onCreateViewHolder(ViewGroup parent, int viewType) {

        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.place_notes_item, parent, false);

        PlaceNotesRecyclerViewAdapter.ViewHolder viewHolder =
                new PlaceNotesRecyclerViewAdapter.ViewHolder(view);
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(PlaceNotesRecyclerViewAdapter.ViewHolder holder, int position) {
        final int itemPos = position;
        final PlaceNotes place = placeNotesList.get(position);
        holder.name.setText(place.getPlaceName());
        holder.address.setText(place.getAddress());

        holder.viewNotes.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                viewNotes(place);
            }
        });

    }

    public class ViewHolder extends RecyclerView.ViewHolder {

        public TextView name;
        public TextView address;

        public Button viewNotes;

        public ViewHolder(View view) {

            super(view);

            name = view.findViewById(R.id.name);
            address = view.findViewById(R.id.address);

            viewNotes = view.findViewById(R.id.view_notes);
        }
    }

    private void viewNotes(PlaceNotes placeNotes){
        //get notes for the selected item and display in dialog
    }
}

Recycler View Item Layout

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_marginTop="4dp"
    android:layout_margin="8dp"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    tools:context="zoftino.com.places.ViewPlaceNotesActivity">
    <TextView
        android:id="@+id/name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="@style/TextAppearance.AppCompat.Large"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="parent" />
    <TextView
        android:id="@+id/address"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="4dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/name"/>
    <Button
        android:id="@+id/view_notes"
        style="@style/Widget.AppCompat.Button.Colored"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:minHeight="0dp"
        android:minWidth="0dp"
        android:text="View Notes"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/address"/>
</android.support.constraint.ConstraintLayout>

It is required that certain attributes need to be displayed on the screen which shows places data to comply with policies and terms of using Google places API. For more information, see Google places API attributions.