How to reference other views in Anko DSL?
Asked Answered
S

5

15

I'm using Anko in my Android project, but I don't know how can it reference the child views I created in the DSL when the referenced view is not at the same level where I reference it.

The following code works:

alert {
    customView {
        val input = textInputLayout {
            editText {
                hint = "Name"
                textColor =resources.getColor(R.color.highlight)
            }
        }


        positiveButton("OK") { "${input.editText.text}" }
    }
}.show()

but the following code does not work:

alert {
    customView {
        val vertical = verticalLayout {
            textView {
                text = "Edit device name"
                textColor = resources.getColor(R.color.highlight)
                textSize = 24F
            }
            val input = textInputLayout {
                editText {
                    hint = "Name"
                    textColor = resources.getColor(R.color.highlight)
                }
            }
        }

        positiveButton("OK") { "${vertical.input.editText.text}" }  // Cannot resolve "input"
    }
}.show()
Soult answered 11/12, 2015 at 6:58 Comment(0)
L
6

As I see it there are two ways. The super hacky way would be to declare the positive button within the textInputLayout block. This is possible because you can access all outer scopes from within any nested scope and the positiveButton method is declared in the alert scope:

alert {
    customView {
        verticalLayout {
            textInputLayout {
                val editText = editText {
                    hint = "Name"
                }

                positiveButton("OK") { toast("${editText.text}") }
            }
        }
    }
}.show()

The less hacky way would be to declare a variable that can be accessed from both scopes. However you would need to make it nullable since you can't initialize it immediately:

alert {
    var editText: EditText? = null

    customView {
        verticalLayout {
            textInputLayout {
                editText = editText {
                    hint = "Name"
                }
            }
        }
    }

    positiveButton("OK") { toast("${editText!!.text}") } 
}.show()
Lenny answered 11/12, 2015 at 11:14 Comment(5)
the lateinit property could be used to keep it non-null as long as you're sure it's going to get initialized before it gets used.Cosetta
I think that's the wrong approach for an alert dialog. You would leak the dialog after it's closed.Lenny
I think the reference can be recycled once the enclosing scope can be recycled. I'm not sure nullability would have anything to do with it unless I'm wrong?Cosetta
Are you talking about properties or local variables? Because variables cannot have the lateinit modifier.Lenny
Good news here: Kotlin supports lateinit on local variables since 1.2 :-)Jsandye
D
2

I propose using findViewById()

alert {
        customView {
            val vertical = verticalLayout {
                textView {
                    text = "Edit device name"
                    textSize = 24F
                }
                val input = textInputLayout {
                    editText {
                        id = R.id.my_id_resource // put your id here
                        hint = "Name"
                    }
                }
            }
            positiveButton("OK") { "${(vertical.findViewById(R.id.my_id_resource) as? EditText)?.text}" }  
        }
    }.show()
Duntson answered 14/12, 2015 at 1:12 Comment(4)
I used this method to work around, but it's a bit overelaborate to define an id in an xml resource then use it in DSL.Soult
yes, but you can generate ids straight in your code @IdRes val DIALOG_ITEM_TEXT = View.generateViewId() also, you can use a handy findVIewById shortcut, something like: inline fun <reified T : View> View.byId(@IdRes id: Int): T = findViewById(id) as TStrung
@AdelNizamutdinov View.generateViewId() needs API 17, while my minimum sdk is 16Contuse
@Contuse it's time for minSdk 19 :) but seriously, make your own function generateViewId() then, that would guarantee unique valuesStrung
F
1

You can always elevate a view, passing the context vertical manually:

customView {
    val vertical = verticalLayout {
        textView {
            text = "Edit device name"
            textColor = resources.getColor(R.color.highlight)
            textSize = 24F
        }
    }

    val input = /*here:*/ vertical.textInputLayout {
        editText {
            hint = "Name"
            textColor = resources.getColor(R.color.highlight)
        }
    }

    positiveButton("OK") { "${input.editText.text}" }
}
Fahy answered 14/12, 2015 at 8:58 Comment(2)
It's almost perfect, but this way the code structure does not keep the same as the view structure, readability impacted.Soult
I myself usually brake the code into smaller peaces and put them into global variables. Then combine them somewhere else.Fahy
C
1

I usually declare a view as a property in a class with lateinit modifier; this way it's not nullable and most of views are declared in one place, improving readability:

lateinit var toolbar: Toolbar

...
appBarLayout {
    toolbar = toolbar {}.lparams(width = matchParent, height = matchParent)
             }.lparams(width = matchParent)
...
setSupportActionBar(toolbar)
Criticism answered 19/4, 2017 at 8:33 Comment(0)
S
0

Probably the best way is to use Android IDs for the elements you need to reference later, and the find<T : View>(Int) : T function. This allows you to reference them from anywhere, as long as the view still exists, and you have access to the application/activity scope.

See the Anko Documentation for details

Example case: Dynamically adding buttons to an existing view

verticalLayout {
  id = R.id.button_container
}
//note that the code below here may be executed anywhere after the above in your onCreate function
//verticalLayout is a Anko subclass of LinearLayout, so using the android class is valid.
val buttonContainer = find<LinearLayout>(R.id.button_container)

val containerContext = AnkoContext.Companion.create(ctx, buttonContainer)
val button = ctx.button {
  text = "click me"
  onClick = { toast("created with IDs!") }
}
buttonContainer.addView(button.createView(containerContext, buttonContainer))
Sphygmoid answered 1/8, 2016 at 18:58 Comment(1)
How to solve: Unresolved reference: button_container?Contuse

© 2022 - 2024 — McMap. All rights reserved.