Lab 6 - Background Threads, AsyncTask
Create a new Android Studio project with a blank MainActivity.
To get you started, copy the code from MainActivity.kt into your MainActivity and from main_activity.xml to your view XML.
The MainActivity UI has a Spinner (a dropdown), an ImageView, and 3 buttons. The code also takes of permission management for you.
Your task is to display a list of files in the applications external storage directory in the spinner. When a file is selected in the spinner, then clicking a button will show the file in the imageview, using different methods (depending on which button was pressed).
1. Setting up Spinner and Files
The below code should be written inside setUpFileSpinner() function
- To get the app-specific external storage folder for pictures, you can use
getExternalFilesDir(Environment.DIRECTORY_PICTURES)
, which returns a object of type File. (The same File type is used for both files & folders)- To do this, you need permission android.permission.READ_EXTERNAL_STORAGE in your manifest file
- Until you call the getExternalFilesDir(..) above, this folder won't be created. After it gets created, initially, this folder will be empty. Push some image files to it using Android Studio's Device File explorer.
- with the above File object, you can call list(), which returns a list of filenames
- Assign the above two objects to the lateinit vars directory, fileNameList defined at the start of MainActivity.kt
- It's a good idea to try and log the exact directory of the pictures folder, e.g. you can call getAbsolutePath() on File objects.
- We will attach the fileNameList to the spinner using an array adapter
- Create an ArrayAdapter<String>, using 3 arguments:
- Activity context (this)
- The layout resource
android.R.layout.simple_spinner_dropdown_item
- filename list object we acquired by calling list() above
- Set the spinners adapter to the arrayAdapter
<<my_spinner_id>>
.adapter =<<myAdapter>>
- Create an ArrayAdapter<String>, using 3 arguments:
You should now be able to see files in your apps external Pictures folder appearing in the spinner
2. Loading image into ImageView
Let's first read the file and display it on the UI thread.
inside setupMainThreadButton(), set a click listener to the button with id button_ui
which:
- gets the element from the fileNameList using spinner.selectedItemPosition as index
- Creating a File object of the selected element by resolving the full path to the selected file with directory.resolve(fileName)
Now, we have the chosen picture file, let's read it from the storage.
- Create a new function loadImage(), that takes as argument a File and returns a Bitmap - which is a class for representing images.
- The file object can be read from storage into a Bitmap using
BitmapFactory.decodeStream(<<myFile>>.inputStream())
- Since very large images can cause crashes, let's scale the image down before returning it:
- The file object can be read from storage into a Bitmap using
val ratio = fullBitmap.width.toDouble() / fullBitmap.height val scaledBitmap = Bitmap.createScaledBitmap(fullBitmap, (800*ratio).toInt(), 800, false)
- return the scaled bitmap.
- Update the clickListener defined inside setupMainThreadButton() so that it passes the File to the loadImage() function.
- Display the result of loadImage() in ImageView, using
picture_view.setImageBitmap(bitmap)
Run the application and verify that you can see the picture appear in the ImageView
2.1 Running in background thread
Finish the code for setupWorkerThreadButton(), setting up a click listener for button button_worker
, loading & displaying the image, except this time, call loadImage() inside a background thread.
- Threads can be created with
val myThread = Thread { // do some work }
- To run the thread, you have to call
start()
, e.g. myThread.start() - To update the UI from inside the thread, you can use
<<activityContext>>.runOnUiThread { // some work }
or<<some_ui_view_object>>.post { // some work }
2.2 Running in AsyncTask
Finish the setupAsyncTaskButton(), which should set a clickHandler that runs an AsyncTask to load the image.
- Define an inner class that extends AsyncTask. We are using type File for input of doInBackground(), and type Bitmap as the output of the background task, which in turn is used as input for onPostExecute()
inner class MyAsyncTask : AsyncTask<File, Void, Bitmap>(){ override fun doInBackground(vararg params: File?): Bitmap { // do work and return bitmap (asynctask runs this on background thread) } override fun onPostExecute(result: Bitmap?) { // display image in ui (this runs on ui thread) } }
- Run the AsyncTask in the click handler by calling
MyAsyncTask().execute(imgFile)