Cohort+Problem+3+-+Google+Maps

Google Maps Intents For Android, Menu, Preferences
In this cohort problem, we'll see how to use the Google Maps Intents For Android. This allows our app to launch the Google Maps app with information that our app specifies. We'll also learn how to do a preference fragment and use the Shared Preferences Fragment

=Java Note - builder design pattern.=

We'll encounter code in this Example that uses the builder design pattern. You might have already used it when making an AlertDialog.

=Overall View Of Main Activity=

Create the layout
Create the layout as shown. Again, the base layout is LinearLayout with vertical orientation.



XML file for widgets
Again, the strings are hardcoded ...

code format="xml" 









 

 code

Code Skeleton
The structure of **MainActivity.java** at the beginning will be as follows. Any TO DO within a pair of brackets represents actions taken outside this file.

code format="java" public class MainActivity extends AppCompatActivity {

EditText searchLocation; Button getMap;

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

// TO DO 0.1 - set up by establishing references to the searchLocation widget and the getMapButton

//** PART 3 - READING FROM SharedPreferences ** // TO DO 3.1 - [create a Preference Fragment and embed it in SettingsActivity] // TO DO 3.2 - read from SharedPreferences

}

// ** PART 1 - CALLBACK FOR GET MAP BUTTON ** // TO DO 1 - onClick callback

// *** PART 2 - CREATING A MENU *** // TO DO 2.1 - [in res/menu folder], create an xml file defining your menu // TO DO 2.2 - inflate your menu in onCreateOptionsMenu // TO DO 2.3 - [Create a new activity called SettingsActivity] // TO DO 2.4 - create an intent in onOptionsItemSelected

// ** PART 3 - READING FROM SharedPreferences ** // TO DO 3.3 onSharedPreferencesListener

} code

=Understanding URIs=

URI stands for **Universal Resource Identifier**, which is a string of characters used to identify a resource.


 * Absolute URIs** specify a scheme e.g.
 * http://www.google.com
 * file:/Users/Macintosh/Downloads/url.html
 * geo:0.0?q=test
 * mailto: test@sutd.edu.sg

[ //scheme //**: **][authority //][ //path //][ **<span style="color: #006600; font-family: Consolas,'Liberation Mono',Menlo,Monaco,Courier,monospace; font-size: 13px;">? **//<span style="color: rgba(0,0,0,0.682353); font-family: Roboto,sans-serif; font-size: 14px;">query //<span style="color: rgba(0,0,0,0.682353); font-family: Roboto,sans-serif; font-size: 14px;">][ **<span style="color: #006600; font-family: Consolas,'Liberation Mono',Menlo,Monaco,Courier,monospace; font-size: 13px;"># **//<span style="color: rgba(0,0,0,0.682353); font-family: Roboto,sans-serif; font-size: 14px;">fragment //<span style="color: rgba(0,0,0,0.682353); font-family: Roboto,sans-serif; font-size: 14px;">]
 * Hierachical URIs** have a slash character after the scheme and can be parsed as follows:

[scheme : ][opaque part][? query]
 * Opaque URIs** do not have a slash characters and can be parsed as follows:

=Part 1 (upper half) - Displaying a particular location in Google Maps=

Skeleton of code
The skeleton of the callback method within MainActivity.java associated with Get Location button is given below.

code format="java" @Override public void onClick(View v){

//TO DO 1.1 - get the search location from the EditText widget

//TO DO 1.2 - build the Universal Resource Indicator (URI) that

//TO DO 1.3 - write the intent

} code

TO DO 1.1 and 1.2 - Parsing the URI
You'll need the information here in Common Intents and Google Maps Intents For Android.

This is the callback for the "Get Location" button. Within the callback method, you should 1. set up the URI so that you can display the location (refer to the docs for the exact format) 2. set up an Intent and pass this URI as a message together with the Intent

The URI for maps has the following format: //scheme : opaque part// Also, in a URI, any part after a ? is called a //query//.

You could do string operations to build the URI needed. However, the URI library helps us to manage the process and will be useful for longer URIs. Hence, for TO DO 1.1 and 1.2:

code format="java" //complete the code below to get the text from the EditText widget. String myLocation = "";

Uri.Builder builder = new Uri.Builder; builder.scheme("geo").opaquePart("0,0").appendQueryParameter("q",myLocation);

Uri geoLocation = builder.build; code

TO DO 1.3 - build the intent
Following which, refer to the documentation on Common Intents to build your intent.

You should now be able to build the app and run. Try running on an actual android device.

If you have more than one Map app installed on your phone (e.g. Google Maps and Waze) you will be asked which app you'd like to use. This is what an Implicit Intent is like.

Lower Half Of the Screen
As an exercise, implement the bottom half of the first screen (the radio buttons that lead to screen 3).

You can refer to the documentation on Common Intents to build your intent.

Based on the documentation, go from Screen 1 to Screen 3.

=Part 2 - Menu Bar for navigating within app=

What the screen looks like for Parts 2 and 3
Screenshots are shown below.

Making the menu bar
The android documentation for a menu bar gives a detailed explanation. In this section, we'll take you through the basic steps of creating a menu.

TO DO 2.1 - Creating a menu directory and xml file
Right-click on the res folder and select New->Android Resource Directory. The following should be entered //Directory Name//: menu //Resource Type//: menu

The menu folder should be created and now you can right-click on this folder to add a new resource file. Let's call it **menu_main.xml.**

Within this file, here's how you specify the menu items in the menu resource.

code format="java" <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/settings" android:title="settings" /> <item android:id="@+id/help" android:title="help" /> code

TO DO 2.2 - Inflate the menu in MainActivity
You have the menu layout in menu_main.xml, you now need to tell MainActivity.java to inflate it upon creation. This is done through the **onCreateOptionsMenu** callback. Hence, within MainActivity.java, you need the following method:

code format="java" @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater; inflater.inflate(R.menu.menu_main, menu); return true; } code Notice how you reference your menu layout file in the R class.

