Friday, 1 July 2016

Android Google Maps With Nearyby Places

Show User Current Location and Load Nearby Places (within 5km radius) using Volley.

Today im gonna show you how to show current location and load nearby places in android.
Previously had some trouble with other google places and maps tutorial coz of complexity and underinformations, but finally, here comes the simplest and cleanest code of all.

Project Structure:


1. Obtaining Api keys; Enabling Google Maps, Places for Android and Google Places Web Service

> We will get two api keys, one for Android Api Key for google maps and another one for Browser Api Key for google maps.
> In that case, we will enable three Apis:
1. Google Maps for Android
2. Google Places for Android
3. Google Places for Web Service


- This the one procedure that is very confusing, more especially when you use a wrong api key and your app fails to load all the nearby places.
* The best way around this is to create an android project with a map activity in android studio, there are some credentials we will need from the app to get the api keys.

i) Create a new android maps activity project and name is GPlaces, do review my previous maps project <Show Current Location> to get it right, because our app will built on top of that.

ii) Pick the api url from the google_maps_api.xml and paste it to the browser and follow the link to create an Android Api Key as shown in my previous maps post.

iii) Don't forget to copy the Android Key you created at the console and paste it to the  google_maps_api.xml


a) Generating the Browser API key

- On your Api Manager, go to Credentials tab from the left panel, All your Api Keys should be enlisted here. In our case, the Android Key that we enabled.



- Click on the Create credentials, which will popup a drop down list.



- Select API key, another popup will appear to prompt you to create a new key,



- Select Browser key, another page will open up, you can choose to rename the key and press Create button to complete. Just leave the other field blank.



- Copy the Api key from the popup that will appear and save it somewhere, will need it too.




- Now in your Credentials, You should have two keys.




b) Enabling Google Places Api for Android and Google Api Web Service  ,

- On your Api Manager, go to Overview tab from the left panel,
- Go to the Google Maps Apis, Click More, to see the Google Places Api for Web Service



- Click on Google Places API Web Service, which will load another page



- Click on the Enable button and go back

- * Follow the above steps to enable Google Places API for Android <Same Procedure>*



>> If you go back to Overview, then click on the Enabled APIs tab, You'll see 3 APIs Enabled



*** That's it, we are done with the Api stuffs, let's head to actual coding ***

2. Android app,

i) Create a New Android Project in android studio and select Scrolling Activity



ii) Import Volley Library; how to import custom library
compile 'com.mcxiaoke.volley:library:1.0.19'

Your build.gradle(Module: App)

apply plugin: 'com.android.application'
android {
    compileSdkVersion 24
    buildToolsVersion "24.0.0"
    defaultConfig {
        applicationId "com.androidmastermind.gplaces"
        minSdkVersion 15
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'),
 'proguard-rules.pro'        }
    }
}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:24.0.0'
    compile 'com.google.android.gms:play-services:9.2.0'
    compile 'com.mcxiaoke.volley:library:1.0.19'
}

iii) Create AppController.java class, this will extend Application. Volley will use this class to make the json request for us.

package com.androidmastermind.gplaces;

import android.app.Application;
import android.text.TextUtils;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.Volley;

import static com.androidmastermind.gplaces.AppConfig.TAG;

public class AppController extends Application {

    private RequestQueue mRequestQueue;
    private static AppController mInstance;

    @Override
    public void onCreate() {
        super.onCreate();
        mInstance = this;

    }

    public static synchronized AppController getInstance() {
        return mInstance;
    }

    public RequestQueue getRequestQueue() {
        if (mRequestQueue == null) {
            mRequestQueue = Volley.newRequestQueue(getApplicationContext());
        }
        return mRequestQueue;
    }

    public <T> void addToRequestQueue(Request<T> req, String tag) {
        req.setTag(TextUtils.isEmpty(tag) ? TAG : tag);
        getRequestQueue().add(req);
    }

    public <T> void addToRequestQueue(Request<T> req) {
        req.setTag(TAG);
        getRequestQueue().add(req);
    }

    public void cancelPendingRequests(Object tag) {
        if (mRequestQueue != null) {
            mRequestQueue.cancelAll(tag);
        }
    }
}

iv) Create another class, AppConfig.java, this will hold all the references that we will need. also the keys for the json that we will have read from google after querying for the nearby places. Will also hold our Browser API key, you;ll paste it here

package com.androidmastermind.gplaces;

/** * Created by sydney on 6/26/2016. */
public final class AppConfig {

    public static final String TAG = "gplaces";

    public static final String RESULTS = "results";
    public static final String STATUS = "status";

