Differences

This shows you the differences between two versions of the page.

Link to this comparison view

smd:laboratoare:05 [2019/03/06 14:59]
adriana.draghici removed
smd:laboratoare:05 [2021/05/18 22:17] (current)
adriana.draghici [Tasks]
Line 1: Line 1:
-===== Labs 05,06Permissions ​=====+===== Lab 5HTTP and web services ​=====
  
 +==== Objectives ====
  
-=== Task 1 - List permissionspackages, features from CLI (2p) ===+  *  Learn to make secure network calls 
 +  *  Understand and practice making network calls using HttpUrlConnectionHttpsUrlConnection 
 +  *  Learn how to use an API using the Retrofit network library 
 +  *  Extra: Securing the app by integrating Firebase authentication services ​
  
-In the command line (**adb shell**): +==== HttpUrlConnection ====
-  * List all permissions currently known by the system.  +
-  * List all permission groups known by the system. +
-  * Display more information about the permissions using **-f** (defining package, label, description and protection level). +
-  * Display all permissions in **android.permission-group.PHONE**. +
-  * Display detailed information about all permissions in **android.permission-group.PHONE**. +
-  * List all packages installed in the system. +
-  * List all features of the system (hardware & software).+
  
-Hint: [[http://androiddoc.qiniudn.com/tools/help/shell.html#pm|pm commands]].+In order to make network operations in an Android application ​ [[https://​developer.android.com/​reference/​java/​net/​HttpURLConnection.html|HTTPUrlConnection]] is used, which is a specialization of [[https://developer.android.com/reference/java/net/​URLConnection.html|URLConnection]] with HTTP capabilities. This class manages the client-server communication
  
-=== Task 2 - MyCamera application ​(7p===+In order to use this class we need to follow these steps: 
 +  * Create an instance of HttpURLConnection by calling [[https://​developer.android.com/​reference/​java/​net/​URL.html#​openConnection()|openConnection()]]. The result needs to be casted HttpURLConnection. 
 +  * Add information to the request besides the URI. 
 +  * If needed, add a request body and send it using URLConnection.getOutputStream(). 
 +  * Read response from URLConnection.getInputStream(). 
 +  * Finally don’t forget to disconnect.
  
-Implement an application **MyCamera** that captures photos and saves them full-sized on the SDCARD (/​sdcard/​Pictures/​). ​ Hint: Use this [[https://​developer.android.com/​training/​camera/​photobasics.html|tutorial]] (Sections: Save the full-size photo, Decode ​scaled image). \\+Here is a simple example of a using HTTPUrlConnection to get the content of webpage into a String:
  
-The application will contain an Activity called **MainActivity** that will capture the photo and display itThe activity will include a **Button** and an **ImageView**. When pressing the Button, the camera should appear to let the user take a photo. Then, the photo will be displayed in the ImageView+<​code>​ 
 +    URL url = new URL("​http://​example.com"​);​ 
 +    HttpsURLConnection connection = null; 
 +    try { 
 +        connection = (HttpsURLConnection) url.openConnection();​
  
-Hint: Use **Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)** to obtain the directory /​sdcard/​Pictures. Also, when specifying the provider'​s external path use "​Pictures"​. +        InputStream ​is = connection.getInputStream(); 
- +        ​ByteArrayOutputStream result = new ByteArrayOutputStream(); 
-The camera ​is started using an **Intent** with the action **MediaStore.ACTION_IMAGE_CAPTURE**. In **onActivityResult**,​ if the request was successful, display the picture. +        ​byte[] buffer = new byte[1024]; 
- +        ​int length; 
-<​code>​ +        while ((length ​is.read(buffer)) !-1) { 
-    @Override +            ​result.write(buffer, 0, length);
-    protected void onActivityResult(int requestCode,​ int resultCode, Intent data){ +
-        ​super.onActivityResult(requestCode,​ resultCode, data); +
-        ​// Check which request we're responding to +
-        ​if (requestCode ​== REQUEST_TAKE_PHOTO) { +
-            ​// Make sure the request was successful +
-            if (resultCode == RESULT_OK) { +
-                setPic(); +
-            }+
         }         }
 +        return result.toString("​UTF-8"​);​
 +    } catch (IOException ​ e) {
 +        Log.e(TAG, e.toString());​
 +    } finally {
 +        if(connection != null) connection.disconnect();​
     }     }
 </​code>​ </​code>​
  
-Declare ​the necessary permissions and request ​them at runtime. You will need the dangerous ​permissions:​ **android.permission.CAMERA** and **android.permission.WRITE_EXTERNAL_STORAGE**. ​+If order for the network ​request ​to work we need to have necessary ​permissions:​ 
 +  ​For Internet access 
 +  
 +<​code><​uses-permission ​android:​name="​android.permission.INTERNET"​ /></​code>​
  
 +  * For accessing the network state. This will help us check the connectivity so that we can initiate requests
  
-=== Task 3 Custom Permissions (7p) ===+<​code><​uses-permission android:​name="​android.permission.ACCESS_NETWORK_STATE"​ /></​code>​
  
-Extend the previous application (MyCamera) by adding a new Activity called **DisplayPictureActivity** and move the **ImageView** in this activity. Also move the method that displays the picture (**setPic()**) in this activity. ​ 
  
-**DisplayPictureActivity** is started from **MainActivity** using an **Intent**, containing ​the path of the photo that was just taken as extraThe **displayPicture()** method is called from **onActivityResult**,​ after the picture has been captured and saved.+If your app needs to check the network'​s state it can use the system service ConnectivityManager - [[https://​developer.android.com/​training/​basics/​network-ops/​reading-network-state|documentation examples]]
  
-<​code>​ +If your app needs to react to connectivity changes (e.g. send a file when the network becomes available) you can use Android'​s job scheduling API called [[https://​developer.android.com/​topic/​libraries/​architecture/​workmanager|WorkManager]],​ since newer Android versions removed the system broadcast for connectivity changes. Here's an example (scenario #3) of how to use it: [[https://​medium.com/​ki-labs-engineering/​monitoring-wifi-connectivity-status-part-1-c5f4287dd57|example]]. 
-    ​private void displayPicture(){ + 
-        ​Intent intent ​new Intent(); + 
-        ​intent.setAction("com.smd.lab5.mycamera.startDisplayPictureActivity")+<note important>​Note for Android 9 and above HTTP calls will receive an IOException because cleartext network traffic is disabled. You can add your application trusted domains in a network security configuration file or to enable cleartext within the application manifest. Check [[https://​developer.android.com/​training/​articles/​security-config#​CleartextTrafficPermitted|Cleartext Traffic Permitted]]</​note>​ 
-        intent.putExtra("photoPath", mCurrentPhotoPath); + 
-        ​startActivity(intent); +==== HttpsUrlConnection ==== 
-    }+ 
 +For only //https// connections we can use [[https://​developer.android.com/​reference/​javax/​net/​ssl/​HttpsURLConnection|HttpsURLConnection]]. By default HttpUrlConnection can receive use both //HTTP// and //HTTPS// because it is a superclass of HttpsUrlConnection. When we call 
 +  
 +<code> HttpURLConnection connection = (HttpURLConnection) url.openConnection();</​code> 
 + 
 +url.openConnection() will return a HttpsUrlConnetion object that can be cast to HttpUrlConnection. ​ 
 + 
 +<​note>​ From [[https://​developer.android.com/​reference/​kotlin/​java/​net/​URL?​hl=en#​openconnection|openConnection()]] declaration javadoc: //"For example, for HTTP an HttpURLConnection will be returned, and for JAR a JarURLConnection will be returned."//</​note>​ 
 + 
 + 
 +==== Retrofit ==== 
 + 
 +[[https://​square.github.io/​retrofit/​|Retrofit]] is a type-safe HTTP Client for Android and Java. The basic functionality it is to turn the HTTP API into a Java interface.  
 + 
 +When working with retrofit you need to consider the following:​ 
 +  * How is the data transmitted. For example, one popular way is to use JSON objects. Retrofit can handle their conversion to Java objects 
 +  * A **model** for the data you need transmitted 
 +    * this is usually a class with fields that reflect the JSON schema you use in your requests 
 +  * Define the endpoints and the requests you need to make in an interface, we refer to it as **service** ​(not to be confused with Android services) 
 +  
 + 
 +Basic steps for using this library: 
 +  - Add dependencies in the build.gradle file of your project (see example below) 
 +  - Add the INTERNET permission in the manifest file 
 +  - Create the classes for your model 
 +  - Define the requests and endpoints you need 
 +     * create ​"service interfaces"​ and use [[https://​futurestud.io/​tutorials/​java-basics-for-retrofit-annotations|annotations]] such as @GET, @POST to define the operations you need. 
 +  - Create a retrofit instance and provide it the service interface 
 +  - Make calls to the api by calling the service'​s methods. 
 +     * provide callbacks to the calls in order to react to the results or failures 
 + 
 + 
 +In this section'​s code examples we're going to create a basic HTTP GET request for a weather API serviceThis will receive //​location//​ as a parameter and will return a Weather object that has all the data related to the current weather for that location. 
 + 
 +<note important>​When using Retrofit, there is no need to run the HTTP call using Retrofit in a separate thread. Retrofit already does this, creating threads for requests so that the user doesn'​t have to do this.</​note>​ 
 + 
 +As stated above in Retrofit we only need to declare our service interface. Below we declared our //​WeatherService//​ which has a //GET// request for the location weather. Retrofit uses [[https://​docs.oracle.com/​javase/​tutorial/​java/​annotations/​|Annotations]] to show how a request will be handled. In our case we simply defined in the GET request the path to the weather endpoint and the //​{location}//​ parameter using //{}//. In order to complete this we added the //​@Path("​location")// annotation before the location parameterThis way we link the method parameter to the URL parameter.  
 + 
 +<coden java> 
 +public interface WeatherService { 
 + @GET("/​api/​weather?​location={location}") 
 + Call<​Weather>​ getLocationWeather(@Path("​location"​) String location); 
 +}
 </​code>​ </​code>​
  
-**DisplayPictureActivity** must have an intent filter (with the same action) defined in the Manifest:+From here the [[https://​square.github.io/​retrofit/​2.x/​retrofit/​retrofit2/​Retrofit.html|Retrofit]] class generates ​an implementation of the WeatherService interface. When creating ​the Retrofit instance we need to pass our //​baseUrl//​. Using this and appending the requests path will create the complete URL.
  
-<​code>​ +<​code ​java
-    <​intent-filter>​ +Retrofit retrofit = new Retrofit.Builder() 
-        <​category android:name="​android.intent.category.DEFAULT" ​/> + .baseUrl("​https://my.weather.com/") 
-        <​action android:​name="com.smd.lab5.mycamera.startDisplayPictureActivity"​ /> + .build(); 
-    </​intent-filter>​+ 
 +WeatherService service ​retrofit.create(WeatherService.class);
 </​code>​ </​code>​
  
-In **onCreate()** method of **DisplayPictureActivity**,​ get the Intent and extract ​the photo path from the IntentDo NOT call **setPic()** from **onCreate()** because ​the ImageView does not have a width and height yet**setPic()** must be called from **onWindowFocusChanged**:​+Using the service we can make the [[https://​square.github.io/​retrofit/​2.x/​retrofit/​retrofit2/​Call.html|Call]] that will make synchronous or asynchronous HTTP request to the remote webserver.
  
-<​code>​ +<​code ​java
-    ​@Override +Call<​Weather>​ weatherCall = service.getLocationWeather("​Bucharest"​);
-    public void onWindowFocusChanged(boolean hasFocus) { +
-        super.onWindowFocusChanged(hasFocus); +
-        if (photoPath != null){ +
-            setPic(); +
-        } +
-    }+
 </​code>​ </​code>​
  
-If the Intent does not contain an extra, use this method ​to obtain ​the path of the last photo in the folder.+In order to make the call and get the result ​in our app will need to call [[https://​square.github.io/​retrofit/​2.x/​retrofit/​retrofit2/​Call.html#​enqueue-retrofit2.Callback-|enqueue()]]. This sends asynchronously the request and notifies the application through ​the //​onResponse()//​ callback when a response is received or through //​onFailure()//​ callback if something goes wrong. This call is handled on a background thread by Retrofit
  
-<​code>​ +<​code ​java
-    ​public String lastFilePath() { +weatherCall.enqueue(new Callback<​Weather>​() { 
-        File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);​ + @Override 
-        File[] files = storageDir.listFiles(new FileFilter() { + public ​void onResponse(Call<​Weather>​ call, Response<​Weather>​ response) { 
-            public ​boolean accept(File file) { + displayWeather(response.body()); 
-                ​return file.isFile(); +
-            + 
-        }); + @Override 
-        long lastMod = Long.MIN_VALUE;​ + public void onFailure(Call<​Weather>​ call, Throwable t) { 
-        File choice = null; + Toast.makeText(MainActivity.this, "​Something went wrong. Please try again later!",​ Toast.LENGTH_SHORT).show(); 
-        for (File file : files) { +
-            ​if ​(file.lastModified(> lastMod) { +}
-                choice = file; +
-                lastMod = file.lastModified(); +
-            +
-        } +
-        return choice.getAbsolutePath();​ +
-    ​}+
 </​code>​ </​code>​
  
-Add a custom permission in the Manifest file of **MyCamera** (permission tree, permission group and the actual permission)The permission must have the protection level **dangerous**In the Manifestrequest the new permission ​for starting the **DisplayPictureActivity** (**android:permission**)+If the service responds with [[https://​www.json.org/|JSON]] objectsRetrofit can help deserialize them by using //​converters//​. A popular JSON library ​for Java is [[https://​github.com/​google/​gson|GSON]]. If you are using Kotlin we recommend  
 +[[https://​github.com/​square/​moshi|Moshi]]
  
-Create another application **UseMyCamera**. In the activity include a **Button** which will be used to start the activity of the application MyCamera+The GSON library helps you convert JSON responses into [[https://​en.wikipedia.org/​wiki/​Plain_old_Java_object|POJO]]s. In order to use this we need to add a [[http://​square.github.io/​retrofit/​2.x/​converter-gson/​retrofit2/​converter/​gson/​GsonConverterFactory.html|GsonConvertorFactory]] when creating the service:
  
-For this, we will use an implicit intent for launching ​the **DisplayPictureActivity** from the first application:+<code java> 
 +Retrofit retrofit = new Retrofit.Builder() 
 + .baseUrl("​https://​my.weather.com"​) 
 + .addConverterFactory(GsonConverterFactory.create()) 
 + .build();​ 
 + 
 +WeatherService service = retrofit.create(WeatherService.class);​ 
 +</​code>​ 
 + 
 +In order to add the two libraries to our application ​we need to add them as dependencies in the //​app/​build.gradle//​ configuration file.
  
 <​code>​ <​code>​
-Intent i = new Intent(); +dependencies { 
-i.setAction("​startDisplayPictureActivity"​); ​ +    ​implementation 'com.squareup.retrofit2:​retrofit:​2.9.0'​ 
-startActivity(i);​+    implementation '​com.google.code.gson:​gson:​2.8.6'​ 
 +}
 </​code>​ </​code>​
  
-Run the application. ​When pressing ​the button you should get SecurityException ​(Permission Denied). For solving ​thisuse the declared permission ​in the manifest ​of the second ​application. ​Also, you need to request ​the permission at runtime because it is dangerous permission.+If you plan to use Moshi, here's a Kotlin example (although Moshi supports Java too): 
 +<code java> 
 +val retrofit: Retrofit = Retrofit.Builder() 
 +        .baseUrl("​https://​my.weather.com"​) 
 +        .addConverterFactory(MoshiConverterFactory.create()) 
 +        .build() 
 +</​code>​ 
 +<​code>​ 
 +apply plugin: '​kotlin-kapt'​ 
 + 
 +dependencies { 
 +    implementation '​com.squareup.retrofit2:​retrofit:​2.9.0'​ 
 +    implementation '​com.squareup.retrofit2:​converter-moshi:​2.9.0'​ 
 +    implementation '​com.squareup.moshi:​moshi-kotlin:​1.12.0'​ 
 + 
 +    kapt "​com.squareup.moshi:​moshi-kotlin-codegen:​1.11.0"​ 
 +
 +</​code>​ 
 + 
 +<​note>​Kotlin example for retrofit on our lab's repo: [[https://​github.com/​SMD-UPB/​labs/​tree/​main/​lab5-demos/​retrofit-demo-kotlin|retrofit-demo-kotlin]]</​note>​ 
 +==== Tasks ==== 
 + 
 +=== Task 0 - Init === 
 + 
 +Create a new Android application project for API version >= Oreo.  
 + 
 +Download the lab archive and replace the content of activity_main.xml and AndroidManifest.xml with the one provided in the archive. 
 + 
 +=== Task 1 - Fetch web page through HTTP (3p) === 
 + 
 +In this task we will download a web page content and display it.  
 +  * Use the EditText to enter the webpage URL 
 +  * Use the TextView to show the result  
 +  * Use the Button to initiate the request.  
 +  * Use HttpURLConnection to make the network request.  
 +  * Use Runnable to do work on another thread. 
 + 
 +Try different websites using //http// or //https//. Are there any differences?​  
 + 
 +Remove from the manifest inside ​the application ​tag the attribute //​android:​usesCleartextTraffic="​true"//​Run the app again and make the webpage request? What happens? 
 + 
 +=== Task 2 - Fetch web page through HTTPS (1p) === 
 + 
 +Update the code from Task 1 in order to use //​HttpsURLConnection//​. Add the WIFI connectivity check before making the call. 
 + 
 +Try different websites using //http// or //https//. Are there any differences?​ 
 + 
 +=== Task 3 - Retrofit call (4p) ===  
 + 
 +Using the code examples from the lab change the HttpURLConnection network call with Retrofit implementation. The webpage URL will be set as a base URL for the Retrofit instance. 
 + 
 +<note hint>Use @GET("​."​for the service call.</​note>​ 
 +<note hint>Use //​ResponseBody//​ as a return object from the call. In order to get the webpage content call //​response.body().string()//​.</​note>​ 
 + 
 +=== Task 4 - Retrofit call to API (2p) === 
 + 
 +For this task will use the [[http://​open-notify.org/​Open-Notify-API/​|Open Notify]] API which gives information about the [[https://​en.wikipedia.org/​wiki/​International_Space_Station|ISS]]. 
 +From this API will use http://​api.open-notify.org/​astros.json which gives us the current number of people ​in space. Using the lab code examples create a Retrofit service for this URL. Make a call to get the number ​of people in space and return a POJO matching ​the JSON file structure. Make the request to the API and present the current number of people in space in a TextView. 
 + 
 +<note hint>The POJO object should contain only the attributes that we need in our application.</​note>​ 
 + 
 +=== Optional ===  
 +=== Task 5 - Firebase Authentication with Email address and Password (2p) === 
 + 
 +We want to have a more secure application so that our users need to log in to see their data. In this task we will implement sign up and sign in using [[https://​firebase.google.com/​docs/​auth/​android/​password-auth|Firebase Authentication with Email address and Password]]. In order to add this to the application go to //​Tools->​Firebase//​ and new window will appear. Go to //​Authentication//​ and follow the steps presented there. Use the activity_onboard_layout.xml file from the lab archive as a layout for the signup/​signin activity
  
-=== Task 4 - Boot completed (4p) ===+=== Resources ​===
  
-Extend ​**UseMyCamera** application,​ to receive BOOT COMPLETED broadcast messageImplement ​broadcast receiver for the action **android.intent.action.BOOT_COMPLETED**. In order to receive those messages, you need to declare the permission **android.permission.RECEIVE_BOOT_COMPLETED** in the Manifest.+   {{:​smd:​laboratoare:​lab5_skel.zip|}} 
 +   [[https://​github.com/​SMD-UPB/​labs/​tree/​main/​demos|Demo app for Retrofit shown during ​lab]]
  
  
smd/laboratoare/05.1551877144.txt.gz · Last modified: 2019/03/06 14:59 by adriana.draghici
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0