You may try to run your code now and see the menu. Now we need to make something happen when the menu items are selected.

TO DO 2.3 - Create a new activity
To show how the menu item can access another activity, create a new activity named SettingsActivity (be sure to generate both the java file and the xml layout file). Within the layout file, put a TextView widget containing any text you like. You may also want to modify the AndroidManifest as well to specify the parent of this activity. If you are not sure of these steps, refer to Cohort Problem 2.

TO DO 2.4 - Create the intent when the menu item is selected
We need another callback for these menu items, this is done in **onOptionsItemSelected**. We want the user to be brought to SettingsActivity when the "settings" item of the menu is selected. This is how:

code format="java" @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId;

if(id == R.id.settings){ //code for the intent goes here return true; }       return true; }

code Since there is more than one item in the menu, you can write similar code within this callback to display a Toast or an AlertDialog (as you wish) temporarily for the other item.

Auto-generate the code (in future)
Much of this code can be auto-generated by choosing **Basic Activity** instead of **Empty Activity.**

This should save you work in future. Refer to the textbook for a better description.

=Part 3 - Preference Fragment & Persisting Data=

Introduction to Preference Fragments
Many apps have special user interface to store your user preferences. These preferences remain even after the app is closed and restarted.

This behaviour is achieved with preference fragments. Detailed documentation is found in the Settings documentation, but we'll take you through the steps.

This is your first introduction to fragments. You can think of fragments as a mini-activity within an activity.

Create the fragment layout
Let's make a simple preference layout. In the res folder, create a new resource folder called "xml". This is specified in the documentation.

Inside this folder, create a new layout called **preferences.xml.** Let's just implement one checkbox preference.

code format="java" <?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <CheckBoxPreference android:id="@+id/chkBoxLargeFont" android:defaultValue="false" android:key="@string/chkBoxLargeFontKey" android:summaryOff="Normal" android:summaryOn="Large" android:title="Large Buttons" /> </PreferenceScreen> code

Create the fragment class
Create a **SettingsFragment.java** class that inherits **PreferenceFragment**. This just creates the fragment by inflating the layout from preferences.xml.

code format="java" public class SettingsFragment extends PreferenceFragment {

@Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.preferences); }

} code

Modify our settings activity layout
Next, the fragment must reside in a container within an activity. Modify **activity_settings.xml** to provide that container in the form of a fragment widget. Modify the **name** attribute to reflect your actual package name.