    public static final String OK = "OK";
    public static final String ZERO_RESULTS = "ZERO_RESULTS";
    public static final String REQUEST_DENIED = "REQUEST_DENIED";
    public static final String OVER_QUERY_LIMIT = "OVER_QUERY_LIMIT";
    public static final String UNKNOWN_ERROR = "UNKNOWN_ERROR";
    public static final String INVALID_REQUEST = "INVALID_REQUEST";

    //    Key for nearby places json from google
    public static final String GEOMETRY = "geometry";
    public static final String LOCATION = "location";
    public static final String LATITUDE = "lat";
    public static final String LONGITUDE = "lng";
    public static final String ICON = "icon";
    public static final String SUPERMARKET_ID = "id";
    public static final String NAME = "name";
    public static final String PLACE_ID = "place_id";
    public static final String REFERENCE = "reference";
    public static final String VICINITY = "vicinity";
    public static final String PLACE_NAME = "place_name";

    // remember to change the browser api key
    public static final String GOOGLE_BROWSER_API_KEY =
 "AIzaSyBJvlD3dqnz42r9obhEClc2dEJAdXt9IK8";
    public static final int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
    public static final int PROXIMITY_RADIUS = 5000;
    // The minimum distance to change Updates in meters
    public static final long MIN_DISTANCE_CHANGE_FOR_UPDATES = 10; // 10 meters
    // The minimum time between updates in milliseconds
    public static final long MIN_TIME_BW_UPDATES = 1000 * 60 * 1; // 1 minute
}

- If you wanna test your Browser API key now would be the best time, paste the below link on your browser and rem to change the key with your Browser api key. <Its very imperative that you know how the data is obtained, many other maps and places tutorials don't really show the kind of data your expecting>

https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=-1.291863,36.824487&radius=5000&types=grocery_or_supermarket&sensor=true&key=AIzaSyBJvlD3dqnz42r9obhEClc2dEJAdXt9IK8

**** Sample of the query of the nearby places, The above url will show all the supermarkets around Technical University of Kenya within a radius of 5km <its in json format, and thus where volley comes in handy>****

v) The activity_maps.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/mainCoordinatorLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context=".MapsActivity">

    <fragment
        android:id="@+id/map"
        android:name="com.google.android.gms.maps.SupportMapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

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

vi) Finally, lets alter our MapsActivity.java and add some codes for fetching the nearby places and displaying the markers of all the places on the map.

package com.androidmastermind.gplaces;

import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.provider.Settings;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
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.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import static com.androidmastermind.gplaces.AppConfig.*;

