Testing the App – Part 2

In this second part regarding tested, we will go through the next fours tasks which took place in the testing phase. In the previous blog post, three of the four test were successful, therefore 75% of the testing was successful. In this post, the following tests will be conducted:

  1. Checking the notification on different devices once an event is nearing.

Following the test that events are successfully parsed from the database, in the test, we made sure that the notification is shown 2 days before the event takes place.

This test was successful.

2.   Checking access to the TiBi facebook page.

This test was used to make sure that the TiBi official Facebook page is shown in a webview when the fragment is loaded. Most of all, we made sure that the latest updates are also being shown on the Facebook page and parsed into the application to be shown to the users.

tibi

This test was successful.

3.   Checking that an email is sent to the correct address when providing feedback.

When sending feedback to the administrative team, we needed to make sure that the details would be sent correctly and that the email would be processed to the correct address. In this test, we also made sure that the app is able to connect to the internet and raise an exception if not. Both tests were successful.

4.    Checking that the location of the selected event is viewed correctly on the map.

A final test makes sure that the map is updated to show the correct geolocation of the event once an event is clicked. We made sure that the map was updated once the event location changed. This test was successful too.
tibi

Testing the App – Part 1

Now the project has heen completed, the testing phase needed to take over actual coding of the program, to make sure that both server and client ends of the project work in syncronisation to achieve a sound experience on both levels. To test the project, a number of potential users were selected to be able to test the app.

The tests which were made are the ones detailed below:

  1. Creation of a new user on the Database and test login on client app.
  2. Resetting the password for a user.
  3. Creation of a new event  and inspiriational quote in the Database end and its view on the client app.
  4. Creation of a new group chat, making sure chat log is sychronised across all devices.
  5. Checking the notification on different devices once an event is nearing.
  6. Checking access to the TiBi facebook page.
  7. Checking that an email is sent to the correct address when providing feedback.
  8. Checking that the location of the selected event is viewed correctly on the map.

1. Creation of a new user on the database and test login credentials on the app

Here, we created a new user on the database first of all. All user detials will be handled by a user who is familiar with the phpMyAdmin interface, and therefore will be using it as the main backend interface. For this reason, for the time being, there will be no need to develop a backoffice online interface. If the need rises, this will be developed later.

newUser

login

The test has been successful.

2. Resetting a password for a user

This test was meant to ensure that the update statement included in the resseting of the password works correctly. The user Abigail Muscat had her password reset, through the front end as follows:

newpass

newuserdb

This test was also successful.

3. Creation of a new event  and inspiriational quote in the Database end and its view on the app.

For this test, we made sure that the app would extract the daily inspirational message from the database and parse it to be shown to the users on the screen.

quote

quoteapp

Here again, the test was successful.

4. Creation of a new group chat, making sure chat log is sychronised across all devices.

For the fourth test, we needed to make sure that the chat application was being syncronised among all devices, and that each device received the group messages in a timely manner. This test failed for the reason that the code used to call the webservice was depreciated, and is not anymore supported. Therefore the fragment itself could not build.

For this reason, we have decided to pull out this part of the project from the application and research the capabilities of building a chat screen as part of our application, which will be released in future updates to the app. It was also decided that if this feature were to be developed in the future, then the requirements would include giving each group chat particular subjects, giving this feature the idea of a forum, rather that a simple chat feature.

Sending Notifications to users

The notification service will be used whenever an event is nearing by, to promote it and remind all the users of the application about the event. This can be done through a number of ways, which are detailed below:

Google Cloud Messaging: As per Google’s documentation “Google Cloud Messaging (acronymed as GCM) is a service that helps developers send data from servers to their Android apps”. Using this service you can send data to an application whenever new data is available instead of making requests to server in a timely fashion. Integrating GCM in your android application enhances user experience most of all, apart from saving loads of battery consumption. The servers can be of any kind, however, Android tends to work best with the PHP servers.

Android Notification Service: A notification is a message you can display to the user outside of your application’s normal UI. When you tell the system to issue a notification, it first appears as an icon in the notification area. To see the details of the notification, the user opens the notification drawer. Both the notification area and the notification drawer are system-controlled areas that the user can view at any time. Notifications, as an important part of the Android user interface, have their own design guidelines. The material design changes introduced in Android 5.0 (API level 21) are of particular importance.

