A Kotlin-based Introduction to Compound Components on Android — Part 3
Creating awesome custom views on Android by leveraging the power of Compound Components.
Hello there, and welcome.
Series Roadmap
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.
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.
And when the share
icon is clicked?
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:
- Hide the share and info icons if no file has been selected
- 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.
- Resetting the component, a text or icon button that resets the compound component back to an empty state.
- Add more attributes, they are never too much
Code for this project can be found here, thanks for reading! Cheers!!