public class MapsActivity extends AppCompatActivity implements OnMapReadyCallback,
 LocationListener {

    private GoogleMap mMap;
    LocationManager locationManager;
    CoordinatorLayout mainCoordinatorLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (!isGooglePlayServicesAvailable()) {
            return;
        }
        setContentView(R.layout.activity_maps);

        SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
                .findFragmentById(R.id.map);
        mapFragment.getMapAsync(this);

        mainCoordinatorLayout = (CoordinatorLayout) findViewById(R.id.mainCoordinatorLayout);
        locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
        if (!locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
            showLocationSettings();
        }
    }

    private void showLocationSettings() {
        Snackbar snackbar = Snackbar
                .make(mainCoordinatorLayout, "Location Error: GPS Disabled!",
 Snackbar.LENGTH_LONG)
                .setAction("Enable", new View.OnClickListener() {
                    @Override                    public void onClick(View v) {

                        startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
                    }
                });
        snackbar.setActionTextColor(Color.RED);
        snackbar.setDuration(Snackbar.LENGTH_INDEFINITE);

        View sbView = snackbar.getView();
        TextView textView = (TextView) sbView
.findViewById(android.support.design.R.id.snackbar_text);
        textView.setTextColor(Color.YELLOW);

        snackbar.show();
    }

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

        if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION)
 != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this,
 android.Manifest.permission.ACCESS_COARSE_LOCATION)
 != PackageManager.PERMISSION_GRANTED) {
            return;
        }
        mMap.setMyLocationEnabled(true);
        mMap.getUiSettings().setCompassEnabled(true);
        mMap.getUiSettings().setZoomControlsEnabled(true);

        showCurrentLocation();
    }

    private void showCurrentLocation() {
        Criteria criteria = new Criteria();
        String bestProvider = locationManager.getBestProvider(criteria, true);
        if (ActivityCompat.checkSelfPermission(this,
 android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && 
ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_COARSE_LOCATION)
 != PackageManager.PERMISSION_GRANTED) {
            return;
        }
        Location location = locationManager.getLastKnownLocation(bestProvider);

        if (location != null) {
            onLocationChanged(location);
        }
        locationManager.requestLocationUpdates(bestProvider, MIN_TIME_BW_UPDATES,
 MIN_DISTANCE_CHANGE_FOR_UPDATES, this);
    }

    private void loadNearByPlaces(double latitude, double longitude) {
//YOU Can change this type at your own will, e.g hospital, cafe, restaurant.... and see how it all works
        String type = "grocery_or_supermarket";
        StringBuilder googlePlacesUrl = 
new StringBuilder("https://maps.googleapis.com/maps/api/place/nearbysearch/json?");
        googlePlacesUrl.append("location=").append(latitude).append(",").append(longitude);
        googlePlacesUrl.append("&radius=").append(PROXIMITY_RADIUS);
        googlePlacesUrl.append("&types=").append(type);
        googlePlacesUrl.append("&sensor=true");
        googlePlacesUrl.append("&key=" + GOOGLE_BROWSER_API_KEY);

        JsonObjectRequest request = new JsonObjectRequest(googlePlacesUrl.toString(),
 new Response.Listener<JSONObject>() {
            @Override
            public void onResponse(JSONObject result) {

                Log.i(TAG, "onResponse: Result= " + result.toString());
                parseLocationResult(result);
            }
        },
                new Response.ErrorListener() {
                    @Override                    public void onErrorResponse(VolleyError error) {
                        Log.e(TAG, "onErrorResponse: Error= " + error);
                        Log.e(TAG, "onErrorResponse: Error= " + error.getMessage());
                    }
                });

        AppController.getInstance().addToRequestQueue(request);
    }

    private void parseLocationResult(JSONObject result) {

        String id, place_id, placeName = null, reference, icon, vicinity = null;
        double latitude, longitude;

        try {
            JSONArray jsonArray = result.getJSONArray("results");

            if (result.getString(STATUS).equalsIgnoreCase(OK)) {

                mMap.clear();

                for (int i = 0; i < jsonArray.length(); i++) {
                    JSONObject place = jsonArray.getJSONObject(i);

                    id = place.getString(SUPERMARKET_ID);
                    place_id = place.getString(PLACE_ID);
                    if (!place.isNull(NAME)) {
                        placeName = place.getString(NAME);
                    }
                    if (!place.isNull(VICINITY)) {
                        vicinity = place.getString(VICINITY);
                    }
                    latitude = place.getJSONObject(GEOMETRY).getJSONObject(LOCATION)
.getDouble(LATITUDE);
                    longitude = place.getJSONObject(GEOMETRY).getJSONObject(LOCATION)
.getDouble(LONGITUDE);
                    reference = place.getString(REFERENCE);
                    icon = place.getString(ICON);

                    MarkerOptions markerOptions = new MarkerOptions();
                    LatLng latLng = new LatLng(latitude, longitude);
                    markerOptions.position(latLng);
                    markerOptions.title(placeName + " : " + vicinity);

                    mMap.addMarker(markerOptions);
                }

                Toast.makeText(getBaseContext(), jsonArray.length() + " Supermarkets found!",
 Toast.LENGTH_LONG).show();
            } else if (result.getString(STATUS).equalsIgnoreCase(ZERO_RESULTS)) {
                Toast.makeText(getBaseContext(), "No Supermarket found in 5KM radius!!!",
 Toast.LENGTH_LONG).show();
            }

        } catch (JSONException e) {

            e.printStackTrace();
            Log.e(TAG, "parseLocationResult: Error=" + e.getMessage());
        }
    }

    @Override
    public void onLocationChanged(Location location) {
        double latitude = location.getLatitude();
        double longitude = location.getLongitude();

        LatLng latLng = new LatLng(latitude, longitude);
        mMap.addMarker(new MarkerOptions().position(latLng).title("My Location"));
        mMap.moveCamera(CameraUpdateFactory.newLatLng(latLng));
        mMap.animateCamera(CameraUpdateFactory.zoomTo(15));

        loadNearByPlaces(latitude, longitude);
    }

    @Override
    public void onStatusChanged(String s, int i, Bundle bundle) {
    }

    @Override
    public void onProviderEnabled(String s) {
    }

    @Override
    public void onProviderDisabled(String s) {
    }

    private boolean isGooglePlayServicesAvailable() {
        GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance();
        int resultCode = apiAvailability.isGooglePlayServicesAvailable(this);
        if (resultCode != ConnectionResult.SUCCESS) {
            if (apiAvailability.isUserResolvableError(resultCode)) {
                apiAvailability.getErrorDialog(this, resultCode, PLAY_SERVICES_RESOLUTION_REQUEST).show();
            } else {
                Log.i(TAG, "This device is not supported.");
                finish();
            }
            return false;
        }
        return true;
    }
}

