Homework 6 - CatGrabber App
We will create an application that downloads cat images from the web (https://cataas.com/) and displays them as ImageViews inside a Grid, forming a sort of gallery.
The user can enter the number of cats to download, and a background service will download them and create a broadcast for each downloaded image. The UI is updated based on the received broadcasts.
1. Showing a Grid of ImageViews ( 1 pt)
Your MainActivity should include a RecyclerView, using GridLayoutManager. The GridLayout should be configured so that it has 3 columns (see app screenshot).
The contents of the RecyclerView will be filled using a custom Adapter. Create a Class called CatAdapter that extends RecyclerView.Adapter<CatAdapter.CatViewHolder>(), with a field member var imageList = MutableList<Bitmap>()
.
It should also define the ViewHolder as an inner class:
class CatViewHolder(v: View): RecyclerView.ViewHolder(v){}
Implement the onCreateViewHolder(), onBindViewHolder(), getItemCount() methods, similar to lab 4 / hw 4. For onCreateViewHolder() function, you can use the following snippet, which inflates it from a custom cat_square.xml
//onCreateViewHolder: val view = LayoutInflater.from(parent.context).inflate(R.layout.cat_square, parent, false) return CatViewHolder(view)
2. Cat Downloader Service
The user will enter a number (how many cats to grab) into the EditText, when the user clicks a button in Activity, a (started/background) service is launched (as discussed in lecture 6). The intent which launches the service should contain the EditText number value as an extra.
2.1 Downloading cats (1 pt)
Based on the number specified in the intent extra, the service should load the same no. of images from https://cataas.com/cat?width=400&height=400 - a web service which returns a random image of the specified size.
Follow these tips:
- Apps which use internet need to declare
android.permission.INTERNET
permission in their manifest. (This is a "not-dangeours" permission, so it is granted automatically.) - To download an image and decode it into a Bitmap use:
val url = URL("some_url_here") val fullBitmap = BitmapFactory.decodeStream(url.openConnection().getInputStream())
- It may be useful to scale the image before adding it to the intent extras, as we did in lab 6.
- You coulduse a for-loop, but Kotlin
repeat( n ){ //some code }
is useful in this case also.
Every download should be run as a separate coroutine! The coroutine scope should be shared between the downloads.
2.2 Broadcasting service results (2pt)
- For each downloaded Bitmap, the service should send a broadcast. The sent broadcast should include a Bitmap extra (the downloaded file). Note that extras are limited by size.
- MainActivity should register a BroadcastReceiver that can receive the broadcasts sent from the service (use an IntentFilter to identify the correct broadcasts). Upon receiving a broadcast, the Bitmap extra contained in the Intent should be added to the adapters imageList and notifyDatasetChanged() should be called.
- Tip: when attaching a Bitmap as an Intent Extra, you can read its value using getParcelableExtra(..)
Note: Broadcast intent extras are designed only to hold light data. So generally you should only attach smaller images (thumbnails, icons) to them. In this homework, we are slightly misusing this by sending several images via Broadcast Intent Extras. The proper approach would be to store the downloaded images to disk, and send a URI to the disk-stored image instead (not required for this homework).
3 Supporting different dispatchers (1 pt)
Update the App, so that using a Spinner widget, the user can select which Dispatcher the Service will use when running the coroutine, it should support these options:
- Dispatchers.IO
- Dispatchers.Default
- Dispatchers.Main
- Use Intent extras to pass the dispatcher value to the service when it is started.
- In the service, based on the value, run the coroutines in the corresponding dispatcher.
- You have to use withContext() within the Service to avoid crashes when using Dispatchers.Main.
Note: (Optional) Try to observe if you can find any behavioural difference between the IO and Default dispatchers. Read the documentation to get an idea of why this happens.
Other information
- If you are a dog person, feel free to adapt the homework accordingly, if you can find a similar webservice.
- If downloading more than ~15 cats a time causes the app to crash, it is fine.
Troubleshooting
Sometimes, it seems Emulators randomly lose internet connection. This is indicated by "x" symbols on the Wifi/4G icons on the top bar. You can verify if your emulator has internet connectivity by e.g. trying to visit a webpage from the browser. To fix it, you can try fully closing the emulator. From Android Studio AVD there is also an option for "cold boot". If you find a good solution for this, you may share it with all of us on the google group!