code format="xml" <?xml version="1.0" encoding="utf-8"?> <fragment xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/mySettingsFragment" android:layout_width="match_parent" android:layout_height="match_parent" android:name="com.example.tablet0007.mapintentwithpreferences.SettingsFragment" />

code

Inflate the entire activity
You are now ready to inflate the entire layout in SettingsActivity.java. android.R.id.content helps you get the root layout.

code format="java" public class SettingsActivity extends AppCompatActivity {

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

getFragmentManager.beginTransaction .replace(android.R.id.content, new SettingsFragment) .commit; } } code

You may now run your app to see the preference setting appear in it.

At this stage, changing your preferences setting (you have only one) has no effect on the rest of the app. However, by using the PreferenceFragment class, the settings are automatically stored in the SharedPreferences class. We'll see how to get the information from this class.

TO DO 3.2 - Read from SharedPreference
Implement the **SharedPreferences.onSharedPreferenceChangeListener** interface in your **MainActivity** class and create a class variable.

code format="java" public class MainActivity extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener {

//rest of your code SharedPreferences sharedPref; //rest of your code code

in the **onCreate** method, we have to do the following.
 * listen out for changes in the preferences
 * make the changes in the app upon creation as specified in the preferences

We are told in the PreferenceFragment documentation how to create an instance of the SharedPreferences class. Following which, we need to implement a listener to detect changes in any of the preferences. From the Shared Preferences documentation, the listener requires an instance of the
 * SharedPreferences.OnSharedPreferencesListener** class as input.

Hence, code format="java" sharedPref = PreferenceManager.getDefaultSharedPreferences(this); sharedPref.registerOnSharedPreferenceChangeListener(this); code

Next, we are read to make the changes in the app as specified by the preference. Write the **changeSomeAttribute** method to change the attribute of any widget that you like.

code format="java" //get the key attribute specified in the preferences.xml file String chkBoxLargeFontKey = getString(R.string.chkBoxLargeFontKey);

//1st argument: using this key, get the value stored in sharedPreferences //2nd argument: if there is no value stored, then the default value is false boolean isButtonLargeFont = sharedPref.getBoolean(chkBoxLargeFontKey,false);

//TO DO - write a method that makes use of the setting to change whatever attribute you like changeSomeAttribute(isButtonLargeFont);

code

TO DO 3.3 - override the onSharedPreferenceChanged Method
Next, you make your app changes its features as the user changes the preferences.

The **SharedPreferences.onSharedPreferenceChangeListener** interface has one method to override.

In this method, you check for the preference widgets that have changed (using their key attribute) and take the action accordingly.

code format="java" @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {

if (key.equals(getString(R.string.chkBoxLargeFontKey))){

//same code as above boolean checked = sharedPreferences.getBoolean(key,false);

//REMINDER - write code for this method changeSomeAttribute(checked);

}   } code This example is now done, and you can see that the preferences specified in the settings activity are retained.

=Post-Class Remarks=

Updated 8 Nov 2017

Building the URI
You might see different ways of building the URI in the documentation.

If you would like to parse an entire string as a URI, this is the code: code format="java" Uri gmmIntentUri = Uri.parse("geo:37.7749,-122.4194");

code

If you hardcode part of the string and then want to use the builder, this is the code:

code format="java" Uri geoLocation = Uri.parse("geo:0,0?").buildUpon .appendQueryParameter("q", myLocation) .build; code For this code, the buildUpon method returns a Uri.Builder object. Use the instanceof keyword to verify this. On this builder object, then you can call appendQueryParameter. Then what does build do?

Implicit Intents
The textbook teaches how to do implicit intents in a different way from the Google Maps Intents documentation.

From the Google Maps Intents, under "Location Search" code format="java" Uri gmmIntentUri = Uri.parse("geo:0,0?q=1600 Amphitheatre Parkway, Mountain+View, California"); Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri); mapIntent.setPackage("com.google.android.apps.maps"); startActivity(mapIntent); code

From the textbook section 2.3, or Common Intents documentation, under maps

code format="java" Uri geoLocation = ... ; //complete this yourself Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(geoLocation); if (intent.resolveActivity(getPackageManager) != null) { startActivity(intent); } code

Both ways should work.