Cohort+Problem+6+-+Connecting+to+the+Internet

=Objectives=

In this Example, we'll describe how to use your android app to connect to the internet.

Many websites provide an API where you can query to get data e.g. Singapore's public data.

You'll learn about
 * how to structure your URL query using the URI library
 * how to determine the type of network connection that your phone has
 * how to use AsyncTasks to load the data
 * how to parse the JSON data using the GSON library and display it in your app
 * how to use AsyncTaskLoaders to load the data

We'll use the xkcd API, as I find it easy to parse (so far). The final app should look like this.

=Overview=

You'll first have to understand how to query a web API and understand the kind of data it gives you. The data could be in JSON or XML format.

Subsequently, you'll have to
 * build the query URL
 * download the data
 * parse it
 * display the result in your UI

Android specifies to developers to run UI tasks and non-UI tasks on separate threads, so as to avoid the UI freezing (and hence the "app is not responding" message). Thus, the downloading of data is carried out in the **AsyncTasks** class. = =

Resources you'll need
xkcd website - "about" section scroll down to "is there an interface for automated systems .. "

AsyncTasks section of textbook

Explanation of JSON objects

An online JSON viewer. I use this, but others are fine.

Documentation on the URL class

Start an Android project with one Empty Activity and paste the code fragments in Part 1 (below) inside.

=Part 1 - Create the Layout=

Get familiar with the API
You can read about it here. You should be familiar with
 * how to query the API
 * the format of the data (XML? JSON? in what structure?)

Create the Layout For Main Activity
Within a LinearLayout (vertical orientation) put the following widgets.

What is the name of the callback method when the button is clicked?

code format="xml" 







 code

Code Structure
Here's the overall view of MainActivity.java. Go ahead and complete TO DO 1.

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

EditText editTextComicNo; ImageView imageViewComic; TextView textViewURL; TextView textViewJSON;

//TO DO 2.2 assign these static variables to help you build the URL final String xkcd_BASE = ; final String xkcd_TAIL = ; final String xkcd_SCHEME = ;

String imgUrlString; String comicJsonResultString = null;

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //TO DO 1 get references to the widgets in the layout

}

