Kotlin / anko

Pleasant Android application development
Apache License 2.0
15.89k stars 1.29k forks source link

DSL: Unable to refer to views outside of the current scope #17

Closed DavidYKay closed 9 years ago

DavidYKay commented 9 years ago

Anko team,

Thanks for the great library. :)

I'm having trouble referring to views outside the current scope.

My use case is to call an EditText from a button that lives in a different layout/ViewGroup.

Please look at the below examples to see what I am trying to accomplish. It's possible that there's a way that I'm not seeing.

Example A - Button and EditText as siblings

Works! :)

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    verticalLayout {
        val name = editText() {
            id = nameId
        }
        button("Register") {
            onClick {
                toast("Hello, ${name.text}!")
            }
        }
    }
}

Example B - Button and EditText in separate ViewGroups

Won't compile - name is used outside scope

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    relativeLayout {
        verticalLayout {
            val name = editText() {
                id = nameId
            }
        }
        button("Register") {
            onClick {
                toast("Hello, ${name.text}!")
            }
        }
    }
}

Example C - Passing instantiated EditText into DSL

Runs, but name doesn't create an actual instance of editText inside relativeLayout. So there's no editText in the UI.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val name = editText() {

    relativeLayout {
        verticalLayout {
            val name = editText() {
                id = nameId
            }
        }
        button("Register") {
            onClick {
                toast("Hello, ${name.text}!")
            }
        }
    }

}

Example D - findViewById

Crash at runtime - findViewById returns a View, not an EditText

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val name = editText() {

    val nameId = 1
    relativeLayout {
        verticalLayout {
            val name = editText() {
                id = nameId
            }
        }
        button("Register") {
            onClick {
                // fails with: android.view.View! cannot be cast to T
                val name = find<EditText>(nameId)
                // fails with: android.view.View! cannot be cast to android.widget.EditText
                val name: EditText = findViewById(nameId) as EditText
                toast("Hello, ${name.text}!")
            }
        }
    }

}

Many thanks for your time and attention. Let me know how I can help get this resolved.

DavidYKay commented 9 years ago

Update:

I got this working by using an instance variable, like so:

var nameBox: EditText? = null

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    relativeLayout {
        verticalLayout {
            val name = editText() {
                id = nameId
            }
            nameBox = name
        }
        button("Register") {
            onClick {
                toast("Hello, ${nameBox!!.text}!")
            }
        }
    }
}
yoavst commented 9 years ago

better use var nameBox: EditText by Delgates.notNull()

DavidYKay commented 9 years ago

Excellent. I'm a Kotlin newbie and was unaware of that. Thanks again and sorry for not reading the docs more closely!

yanex commented 7 years ago

Just in case: Kotlin now has late-initialized variables (https://kotlinlang.org/docs/reference/properties.html#late-initialized-properties) which don't create an extra delegate object.