ZOFTINO.COM android and web dev tutorials

Current Location and Nearby Places Android Example

If you want to provide a feature in your app that should display a list of places such as banks, hospitals, business, parks, transport, etc. near the current location of user device, then you need to use Google places API for android to implement it.

Google places API provides places data from Google database which is used for Google maps. Place represents any geographic locations, point of interests which exist on the map like schools, administrative areas, zoos, lakes, any businesses, etc. You can find list of place types at Place.

Places API consists of PlacePicker and Autocomplete UI widgets and GeoDataClient and PlaceDetectionClient APIs. In this example, we will use PlaceDetectionClient API to get list of nearby places based on device current location.

In this post, you can learn setting up your android project for using Google places API for android, getting list of places near current location of device, displaying list of places in recycler view with each place item containing details such as name, address, phone, website URI, etc and showing a selected place on the Google map.

Project Setup

To use Google places API in your project, first you need to make sure that project level build.gradle file contains google() repository entry. Then add places library to your app level build.gradle file.

implementation 'com.google.android.gms:play-services-places:11.8.0' 

Creating API Key

API key needs to created by following below steps and be added to android manifest xml file.

First login to Google developer console and click project drop down to select existing project or create a new project.

google developer console dashboard

Then select project or create new project by clicking the button highlighted in the below picture.

google developer console create or select project

Then, click ENABLE APIS AND SERVICES link on dashboard, highlighted in the first screen above.

Then search for Google places API for Android, click the API and then click enable button in the next screen that appears.

Then in the left navigation, click credentials, then click create credentials drop down and then select api key to create key.

google api create credentials

Then in the next window, click restrict key button to add restrictions on the key.

google api key restrict key

Then in the restrictions section, select android apps, enter SHA1 of your app’s digital certificate, package name and save it. For information on creating SHA1 of digital certificate, please see Generating key hashes tutorials.

google api key restriction android apps

After saving the restriction, copy the key from the next screen and add it to manifest file in your android project.

 <meta-data
android:name="com.google.android.geo.API_KEY"
android:value="AIzklsajdflkajfasklaaklsfdjsakc"/> 

Nearby Places Android Example

Now let’s see how to use places API to get list of nearby places based on user’s current location, display them in recycler view and show the selected place on Google maps.

Using the Places API requires ACCESS_FINE_LOCATION permission, so you need to request that in your activity.

In this example, since we are using Google maps, you need to add Google maps library dependency to gradle.build file. You also need to enable Google maps API in Google developers. To know more details about using Google maps in android, please see Google maps for android tutorial and custom info window on Google maps example.

Now let’s see how to get places based on current device location. For that, you need to instantiate PlaceDetectionClient object by calling getPlaceDetectionClient method on Places class. Then call getCurrentPlace method on PlaceDetectionClient , then add OnCompleteListener listener to resulting task and retrieve places from PlaceLikelihoodBufferResponse response object as shown below.

 PlaceDetectionClient placeDetectionClient = Places.
		getPlaceDetectionClient(this, null);

Task<PlaceLikelihoodBufferResponse> placeResult = placeDetectionClient.
getCurrentPlace(null);
placeResult.addOnCompleteListener(new OnCompleteListener<PlaceLikelihoodBufferResponse>() {
	@Override
	public void onComplete(@NonNull Task<PlaceLikelihoodBufferResponse> task) {
		Log.d(TAG, "current location places info");
		List<Place> placesList = new ArrayList<Place>();
		PlaceLikelihoodBufferResponse likelyPlaces = task.getResult();
		for (PlaceLikelihood placeLikelihood : likelyPlaces) {
			placesList.add(placeLikelihood.getPlace().freeze());
		}
		likelyPlaces.release();

	}
}); 

Nearby Places Example Output

Below screen shows list of places near current location.

Google play services nearby places current location android example