private URL buildURL{ URL xkcdURL = null; //TO DO 2.1 get the comic number from the editText widget //TO DO 2.1 (only if you are interested) write a Regex to ensure that the comic number is valid //TO DO 2.2 fill in the URL static variables above //TO DO 2.3 build the URL //TO DO 2.4 [within the android manifest] add the permissions to connect to the internet return xkcdURL; }

private String getHttpURL(URL url){ InputStream inputStream; String output = ""; //TO DO 3.1 query the API with a URL return output; }

protected void onClickGetComic(View view){

//TO DO 2 Build the query url method in BuildURL and display the result in a widget

//TO DO 3 Write the various steps in the GetComicTask inner class

//TO DO 4.1 Determine if a network connection exists //TO DO 4.2 If a network connection exists then execute GetComicTask, else display an error message

}

public void parseJson(String jsonString){ //TO DO 3.2 parse the JSON response to extract the URL }

public class GetComicTask extends AsyncTask{

@Override protected Bitmap doInBackground(URL... urls) { URL url = urls[0]; Bitmap xkcdPic;

try{ //TO DO 3.1 (see above) write getHttpURL; comicJsonResultString = getHttpURL(url); //TO DO 3.2 (see above) complete parseJson method to extract the image URL //          and invoke it here //TO DO 3.3 (write code here) Download the image

}catch(Exception e){ //TO DO 3.1a do what helps you }

return xkcdPic; }

@Override protected void onPostExecute(Bitmap s) {

//TO DO 3.1b (if you wish) display the JSON result in a text view widget for verification purpose

//TO DO 3.5 Assign it to the text view widget

}   }

} code

=Part 2 - Build the URL=

URI and URL
URI stands for Uniform Resource Identifier, which is a string to identify a resource. There are various kinds of URIs.

A URL is one form of URL, which identifies web resources.

A URL has the following format.

code format="vim" [scheme:]//[authority][path][?query][#fragment] code Most of the time, we'll be familiar with schemes of http or https for web URLs.

We seldom see the authority in URLs, it is meant to transmit user, password and the port number.

TO DO 2.1 and 2.2 - get the parts of the URL
Get the comic number from the EditText widget and store it in a string. Don't forget to strip it of leading and trailing spaces. You may want to write a regular expression so that invalid inputs are detected.

Next, based on the information given by xkcd.com, fill in the parts of the URL that do not change as static instance variables.

TO DO 2.3 - build the URL
We'll use the URI class and the associated helper class, Uri.Builder to build the URL.

You might ask why not use string operations. Using the Uri.Builder class helps when building more complicated URLs.

code format="java" Uri.Builder myUriBuilder = new Uri.Builder; myUriBuilder.scheme(xkcd_SCHEME) .authority(xkcd_BASE) .appendPath(comicNo) .appendPath(xkcd_TAIL); Uri myUri = myUriBuilder.build; code

An alternative way of building the URL is introduced in the [|textbook].

The xkcd API says that if the comic number is not provided, the latest comic is retrieved. Write code to take care of this case.

Once you have done so, you are ready to build the URL using the [|URL class]. It needs to be in a try-catch block to detect malformed URLs. Read the documentation and code accordingly.

TO DO 2.4 - Add permissions in the android manifest
You'll need to specify that the app is allowed to connect to the internet.

The following two permissions have to be added in the android manifest.

code format="xml"  

code

=Part 3 - Write the background Tasks=

Overview
Downloading data from a web API could take a significant amount of time, especially if large amounts of data are involved.

This could make your app unresponsive and it will show a "app is not responding" message after 5 seconds.

The AsyncTask class moves such tasks that don't involve the UI into a background thread.

AsyncTask abstract class and methods
A class that is meant to do work in the background will extend AsyncTask.

It has four methods that you can override. You need not override all of them.
 * onPreExecute
 * doInBackground
 * onProgressUpdate
 * onPostExecute

It is also generic abstract class that has three parameters:
 * Params - what is passed to the task
 * Progress - what is published during the background computation
 * Result - what is returned when the task completes

The AsyncTask documentation has more details.

Understanding the code
From the class definition & the code skeleton
 * what data type of inputs does GetComicTask require?
 * what type of result does it return?
 * which methods did we override?
 * how many inputs does each method take in?

TO DO 3.1 - Write the getHttpURL code
We have to ensure that fetching the data from the server is done in the background. Hence, our getHttpURL method is executed in doInBackground.

The code for connecting is given in Section 7.2 of the developer fundamentals textbook.

There are many ways of converting the input stream to a string. Here's another. If you can find a faster way, do share in the discussion below.

code format="java" String line;

BufferedReader reader = new BufferedReader( new InputStreamReader(inputStream,"UTF-8"));

while( (line = reader.readLine) != null){ output = output + line; } code

Before you move on, you may want to display the JSON results in a textView widget or in the Log. Write code for this in onPostExecute.

TO DO 3.2 - Parse the JSON
The objective of parsing the JSON result is to obtain the image URL, which we can then use to launch another http query.

Familiarise yourself with the JSON format.

Then get any xkcd query result and study the structure.

We have two ways of extracting the information.

TO DO 3.2 (option 1) - Parse the JSON using the JSONObject class
We notice that one JSON object is returned and stored in the instance variable comic.

The image URL is stored in the key called "img". code format="java" JSONObject jsonObject = new JSONObject(comicJsonResultString); imgUrlString = jsonObject.getString("img"); code

TO DO 3.2 (option 2) - Parse the JSON using the GSON library
Option 1 was easy as you only needed to extract only key-value pair.

If you'd like to extract all the information in the JSON object, Option 1 can be tedious, so we can make use of the GSON Library.

Firstly, under the "Gradle Scripts" portion, select "build.gradle (Module: app)" within the "dependencies" portion, include this line (you'll see some similar lines): code format="make" compile 'com.google.code.gson:gson:2.8.2' code

Write an inner class ComicJSON whose fields have exactly the same spelling as the keys in the JSON object. It will help if you take the JSON query and paste in an online viewer.

code format="java" private class ComicJSON{

String month; //complete the rest

} code

Next, within parseJSON, do this instead:

code format="java" Gson gson = new Gson; ComicJSON comicJSON=gson.fromJson(comicJsonResultString,ComicJSON.class); imgUrlString = comicJSON.img; code

Compared to Option 1, it has taken more code to extract one image URL. However, you now have the entire JSON object stored in your class.

TO DO 3.3 Download the Image
Once we have the image URL, we are ready to download it. We convert the string to an URL object, then call the openStream method to download the data into an InputStream object.

We then use the decodeStream method of the BitmapFactory class and store the result in a Bitmap object. The code follows:

code format="java" URL imageURL = new URL(imgUrlString); InputStream in = imageURL.openStream; pic1 = BitmapFactory.decodeStream(in); code

TO DO 3.4 Assign the downloaded Bitmap to the ImageView widget.
To do this, call the setImageBitmap method of the imageView widget object.

=Part 4 - Check for Network Connection and Execute the Tasks=

TO DO 4.1 - Determine if a network connection exists.
In the documentation on Determining and Monitoring the Connectivity Status, the code that you need is given. You'll have to adjust the code a little bit.

TO DO 4.2 - If a network connection exists then execute GetComicTask, else display an error message.
I leave this to you. You execute the GetComicTask class by getting a new instance, then calling the execute method. code format="java" GetComicTask getComicTask = new GetComicTask; getComicTask.execute(url); code

=Part 5 - Try Out Yourself/Other Things We Might Discuss =

WIFI-only connection
You may not want your app to consume data from your mobile phone plan i.e. your app should make the query only when the connection is WIFI. Write code to take care of it.

Explore the use of onProgressUpdate
Read the AsyncTask documentation to explore the use of this method. It was not possible to write to the UI within doInBackground. Can you use this method to write to the UI instead?

The comic disappears when the screen is rotated!
When you rotate the screen, the comic disappears. During a configuration change, you can use use onSavedInstanceState to store data as key-value pairs, but the documentation on handling configuration changes states that it is not designed to retain data like bitmaps.

Implement the workaround stated in the documentation. Decide which of the activity lifecycle methods you should use to reload the image after a configuration change.

Before the comic is loaded, you rotate the screen, and the comic never loads
While the data is loading, rotate your phone. What happens? Recall the lesson from the activity lifecycle that upon rotation, the screen is destroyed and rebuilt. The AsyncTask class is not able to access this newly created activity.

The AsyncTaskLoader improves upon this situation.

=For Cohort 3 Monday 9 am on 13 Nov=

My app wasn't able to access the internet because I placed the permissions tag in the wrong place.

Here's how to fix it: code format="xml"    

<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" />

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

code