Objectives

This lab is the final lab in the current version and completes our Case Study CoffeeMate with the introduction of Location Awareness and Google Maps in version CoffeeMate.6.0.

Setup - Starter Code

Unlike previous labs, you don't need to download the starter code for this lab as it's basically a copy of CoffeeMate.5.0, so if you wish, you could just continue on with your own version. If you didn't get a chance to finish the previous lab you can download the CoffeeMate.5.0 solution here - CoffeeMate.5.0.Solution.

Either way, It's probably still a good idea to run the App and confirm that the app (our your app) is configured properly and (still) running.

In this lab, you are required to do the following:

  • Add Location awareness to the App (via a custom MapsFragment class)

  • Add a Google Map, so the user can see their coffees displayed on a Map

  • Add Volley Support to CoffeeMate to manage our coffees and their locations on the server (our APi already does this)

The following steps will guide you through these requirements, but before we can do anything with the Google Maps, you need to obtain your own Google Maps Key to add to your manifest file.

Obtaining your Google Maps Key

First of all, you need to visit Get an API Key on the Android Developer site as it contains all the info you need to obtain your Key. You'll have some of the work done already (from the previous lab) but there's still a bit of work to do, so if you get stuck just ask!

Once you have your key, the next thing to do is add the following to your strings.xml

<string name="title_map">Map</string>
<string name="google_maps_key">abcdefghijklmnopetcetcetc</string>

where 'abcdefghijklmnopetcetcetc' is your API Key.

Next, open up your manifest file and add the following just before the closing "application" tag

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

Also, in your manifest file add the following permissions

<uses-permission android:name="ie.cm.permission.MAPS_RECEIVE"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.CAMERA"/>

At the time of writing (July 2017) there were a few updates to dependency versions made (which this lab is based on) so can you confirm your app build.gradle is very similar to the following

apply plugin: 'com.android.application'