Below screen shows a selected place on the Google map

 Google play services nearby places current location on google map android example

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.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.location.places.GeoDataClient;
import com.google.android.gms.location.places.Place;
import com.google.android.gms.location.places.PlaceDetectionClient;
import com.google.android.gms.location.places.PlaceLikelihood;
import com.google.android.gms.location.places.PlaceLikelihoodBufferResponse;
import com.google.android.gms.location.places.Places;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;

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

public class CurrentLocationNearByPlacesActivity extends AppCompatActivity {

    public static final String TAG = "CurrentLocNearByPlaces";
    private static final int LOC_REQ_CODE = 1;

    protected GeoDataClient geoDataClient;
    protected PlaceDetectionClient placeDetectionClient;
    protected RecyclerView recyclerView;

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

        Toolbar tb = findViewById(R.id.toolbar);
        setSupportActionBar(tb);
        tb.setSubtitle("Near By Places");

        recyclerView = findViewById(R.id.places_lst);

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

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

        placeDetectionClient = Places.getPlaceDetectionClient(this, null);

        getCurrentPlaceItems();

    }

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

    @SuppressLint("MissingPermission")
    private void getCurrentPlaceData() {
        Task<PlaceLikelihoodBufferResponse> placeResult = placeDetectionClient.
                getCurrentPlace(null);
        placeResult.addOnCompleteListener(new OnCompleteListener<PlaceLikelihoodBufferResponse>() {
            @Override
            public void onComplete(@NonNull Task<PlaceLikelihoodBufferResponse> task) {
                Log.d(TAG, "current location places info");
                List<Place> placesList = new ArrayList<Place>();
                PlaceLikelihoodBufferResponse likelyPlaces = task.getResult();
                for (PlaceLikelihood placeLikelihood : likelyPlaces) {
                    placesList.add(placeLikelihood.getPlace().freeze());
                }
                likelyPlaces.release();

                PlacesRecyclerViewAdapter recyclerViewAdapter = new
                        PlacesRecyclerViewAdapter(placesList,
                        CurrentLocationNearByPlacesActivity.this);
                recyclerView.setAdapter(recyclerViewAdapter);
            }
        });
    }

    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) {
                getCurrentPlaceData();
            }
        }
    }

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

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.view_places_m:
                getCurrentPlaceItems();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }
}

Activity 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"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>
    <android.support.v7.widget.RecyclerView
        android:id="@+id/places_lst"
        android:scrollbars="vertical"
        android:layout_width="match_parent"
        android:layout_height="250dp"
        android:layout_marginTop="8dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/toolbar"/>
    <FrameLayout
        android:id="@+id/map_frame"
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:layout_marginTop="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginStart="8dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/places_lst"/>
</android.support.constraint.ConstraintLayout>

RecyclerView Adapter

import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.FragmentManager;
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.RatingBar;
import android.widget.TextView;

import com.google.android.gms.location.places.Place;

import java.util.List;

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

    private List<Place> placesList;
    private Context context;

    public PlacesRecyclerViewAdapter(List<Place> list, Context ctx) {
        placesList = list;
        context = ctx;
    }
    @Override
    public int getItemCount() {
        return placesList.size();
    }

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

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

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

    @Override
    public void onBindViewHolder(PlacesRecyclerViewAdapter.ViewHolder holder, int position) {
        final int itemPos = position;
        final Place place = placesList.get(position);
        holder.name.setText(place.getName());
        holder.address.setText(place.getAddress());
        holder.phone.setText(place.getPhoneNumber());
        if(place.getWebsiteUri() != null){
            holder.website.setText(place.getWebsiteUri().toString());
        }

       if(place.getRating() > -1){
           holder.ratingBar.setNumStars((int)place.getRating());
       }else{
           holder.ratingBar.setVisibility(View.GONE);
       }

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

    }

    public class ViewHolder extends RecyclerView.ViewHolder {

        public TextView name;
        public TextView address;
        public TextView phone;
        public TextView website;
        public RatingBar ratingBar;

        public Button viewOnMap;

        public ViewHolder(View view) {

            super(view);

            name = view.findViewById(R.id.name);
            address = view.findViewById(R.id.address);
            phone = view.findViewById(R.id.phone);
            website = view.findViewById(R.id.website);
            ratingBar = view.findViewById(R.id.rating);

            viewOnMap = view.findViewById(R.id.view_map_b);
        }
    }

    private void showOnMap(Place place){
        FragmentManager fm = ((CurrentLocationNearByPlacesActivity)context)
                   .getSupportFragmentManager();

        Bundle bundle=new Bundle();
        bundle.putString("name", (String)place.getName());
        bundle.putString("address", (String)place.getAddress());
        bundle.putDouble("lat", place.getLatLng().latitude);
        bundle.putDouble("lng", place.getLatLng().longitude);

        PlaceOnMapFragment placeFragment = new PlaceOnMapFragment();
        placeFragment.setArguments(bundle);

        fm.beginTransaction().replace(R.id.map_frame, placeFragment).commit();
    }
}