In my app, I have decided to implement the second method.

Below is the code which sets up a new notification:

public class NotificationView extends Activity {
@Override
 public void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.notification);
 }
}

Once this is done, the next step would be to create another class which extends the Broadcast receiver:

public class NotificationEvent extends BroadcastReceiver{
 private int notifID = 100;
 private int notifNum = 0;
 private NotificationManager notifManager;
@Override
 public void onReceive(Context context, Intent intent) {
 popupNotif(context);
}
protected void popupNotif(Context context) {
 PendingIntent contentIntent = PendingIntent.getActivity(context, 0, new Intent(context, NotificationEvent.class), 0);
 NotificationCompat.Builder evtNotif = new NotificationCompat.Builder(context);
 evtNotif.setContentTitle("TiBi Event");
 evtNotif.setContentText("");
 evtNotif.setTicker("");
 evtNotif.setSmallIcon(R.drawable.ic_launcher);
 evtNotif.setDefaults(Notification.DEFAULT_SOUND);
 evtNotif.setAutoCancel(true);
 evtNotif.setNumber(++notifNum);
 Intent result = new Intent(context, NotificationView.class);
 TaskStackBuilder stack = TaskStackBuilder.create(context);
 stack.addParentStack(NotificationView.class);
 stack.addNextIntent(result);
 evtNotif.setContentIntent(contentIntent);
 notifManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
 notifManager.notify(notifID, evtNotif.build());
 }
protected void cancelNotif() {
 notifManager.cancel(notifID);
}
}

Once this is completed, the final step would be to call the event notifier in the main activity to display it to the users. This is done through this code:

Intent intent = getIntent();
 String date = intent.getStringExtra(EventFragment.DATE);
 String time = intent.getStringExtra(EventFragment.TIME);
long _date = Long.parseLong(date);
 SimpleDateFormat year = new SimpleDateFormat("yyyy");
 SimpleDateFormat month = new SimpleDateFormat("MMM");
 SimpleDateFormat day = new SimpleDateFormat("dd");
long _time = Long.parseLong(time);
 SimpleDateFormat hour = new SimpleDateFormat("hh");
 SimpleDateFormat minute = new SimpleDateFormat("mm");
year.format(new Date(_date));
 month.format(new Date(_date));
 day.format(new Date(_date));
 hour.format(new Date(_time));
 minute.format(new Date(_time));
int yyyy = Integer.valueOf(String.valueOf(year));
 int MM = Integer.valueOf(String.valueOf(month));
 int dd = Integer.valueOf(String.valueOf(day));
 int mm = Integer.valueOf(String.valueOf(minute));
 int hh = Integer.valueOf(String.valueOf(hour));
cal = Calendar.getInstance();
 cal.set(Calendar.HOUR_OF_DAY, hh - 1);
 cal.set(Calendar.MINUTE, mm);
 cal.set(Calendar.DATE, dd);
 cal.set(Calendar.MONTH, MM);
 cal.set(Calendar.YEAR, yyyy);
Intent notifEvent = new Intent(this, NotificationEvent.class);
 PendingIntent pi = PendingIntent.getService(this, 0 , notifEvent, PendingIntent.FLAG_UPDATE_CURRENT);
 AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
 am.setRepeating(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), AlarmManager.INTERVAL_DAY, pi);

Android Volley – Second Part of Two

The whole reason why the Android Volley library is being used, will be shown in the following tutorial. It will be used to parse the JSON data being retrieved from the database. JSON is very light weight, structured, easy to parse and much human readable. JSON is best alternative to XML when an Android app needs to interchange data with the PHP server.

Written by Ficus Kirkpatrick and his team, Volley is a library recently released by Google at I/O 2013. The Google Play Store and a number of apps by Google use this library to perform network requests and remote image loading. The developers at Google claim that network requests performed through Volley are up to 10 times faster than other libraries according to their tests. Volley is a Google library for Android that makes networking and remote image loading easier and faster.

In the below code, the two methods makeJsonObjectRequest() and makeJsonArrayRequest() are instantiantaed but are kept empty for the time being:

private String jsonResponse;
    private void makeJsonObjectRequest() {
    }

    private void makeJsonArrayRequest() {
    }