android {
    compileSdkVersion 25
    buildToolsVersion '25.0.0'

    defaultConfig {
        applicationId "ie.cm"
        minSdkVersion 24
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile project(':volley')
    compile 'com.android.support:appcompat-v7:25.2.0'
    compile 'com.android.support:support-v4:25.2.0'
    compile 'com.android.support:design:25.2.0'
    compile 'com.makeramen:roundedimageview:2.2.1'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    compile 'com.google.code.gson:gson:2.7'
    compile 'com.google.android.gms:play-services-auth:11.0.2'
    compile 'com.google.android.gms:play-services-maps:11.0.2'
    compile 'com.google.android.gms:play-services-location:11.0.2'
    testCompile 'junit:junit:4.12'
}

apply plugin: 'com.google.gms.google-services'

Then, go ahead and create a new Empty Activity and name the Layout activity_map - this isn't really that important as we will be disgarding the activity in the next step including the layout - we are just using it here to confirm we have configured our key etc. correctly.

now add the following to the layout

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

Finally, (for this step) add the following to your Home Activity, to temporarily handle launching our new Map activity, if the user selects the menu option.

else if (id == R.id.nav_map) {
startActivity(new Intent(this, Map.class));
}

Run your app and select "View on Map"

and if everything goes according to plan, you should get

Congratulations - you can now go ahead and build map based apps!

View Users Current Location

At the moment, when the user selects the 'Map' menu option, they get to see a standard map, but not their own location (or even their coffees locations), so this step (and the next) is about implementing that (we'll look at the coffees location in later steps).

As we want to keep in line with the UI guidelines and approach, it makes sense to use a Fragment so first of all go ahead and create a new (Blank) Fragment called MapsFragment (NOT MapFragment) but DON'T create a layout or include interface callbacks

I used this link for some of the functionality we needed using the latest features of the Api (July 2017)

https://github.com/googlesamples/android-play-location/blob/master/LocationUpdates/app/src/main/java/com/google/android/gms/location/sample/locationupdates/MainActivity.java

Make sure it extends from MapFragment and implements the following interfaces, like so

public class MapsFragment extends MapFragment implements
        GoogleMap.OnInfoWindowClickListener,
        GoogleMap.OnMapClickListener,
        GoogleMap.OnMarkerClickListener,
        OnMapReadyCallback {
...
}

Fix the errors and replace the existing newInstance() method with this one

public static MapsFragment newInstance() {
    MapsFragment fragment = new MapsFragment();
return fragment;
}

Replace the existing instance variables with these

private LocationRequest             mLocationRequest;
    private FusedLocationProviderClient mFusedLocationClient;
    private LocationCallback            mLocationCallback;
    private List<Coffee>                mCoffeeList;
    private long                        UPDATE_INTERVAL = 5000; /* 5 secs */
    private long                        FASTEST_INTERVAL = 1000; /* 1 sec */
    private GoogleMap                   mMap;
    private float                       zoom = 13f;

    public CoffeeMateApp                app = CoffeeMateApp.getInstance();

    private static final int            PERMISSION_REQUEST_CODE = 200;

    private final int[]                 MAP_TYPES = {
                                            GoogleMap.MAP_TYPE_SATELLITE,
                                            GoogleMap.MAP_TYPE_NORMAL,
                                            GoogleMap.MAP_TYPE_HYBRID,
                                            GoogleMap.MAP_TYPE_TERRAIN,
                                            GoogleMap.MAP_TYPE_NONE
                                            };

    private int                         curMapTypeIndex = 1;

Replace onCreate() with

@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        try {
            mFusedLocationClient = LocationServices.getFusedLocationProviderClient(getActivity());
            createLocationCallback();
            createLocationRequest();
        }
        catch(SecurityException se) {
            Toast.makeText(getActivity(),"Check Your Permissions",Toast.LENGTH_SHORT).show();
        }
    }

and add

private void createLocationRequest() {
      mLocationRequest = new LocationRequest();
      mLocationRequest.setInterval(UPDATE_INTERVAL);
      mLocationRequest.setFastestInterval(FASTEST_INTERVAL);
      mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
      //mLocationRequest.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
  }

   /* Creates a callback for receiving location events.*/
  private void createLocationCallback() {
      mLocationCallback = new LocationCallback() {
          @Override
          public void onLocationResult(LocationResult locationResult) {
              super.onLocationResult(locationResult);

              app.mCurrentLocation = locationResult.getLastLocation();
              initCamera(app.mCurrentLocation);
          }
      };
  }

Remove onCreateView() and replace with

@Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        setHasOptionsMenu(true);

        TextView titleBar = (TextView) getActivity().findViewById(R.id.recentAddedBarTextView);
        titleBar.setText("Coffee Map");

        app.mGoogleApiClient.registerConnectionCallbacks(this);
    }

Add/implement the following methods

public void initListeners() {
       mMap.setOnMarkerClickListener(this);
       mMap.setOnInfoWindowClickListener(this);
       mMap.setOnMapClickListener(this);
   }

   @Override
   public void onResume() {
       super.onResume();
       getMapAsync(this);
       if (checkPermission()) {
           if (app.mCurrentLocation != null) {
               Toast.makeText(getActivity(), "GPS location was found!", Toast.LENGTH_SHORT).show();
           } else {
               Toast.makeText(getActivity(), "Current location was null, Setting Default Values!", Toast.LENGTH_SHORT).show();
               app.mCurrentLocation = new Location("Waterford City Default (WIT)");
               app.mCurrentLocation.setLatitude(52.2462);
               app.mCurrentLocation.setLongitude(-7.1202);
           }
           if(mMap != null) {
               initCamera(app.mCurrentLocation);
               mMap.setMyLocationEnabled(true);
           }
           startLocationUpdates();
       }
       else if (!checkPermission()) {
           requestPermission();
       }
   }

   private void initCamera(Location location) {
       if (zoom != 13f && zoom != mMap.getCameraPosition().zoom)
           zoom = mMap.getCameraPosition().zoom;

       CameraPosition position = CameraPosition.builder()
               .target(new LatLng(location.getLatitude(),
                       location.getLongitude()))
               .zoom(zoom).bearing(0.0f)
               .tilt(0.0f).build();

       mMap.animateCamera(CameraUpdateFactory
               .newCameraPosition(position), null);
   }

   public void startLocationUpdates() {
       try {
               mFusedLocationClient.requestLocationUpdates(mLocationRequest,
                   mLocationCallback, Looper.myLooper());
       }
       catch(SecurityException se) {
           Toast.makeText(getActivity(),"Check Your Permissions on Location Updates",Toast.LENGTH_SHORT).show();
       }
   }