vi) AndroidManifest.xml file

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.androidmastermind.gplaces">

    <permission android:name="com.androidmastermind.gplaces.permission.MAPS_RECEIVE"
        android:protectionLevel="signature"/>

    <uses-permission android:name="com.androidmastermind.gplaces.permission.MAPS_RECEIVE"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

    <uses-feature
        android:glEsVersion="0x00020000"
        android:required="true"/>

    <application
        android:name=".AppController"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

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

        <activity
            android:name=".MapsActivity"
            android:label="@string/title_activity_maps">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>

</manifest>

Running the project.

* First, set a location on the emulator.

a) If GPS is disabled



b) If GPS is enabled in your emulator or phone



***That's it, you've successfully loaded all the nearby supermarkets within the radius of 5km and are updated when a user moves for 10m***


See Also:
* Show User's Current Location



Thursday, 30 June 2016

Genymotion: Set a location in Running Emulator

Hae there, this is probably one of the simplest things i get to do while running a project that requires location.
By default, a running genymotion virtual device location is disabled and it's usually preset at latitude =65.9667 and longitude = -18.5333

How to enable the location service and adding a custom location.

1. Open Genymotion,






2. Run a virtual device - i like using Google nexus 6 - API 22



3. On the side menu of the emulator, click on the GPS, and a GPS window will popup.



4. Turn the GPS on notice the default latitude and longitude values, those are the onces we are going to change




5. Get the co-ordinate of a place 

- Click here, then scroll down the page, Enter the name of the place you want e.g Technical University of Kenya and press Get GPS Coordinates.
latitude will be = -1.292001
and longitude is= 36.824594



- Copy the latitude and longitude and paste it to the virtual device GPS respectively, leave the rest to default.



- Close the GPS popup window, then you'll be good to go,

*** In case you want the emulator to virtualize your location as if your moving, just get other coordinates and enter them, you'll notice the location changing on the map in real time***

See Also:

* Show User's Current Location

Saturday, 25 June 2016

Android Google Maps: Show current Location

Today im gonna show you how to create an android app that shows your current location on the map

Project Structure:



Procedure:
1. To start with, Create an new android project,
Go to start 'start a new project' from the welcome screen > Name it 'CurrentLocation' > Choose 'Google maps activity' *This is the main point* and finish to create the project.

2. After gradle has synced successfully, android studio will open google_maps_api.xml by default. in this xml file, we are required to get our api key from google console and replace it where there is this
phrase "YOUR_KEY_HERE"


 


* Android studio makes it very easy to get the api key because after creating the project, the hash signature is already written in the google_maps_api.xml file.

- Copy the url from the file and paste it to your browser, e.g in my case it's on line 7 in my editor,

https://console.developers.google.com/flows/enableapi?apiid=maps_android_backend&keyType=CLIENT_SIDE_ANDROID&r=
8F:D7:78:DF:61:AB:F8:5B:53:E9:48:39:56:D4:3B:1F:05:E3:4E:05%3Bcom.androidmastermind.currentlocation

> Rememeber to use your projects url, else it won't work.

- The link takes you straight to google api console which will prompt you to enable the maps api by creating a project

- After the page loads, just click on continue...,  A project will be created as 'My Project' ,(will change this later)



-  Another page will load after some few seconds, click on 'Go to credentials'



- A page will load with the package name of your app and the sha-1 certificate fingerprint, You can rename the key the way you want, for my case ill leave it to default and press create.



- A popup window will appear with your api key, just copy it and click ok. At this point we are done with the console,



-  Go back to our android project, in the google_maps_api.xml, paste the api key

<resources>
    <!--    TODO: Before you run your application, you need a Google Maps API key.

    To get one, follow this link, follow the directions and press "Create" at the end:
    https://console.developers.google.com/flows/enableapi?
apiid=maps_android_backend&keyType=CLIENT_SIDE_ANDROID&r=
8F:D7:78:DF:61:AB:F8:5B:53:E9:48:39:56:D4:3B:1F:05:E3:4E:05%
3Bcom.androidmastermind.currentlocation
    You can also add your credentials to an existing key, using this line:
    8F:D7:78:DF:61:AB:F8:5B:53:E9:48:39:56:D4:3B:1F:05:E3:4E:05;com.androidmastermind.currentlocation
    Alternatively, follow the directions here:
    https://developers.google.com/maps/documentation/android/start#get-key
    Once you have your key (it starts with "AIza"), replace the "google_maps_key"    string in this file.    -->
    <string name="google_maps_key" templateMergeStrategy="preserve" translatable="false">