The above methods need to wrap the JSONParser class.

Normally JSON responses can be of two different types. It can be either a JSON object or JSON array. If the json starts with {, it is considered to be JSON Object, while when starting with [, then it is a JSON Array. Volley provides JsonObjectRequest class to make json object request. Here we are fetching the JSON data by making a call to a url and parsing it. Finally the parsed response is appended to a string and displayed on the screen.

The JSON parsing code remains the same as implemented beforehand.

Android Volley – First Part of Two

Android volley is a networking library which was introduced to make networking calls much easier, faster without writing tons of code. By default all the volley network calls work asynchronously, so developers don’t need to worry about using asynctask anymore. Therefore this would replace the current implementation of JSON parsing.

Volley comes with lot of features. Some of them are

  1. Request queuing and prioritization
  2. Effective request cache and memory management
  3. Extensibility and customization of the library to our needs
  4. Cancelling the requests

Volley excels at RPC-type operations used to populate a UI, such as fetching a page of search results as structured data. It integrates easily with any protocol and comes out of the box with support for raw strings, images, and JSON. By providing built-in support for the features you need, Volley frees you from writing boilerplate code and allows you to concentrate on the logic that is specific to your app.

Volley is not suitable for large download or streaming operations, since Volley holds all responses in memory during parsing. For large download operations, consider using an alternative like DownloadManager.

The core Volley library is developed in the open AOSP repository at frameworks/volley and contains the main request dispatch pipeline as well as a set of commonly applicable utilities, available in the Volley “toolbox.”

The best way to maintain volley core objects and request queue is, making them global by creating a singleton class which extends the Application object. To achieve this, a  controller class was created:

public class AppController extends Application {
    public static final String TAG = AppController.class.getSimpleName();

    private RequestQueue mRequestQueue;
    private ImageLoader mImageLoader;
    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 ImageLoader getImageLoader() {
        getRequestQueue();
        if (mImageLoader == null) {
            mImageLoader = new ImageLoader(this.mRequestQueue,
                    new LruBitmapCache());
        }
        return this.mImageLoader;
    }
     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);
        }
    }
}
Volley is shipped with powerful cache mechanism to maintain the requested cache. This saves lot of internet bandwidth and reduces user waiting time. Following is the implementation of the Volley cache methods:
Cache cache = AppController.getInstance().getRequestQueue().getCache();
Entry entry = cache.get(url);

