How to extend FrameLayout (with constructors that require different minSdk)


#1

I’m trying to extend android.widget.FrameLayout in my class in kotlin. The problem is that the longest(4 arguments) constructor of FrameLayout requires minSdk 21, but my lib needs to work on api level 19 as well. So I’m trying to do the following but in kotlin:

class PullDownAnimationLayout extends FrameLayout
        implements Animator.AnimatorListener, PullDownAnimation {

    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    PullDownAnimationLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        initializeStyledAttributes(attrs);
    }

    PullDownAnimationLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr)
        initializeStyledAttributes(attrs);
    }

    PullDownAnimationLayout(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0)
    }

    PullDownAnimationLayout(Context context, @Nullable AttributeSet attrs) {
        this(context, null)
    }

    private void initializeStyledAttributes(@Nullable AttributeSetattrs) {
        // ...
    }

Here’s my 1st way I tried:

class PullDownAnimationLayout : FrameLayout, Animator.AnimatorListener, PullDownAnimation {
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) :
            super(context, attrs, defStyleAttr, defStyleRes)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
            super(context, attrs, defStyleAttr)
    constructor(context: Context, attrs: AttributeSet?) :
            this(context, attrs, 0)
    constructor(context: Context) :
            this(context, null)

    init {
        initializeStyledAttributes(attrs) // ERROR: Unresolved reference: attrs
    }
    private fun initializeStyledAttributes(attrs) {...}

Unfortunately init doesn’t see attrs because there’s no primary constructor. So I tried to add one:

class PullDownAnimationLayout(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
            FrameLayout(context, attrs, defStyleAttr), Animator.AnimatorListener, PullDownAnimation {
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) :
            super(context, attrs, defStyleAttr, defStyleRes) // ERROR: Primary constructor call expected
    constructor(context: Context, attrs: AttributeSet?) :
            this(context, attrs, 0)
    constructor(context: Context) :
            this(context, null)

    init {
        initializeStyledAttributes(attrs)
    }
    private fun initializeStyledAttributes(attrs) {...}

The problem here is that I need to call the 4-args constructor’s super that is only available in minSdk 21, but I get an error that I need to call the primary constructor.


#2

You will want to not use any primary constructor, but only secondary constructors (there is a quickfix on the primary to make it secondary that will do this). If you don’t have a primary, all secondaries can call a different super-constructor. This is also what happens if you convert such a class from Java.


#3

Did you try to convert the Java file to Kotlin. Because I am also using FrameLayout as Custom View in my code and I am not facing any issue constructor.

Can you please try to use it like that:

class PullDownAnimationLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
    defStyleAttr: Int = 0) : FrameLayout(context, attrs, defStyleAttr) {

init{
initializeStyledAttributes(attrs)
}

// your code
}

#4

if there’s no primary constructor then init doesn’t see attrs


#5

Yes, I tried this. This almost works. There are only 2 problems with this:

  1. it doesn’t give the functionality of api 21+ with the 4th argument in FrameLayout. That was the whole point of the question.

  2. For some reason when I use the @JvmOverloads annotation then Instant Run fails when I’m in an activity that uses this layout. The only way I managed to fix that is that I manually wrote 1 primary constructor (like yours, but without the annotation and with all 3 parameters required [w/o default values] and 2 secondary constructor that basically does the same as the calls to pass 0 and null. Basically it’s the same as my last example in the question, only w/o the 4 argument secondary constructor


#6

You will want to do init the java way (with a regular method). What you want can only be done without primary constructors.


#7

OK, so I guess I’ll need to change my val-s that I would set from init (and use the attrs value) to var-s.

Also, what does that mean that I don’t have a primary constructor? Does it mean that there’s also a constructor that has no arguments (not even context)?


#8

First of all, as long as you define a constructor there will not be a no-arg constructor created. Basically Kotlin supports two approaches to construction, one with primary constructor and secondary constructors, one the Java way with multiple constructors that can delegate to the superclass directly.

It may mean indeed that you need to use lateinit var instead of val to avoid code duplication. I don’t think you could avoid non-final fields in Java either. Unfortunately the needs of binary compatibility make ugliness unavoidable (probably why Kotlin has both lateinit and classes without primary constructor).