Places 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.CurrentLocationNearByPlacesActivity">
    <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"/>
    <TextView
        android:id="@+id/phone_l"
        android:layout_marginTop="4dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="@style/TextAppearance.AppCompat.Medium"
        android:text="Phone: "
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/phone"
        app:layout_constraintTop_toBottomOf="@+id/address" />
    <TextView
        android:id="@+id/phone"
        android:layout_marginTop="4dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintLeft_toRightOf="@+id/phone_l"
        app:layout_constraintTop_toBottomOf="@+id/address" />
    <TextView
        android:id="@+id/website_l"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="@style/TextAppearance.AppCompat.Medium"
        android:text="Website: "
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/website"
        app:layout_constraintTop_toBottomOf="@+id/phone" />
    <TextView
        android:id="@+id/website"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintLeft_toRightOf="@+id/website_l"
        app:layout_constraintTop_toBottomOf="@+id/phone" />
    <RatingBar
        android:id="@+id/rating"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:numStars="5"
        android:isIndicator="true"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/website"/>
    <Button
        android:id="@+id/view_map_b"
        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 On Map"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/rating"/>
</android.support.constraint.ConstraintLayout>

Map Fragment

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;

public class PlaceOnMapFragment  extends Fragment implements OnMapReadyCallback {

    private GoogleMap mMap;
    private double lng;
    private double lat;
    private String name;
    private String address;

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

        if(getArguments() != null){
           lng = getArguments().getDouble("lng");
           lat = getArguments().getDouble("lat");
            name = getArguments().getString("name");
            address = getArguments().getString("address");
        }

        SupportMapFragment mapFragment = (SupportMapFragment) getChildFragmentManager()
                .findFragmentById(R.id.gmap);
       mapFragment.getMapAsync(this);

        return view;
    }


    @Override
    public void onMapReady(GoogleMap googleMap) {
        mMap = googleMap;

        mMap.getUiSettings().setZoomControlsEnabled(true);
        mMap.setMinZoomPreference(11);

        LatLng placeLoc = new LatLng(lat, lng);

        MarkerOptions markerOptions = new MarkerOptions();
        markerOptions.position(placeLoc)
                .title(name)
                .snippet(address)
                .icon(BitmapDescriptorFactory.defaultMarker( BitmapDescriptorFactory.HUE_BLUE));

        Marker m = mMap.addMarker(markerOptions);
        mMap.moveCamera(CameraUpdateFactory.newLatLng(placeLoc));
    }
}

Map Fragment 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=".CurrentLocationNearByPlacesActivity">
    <fragment
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/gmap"
        android:name="com.google.android.gms.maps.SupportMapFragment"/>
</LinearLayout>

AndroidManifest XML

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="zoftino.com.places">
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".CurrentLocationNearByPlacesActivity"></activity>
        <meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="UUIUIUIUIIIIIIIIIIIIIIIIII"/>
        <meta-data
            android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />
    </application>
</manifest>

Displaying Attributes

It is required to display certain attributes to comply with policies and terms of using Google places API. You need to show Powered by Google attribution wherever you display data returned by Google places API. For more information, see Google places API attributions.