if(entry != null){
    try{
        String data = newString(entry.data, "UTF-8");
        } catch(UnsupportedEncodingException e) {      
        e.printStackTrace();
        }
    }
}else{
}
In the same way, requests can be cancelled, deleted or permanentely switched off. Requests can also be prioritised. The priory can be Normal, Low, Immediate and High, according to which are the most important requests, in the app.
private Priority priority = Priority.HIGH;
StringRequest strReq = new StringRequest(Method.GET,Const.URL_STRING_REQ, new Response.Listener<String>() {
        @Override
        public void onResponse(String response) {
              Log.d(TAG, response.toString());
              msgResponse.setText(response.toString());
              hideProgressDialog(); 
         }
}, new Response.ErrorListener() {
         @Override
         public void onErrorResponse(VolleyError error) {
               VolleyLog.d(TAG, "Error: " + error.getMessage());
               hideProgressDialog();
         }
          @Override
          public Priority getPriority() {
                return priority;
          }
};

Using third-party API to implement Chat features

One of the main features of the app will be that of letting all users who have the app installed to communicate with each other through a group chat. In order to achieve this, a third party API had to be used. We have seen a large number of apps being developed in the recent past, all of which help users to connect with each other across different mediums. Apps like Facebook Messenger, Whatsapp, Viber and Couple, bring users of the same app together to be able to communicate with each other. One would be surprised to learn that its rather quite easy to develop a chat app of your own, with some great features which can differ from the already existing solutions on the market.

The chat application can be achieved by building a simple group chat app using Java sockets. This is not the only way to build a chat app, but using this method, one could buld a simple group chat easily. An other differnet approach to achieve such a solution would be using push notifications instead of sockets, but this is a more complex solution and not necessarily needed in our work.

This chat room will only be accessible to users who have the app installed on their mobile device or tablet. Therefore it won’t be accessible through a web application.

We will start by creating the necessary xml files for the application to be visually seen in our development environment. Primarily we will need to create the following xml files:

bg_message_from.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
 android:shape="rectangle" >
<!-- view background color -->
 <solid android:color="@color/bg_msg_from" >
 </solid>
<corners android:radius="2dp" >
 </corners>
</shape>

bg_message_you.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
 android:shape="rectangle" >
<!-- view background color -->
 <solid android:color="@color/bg_msg_you" >
 </solid>
<corners android:radius="2dp" >
 </corners>
</shape>

The above xml files contain details for the different colors of chats coming from other participants and other chats coming from the user of the app. Next are the actual chat bubbles:

left_bubble.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:orientation="vertical"
 android:paddingBottom="5dp"
 android:paddingTop="5dp"
 android:paddingLeft="10dp">
<TextView
 android:id="@+id/lblMsgFrom"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:textSize="12dp"
 android:textColor="@color/lblFromName"
 android:textStyle="italic"
 android:padding="5dp"/>
<TextView
 android:id="@+id/txtMsg"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:textSize="16dp"
 android:layout_marginRight="80dp"
 android:textColor="@color/title_gray"
 android:paddingLeft="10dp"
 android:paddingRight="10dp"
 android:paddingTop="5dp"
 android:paddingBottom="5dp"
 android:background="@drawable/bg_msg_from"/>
</LinearLayout>

right_bubble.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:gravity="right"
 android:orientation="vertical"
 android:paddingBottom="5dp"
 android:paddingRight="10dp"
 android:paddingTop="5dp" >
<TextView
 android:id="@+id/lblMsgFrom"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:padding="5dp"
 android:textColor="@color/lblFromName"
 android:textSize="12dp"
 android:textStyle="italic" />
<TextView
 android:id="@+id/txtMsg"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:layout_marginLeft="80dp"
 android:background="@drawable/bg_msg_you"
 android:paddingBottom="5dp"
 android:paddingLeft="10dp"
 android:paddingRight="10dp"
 android:paddingTop="5dp"
 android:textColor="@color/white"
 android:textSize="16dp" />
</LinearLayout>

Finally, the xml containing the complete fragment content to hold all details for the chat screen in it:

<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:background="@drawable/tile_bg"
 android:orientation="vertical" >
<ListView
 android:id="@+id/list_view_messages"
 android:layout_width="fill_parent"
 android:layout_height="0dp"
 android:layout_weight="1"
 android:background="@null"
 android:divider="@null"
 android:transcriptMode="alwaysScroll"
 android:stackFromBottom="true">
 </ListView>
<LinearLayout
 android:id="@+id/llMsgCompose"
 android:layout_width="fill_parent"
 android:layout_height="wrap_content"
 android:background="@color/white"
 android:orientation="horizontal"
 android:weightSum="3" >
<EditText
 android:id="@+id/inputMsg"
 android:layout_width="0dp"
 android:layout_height="fill_parent"
 android:layout_weight="2"
 android:background="@color/bg_msg_input"
 android:textColor="@color/text_msg_input"
 android:paddingLeft="6dp"
 android:paddingRight="6dp"/>
<Button
 android:id="@+id/btnSend"
 android:layout_width="0dp"
 android:layout_height="wrap_content"
 android:layout_weight="1"
 android:background="@color/bg_btn_join"
 android:textColor="@color/white"
 android:text="Send" />
 </LinearLayout>
</LinearLayout>

Moving on to the java class, the following code shows the connection with the websocket. The code below will check for a new message and display it to the user:

private void sendMessageToServer(String message) {
 if (client != null && client.isConnected()) {
 client.send(message);
 }
 }
private void parseMessage(final String msg) {
try {
 JSONObject jObj = new JSONObject(msg);
 String flag = jObj.getString("flag");
 if (flag.equalsIgnoreCase(TAG_SELF)) {
String sessionId = jObj.getString("sessionId");
 utils.storeSessionId(sessionId);
Log.e(TAG, "Your session id: " + utils.getSessionId());
} else if (flag.equalsIgnoreCase(TAG_NEW)) {

 String name = jObj.getString("name");
 String message = jObj.getString("message");
 String onlineCount = jObj.getString("onlineCount");
showToast(name + message + ". Currently " + onlineCount
 + " people online!");
} else if (flag.equalsIgnoreCase(TAG_MESSAGE)) {

 String fromName = name;
 String message = jObj.getString("message");
 String sessionId = jObj.getString("sessionId");
 boolean isSelf = true;
 if (!sessionId.equals(utils.getSessionId())) {
 fromName = jObj.getString("name");
 isSelf = false;
 }
Message m = new Message(fromName, message, isSelf);
 appendMessage(m);
} else if (flag.equalsIgnoreCase(TAG_EXIT)) {

 String name = jObj.getString("name");
 String message = jObj.getString("message");
showToast(name + message);
 }
} catch (JSONException e) {
 e.printStackTrace();
 }
}
@Override
 public void onDestroy() {
 super.onDestroy();
if(client != null & client.isConnected()){
 client.disconnect();
 }
 }
 private void appendMessage(final Message m) {
 getActivity().runOnUiThread(new Runnable() {
private void showToast(final String message) {
getActivity().runOnUiThread(new Runnable() {
@Override
 public void run() {
 Toast.makeText(getActivity().getApplicationContext(), message,
 Toast.LENGTH_LONG).show();
 }
 });
}

In the same class, we need a method which loads the rootView in order to connect the xml file to the java code. The code is the following:

@Override
 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 View rootView = inflater.inflate(R.layout.fragment_forum, container,false);
btnSend = (Button) rootView.findViewById(R.id.btnSend);
 inputMsg = (EditText) rootView.findViewById(R.id.inputMsg);
 listViewMessages = (ListView) rootView.findViewById(R.id.list_view_messages);
utils = new Utils(getActivity().getApplicationContext());
btnSend.setOnClickListener(new View.OnClickListener() {
@Override
 public void onClick(View v) {

 sendMessageToServer(utils.getSendMessageJSON(inputMsg.getText()
 .toString()));
 inputMsg.setText("");
 }
 });
listMessages = new ArrayList<Message>();
adapter = new MessagesListAdapter(getActivity(), listMessages);
 listViewMessages.setAdapter(adapter);
 client = new WebSocket(URI.create(WsConfig.URL_WEBSOCKET
 + URLEncoder.encode(name)), new WebSocket.Listener() {
public void onConnect() {
}
public void onMessage(String message) {
 Log.d(TAG, String.format("Got string message! %s", message));
parseMessage(message);
}
public void onMessage(byte[] data) {
 Log.d(TAG, String.format("Got binary message! %s",
 bytesToHex(data)));
// Message will be in JSON format
 parseMessage(bytesToHex(data));
 }
 @Override
 public void onDisconnect(int code, String reason) {
String message = String.format(Locale.US,
 "Disconnected! Code: %d Reason: %s", code, reason);
showToast(message);
 utils.storeSessionId(null);
 }
@Override
 public void onError(Exception error) {
 Log.e(TAG, "Error! : " + error);
showToast("Error! : " + error);
 }
}, null);
client.connect();
return rootView;
 }

Below is then the construction of the message in a seperate class:

public class Message {
 private String fromName, message;
 private boolean isSelf;
public Message() {
 }
public Message(String fromName, String message, boolean isSelf) {
 this.fromName = fromName;
 this.message = message;
 this.isSelf = isSelf;
 }
public String getFromName() {
 return fromName;
 }
public void setFromName(String fromName) {
 this.fromName = fromName;
 }
public String getMessage() {
 return message;
 }
public void setMessage(String message) {
 this.message = message;
 }
public boolean isSelf() {
 return isSelf;
 }
public void setSelf(boolean isSelf) {
 this.isSelf = isSelf;
 }
}

The message list adapter takes care of the listview and the chat history, as per the following code:

public class MessagesListAdapter extends BaseAdapter {
private Context context;
 private List<Message> messagesItems;
public MessagesListAdapter(Context context, List<Message> navDrawerItems) {
 this.context = context;
 this.messagesItems = navDrawerItems;
 }
@Override
 public int getCount() {
 return messagesItems.size();
 }
@Override
 public Object getItem(int position) {
 return messagesItems.get(position);
 }
@Override
 public long getItemId(int position) {
 return position;
 }
@Override
 public View getView(int position, View convertView, ViewGroup parent) {
Message m = messagesItems.get(position);
LayoutInflater mInflater = (LayoutInflater) context
 .getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
 if (messagesItems.get(position).isSelf()) {
 convertView = mInflater.inflate(R.layout.list_item_message_right,
 null);
 } else {
 // message belongs to other person, load the left aligned layout
 convertView = mInflater.inflate(R.layout.list_item_message_left,
 null);
 }
TextView lblFrom = (TextView) convertView.findViewById(R.id.lblMsgFrom);
 TextView txtMsg = (TextView) convertView.findViewById(R.id.txtMsg);
txtMsg.setText(m.getMessage());
 lblFrom.setText(m.getFromName());

return convertView;
}
}

On the server side, we need to configure the server to accept web sockets and process the messages throughout all users in the group chat. This is done by configuring a connection to the server, which has already been installed on the machine accepting the connection:

public class WSConfig {
 public static final String URL_WEBSOCKET = "ws://192.168.0.102:8080/WebMobileGroupChatServer/chat?name=";
}

Generating the Events Locations through Geo-Location

One of the requirements of the assignment was to use geo-location to give location details of where the events were going to take place. The longitudinal and latitudinal details are being parsed from the database, and are included in the location details for each event. Since Google Maps API is one of the best geo-location services and since it is easily integrated to any Android App, this particular API will be used in the project to get the location of each event.

In order to use google maps one first needs to obtain a map key through the Google API console. Assuming that a Windows PC is being used, the is the way it is done using MD5 key encryption from the jdk installation.

Through the command prompt, one needs to run the following script:

c:\<path-to-jdk-dir>\bin\keytool.exe -list -alias androiddebugkey -keystore "C:\users\<user-name>\.android\debug.keystore" -storepass android -keypass android

This would output the MD5 key similar to the one below:

keytool.exe -list -alias androiddebugkey -keystore "C:\users\mlaferla\.android\debug.keystore" -storepass android -keypass android

Using the Google API Console, the generated key needs to be uploaded and regstered to use the Google Maps API for Android devices. This associated the map we will be using to the key by giving it the MD5 fingerprint. The reason for doing so is to associate the Android application with the Maps API. Otherwise, the map cannot be rendered on the app.

The hashed code given by the Google API Console needs to then be copied as is into the manisfest to be able to call it correctly when using the app.

Below is the xml content to generate the map screen:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="fill_parent"
 android:layout_height="fill_parent"
 android:background="#F38121" >
<fragment
 android:id="@+id/mapView"
 android:name="com.google.android.gms.maps.SupportMapFragment"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:layout_margin="15dp"
 android:padding="15dp" />
</RelativeLayout>

And here below, the code to build the map screen:

package com.chaplaincy.jc.tibiapp;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;
/**
 * Created by mlaferla on 17/02/2015.
 */
public class MapFragment extends Fragment {
private SupportMapFragment fragment;
 private GoogleMap map;
private int mapType = GoogleMap.MAP_TYPE_NORMAL;
@Override
 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 return inflater.inflate(R.layout.fragment_googlemap, container, false);
 }
@Override
 public void onActivityCreated(Bundle savedInstanceState) {
 super.onActivityCreated(savedInstanceState);
 FragmentManager fm = getChildFragmentManager();
 fragment = (SupportMapFragment) fm.findFragmentById(R.id.mapView);
 if (fragment == null) {
 fragment = SupportMapFragment.newInstance();
 fm.beginTransaction().replace(R.id.mapView, fragment).commit();
 }
 }
@Override
 public void onResume() {
 super.onResume();
 if (map == null) {
 map = fragment.getMap();
 map.addMarker(new MarkerOptions().position(new LatLng(37.7750, -122.4183)));
 }
 }
@Override
 public void onSaveInstanceState(Bundle outState) {
 super.onSaveInstanceState(outState);
// save the map type so when we change orientation, the mape type can be restored
 LatLng cameraLatLng = map.getCameraPosition().target;
 float cameraZoom = map.getCameraPosition().zoom;
 outState.putInt("map_type", mapType);
 outState.putDouble("lat", cameraLatLng.latitude);
 outState.putDouble("lng", cameraLatLng.longitude);
 outState.putFloat("zoom", cameraZoom);
 }
}