And replace the relevant methods with the following

@Override
    public void onMapReady(GoogleMap googleMap) {
        mMap = googleMap;
        mMap.setMapType(MAP_TYPES[curMapTypeIndex]);

        initListeners();
        if(checkPermission()) {
            mMap.setMyLocationEnabled(true);
            initCamera(app.mCurrentLocation);
        }
        else if (!checkPermission()) {
            requestPermission();
        }
            mMap.getUiSettings().setMapToolbarEnabled(true);
            mMap.getUiSettings().setCompassEnabled(true);
            mMap.getUiSettings().setMyLocationButtonEnabled(true);
            mMap.getUiSettings().setAllGesturesEnabled(true);
            mMap.setTrafficEnabled(true);
            mMap.setBuildingsEnabled(true);
            mMap.getUiSettings().setZoomControlsEnabled(true);
    }

    //http://www.journaldev.com/10409/android-handling-runtime-permissions-example
    private boolean checkPermission() {
        int result = ContextCompat.checkSelfPermission(getActivity(), ACCESS_FINE_LOCATION);
        int result1 = ContextCompat.checkSelfPermission(getActivity(), CAMERA);

        return result == PackageManager.PERMISSION_GRANTED && result1 == PackageManager.PERMISSION_GRANTED;
    }

    private void requestPermission() {
        ActivityCompat.requestPermissions(getActivity(), new String[]{ACCESS_FINE_LOCATION, CAMERA},
                PERMISSION_REQUEST_CODE);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        switch (requestCode) {
            case PERMISSION_REQUEST_CODE:
                if (grantResults.length > 0) {

                    boolean locationAccepted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
                    boolean cameraAccepted = grantResults[1] == PackageManager.PERMISSION_GRANTED;

                    if (locationAccepted && cameraAccepted) {
                        Snackbar.make(getView(), "Permission Granted, Now you can access location data and camera.",
                                Snackbar.LENGTH_LONG).show();
                        if(checkPermission())
                            mMap.setMyLocationEnabled(true);
                        startLocationUpdates();
                    }
                    else {

                        Snackbar.make(getView(), "Permission Denied, You cannot access location data and camera.",
                                Snackbar.LENGTH_LONG).show();

                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                            if (shouldShowRequestPermissionRationale(ACCESS_FINE_LOCATION)) {
                                showMessageOKCancel("You need to allow access to both the permissions",
                                        new DialogInterface.OnClickListener() {
                                            @Override
                                            public void onClick(DialogInterface dialog, int which) {
                                                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                                                    requestPermissions(new String[]{ACCESS_FINE_LOCATION, CAMERA},
                                                            PERMISSION_REQUEST_CODE);
                                                }
                                            }
                                        });
                                return;
                            }
                        }
                    }
                }
                break;
        }
    }

    private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {
        new AlertDialog.Builder(getActivity())
                .setMessage(message)
                .setPositiveButton("OK", okListener)
                .setNegativeButton("Cancel", null)
                .create()
                .show();
    }

Now, open your Home Activity and instead of loading the Map Activity (as is currently the case) implement the necessary code to display our MapsFragment.

If you run the app now you will probably get a

java.lang.NullPointerException: Appropriate Api was not requested.

error so you need to add

.addApi(LocationServices.API)

to your GoogleClient when you build it in your Login activity, so fix that now.

If you've followed all the steps correctly, and you run the app again you should be seeing something like this (make sure to accept the permissions)

and then this

Experiment with different coordinates and restarting your app and then use the emulator to send coordinates while the app is running (as below) as see what happens?