AIzaSyBIfHPW-nb4JdNK1TlWuV3XiVIWhmuDvqwN</string>
</resources>

- You can now test your app and see if it displays a map, by default, your supposed to see a map with a marker, if you click it it will popup with 'marker in sydney'
- For this, just fire up genymotion, or plug your device in debug mode.




- Now lets go back to our MainActivity.java and make some changes,
- first thing we will implement LocationListener, this will be monitoring the location in case you move from the current location.
* Be careful to import <<import android.location.LocationListener;>> for the locationlistner, else you may meet some errors later.


- Will also create a method, getCurrentLocation(), that will be fethching the current location, and check if google play services are available before displaying the location.

package com.androidmastermind.currentlocation;

import android.content.pm.PackageManager;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.FragmentActivity;
import android.util.Log;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
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.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;

public class MapsActivity extends FragmentActivity implements OnMapReadyCallback,LocationListener {

    private static final String TAG = "CurrentLocation";
    private GoogleMap mMap;
    protected LocationManager locationManager;
    private static final int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;


    @Override    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (!isGooglePlayServicesAvailable()) {
            return;
        }
        setContentView(R.layout.activity_maps);

        // Obtain the SupportMapFragment and get notified when the map is ready to be used.
        SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
                .findFragmentById(R.id.map);
        mapFragment.getMapAsync(this);
    }


    /**     * Manipulates the map once available.
     * This callback is triggered when the map is ready to be used.
     * This is where we can add markers or lines, add listeners or move the camera. In this case,
     * we just add a marker near Sydney, Australia.
     * If Google Play services is not installed on the device, the user will be prompted to install
     * it inside the SupportMapFragment. This method will only be triggered once the user has
     * installed Google Play services and returned to the app.
     */
    @Override
    public void onMapReady(GoogleMap googleMap) {
        mMap = googleMap;

        displayCurrentLocation(mMap);
    }

    private void displayCurrentLocation(GoogleMap mMap) {
        if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) 
!= PackageManager.PERMISSION_GRANTED
 && ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_COARSE_LOCATION)
 != PackageManager.PERMISSION_GRANTED) {
            // TODO: Consider calling
            //    ActivityCompat#requestPermissions
            // here to request the missing permissions, and then overriding
            //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
            //                                          int[] grantResults)
            // to handle the case where the user grants the permission. See the documentation
            // for ActivityCompat#requestPermissions for more details.
            return;
        }
        mMap.setMyLocationEnabled(true);
        mMap.getUiSettings().setCompassEnabled(true);
        mMap.getUiSettings().setZoomControlsEnabled(true);

        locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);

        Criteria criteria = new Criteria();
        String bestProvider = locationManager.getBestProvider(criteria, true);
        Location location = locationManager.getLastKnownLocation(bestProvider);

        if (location != null) {
            onLocationChanged(location);

        }

        locationManager.requestLocationUpdates(bestProvider, 20000, 0, this);

    }

    private boolean isGooglePlayServicesAvailable() {
        GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance();
        int resultCode = apiAvailability.isGooglePlayServicesAvailable(this);
        if (resultCode != ConnectionResult.SUCCESS) {
            if (apiAvailability.isUserResolvableError(resultCode)) {
                apiAvailability.getErrorDialog(this, resultCode, PLAY_SERVICES_RESOLUTION_REQUEST).show();
            } else {
                Log.i(TAG, "This device is not supported.");
                finish();
            }
            return false;
        }
        return true;
    }

    @Override    public void onLocationChanged(Location location) {
        double latitude = location.getLatitude();
        double longitude = location.getLongitude();

        LatLng currentLocation = new LatLng(latitude, longitude);
        mMap.addMarker(new MarkerOptions().position(currentLocation).title("My Location"));
        mMap.moveCamera(CameraUpdateFactory.newLatLng(currentLocation));
        mMap.animateCamera(CameraUpdateFactory.zoomTo(15));
    }

    @Override
    public void onStatusChanged(String s, int i, Bundle bundle) {

    }

    @Override
    public void onProviderEnabled(String s) {

    }

    @Override
    public void onProviderDisabled(String s) {

    }
}

* That's it, you've successfully created an app that monitors your current location.




> My current location was at Technical University of Kenya
* Run it on a real device to see how it behaves when you move around and change location

See Also:
* Show Nearby Places From a User's Location



Thursday, 23 June 2016

Android login: using volley, php, mysql, pdo

Today you gonna learn how to create a login activity that uses volley for request. Click here If you want more information about volley library.
To begin with, will first create a back-end using PHP and my SQL. I'm using wamp server to host the php scripts.

1. Backend - PHP, MYSQL, PDO

Project Structure:




