External Services
CSE 5236: Mobile Application Development
Course Coordinator: Dr. Rajiv Ramnath
Instructor: Dr. Adam C. Champion
Reading: Big Nerd Ranch Guide, Chap. 30 (WebView);
Chaps. 33, 34 (Google Play services, maps)
1
External Services
• Viewing websites
• Location- and map-based functionality
• REST-based Web services invocation
2
Invoking Browser App
Java Kotlin
// HelpFragment.java // HelpFragment.kt
private void launchBrowser( private fun launchBrowser(
String url) { url: String) {
Uri theUri = Uri.parse(url); val theUri = Uri.parse(url)
Intent LaunchBrowserIntent = val LaunchBrowserIntent =
new Intent(Intent.ACTION_VIEW, Intent(Intent.ACTION_VIEW,
theUri); theUri)
startActivity( startActivity(
LaunchBrowserIntent); LaunchBrowserIntent)
} }
URL: http://en.wikipedia.org/wiki/Tictactoe
3
Note: Activity stacking due to re-launch of browser on mobile page
Embedded WebView - Layout
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
...>
<LinearLayout ...
...>
<WebView
android:id="@+id/helpwithwebview"
android:layout_width="match_parent"
android:layout_height="200dip"
android:layout_weight="1.0"/>
<Button
...
android:text="Exit"/>
</LinearLayout>
</ScrollView>
4
Embedded WebView: Java
// HelpWebViewFragment.java
public View onCreateView(. . .) {
View v = inflater.inflate(R.layout.fragment_help_webview, ...);
WebView helpInWebView = v.findViewById(R.id.helpwithwebview);
mProgressBar = v.findViewById(R.id.webviewprogress);
mProgressBar.setMax(100);
View buttonExit = v.findViewById(R.id.button_exit);
buttonExit.setOnClickListener(this);
Bundle extras = getActivity().getIntent().getExtras();
if (extras != null) {
mUrl = extras.getString(ARG_URI); /* . . . */
}
// . . .
helpInWebView.loadUrl(mUrl);
return v;
}
5
Embedded WebView: Kotlin
// HelpWebViewFragment.kt
override fun onCreateView( . . . ): View? {
val v = inflater.inflate(R.layout.fragment_help_webview, . . .)
val helpInWebView = v.findViewById(R.id.helpwithwebview)
mProgressBar = v.findViewById(R.id.webviewprogress)
mProgressBar.apply { max = 100 }
val buttonExit = v.findViewById<Button>(R.id.button_exit)
buttonExit.setOnClickListener(this)
val extras = activity.intent.extras
if (extras != null) {
mUrl = extras.getString(ARG_URI)
}
/* . . . */
helpInWebView.loadUrl(mUrl)
return v
}
6
Location-Based Applications
These mix-and-match the following actions:
– Opening a map
– Invoking a geocoding service on a point of
interest
– Navigating the map to a position or make a
map-based calculation
– Determining the user’s geolocation from the
device (latitude, longitude)
7
Additional Requirements for Maps
• Use built-in GoogleMap with a FragmentActivity
• Link against Google APIs for Android (not “Android SDK”)
• Request permissions: <uses-permission
android:name="<permission>"/>, where <permission>
includes ACCESS_NETWORK_STATE,
ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION,
WRITE_EXTERNAL_STORAGE READ_GSERVICES
• Request a Map API key: https://developers.google.com/maps/
documentation/android/start
• Need OpenGL ES v2: <uses-feature
android:glEsVersion="0x00020000”
android:required="true"/>
8
Map Fragment Layout
<fragment
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/map"
tools:context=".MapsActivity"
android:name="com.google.android.gms.maps.
SupportMapFragment"/>
• Google Maps uses Fragments
• UI Fragment loaded dynamically
9
Map Fragment Code: Java (1)
// MapsFragment.java
public class MapsFragment extends SupportMapFragment implements
OnMapReadyCallback {
private GoogleMap mMap;
private GoogleApiClient mApiClient;
private static final String[] LOCATION_PERMISSIONS = new String[] {
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION };
private FusedLocationProviderClient mFusedLocationProviderClient;
private Location mLocation;
private LatLng mDefaultLocation;
private static final int REQUEST_LOCATION_PERMISSIONS = 0;
private boolean mLocationPermissionGranted = false;
. . .
// MapsActivity.java just uses a SingleFragmentActivity
10
Map Fragment Code: Java (2)
// MapsFragment.java (continued)
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
mApiClient = new GoogleApiClient.Builder(getActivity())
.addApi(LocationServices.API)
.addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
@Override
public void onConnected(@Nullable Bundle bundle) {
getActivity().invalidateOptionsMenu(); }
@Override
public void onConnectionSuspended(int i) { /*...*/ } }).build();
getMapAsync(this);
}
@Override
public void onResume() {
super.onResume();
setUpEula(); 11
findLocation(); }
Map Fragment Code: Java (3)
// MapsFragment.java (continued)
@Override
public void onStart() { mApiClient.connect(); }
@Override
public void onStop() { mApiClient.disconnect(); }
private void findLocation() {
updateLocationUI();
if (hasLocationPermission()) {
mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(
getActivity());
mDefaultLocation = new LatLng(40.0, -83.0);
LocationRequest locationRequest = LocationRequest.create();
/* Set location request parameters */
FusedLocationProviderClient locationProvider =
LocationServices.getFusedLocationProviderClient(getActivity());
Task locationResult = locationProvider.getLastLocation();
locationResult.addOnCompleteListener(getActivity(), new OnCompleteListener() {
@Override
public void onComplete(@NonNull Task task) {
if (task.isSuccessful()) {/* Move camera */ } } else { /* ... */ } }});}
12
// Else request permissions, end of method.
Map Fragment Code: Java (4)
// MapsFragment.java (continued)
private void setUpEula() {
mSettings = getActivity().getSharedPreferences(getString(R.string.prefs), 0);
boolean isEulaAccepted = mSettings.getBoolean(getString(R.string.eula_accepted_key),
false);
if (!isEulaAccepted) {
DialogFragment eulaDialogFragment = new EulaDialogFragment();
eulaDialogFragment.show(getActivity().getSupportFragmentManager(), "eula"); }
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { // . . .
inflater.inflate(R.menu.maps_menu, menu); }
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_showcurrentlocation:
Log.d(TAG, "Showing current location");
if (hasLocationPermission()) { findLocation(); }
else { /* Request permissions */ }
break; }
return true; 13
}
Map Fragment Code: Java (5)
// MapsFragment.java (continued)
@Override
public void onRequestPermissionsResult(. . .) {
mLocationPermissionGranted = false;
switch (requestCode) {
case REQUEST_LOCATION_PERMISSIONS: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0 &&
grantResults[0]==PackageManager.PERMISSION_GRANTED) {
mLocationPermissionGranted = true; } } }
updateLocationUI(); }
private void updateLocationUI() {
if (mMap == null) { return; }
if (mLocationPermissionGranted) { /* Enable location */ }
else { /* Disable location, request permissions */ }
}
@Override
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;
mMap.addMarker(new MarkerOptions().position(new LatLng(40.0,-83.0)).title("O.S.U."));
}
14
private boolean hasLocationPermission() { /* . . . */ }
Map Fragment Code: Kotlin (1)
// MapsFragment.kt
class MapsFragment : SupportMapFragment(), OnMapReadyCallback {
private lateinit var mMap: GoogleMap
private lateinit var mApiClient: GoogleApiClient
private lateinit var mFusedLocationProviderClient: FusedLocationProviderClient
private var mLocation: Location? = null
private var mDefaultLocation: LatLng? = null
private var mLocationPermissionGranted = false
private var mMapReady = false
companion object {
private val LOCATION_PERMISSIONS = arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION)
private val REQUEST_LOCATION_PERMISSIONS = 0
}
15
Map Fragment Code: Kotlin (2)
// MapsFragment.kt (continued)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
mApiClient = GoogleApiClient.Builder(activity)
.addApi(LocationServices.API)
.addConnectionCallbacks(object: GoogleApiClient.ConnectionCallbacks {
override fun onConnected(bundle: Bundle?) { /* ... */ }
override fun onConnectionSuspended(i: Int) { /* ... */ }
}).build()
getMapAsync(this)
}
override fun onResume() {
super.onResume()
setUpEula()
findLocation()
}
16
Map Fragment Code: Kotlin (3)
// MapsFragment.kt (continued)
override fun onStart() { mApiClient.connect() }
override fun onStop() { mApiClient.disconnect() }
private fun findLocation() {
updateLocationUI()
if (hasLocationPermission()) {
mFusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(activity)
mDefaultLocation = LatLng(40.0, -83.0)
val locationRequest = LocationRequest.create()
/* Set locationRequest properties */
val locationProvider = LocationServices.getFusedLocationProviderClient(activity)
val locationResult = locationProvider.lastLocation
locationResult.addOnCompleteListener(activity, object:OnCompleteListener<Location> {
override fun onComplete(task: Task<Location>) {
if (task.isSuccessful) {
// Set map's camera position to current device location
} else { /* Disable location */ } } }) }
// Else request permissions, end of method.
17
Map Fragment Code: Kotlin (4)
// MapsFragment.kt (continued)
private fun setUpEula() {
mSettings = activity.getSharedPreferences(getString(R.string.prefs), 0)
val isEulaAccepted = mSettings!!.getBoolean(getString(R.string.eula_accepted_key),
false)
if (!isEulaAccepted) {
val eulaDialogFragment = EulaDialogFragment()
eulaDialogFragment.show(activity.supportFragmentManager, "eula") }
}
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
super.onCreateOptionsMenu(menu, inflater)
inflater?.inflate(R.menu.maps_menu, menu)
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item?.itemId) {
R.id.menu_showcurrentlocation -> {
if (hasLocationPermission()) { findLocation() }
else { requestPermissions(LOCATION_PERMISSIONS,REQUEST_LOCATION_PERMISSIONS) }
}
}
} 18
Map Fragment Code: Kotlin (5)
// MapsFragment.kt (continued)
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>,
grantResults: IntArray) {
mLocationPermissionGranted = false
when (requestCode) {
REQUEST_LOCATION_PERMISSIONS -> {
// If request is cancelled, the result arrays are empty.
if (grantResults.isNotEmpty() &&
grantResults[0] == PackageManager.PERMISSION_GRANTED) {
mLocationPermissionGranted = true } } }
updateLocationUI() }
private fun updateLocationUI() {
if (mMapReady) {
if (mLocationPermissionGranted) { /* Enable location */ }
else { /* Disable location, request permissions */ }
} }
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap /* Prepare map; set mMapReady to true */ } 19
Map Menu Layout
<menu
<!-- Details omitted --> >
<item
android:id="@+id/
menu_showcurrentlocation"
android:orderInCategory="100"
android:title="@string/show_location"
android:icon="@android:drawable/
ic_menu_mylocation"
app:showAsAction="ifRoom"/>
</menu>
20
License Agreement Fragment: Java
• We need to get the user’s consent
public class EulaDialogFragment extends DialogFragment {
public void setEulaAccepted() {
SharedPreferences prefs = getActivity().getSharedPreferences(
getString(R.string.prefs), 0);
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(getString(R.string.eula_accepted_key), true)
.apply(); }
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.eula_title)
.setMessage(Html.fromHtml(getString(R.string.eula)))
.setPositiveButton(R.string.accept, new DialogInterface.OnClickListener() {
/* Call setEulaAccepted() */ } })
.setNegativeButton(R.string.decline, new DialogInterface.OnClickListener() {
/* Cancel dialog, finish activity */
return builder.create(); }
}
21
License Agreement Fragment: Kotlin
class EulaDialogFragment : DialogFragment() {
fun setEulaAccepted() {
val prefs = activity.getSharedPreferences(getString(R.string.prefs), 0)
val editor = prefs.edit()
editor.putBoolean(getString(R.string.eula_accepted_key), true).apply()
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
// Use the Builder class for convenient dialog construction
val builder = AlertDialog.Builder(activity)
builder.setTitle(R.string.about_app)
.setMessage(Html.fromHtml(getString(R.string.eula)))
.setPositiveButton(R.string.accept) { /* Call setEulaAccepted() */ }
.setNegativeButton(R.string.decline) {
/* Cancel dialog, finish activity */
}
return builder.create()
}
}
22
Location Determination Practices
• Ways to get location:
– GPS
– Cellular
– Wi-Fi
• Best practices for location-based apps:
– Check for connectivity
– Use threading to ensure responsiveness (Note:
Threading built-in for many SDK components)
23
Thank You
Questions and comments?
24
References
• Chapter 10: “Channeling the Outside World through your Android
Device” from Android SDK 3 Programming for Dummies
• Chapters 33–34: “Locations and Play Services” and “Maps” from
Android Programming: The Big Nerd Ranch Guide (3rd ed.)
• http://developer.android.com/reference/android/webkit/package-summ
ary.html
• http://developer.android.com/reference/android/webkit/WebView.html
• https://developers.google.com/maps/documentation/android/start
• https://developer.android.com/guide/components/fragments.html
• https://developer.android.com/training/basics/fragments/creating.html
25