The next few steps will be about building on the Location Awareness of our App and updating the Map automatically, as the user moves around and adding a 'Marker' to show these movements.

Before you move on, just confirm your app is now Location Aware, like so, when you 'View on Map'

but when you send new coordinates to the emulator, you should see the 'blue dot' move to that new location, as below

View Users Coffee Locations

The last step in this lab involves displaying the users coffees on the map, along with the users location (which was the previous step) so we need to modify a few classes here, namely

  • MapFragment
  • AddFragment
  • CoffeeApi

MapFragment

Here we need to inspect our list of coffees and (using the longitude and latitude coordinates) place a marker on the map indicating the location of each coffee.

So, first, open up your MapFragment class and add the following method

public void addCoffees(List<Coffee> list){
    for(Coffee c : list)
        mMap.addMarker(new MarkerOptions()
            .position(new LatLng(c.marker.coords.latitude, c.marker.coords.longitude))
            .title(c.name + " €" + c.price)
            .snippet(c.shop + " " + c.address)                 
            .icon(BitmapDescriptorFactory.fromResource(R.drawable.coffee)));
}

To ensure our list of coffees is up to date and the most recent one, the MapFragment class needs to implement the VolleyListener interface, so go ahead and complete that now.

Once you've implemented the necessary methods, add a call to addCoffees() in your setList() method.

Now, add the following APi call to your onResume() AFTER getMapAsync(this)

CoffeeApi.attachListener(this);
CoffeeApi.getAll("/coffees/" + app.googleToken, null);

Because we're passing 'null' to our getAll() call, there's a small change you need to make in your CoffeeApi class - so see if you can work out what it is?

Before you run your app, I'd suggest checking the Web App to confirm you have some coffees stored on the server and can view them on the Map in the Browser, so when you run your app, you know it's working correctly if you see your coffees - something like this

Adding a Coffee - Refactored for Location Data

Now that we can see existing coffees on our Map, what about when we add new coffees on the device, not the web app? This is the final step in our Case Study and involves a bit of work in refactoring our AddFragment as we need to grab the current location to save with our coffee details.

And for fun :) we'll also embed our MapFragment inside the AddFragment layout, so we can see where we're adding our coffee, like so

First thing that needs to be done is make our AddFragment 'Location Aware' so go ahead and ensure your Fragment implements the correct callback interface (OnMapReadyCallback) and see if you can implement the necessary code to display the map with all the users coffees - you can refer to the lecture material if necessary?

Now open up your fragment_add and add the following fragment element

<fragment
        android:name="ie.cm.fragments.MapsFragment"
        android:id="@+id/addmap"
        android:layout_width="364dp"
        android:layout_height="138dp"
        android:layout_gravity="center_horizontal|bottom"
       />

Note that the fragment name is our own Custom MapFragment and not the standard MapFragment. Your layout might be a bit all over the place as a result :) so I've since converted the layout to a ConstraintLayout, which you can find below and can replace yours with, if you wish?

