Android : Around me Tutorial

android-256This android tutorial deals about creating an “Around me” screen for your application. “Around me” shows near places around you using your current location. There are different possibilities to retrieve the near points.

The first one is to use Google Places API. You can use it for common places but it may not fit your needs :

The conclusion may be for you : DIY (do it yourself) and this is precisely the goal of this tutorial. I will show you two possible solutions :

Look at the video below to see the result of the tutorial :

Test before
If you wish to test before, you can install the application with the following link. The whole source code of the project is also available on github.

logo-app
Your app idea
Michenux
Free   
pulsante-google-play-store
pulsante-appbrain
qrcode-app

Let’s start coding !

Place bean

The first thing to do is to create the Place class. The distance attribute contains the distance between the place and the user location.

public class Place {

    private long id;
    private String name;
    private String country;
    private String image;
    private Location location;
    private float distance;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Location getLocation() {
        return location;
    }

    public void setLocation(Location location) {
        this.location = location;
    }

    public float getDistance() {
        return distance;
    }

    public void setDistance(float distance) {
        this.distance = distance;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    public String getImage() {
        return image;
    }

    public void setImage(String image) {
        this.image = image;
    }
}

PlaceProvider

Then, we need a service to retrieve the near places. This is the role of the interface PlaceProvider. You will see later about the two implementations : PlaceLocalProvider and PlaceRemoteProvider. The interface has a method onLocationChanged that must called when the user location changes. The method onPlaceLoadFinished of the callback must be invoked by the PlaceProvider implementation when the places are loaded.

public interface PlaceProvider {

    public void onLocationChanged(Location location);

    public void onDestroy();

    public interface PlaceLoaderCallback {
        public void onPlaceLoadFinished( List<Place> places );
    }
}

Layouts

You will need two layouts : one for the list, and one for the list item. In the first layout, we will display information about the user location : city and country and below, the list of the near places. In the layout of the list item, we will display the name of the place, the country, a image, and the distance from the user location.

aroundme_fragment.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:id="@+id/donation_content"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:padding="15dp"
   xmlns:tools="http://schemas.android.com/tools">

    <TextView
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:textAppearance="?android:attr/textAppearanceMedium"
       android:text="@string/aroundme_nolocation"
       android:id="@+id/aroundme_cityname"
       android:layout_alignParentTop="true"
       android:layout_centerHorizontal="true"
       android:layout_marginBottom="15dp"/>

    <ListView
       android:id="@+id/aroundme_listview"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:layout_below="@+id/aroundme_cityname"
       tools:listitem="@layout/aroundme_listitem"/>

</RelativeLayout>

aroundme_listitem.xml

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:orientation="vertical" android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:paddingBottom="5dp"
   android:paddingLeft="5dp"
   android:paddingTop="5dp"
   xmlns:tools="http://schemas.android.com/tools">

    <ImageView
       android:id="@+id/aroundme_placeimage"
       android:layout_alignParentLeft="true"
       android:layout_alignParentTop="true"
       android:layout_centerVertical="true"
       android:adjustViewBounds="true"
       android:scaleType="centerCrop"
       android:layout_width="120dp"
       android:layout_height="80dp"
       android:layout_marginRight="10dp"/>

    <TextView
       android:id="@+id/aroundme_placename"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_toRightOf="@id/aroundme_placeimage"
       android:gravity="center_vertical"
       android:textIsSelectable="true"
       android:textSize="18dp"
       tools:text="Place">
    </TextView>

    <TextView
       android:id="@+id/aroundme_placecountry"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_toRightOf="@id/aroundme_placeimage"
       android:layout_below="@id/aroundme_placename"
       android:gravity="right|center_vertical"
       android:textIsSelectable="true"
       android:textSize="13dp"
       tools:text="country">
    </TextView>

    <TextView
       android:id="@+id/aroundme_placedistance"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_below="@id/aroundme_placecountry"
       android:layout_marginRight="10px"
       android:layout_toLeftOf="@id/aroundme_placename"
       android:gravity="center_vertical"
       android:textColor="#0099CC"
       android:textIsSelectable="true"
       android:textSize="18dp"
       android:layout_alignParentRight="true"
       tools:text="distance">
    </TextView>

</RelativeLayout>

PlaceListAdapter

As we have a ListView, we need the adapter for it. There is nothing special except one thing to notice : i have used the excellent library Android-Universal-Image-Loader to load the images over the network. So, you have to download it and configure it into your project.

public class PlaceListAdapter extends ArrayAdapter<Place> {

    private List<Place> mPlaces;

    public PlaceListAdapter(Context context, int textViewResourceId, List<Place> objects) {
        super(context, textViewResourceId, objects);
        mPlaces = objects;
    }

    public View getView(int position, View view, ViewGroup viewGroup) {

        View updateView;
        ViewHolder viewHolder;
        if (view == null) {
            LayoutInflater inflater = LayoutInflater.from(getContext());
            updateView = inflater.inflate(R.layout.aroundme_listitem, null);

            viewHolder = new ViewHolder();

            //Name
            viewHolder.placeNameView = (TextView) updateView
                    .findViewById(R.id.aroundme_placename);
            //Distance
            viewHolder.placeDistanceView = (TextView) updateView
                    .findViewById(R.id.aroundme_placedistance);

            //Country
            viewHolder.placeCountryView = (TextView) updateView
                    .findViewById(R.id.aroundme_placecountry);

            //Url
            viewHolder.placeImageView = (ImageView) updateView
                    .findViewById(R.id.aroundme_placeimage);

            updateView.setTag(viewHolder);
        } else {
            updateView = view;
            viewHolder = (ViewHolder) updateView.getTag();
        }

        Place place = getItem(position);
        viewHolder.placeNameView.setText(place.getName());
        viewHolder.placeCountryView.setText(place.getCountry());
        viewHolder.placeDistanceView.setText(this.getContext().getString(R.string.aroundme_placedistance, (int) (place.getDistance() / 1000)));
        ImageLoader.getInstance().displayImage(place.getImage(), viewHolder.placeImageView);

        return updateView;
    }

