A Kotlin-based Introduction to Compound Components on Android — Part 2

Creating awesome custom views on Android by leveraging the power of Compound Components.

Fouad Olaore
Programming Geeks
Published in
8 min readJun 29, 2020

--

Series Roadmap

  1. Part 1
  2. Part 2 (you’re here)
  3. Part 3

Hello there, and welcome back. This is the second part in a three-part series tutorial on using Compound Components in Android. If this is your first time here, you might want to check out the first part here.

In the previous part, we successfully displayed our custom view on-screen and we came up with this.

Last point in the previous part, compound component was displayed.

FileDescriptor

In the previous article, we explained the component we would be building, a file descriptor that extracts basic information from a selected file present on the Android File System. So the next step would be adding the functionality to actually select the file.

To that effect, we would be adding a button to the MainActivity that triggers Android’s default file chooser, which allows the user to select a file of interest. The selected file is then passed into the FileDescriptor. Let’s jump right in!

The above layout produces the output below:

demo showing the button added to top of the FileDescriptor
demo showing the button added to the top of our file descriptor

Next, we want to set up a click listener on the button in the MainActivity, we then define the method that launches the file chooser.

Below is the implementation of the selectFile() method invoked on-click of the button.

In the implementation above, we essentially construct an Intent with an action of ACTION_GET_CONTENT. This action informs the android system that content of some kind is to be retrieved by this intent. Next, we set the type of the content to be gotten. Most times, you might want to get a specific type of file, be it a document, media of some sort, or whatever.

These are all MIME types, and they do well to differentiate between the file types and the extensions of the file type. For example, image/* refers to every type (extension) of image, while image/jpeg, image/png and image/gif refers to specific extensions of the image file type.

In essence, using */* says to the android file system: Get me any file type with any extension. So, this dictates to the android file system that this intent is interested in every type of file present on the file system. After setting the type, we can then launch the activity to get the content by calling startActivityForResult and await the arrival of the content.

In onActivityResult, we perform a check to see if the requestCode is the same as the one the intent was launched with 1, and if the result is in good condition. After that, we make sure the data returned is non-null, and if so, we retrieve the URI of the file by getting the data property of the data returned. The data is then stored in our fileUri local variable.

Everything looks good, but we have no way to inform the FileDesriptor of the file that was selected. Well, we can do that by passing the variable into the FileDescriptor to notify the component of the file’s existence.

Firstly, we start by creating an enum class that holds constants for the types of files we would want to manage with our FileDescriptor. We have constants for most file types and an UNKNOWN constant for files we would not be accounting for.

We created a mutable fileUri property that allows us to set the URI of the file we would be describing. A custom setter is then set up to immediately call the setUpFileDescriptor function once a fileUri is set. A File and FileType object is also created, the former is used to store the file located at the URI set up by the file descriptor locally in our component so every needed piece of data can be gotten at one source, while the former denotes the type of file we have set up. Then the setUpFileDescriptor method is also declared.

So, we create a new method called setFile, and call it from the setUpFileDescriptor method, this method is used to query the Android’s content provider for the whole File object associated with the fileUri in the FileDescriptor. This allows us to get accurate data about the File to be displayed in the component.

This method starts by setting up the columns to be queried from the Content Provider, in this case, we would be going with just one — MediaStore.Images.Media.DATA, this gives us basic descriptive data present in the file. We then run a query on the content provider using the fileUri passed in the columns of data to be retrieved. We pass null into the rest of the parameters since we would not be providing any selection clauses or arguments into the query. The query then returns a cursor that holds a reference to the File’s data.

After making sure the cursor isn’t null, we move to the first entry in the cursor. We then need to get the index of the data column we requested for in the whole entry, hence the call to getColumnIndex(), that should return the index of the column that contains our data. To correctly instantiate our File object, we need to get the fully-qualified path, the path is present in the DATA column, so we can get the String value present at the DATA column index in the entry. With that, we get the filePath and the File object is then instantiated with the filePath as a parameter. And that’s it for the File setup.