<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_width="match_parent"
    android:layout_height="match_parent"
    tools:context="ie.cm.fragments.AddFragment">


    <Button
        android:id="@+id/saveCoffeeBtn"
        style="@android:style/Holo.Light.ButtonBar"
        android:layout_width="359dp"
        android:layout_height="83dp"
        android:drawableTop="@drawable/ic_save_coffee"
        android:text="@string/saveCoffeeBtn"
        tools:layout_constraintTop_creator="1"
        tools:layout_constraintRight_creator="1"
        tools:layout_constraintBottom_creator="1"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        tools:layout_constraintLeft_creator="1"
        app:layout_constraintLeft_toLeftOf="parent"
        android:layout_marginTop="8dp"
        app:layout_constraintTop_toBottomOf="@+id/coffeeRatingBar"
        android:layout_marginBottom="8dp"
        app:layout_constraintHorizontal_bias="0.52"
        app:layout_constraintVertical_bias="1.0" />

    <RatingBar
        android:id="@+id/coffeeRatingBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:numStars="5"
        android:progressTint="@color/bannerBGColor"
        android:rating="2"
        android:stepSize="0.5"
        tools:layout_conversion_absoluteX="72dp"
        tools:layout_conversion_absoluteY="300dp"
        tools:layout_conversion_absoluteWidth="240dp"
        tools:layout_conversion_absoluteHeight="57dp"
        android:layout_marginRight="8dp"
        app:layout_constraintRight_toRightOf="parent"
        android:layout_marginLeft="8dp"
        app:layout_constraintLeft_toLeftOf="parent"
        android:layout_marginTop="41dp"
        app:layout_constraintTop_toBottomOf="@+id/priceEditText" />

    <EditText
        android:id="@+id/nameEditText"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginTop="8dp"
        android:ems="10"
        android:inputType="textShortMessage"
        android:labelFor="@id/nameEditText"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.017"
        tools:layout_conversion_absoluteHeight="42dp"
        tools:layout_conversion_absoluteWidth="200dp"
        tools:layout_conversion_absoluteX="112dp"
        tools:layout_conversion_absoluteY="69dp"
        app:layout_constraintHorizontal_bias="0.482">

        <requestFocus
            tools:layout_conversion_absoluteHeight="0dp"
            tools:layout_conversion_absoluteWidth="0dp"
            tools:layout_conversion_absoluteX="112dp"
            tools:layout_conversion_absoluteY="69dp" />

    </EditText>

    <EditText
        android:id="@+id/shopEditText"
        android:layout_width="205dp"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginTop="8dp"
        android:ems="10"
        android:inputType="textShortMessage"
        android:labelFor="@id/shopEditText"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.139"
        tools:layout_conversion_absoluteHeight="42dp"
        tools:layout_conversion_absoluteWidth="200dp"
        tools:layout_conversion_absoluteX="112dp"
        tools:layout_conversion_absoluteY="126dp"></EditText>

    <TextView
        android:id="@+id/ratingTextView"
        android:layout_width="117dp"
        android:layout_height="24dp"
        android:text="@string/coffeeRatingLbl"
        android:textColor="@color/appFontColor"
        android:textSize="18sp"
        tools:layout_conversion_absoluteX="52dp"
        tools:layout_conversion_absoluteY="260dp"
        tools:layout_conversion_absoluteWidth="120dp"
        tools:layout_conversion_absoluteHeight="21dp"
        android:layout_marginRight="8dp"
        app:layout_constraintRight_toRightOf="@+id/coffeeRatingBar"
        android:layout_marginLeft="8dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintHorizontal_bias="0.045"
        app:layout_constraintTop_toTopOf="@+id/shopEditText"
        android:layout_marginTop="120dp"
        tools:layout_editor_absoluteX="19dp" />

    <TextView
        android:id="@+id/nameTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginTop="8dp"
        android:gravity="top"
        android:text="@string/coffeeNameLbl"
        android:textColor="@color/appFontColor"
        android:textSize="18sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHorizontal_bias="0.077"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.04"
        tools:layout_conversion_absoluteHeight="20dp"
        tools:layout_conversion_absoluteWidth="60dp"
        tools:layout_conversion_absoluteX="52dp"
        tools:layout_conversion_absoluteY="69dp" />

    <EditText
        android:id="@+id/priceEditText"
        android:layout_width="205dp"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="89dp"
        android:layout_marginRight="8dp"
        android:layout_marginTop="8dp"
        android:ems="10"
        android:inputType="numberDecimal"
        android:labelFor="@id/priceEditText"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.274"
        tools:layout_conversion_absoluteHeight="42dp"
        tools:layout_conversion_absoluteWidth="100dp"
        tools:layout_conversion_absoluteX="112dp"
        tools:layout_conversion_absoluteY="187dp"
        app:layout_constraintRight_toRightOf="parent" />

    <TextView
        android:id="@+id/shopTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/coffeeShopLbl"
        android:textColor="@color/appFontColor"
        android:textSize="18sp"
        tools:layout_conversion_absoluteX="52dp"
        tools:layout_conversion_absoluteY="136dp"
        tools:layout_conversion_absoluteWidth="49dp"
        tools:layout_conversion_absoluteHeight="21dp"
        android:layout_marginLeft="8dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="@+id/nameTextView"
        android:layout_marginTop="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        android:layout_marginBottom="8dp"
        android:layout_marginRight="8dp"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintHorizontal_bias="0.103"
        app:layout_constraintVertical_bias="0.096" />

    <TextView
        android:id="@+id/priceTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/coffeePriceLbl"
        android:textColor="@color/appFontColor"
        android:textSize="18sp"
        tools:layout_conversion_absoluteHeight="21dp"
        tools:layout_conversion_absoluteWidth="49dp"
        tools:layout_conversion_absoluteX="52dp"
        tools:layout_conversion_absoluteY="197dp"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginTop="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        android:layout_marginBottom="8dp"
        android:layout_marginRight="8dp"
        app:layout_constraintRight_toRightOf="parent"
        android:layout_marginLeft="8dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintHorizontal_bias="0.1"
        app:layout_constraintVertical_bias="0.279" />


    <fragment
        android:name="ie.cm.fragments.MapsFragment"
        android:id="@+id/addmap"
        android:layout_width="364dp"
        android:layout_height="138dp"
        android:layout_gravity="center_horizontal|bottom"
        android:layout_marginLeft="8dp"
        app:layout_constraintLeft_toLeftOf="parent"
        android:layout_marginRight="8dp"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:layout_marginBottom="8dp"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginTop="8dp"
        app:layout_constraintVertical_bias="0.737"
        app:layout_constraintHorizontal_bias="0.533"
        tools:layout_editor_absoluteY="267dp"
        tools:layout_editor_absoluteX="5dp" />