i) fire up the server (wampserver for my case), open browser and go to localhost/phpmyadmin

ii) create a database androidmastermind,

iii) create a table login,

CREATE TABLE IF NOT EXISTS `login` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(30) NOT NULL,
  `password` varchar(50) NOT NULL,
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
)

INSERT INTO `login` (`id`, `username`, `password`, `created_at`)
 VALUES(1, 'admin@gmail.com', 'admin', '2016-06-22 09:34:13');

iv) Create constants.php file, this will hold all the variables that we will need to reuse.

<?php

define("USERNAME","username");
define("PASSWORD","password");

v) Create DbConnection.php file, this will handle the database connection and disconnection when needed.
* Remember to change the username and password*

<?php

    private static $dbName = 'androidmastermind';
    private static $dbHost = 'localhost';
    private static $dbUsername = 'kevynashinski';
    private static $dbUserPassword = 'elegant';
    private static $cont = null;
    public function __construct(){
    }

    public static function connect(){
        // One connection through whole application
        if (null == self::$cont) {
            try {
                self::$cont = new PDO("mysql:host=" . self::$dbHost . ";"
                 . "dbname=" . self::$dbName, self::$dbUsername, self::$dbUserPassword);
                self::$cont->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            } catch (PDOException $e) {
                die($e->getMessage());            }
        }
        return self::$cont;    }

    public static function disconnect(){
        self::$cont = null;    }
}

vi) Finally create login.php file, this will authenticate the user, by receiving thus username and password through POST.

<?php
include 'DbConnection.php';
include 'constants.php';
if ($_SERVER['REQUEST_METHOD'] == 'POST') {

    $username = $_REQUEST[USERNAME];
    $password = $_REQUEST[PASSWORD];
//    open connection
    $conn = DatabaseConnection::connect();
    $sql = "select * from login WHERE username='$username' AND password='$password'";
//    check if a row exists with the credentials
    if ($conn->query($sql)->rowCount() > 0) {
//        shows there's a record
        echo 1;
    } else {
//        shows there's no such record
        echo 0;
    }
} else{
//    shows that nothing was posted
    echo 2;
}

* The index.php serves the purpose of testing our database connection.

<?php
include 'DbConnection.php';
$conn=DatabaseConnection::connect();
if($conn){
    echo "Ready to Roll!!!";
}else{
    echo "connection Error".$conn->errorInfo();
}

2. Frontend - Android

Project Structure:



i) Create a new android project, name it AndroidLogin

ii) Import volley library to your build.gradle (app:module)

compile group: 'com.mcxiaoke.volley', name: 'library', version: '1.0.19'

ii) Create App.java in the package, this class extends Application, it holds the initialization of the app configurations, in our case volley initialization.

package com.androidmatermind.androidlogin;

import android.app.Application;
import android.text.TextUtils;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.Volley;

/** * Created by Kevynashinski on 6/23/2016. */
public class App extends Application{
    public static final String TAG = "androidlogin";

    private ImageLoader mImageLoader;

    private RequestQueue mRequestQueue;
    private static App mInstance;

    @Override    public void onCreate() {
        super.onCreate();
        mInstance = this;
    }


    public static synchronized App getInstance() {
        return mInstance;
    }

    public RequestQueue getRequestQueue() {
        if (mRequestQueue == null) {
            mRequestQueue = Volley.newRequestQueue(getBaseContext());
        }

        return mRequestQueue;
    }

    public <T> void addToRequestQueue(Request<T> req, String tag) {
        // set the default tag if tag is empty
        req.setTag(TextUtils.isEmpty(tag) ? TAG : tag);
        getRequestQueue().add(req);
    }

    public <T> void addToRequestQueue(Request<T> req) {
        req.setTag(TAG);
        getRequestQueue().add(req);
    }

    public void cancelPendingRequests(Object tag) {
        if (mRequestQueue != null) {
            mRequestQueue.cancelAll(tag);
        }
    }
}

iii) Create another file, AppConfig.java, this holds the server link

package com.androidmatermind.androidlogin;

/** * Created by muus on 6/23/2016. */
public final class AppConfig {

    public static final String SERVER_URL="http://192.168.56.1/androidlogin/";
    public static final String URL_LOGIN=SERVER_URL+"login.php";
}

iv) InternetConnection.java class, will help us check if internet is available before login begins

package com.androidmatermind.androidlogin;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;

/** * Created by kevynashinski on 11/9/2015. */
public class InternetConnection {

    Context context;

    public InternetConnection(Context context) {
        this.context = context;
    }

