Android
custom view
constructors
UI development
Android programming

Do I need all three constructors for an Android custom view?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Custom Android views often start simple and then break when used in XML, themes, or preview tools. Constructor overloads are usually the reason. You do not always need three constructors, but you do need the constructor signatures that match how your view will be created.

How Android Chooses a View Constructor

Android can instantiate a view in different ways:

  • Programmatic creation from Kotlin or Java code.
  • XML inflation from layout files.
  • XML inflation with theme style resolution.

Each path maps to a constructor signature. If your class misses a required signature, inflation can fail at runtime with an error that looks unrelated to business logic.

In Java-style custom views, the common constructor set is:

java
1public class MeterView extends View {
2    public MeterView(Context context) {
3        super(context);
4        init(null, 0);
5    }
6
7    public MeterView(Context context, AttributeSet attrs) {
8        super(context, attrs);
9        init(attrs, 0);
10    }
11
12    public MeterView(Context context, AttributeSet attrs, int defStyleAttr) {
13        super(context, attrs, defStyleAttr);
14        init(attrs, defStyleAttr);
15    }
16
17    private void init(AttributeSet attrs, int defStyleAttr) {
18        // shared initialization
19    }
20}

The key point is that all constructors call the same initialization method.

Do You Really Need All Three

A practical decision rule:

  • If the view is only created in code, context-only can be enough.
  • If the view appears in XML, you need the constructor that accepts AttributeSet.
  • If you want style defaults from theme attributes, include defStyleAttr.

Most production apps eventually need XML plus style support, so the full set is usually the safest long-term choice. It prevents future breakage when someone later adds the view to layout XML or applies a custom style.

Kotlin Pattern with @JvmOverloads

Kotlin lets you keep one primary constructor and still expose overloads for Java and XML paths.

kotlin
1class MeterView @JvmOverloads constructor(
2    context: Context,
3    attrs: AttributeSet? = null,
4    defStyleAttr: Int = 0
5) : View(context, attrs, defStyleAttr) {
6
7    private var lineColor: Int = Color.GREEN
8
9    init {
10        initialize(attrs, defStyleAttr)
11    }
12
13    private fun initialize(attrs: AttributeSet?, defStyleAttr: Int) {
14        val ta = context.obtainStyledAttributes(
15            attrs,
16            R.styleable.MeterView,
17            defStyleAttr,
18            0
19        )
20        lineColor = ta.getColor(R.styleable.MeterView_lineColor, Color.GREEN)
21        ta.recycle()
22    }
23
24    override fun onDraw(canvas: Canvas) {
25        super.onDraw(canvas)
26        val paint = Paint().apply {
27            color = lineColor
28            strokeWidth = 8f
29        }
30        canvas.drawLine(0f, height / 2f, width.toFloat(), height / 2f, paint)
31    }
32}

This approach avoids duplicated constructor bodies and keeps parsing logic in one place.

Attribute and Style Handling

If your view supports XML attributes, define them in attrs.xml and parse them once in init logic.

xml
<declare-styleable name="MeterView">
    <attr name="lineColor" format="color" />
</declare-styleable>

Use defStyleAttr when you want theme-driven defaults. That allows design systems to style the custom view globally instead of setting attributes manually in every layout file.

A strong pattern is:

  • Parse XML attributes.
  • Apply style defaults.
  • Apply hardcoded fallback defaults only last.

This order gives expected behavior across themes and app variants.

Testing Constructor Paths

Test all paths that can create the view:

  • Inflate from XML in an Activity.
  • Instantiate in code and add to a container.
  • Apply custom theme style and verify default values.

Simple instrumentation checks catch constructor regressions early, especially when refactoring legacy views.

kotlin
1@Test
2fun meterView_inflatesFromXml() {
3    val scenario = ActivityScenario.launch(TestActivity::class.java)
4    scenario.onActivity { activity ->
5        val view = activity.findViewById<MeterView>(R.id.meter)
6        assertNotNull(view)
7    }
8}

Common Pitfalls

  • Duplicating setup logic across constructors and creating inconsistent behavior.
  • Forgetting to parse AttributeSet in XML-aware constructor paths.
  • Ignoring defStyleAttr, which breaks theme defaults.
  • Missing TypedArray.recycle, which causes resource pressure.
  • Doing heavy work in constructor code instead of lightweight initialization.

Summary

  • Provide constructor signatures that match actual creation paths.
  • Route all constructors to one shared initialization function.
  • Include AttributeSet and defStyleAttr for XML and theme support.
  • Use Kotlin @JvmOverloads to keep code concise and compatible.
  • Test XML, programmatic, and theme-based instantiation paths.

Course illustration
Course illustration

All Rights Reserved.