</android.support.constraint.ConstraintLayout>

If you run your app again and go to 'Add a Coffee' you should see something like this

Take some time to experiment with send different coordinates to the emulator, while you are on the Add Coffee screen, and you should see the map move to that new location - another example of how useful fragments can be!

Adding a Coffee - Updating the Map

Currently we can see the users coffees on a map even while we are on the 'Add a Coffee' screen. Now let's look at updating that map with a newly added Coffee.

First, open your add fragment and remove/comment out the intent to return 'Home', otherwise you'll never see the coffee marker being added.

Now, update the coffee constructor fields to include the latitude and longitude values for the coffee being created and finally, try and implement the code necessary to update our map with the newly created coffee. This is actually a single line of code but refer to the lecture material if you're not sure.

Run your app again, send some coordinates to change your current location, Add a coffee and you should get something like This

and then

It might be useful to reset the fields after a coffee has been added, so go ahead and implement that, using the following method

private void resetFields() {
        name.setText("");
        shop.setText("");
        price.setText("");
        ratingBar.setRating(2);
        name.requestFocus();
        name.setFocusable(true);
    }

If you click on one of the markers you've added from the device (not the webapp) you'll notice that the address field is blank. We need to convert our coordinates into a meaningful address for the user, so that's next.

Adding a Coffee - Reversing Geocoding

At this point when we add a new coffee, we can see it on the map instantly, but the address is empty if this is carried out on the device. To make sure the coffee address (as well as the other info) is stored correctly, we Reverse Geocode the location of the coffee into a meaningful street address and store that.

Introduce the following method into your AddFragment

private String getAddressFromLocation( Location location ) {
       Geocoder geocoder = new Geocoder( getActivity() );

       String strAddress = "";
       Address address;
       try {
           address = geocoder
                   .getFromLocation( location.getLatitude(), location.getLongitude(), 1 )
                   .get( 0 );
           strAddress = address.getAddressLine(0) +
                   " " + address.getAddressLine(1) +
                   " " + address.getAddressLine(2);
       }
       catch (IOException e ) {
       }

       return strAddress;
   }

and see if you can successfully achieve something along the lines of

this (before)

and then this (after)

Hint : you'll have to make a few changes to the Coffee class as well as when you create the coffee before posting it to the server (Well that's how I did it anyway!)

Solution

This is a solution to the lab: