Elye
Elye

Reputation: 60061

Kotlin: How to access the Attrs for a CustomView

I create a custom view in Kotlin, and would like to access it's Attributes Resource.

Below is my code

class CustomCardView : FrameLayout {

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    init {
        LayoutInflater.from(context).inflate(R.layout.view_custom_card, this, true)

        if (attrs != null) {
            val a = context.obtainStyledAttributes(attrs, R.styleable.custom_card_view)
            if (a.hasValue(R.styleable.custom_card_view_command)) {
                var myString = a.getString(R.styleable.custom_card_view_command)
            }
        }
    }
}

Note that this will error in the attrs in the init function. I'm wondering how to access the attrs?

Upvotes: 25

Views: 21218

Answers (5)

Saba
Saba

Reputation: 1541

You can use core-ktx extension function withStyledAttributes(https://android.github.io/android-ktx/core-ktx/androidx.content/android.content.-context/with-styled-attributes.html)

context.withStyledAttributes(set, R.styleable.custom_card_view) {
    val myString = getString(R.styleable.custom_card_view_command)
}

Upvotes: 7

mlthlschr
mlthlschr

Reputation: 174

Why don't you simply skip these verbose constructors with default values and do it like that:

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

init {
    inflate(context, R.layout.view_custom_card, this)

    attrs?.let {
        val typedArray = context.obtainStyledAttributes(it, R.styleable.custom_card_view)
        val myString = typedArray.getString(R.styleable.custom_card_view_command)
    }
}

Upvotes: 6

methodsignature
methodsignature

Reputation: 4392

This is a bit verbose, but should work as expected under all conditions:

import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.FrameLayout

class CustomCardView: FrameLayout {

    constructor(context: Context) : super(context) {
        initialize(context, null)
    }

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
        initialize(context, attrs)
    }

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        initialize(context, attrs)
    }

    private fun initialize(context: Context, attrs: AttributeSet?) {
        LayoutInflater.from(context).inflate(R.layout.view_custom_card, this, true)

        attrs?.let {
            val a = context.obtainStyledAttributes(it, R.styleable.custom_card_view)
            if (a.hasValue(R.styleable.custom_card_view_command)) {
                var myString = a.getString(R.styleable.custom_card_view_command)
            }
        }
    }
}

Upvotes: 1

jpmcosta
jpmcosta

Reputation: 1780

Adapting your code, I think you can also do something like this:

class CustomCardView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
        FrameLayout(context, attrs, defStyleAttr) {

    constructor(context: Context) : this(context, null)

    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)


    init {
        LayoutInflater.from(context).inflate(R.layout.view_custom_card, this, true)

        if (attrs != null) {
            val a = context.obtainStyledAttributes(attrs, R.styleable.custom_card_view)
            if (a.hasValue(R.styleable.custom_card_view_command)) {
                var myString = a.getString(R.styleable.custom_card_view_command)
            }
            a.recycle()
        }
    }
}

Upvotes: 1

Michael
Michael

Reputation: 54705

You cannot access a secondary constructor parameter from an init block. But there're at least two ways how you can implement similar functionality.

The first approach is using a single primary constructor with default parameters instead of multiple secondary constructors. In this case you have to apply the @JvmOverloads annotation to the constructor in order to make Kotlin generate three different constructors.

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

  init {
    LayoutInflater.from(context).inflate(R.layout.view_custom_card, this, true)

    if (attrs != null) {
      val a = context.obtainStyledAttributes(attrs, R.styleable.custom_card_view)
      if (a.hasValue(R.styleable.custom_card_view_command)) {
        var myString = a.getString(R.styleable.custom_card_view_command)
      }
    }
  }
}

An the seconds approach is two chain constructors and move the init block content into the constructor with three arguments.

class CustomCardView : FrameLayout {

  constructor(context: Context) :
      this(context, null)

  constructor(context: Context, attrs: AttributeSet) :
      this(context, attrs, 0)

  constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) :
      super(context, attrs, defStyleAttr) {

    LayoutInflater.from(context).inflate(R.layout.view_custom_card, this, true)

    if (attrs != null) {
      val a = context.obtainStyledAttributes(attrs, R.styleable.custom_card_view)
      if (a.hasValue(R.styleable.custom_card_view_command)) {
        var myString = a.getString(R.styleable.custom_card_view_command)
      }
    }
  }
}

Upvotes: 23

Related Questions