    public boolean isInternetAvailable() {
        ConnectivityManager connectivityManager = (ConnectivityManager)
 context.getSystemService(Context.CONNECTIVITY_SERVICE);

//        boolean isWiFi = activeNetwork.getType() == ConnectivityManager.TYPE_WIFI;
        NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo();
        return activeNetwork != null && activeNetwork.isConnectedOrConnecting();
    }
}

v) Under the MainActivity.java, will when a user click sign in button, will first check if internet is available, then carry out the login operation. The email and password will also be validated

package com.androidmatermind.androidlogin;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.TargetApi;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.CursorLoader;
import android.content.Loader;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.util.Patterns;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static android.Manifest.permission.READ_CONTACTS;

/** * A login screen that offers login via email/password. */
public class MainActivity extends AppCompatActivity implements LoaderCallbacks<Cursor> {

    private static final int REQUEST_READ_CONTACTS = 0;

    // UI references.    private AutoCompleteTextView mEmailView;
    private EditText mPasswordView;
    private View mProgressView;
    private View mLoginFormView;

    @Override    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Set up the login form.
        mEmailView = (AutoCompleteTextView) findViewById(R.id.email);
        populateAutoComplete();

        mPasswordView = (EditText) findViewById(R.id.password);

        Button signInButton = (Button) findViewById(R.id.sign_in_button);
        assert signInButton != null;
        signInButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                if(new InternetConnection(getBaseContext()).isInternetAvailable())
                attemptLogin();
                else {
                    Toast.makeText(getBaseContext(),"Check your internet connection",
Toast.LENGTH_LONG).show();
                }
            }
        });

        mLoginFormView = findViewById(R.id.login_form);
        mProgressView = findViewById(R.id.login_progress);
    }

    private void populateAutoComplete() {
        if (!mayRequestContacts()) {
            return;
        }

        getLoaderManager().initLoader(0, null, this);
    }

    private boolean mayRequestContacts() {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            return true;
        }
        if (checkSelfPermission(READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
            return true;
        }
        if (shouldShowRequestPermissionRationale(READ_CONTACTS)) {
            Snackbar.make(mEmailView, R.string.permission_rationale, Snackbar.LENGTH_INDEFINITE)
                    .setAction(android.R.string.ok, new View.OnClickListener() {
                        @Override
                        @TargetApi(Build.VERSION_CODES.M)
                        public void onClick(View v) {
                            requestPermissions(new String[]{READ_CONTACTS}, REQUEST_READ_CONTACTS);
                        }
                    });
        } else {
            requestPermissions(new String[]{READ_CONTACTS}, REQUEST_READ_CONTACTS);
        }
        return false;
    }

    /**     * Callback received when a permissions request has been completed.     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        if (requestCode == REQUEST_READ_CONTACTS) {
            if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                populateAutoComplete();
            }
        }
    }


    /**     * Attempts to sign in or register the account specified by the login form.
     * If there are form errors (invalid email, missing fields, etc.), the     
     * errors are presented and no actual login attempt is made.
     */
    private void attemptLogin() {
        // Reset errors.
        mEmailView.setError(null);
        mPasswordView.setError(null);

        // Store values at the time of the login attempt.
        String email = mEmailView.getText().toString();
        String password = mPasswordView.getText().toString();

        boolean cancel = false;
        View focusView = null;

        // Check for a valid password, if the user entered one.
        if (!TextUtils.isEmpty(password) && !isPasswordValid(password)) {
            mPasswordView.setError(getString(R.string.error_invalid_password));
            focusView = mPasswordView;
            cancel = true;
        }

        // Check for a valid email address.
        if (TextUtils.isEmpty(email)) {
            mEmailView.setError(getString(R.string.error_field_required));
            focusView = mEmailView;
            cancel = true;
        } else if (!isEmailValid(email)) {
            mEmailView.setError(getString(R.string.error_invalid_email));
            focusView = mEmailView;
            cancel = true;
        }

        if (cancel) {
            // There was an error; don't attempt login and focus the first
            // form field with an error.
            focusView.requestFocus();
        } else {
            // Show a progress spinner, and kick off a background task to
            // perform the user login attempt.
            showProgress(true);
            login(email,password);
        }
    }

    private void login(final String email, final String password) {
        StringRequest strReq = new StringRequest(Request.Method.POST,
                AppConfig.URL_LOGIN, new Response.Listener<String>() {

            @Override
            public void onResponse(String response) {
                Log.d(App.TAG, "Login Response: " + response);

                showProgress(false);

                if(response.equalsIgnoreCase("1")){
                    Toast.makeText(getBaseContext(),"Login Success!!!",Toast.LENGTH_LONG).show();
                }else if(response.equalsIgnoreCase("0")){
                    Toast.makeText(getBaseContext(),"Invalid username or password",
Toast.LENGTH_LONG).show();
                }else if(response.equalsIgnoreCase("2")){
                    Toast.makeText(getBaseContext(),"Server Error!",Toast.LENGTH_LONG).show();
                }
            }
        }, new Response.ErrorListener() {

            @Override
            public void onErrorResponse(VolleyError error) {

                showProgress(false);

                Log.e(App.TAG, "Login Error: " + error.getMessage());
            }
        }) {

            @Override
            protected Map<String, String> getParams() {
                // Posting parameters to login url
                Map<String, String> params = new HashMap<>();
                params.put("username", email);
                params.put("password", password);

                return params;
            }
        };

        // Adding request to request queue
        App.getInstance().addToRequestQueue(strReq);
    }

    private boolean isEmailValid(String email) {
        //TODO: Replace this with your own logic
        return Patterns.EMAIL_ADDRESS.matcher(email).matches();
    }

    private boolean isPasswordValid(String password) {
        //TODO: Replace this with your own logic
        return password.length() > 4;
    }

    /**     * Shows the progress UI and hides the login form.
     */    
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
    private void showProgress(final boolean show) {
        // On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow
        // for very easy animations. If available, use these APIs to fade-in
        // the progress spinner.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
            int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime);

            mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
            mLoginFormView.animate().setDuration(shortAnimTime).alpha(
                    show ? 0 : 1).setListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
                }
            });

            mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
            mProgressView.animate().setDuration(shortAnimTime).alpha(
                    show ? 1 : 0).setListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
                }
            });
        } else {
            // The ViewPropertyAnimator APIs are not available, so simply show
            // and hide the relevant UI components.
            mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
            mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
        }
    }

    @Override
    public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
        return new CursorLoader(this,
                // Retrieve data rows for the device user's 'profile' contact.
                Uri.withAppendedPath(ContactsContract.Profile.CONTENT_URI,
                        ContactsContract.Contacts.Data.CONTENT_DIRECTORY), ProfileQuery.PROJECTION,

                // Select only email addresses.
                ContactsContract.Contacts.Data.MIMETYPE +
                        " = ?", new String[]{ContactsContract.CommonDataKinds.Email
                .CONTENT_ITEM_TYPE},

                // Show primary email addresses first. Note that there won't be
                // a primary email address if the user hasn't specified one.
                ContactsContract.Contacts.Data.IS_PRIMARY + " DESC");
    }

    @Override
    public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
        List<String> emails = new ArrayList<>();
        cursor.moveToFirst();
        while (!cursor.isAfterLast()) {
            emails.add(cursor.getString(ProfileQuery.ADDRESS));
            cursor.moveToNext();
        }

        addEmailsToAutoComplete(emails);
    }

    @Override
    public void onLoaderReset(Loader<Cursor> cursorLoader) {

    }

    private void addEmailsToAutoComplete(List<String> emailAddressCollection) {
        //Create adapter to tell the AutoCompleteTextView what to show in its dropdown list.
        ArrayAdapter<String> adapter =
                new ArrayAdapter<>(MainActivity.this,
                        android.R.layout.simple_dropdown_item_1line, emailAddressCollection);

        mEmailView.setAdapter(adapter);
    }


    private interface ProfileQuery {
        String[] PROJECTION = {
                ContactsContract.CommonDataKinds.Email.ADDRESS,
                ContactsContract.CommonDataKinds.Email.IS_PRIMARY,
        };

        int ADDRESS = 0;
        int IS_PRIMARY = 1;
    }

}

vi) Add the following codes to the activity_main.xml

<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:gravity="center_horizontal"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">

    <!-- Login progress -->
    <ProgressBar
        android:id="@+id/login_progress"
        style="?android:attr/progressBarStyleLarge"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:visibility="gone" />

    <ScrollView
        android:id="@+id/login_form"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:id="@+id/email_login_form"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <android.support.design.widget.TextInputLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

                <AutoCompleteTextView
                    android:id="@+id/email"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:hint="Email Address"
                    android:inputType="textEmailAddress"
                    android:maxLines="1"
                    android:singleLine="true" />

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

            <android.support.design.widget.TextInputLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

                <EditText
                    android:id="@+id/password"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:hint="password"
                    android:inputType="textPassword"
                    android:maxLines="1"
                    android:singleLine="true" />

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

            <Button
                android:id="@+id/sign_in_button"
                style="?android:textAppearanceSmall"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:text="Sign in"
                android:textStyle="bold" />

        </LinearLayout>
    </ScrollView>
</LinearLayout>

vii) Run the project,
a) When login success.



b) When login failure.



Thats all, your good to go!!!