Skip to content

Constructors

ZieIony edited this page Oct 27, 2019 · 2 revisions

Each Android view has up to 4 constructors. In the simple scenario, you usually need only the first two.

View(Context context)

This is the code constructor. It can be called by a programmer directly from code to create a new instance of the view. Pretty simple. This constructor doesn't have access to XML attributes, so you have to fill the parameters manually, using setters.

View(Context context, AttributeSet attrs)

This is the basic XML constructor. Without it, the layout inflater will crash. The AttributeSet parameter contains all attribute values provided via XML. Let's skip accessing the attributes for now.

View(Context context, AttributeSet attrs, int defStyleAttr)

View(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)

The two other constructors are meant to be called by superclasses to provide default style via theme attribute and direct default style resource. The defStyleAttr parameter is the reference to a style attribute defined in the theme. The defStyleRes parameter is the reference to the default style defined in styles.xml. Don't worry, if this explanation is unsatisfying to you. I will go deeper into details in the section about attributes and styles. Now let's take a look at our first custom view code.

public class Button extends android.widget.Button{
  
    public Button(Context context) {
        super(context);
        initButton(null, android.R.attr.buttonStyle, R.style.carbon_Button);
    }
    
    public Button(Context context, AttributeSet attrs) {
        super(context, attrs);
        initButton(attrs, android.R.attr.buttonStyle, R.style.guide_Button);
    }
    
    public Button(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initButton(attrs, defStyleAttr, R.style.guide_Button);
    }
    
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public Button(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        initButton(attrs, defStyleAttr, defStyleRes);
    }
    
    private void initButton(AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        // ..
    }
}

This is an example of a basic, custom Button class. Notice that I'm calling corresponding super constructors in all cases. This is the safest approach because the base class can handle its own default parameters. We could also call the 3-parameter (or 4-parameter on API≥21) constructor assuming that the default style attribute of the base class won't change. That would also reduce the call stack depth.

Now the more interesting case. The following class extends TextView but requires its own style attribute. That's why it calls the 3-parameter constructor. My library runs on API<21, so I can't call the 4-parameter constructor, but on API≥21 that would be valid too. This time in Kotlin.

open class RadioButton : TextView, Checkable {

    constructor(context: Context)
            : super(context, null, android.R.attr.radioButtonStyle) {
        initRadioButton(null, android.R.attr.radioButtonStyle, R.style.guide_RadioButton)
    }

    constructor(context: Context, attrs: AttributeSet)
            : super(context, attrs, android.R.attr.radioButtonStyle) {
        initRadioButton(attrs, android.R.attr.radioButtonStyle, R.style.guide_RadioButton)
    }

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int)
            : super(context, attrs, defStyleAttr) {
        initRadioButton(attrs, defStyleAttr, R.style.guide_RadioButton)
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int)
            : super(context, attrs, defStyleAttr, defStyleRes) {
        initRadioButton(attrs, defStyleAttr, defStyleRes)
    }

    fun initRadioButton(attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) {
        // ..
    }
}

If we called the corresponding super constructors, the view would take our custom parameters via the correct attribute, but other, inherited attributes would come via the original attribute, which is incorrect. Also, it's important to note that the view's style extends the style of the base class so the base class can find its attributes as well.

How about using @JvmOverloads? It works fine in both cases but adds unnecessary calls and can lead to StackOverflow on older devices. Just like using this constructor chain. On API<21 @JvmOverloads would also incorrectly call the 4-parameter constructor, so you would have to provide 3-parameter constructor with @JvmOverloads and a plain, 4-parameter constructor. Phew, so many details for starters.

If you target API≥21, this code is much shorter and works fine as well:

open class RadioButton : TextView, Checkable {

    @JvmOverloads
    constructor(context: Context,
                attrs: AttributeSet? = null,
                defStyleAttr: Int = android.R.attr.radioButtonStyle,
                defStyleRes: Int = R.style.guide_RadioButton)
            : super(context, attrs, defStyleAttr, defStyleRes) {
        initRadioButton(attrs, defStyleAttr, defStyleRes)
    }

    fun initRadioButton(attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) {
        // ..
    }
}

Remember that if you don't plan to extend or publish the view, you can provide only two constructors.

What about custom constructors, to make your life easier? Why not. Here's a simple view class with three constructors - code, custom and XML:

public class TextView extends android.widget.TextView {

    public TextView(Context context) {
        super(context);
        initTextView(null, android.R.attr.textViewStyle);
    }

    public TextView(Context context, String text) {
        super(context);
        initTextView(null, android.R.attr.textViewStyle);
        setText(text);
    }

    public TextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initTextView(attrs, android.R.attr.textViewStyle);
    }
    
    private void initTextView(AttributeSet attrs, int defStyleAttr) {
        // ..
    }
}