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

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

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

--

Hello there, and welcome.

Series Roadmap

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

This is the third part in a three-part series tutorial on using Compound Components in Android. Confused? you might want to check out the previous articles for more clarity.

FileDescriptor

We have successfully completed the core functionality our Compound Component is supposed to exhibit, which happens to be, File Selection, File Metadata Retrieval. At this point, we are done, but I would like to introduce you to one more concept in Custom Views on Android, and that is Custom Attributes.

Custom Attributes

The concept of attributes in Android is one that is widely common, we use attributes to define specific behaviour for Views present in the Android design system. Attributes help us to set customize our Views to look different from the defaults given to us by the plain old View itself. Examples are the textColor, textSize, background, backgroundTint attributes and so much more.

The concept is not so different when it comes to Custom Views in android, just that we would create and manage its’ usability ourselves. Without further ado, let’s jump right in!

What we are going to be doing is defining attributes to specify if the info section of our compound component is to be displayed by default (it’s visible by default), this gives the user the liberty to specify if the view should be visible or hidden. The second attribute would specify the background of the compound component itself, this would be a drawable resource. Up until this point, we used a rectangular drawable with a grey-like background.

Defining Custom Attributes

Start by navigating to res > values folder, and create a new Values Resource File called attr.xml to store the attributes to be defined for this Compound Component. Below is the code defining the attributes for the compound component.

In the attr.xml file, we create a declare-styleable tag, this tag is used to define, or in this case, “declare” attributes for a View that can be styled, or a View that is styleable, get it?. So, this tag picks up a certain Custom View and takes note of the attributes declared in here and allows the Custom View to be styled using those attributes in XML, and also retrievable in code. The declare-styleable tag has a name attribute that defines the styleable Views whose attributes are to be declared. In this case, we go with FileDescriptor.

Inside the tag, we have to define the attributes that would be usable on our Custom View, we make use of the attr tag. The attr tag has two very important attributes, the name and format. The name attribute accepts the name we want to give the attribute (we should follow android’s naming convention for naming identifiers/attributes defined here), and the format specifies the type of value the attribute would accept, this can be a color, string, integer, reference (to a resource), boolean, dimension or a float. Specifying the format this attribute requires enables android to know the set of values that can be entered into this attribute.

Two attributes are declared, showInfoByDefault and viewBackground. The former’s format is set as a Boolean since we would be needing a true or false value to determine the info section’s visibility. The latter demands a reference to a resource which is expected to be a drawable.

Now that all of that attribute setup is done, we need to get the attributes in code and use them as needed. That’s done in the code below.

In the code above, we start by declaring variables that hold the default values for the attributes to be used, even when they are not specified explicitly. We assign false to showInfoByDefault and R.drawable.round_file_descriptor_background to viewBackground. By doing this, we have our default values for the attributes set up.

Normally, attributes are set up when the view is created, meaning, the attributes provided to a View are applied immediately. Since that’s the goal, we can set them up in the init block. Android’s Context object ctx provides an obtainStyledAttributes method that takes in two parameters, an AttributeSet object and a reference pointing to the style declaration for our Custom View.

Clarifying further, it’s important to understand that every attribute declared for a View is packaged into the AttributeSet object, keep in mind that this includes our layout_height, layout_width, layout_weight, textSize and what not. While our view is being inflated, Android retrieves all the defined attributes, packages them and send them over to the inflated object for proper instantiation. As you can see in FileDescriptor class definition, we pass that same attributeSet object into the ConstraintLayout class, this is so that the class can also manage attributes specific to it, or it’s parent.

But one thing is missing, the ConstraintLayout, ViewGroup or View implementations don’t know of our attribute declarations, so it is useless thinking that they can manage the attributes for us. Well, since we declared them for this view, it has to be managed in this view.

Essentially, passing in the attributeSet and a reference to the styleable declaration denotes that all attributes present in the attributeSet object and also happens to be defined in R.styleable.FileDescriptor should be retrieved. This means if attributes xyz is defined in XML (layout) and XML (attr), it should be returned by Android. Whatever is returned is stored in a local attributes object.

Now that we have the attributes, we have to identify which attribute is which. To achieve this every attribute has an ID attached to it, but we did not define any, so where does this come from?

It so happens that by default, the ID for every declared attribute is the name of the Custom View and the name of the attribute separated by an underscore. So, the ID for our name attribute is FileDescriptor_name, this is so that we don’t end up having clashes between attribute names (namespacing), of course one Custom View cannot have two attributes of the same name declared.

There are helper methods on the attributes object that help in getting the values of the set attributes as long as the ID is passed in. We call getBoolean on the attributes object passing the ID of the name attribute, and a default value. The default value is returned if the attribute was not set initially. We then return the Boolean value and store in showInfoByDefault. The same is done for viewBackground, just that this time, we are getting a resource. Now that we have gotten and stored the values of our attributes, we have to call recycle() on the attributes object to avoid memory leaks.

We then go ahead to set the background of the compound component to viewBackground (remember this defaults to R.blah.blah if none is set), and the visibility of the info section is determined by the showInfoByDefault variable. We can also use this time to set a click listener on the file_info toggle, when this is clicked, we invert the value of showInfoByDefault, and reset the visibility of the info section. Great!

The next step is to set the attributes up in the activity_main itself. So we go…

In the code above, we set the showInfoByDefault attribute to true, this automatically shows the info section and the viewBackground is set to a drawable resource. Running the app, we come up with this.

demo for compound components after add different drawable for background as attributes and different state for the info section

In the image above, the background drawable set as an attribute takes effect and the extra space towards the bottom is the info section showing by default.

Finally, we need to add support to share the file’s information to other applications, we go on to add a new method shareDescription which makes sure the file object isn’t null before creating a share intent object. This is just a regular intent object with action set to Intent.ACTION_SEND and an extra attached to it which is the text to be shared. We then need to state the type of the data being shared to be text/plain, then the intent is launched, and we return from the block of code, if it happens to be null, we display a toast requiring a file to be selected initially.

Running the app, we go on to select a video from the File system and the data is retrieved as programmed, clicking on the share icon brings up the sharing options android provides for the type of data that is to be shared, and selecting any of those consumers shares the data appropriately.

Left: demo app showing details about a selected video and the info section visible, Right: demo showing the collapsed version of the compound component

And when the share icon is clicked?

Left: Android’s sharing provider giving a grid of options to which text/plain can be shared to, Right: The data being shared to Whatsapp

Next Steps

We’ve come to the end of the series on Compound Components on Android. Well, it was a roller coaster ride and not so easy, new concepts flying here and there, but if you have any bugging questions, feel free to ask in the comments section or send me a DM on Twitter, or tag me in any of your tweets relating to the subject matter. Well, this component is not perfect as you can keep adding features to it. Some of them you can experiment with are:

  1. Hide the share and info icons if no file has been selected
  2. Embed the file selection process inside the component — we do it from outside the component with the Select File button in the activity_main file, you can find a way to put the functionality inside the component itself.
  3. Resetting the component, a text or icon button that resets the compound component back to an empty state.
  4. Add more attributes, they are never too much

Code for this project can be found here, thanks for reading! Cheers!!

--

--

Fouad Olaore
Programming Geeks

Software Engineer. Speeding up manufacturing with code at Protolabs.