Now that we have our file, we need to set up the correct image (thumbnail) to be displayed depending on the file type, but let us start by defining the logic for the thumbnail selection.

The required images for thumbnails are already in the drawables folder.

Essentially, we declare a private function called setUpFileTypeImage and using a when statement, we correctly determine the drawable to be displayed by the compound component depending on the FileType set by the File object. And if the FileType isn’t recognized, we use the same drawable used when it is in an UNKNOWN state.

That done, we need to actually retrieve the FileType. Fortunately, Android’s content resolver API provides a helper getType method that returns the MIME type associated with our file.

We create a new method that starts by getting a reference to the content resolver android provides, then we run getType on the resolver while passing in the fileUri to denote what file whose type we want to retrieve, the returned type is then stored in the variable type.

Using a basic if statement, we run through the MIME types that we support and if the type returned by the content resolver contains any of the MIME types for the supported files, we go on to set the FileType appropriately.

When that is done, we call setUpFileImage, which does the job of setting up the mini-image denoting the type of file that was selected, then we can call setUpFileType from our setFile() function. So our setFile function looks like this.

Before we move on, now that we have put up the logic to get the file, process and store it’s reference, we can now link the fileUri returned by the FileChooser to the FileDescriptor by doing:

Since our File object is set up, we would need to set up the part of our Compound Component that holds description about the selected File. There exists very useful properties on the File object that provides us with most of the descriptive data that we need. Below is the code that sets up data in the views present in the compound components.

Initially, we start by making sure the File object is not null, and if so, we construct a multi-line string that describes certain details about the file by calling required methods on the File object. After running the app, and selecting the file we are interested in, we get this as a result.

demo displaying the file’s info and name and also selecting correct icon for the file selected from the file system.
demo showing the file selection after initial setup

Now, our compound component is looking pretty good, but the details in the component don’t really provide optimal user experience, the size of the file can be viewed in bytes and the last modified date is a Long value. Therefore, we can define extension functions on the Long data type to modify the data as needed.

The implementation for the date formatting and bytes conversion is provided above, after doing this, our setUpFileDescriptor method can be modified as such:

Running the app, and reproducing the steps for the file selection gives this result:

compound component displaying file data after date and size values have been properly formatted.

Next up, we would need to display a small preview of the image (we surely don’t want the big unknown image on all our files right?). To achieve this, there are varying approaches depending on the type of file whose thumbnail we try to retrieve. So as to not add to the complexity of this series, we would be manually getting the thumbnail of images and videos, but just displaying plain images for other types of file. To that effect, we create an object called ThumbnailGenerator that handles creation of thumbnails for images and videos. Well, for video thumbnail creation, we would need the Glide image loading library, so you can start by adding this dependency in your app-level build.gradle file.

// Glide dependency
implementation 'com.github.bumptech.glide:glide:4.11.0'
kapt 'com.github.bumptech.glide:compiler:4.11.0'

We can now define our ThumbnailGenerator object as such:

The helper functions simply use the Glide library in case of videos, and android’s provided getBitmap method for images.

Back in our FileDescriptor, we need to create the method that effectively retrieves the thumbnail. We then define a retrieveThumbnail method and call if from the setUpFileDescriptor method like this:

What this method does is, depending on the type of file, it either forwards the request to get the thumbnail (image/video) from the ThumbnailGenerator or sets the provided image if the file type is none of the aforementioned. After running the application, and selecting various file types, we are presented with the following:

compound component displaying details for an audio and an image file
compound component displaying details for a PDF document and a video.

Finally, we have achieved the main aim of our compound component, and we correctly display details about a selected file no matter the file type, pretty cool!. You do deserve a round of applause for coming this far.

👏

Well, you’d notice that we still have two main features to implement: sharing of the file’s data through various media, and toggling the visibility of the information section of the compound component, head on to the third part of this article where we deal with Custom Attributes and Sharing In Android.

Big Ups!!

`

--

--

Fouad Olaore
Programming Geeks

Software Engineer. Speeding up manufacturing with code at Protolabs.