    private static class ViewHolder {
        public TextView placeNameView;
        public TextView placeDistanceView;
        public TextView placeCountryView;
        public ImageView placeImageView;
    }
}

Fragment

At least but not the less, here’s the code of the fragment that you will have to include into your project.
There are severals things to notice :

The sequency is the following :

  1. Google Play Services invokes the method onLocationChanged of the fragment when user location has changed.
  2. From the new location, the real address is updated using the Geocoder class.
  3. From the new location, the onLocationChanged of the PlaceProvider is invoked to get the new near places.
  4. The fragment is passed as the callback of the PlaceProvider (implements PlaceLocalProvider.PlaceLoaderCallback). The method onPlaceLoadFinished is invoked when new near places are available, so, in this method, we set up the new data in the list adapter, and notify it that its data has changed.
  5. /ol>

    public class AroundMeFragment extends Fragment implements GooglePlayServicesClient.ConnectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener, LocationListener, PlaceLocalProvider.PlaceLoaderCallback {

        private LocationClient mLocationClient;

        private LocationRequest mRequest;

        private Geocoder mGeocoder;

        private PlaceListAdapter mPlaceListAdapter;

        private String mCityName;

        private PlaceProvider mPlaceProvider ;

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            ((YourApplication) getActivity().getApplication()).inject(this);
            setRetainInstance(true);

            mLocationClient = new LocationClient(this.getActivity().getApplicationContext(), this, this);
            mRequest = LocationRequest.create()
                    .setInterval(15000)
                    .setFastestInterval(5000)
                    .setSmallestDisplacement(50)
                    .setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);

            mGeocoder = new Geocoder(this.getActivity(), Locale.getDefault());

            mPlaceListAdapter = new PlaceListAdapter(getActivity(), R.id.aroundme_placename, new ArrayList<Place>());
            if (savedInstanceState != null) {
                this.mCityName = savedInstanceState.getString("cityName");
            }

            mPlaceProvider = new PlaceLocalProvider(this, this);
        }

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

            ListView listView = (ListView) view.findViewById(R.id.aroundme_listview);
            listView.setAdapter(this.mPlaceListAdapter);
            listView.setOnScrollListener(new PauseOnScrollListener(ImageLoader.getInstance(), false, true));

            if (mCityName != null) {
                TextView textView = (TextView) view.findViewById(R.id.aroundme_cityname);
                textView.setText(mCityName);
            }

            return view ;
        }

        @Override
        public void onResume() {
            super.onResume();

            int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(getActivity());
            if (resultCode == ConnectionResult.SUCCESS){
                if (!mLocationClient.isConnected()) {
                    mLocationClient.connect();
                }
            } else{
                GooglePlayServicesUtil.getErrorDialog(resultCode, this.getActivity(), 1 ).show();
            }
        }

        @Override
        public void onPause() {
            super.onPause();
            if ( mLocationClient.isConnected()) {
                mLocationClient.removeLocationUpdates(this);
                mLocationClient.disconnect();
            }
        }

        @Override
        public void onSaveInstanceState(Bundle outState) {
            super.onSaveInstanceState(outState);
            outState.putString("cityName", this.mCityName);
        }

        @Override
        public void onConnected(Bundle bundle) {
            mLocationClient.requestLocationUpdates(mRequest, this);
        }

        @Override
        public void onDisconnected() {
            mLocationClient.removeLocationUpdates(this);
        }

        @Override
        public void onLocationChanged(Location location) {
            if (BuildConfig.DEBUG) {
                Log.d(YourApplication.LOG_TAG, "AroundmeFragment.onLocationChanged() - new loc: " + location);
            }

            try {
                List<Address> addresses = mGeocoder.getFromLocation(location.getLatitude(), location.getLongitude(), 1);

                if(addresses != null && !addresses.isEmpty()) {
                    Address address = addresses.get(0);
                    this.mCityName = address.getLocality() + " (" + address.getCountryName() + ")";
                    TextView textView = (TextView) getView().findViewById(R.id.aroundme_cityname);
                    textView.setText(this.mCityName);
                }

                this.mPlaceProvider.onLocationChanged( location );
            } catch( Exception e ) {
                Log.e(YourApplication.LOG_TAG, "AroundmeFragment.onLocationChanged()", e );
            }
        }

        @Override
        public void onConnectionFailed(ConnectionResult connectionResult) {
            Log.e(YourApplication.LOG_TAG, "AroundmeFragment.onConnectionFailed()", e );
        }

        @Override
        public void onPlaceLoadFinished(List<Place> places) {
            this.mPlaceListAdapter.clear();
            for( Place place : places ) {
                mPlaceListAdapter.add(place);
            }
            this.mPlaceListAdapter.notifyDataSetChanged();
        }

        @Override
        public void onDestroy() {
            mPlaceProvider.onDestroy();
            super.onDestroy();
        }
    }

    The first step is almost done. Include the fragment into your project and use the FragmentManager to display it. I will not explain about that as it is not the purpose of the tutorial. You can find a complete example in the github of the Your App Idea project.

    You can continue with the following tutorials :

    Share Button

    Comments

    Leave a Reply