Anko: Use DSL for custom components

Created on 31 Oct 2016  路  6Comments  路  Source: Kotlin/anko

Hi, I`ve created ToolbarComponent that I am using in several places in my application and I want to know if there is some way how to easily use this component when creating view or if there is some better way how to simulate "include" tag from xml.

The current usage that works but is not pretty is

UI {
  frameLayout {
    val ankoContext = AnkoContext.Companion.createReusable(ctx, this@BaseFragmentActivity)
    addView(ToolbarComponent<BaseFragmentActivity>().createView(ankoContext))
  }
}.view
fixed question

Most helpful comment

@davidbilik I know this is an old post, but it looks like you didn't quite get your answer, here's how I did it...

// Functions to allow me to use the DemoView directly in DSL
// Note that the init function now accepts the DemoView as the first argument which is how I'm able
// To make calls on the component.
inline fun ViewManager.demoView(theme: Int = 0) = demoView(theme) {}
inline fun ViewManager.demoView(theme: Int = 0, title: String = "", action: String = "",
        init: View.(demoView: DemoView) -> Unit): View {
    val demoView = DemoView(title, action)
    return ankoView({ demoView.createView(AnkoContext.create(it)) }, theme, { init(demoView) })
}

// The DemoView component
class DemoView(val title: String, val action: String): AnkoComponent<Context> {

    private lateinit var ankoContext: AnkoContext<Context>

    private lateinit var buttonView: View

    override fun createView(ui: AnkoContext<Context>): View = with(ui) {
        ankoContext = ui

        linearLayout {
            orientation = HORIZONTAL
            lparams(matchParent, wrapContent) {
                padding = dip(8)
            }
            gravity = Gravity.CENTER_VERTICAL
            textView(title).lparams(0, wrapContent) {
                weight = 1f
            }
            button(action) {
                [email protected] = this
            }.lparams(wrapContent, wrapContent)
        }
    }

    fun onClick(body: (title: String, action: String) -> Unit) {
        buttonView.onClick { body(title, action) }
    }
}

// Small demo activity to create 2 instances of DemoView
class MainActivity: AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        verticalLayout {
            demoView(title = "title1", action = "action1") { demoView ->
                demoView.onClick { title, action -> toast("$title: $action") }
            }
            demoView(title = "title2", action = "action2") { demoView ->
                demoView.onClick { title, action -> toast("$title: $action") }
            }
        }
    }
}

All 6 comments

this is how I did it

//views/utils.kt
fun <T: AnkoComponent<out Context>> ViewGroup.mount(component: T, onMount: (T.() -> Unit)? = null) =
    context.render(component as AnkoComponent<Context>).apply {
        addView(this)
        onMount?.invoke(component)
    }

inline fun Context.render(component: AnkoComponent<out Context>) =
    (component as AnkoComponent<Context>).createView(AnkoContext.createReusable(this))

usage:

class Component: AnkoComponent<SomeActivity> {
      // ...
}
// in a ViewGroup closure
       verticalLayout {
            mount(Component(/*some props*/)){ 
                // do smt with Component, eg: hook up to sinks
            }
        }

1-liner :

ankoView(::CustomToolbarView, 0) {
   backgroundColor = Color.RED
}

Please check Extending Anko.

Well thanks for @lukaspili and @yanex tip, but neither answers my question. I was not talking about custom view but about AnkoComponents

@davidbilik I know this is an old post, but it looks like you didn't quite get your answer, here's how I did it...

// Functions to allow me to use the DemoView directly in DSL
// Note that the init function now accepts the DemoView as the first argument which is how I'm able
// To make calls on the component.
inline fun ViewManager.demoView(theme: Int = 0) = demoView(theme) {}
inline fun ViewManager.demoView(theme: Int = 0, title: String = "", action: String = "",
        init: View.(demoView: DemoView) -> Unit): View {
    val demoView = DemoView(title, action)
    return ankoView({ demoView.createView(AnkoContext.create(it)) }, theme, { init(demoView) })
}

// The DemoView component
class DemoView(val title: String, val action: String): AnkoComponent<Context> {

    private lateinit var ankoContext: AnkoContext<Context>

    private lateinit var buttonView: View

    override fun createView(ui: AnkoContext<Context>): View = with(ui) {
        ankoContext = ui

        linearLayout {
            orientation = HORIZONTAL
            lparams(matchParent, wrapContent) {
                padding = dip(8)
            }
            gravity = Gravity.CENTER_VERTICAL
            textView(title).lparams(0, wrapContent) {
                weight = 1f
            }
            button(action) {
                [email protected] = this
            }.lparams(wrapContent, wrapContent)
        }
    }

    fun onClick(body: (title: String, action: String) -> Unit) {
        buttonView.onClick { body(title, action) }
    }
}

// Small demo activity to create 2 instances of DemoView
class MainActivity: AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        verticalLayout {
            demoView(title = "title1", action = "action1") { demoView ->
                demoView.onClick { title, action -> toast("$title: $action") }
            }
            demoView(title = "title2", action = "action2") { demoView ->
                demoView.onClick { title, action -> toast("$title: $action") }
            }
        }
    }
}

inline fun ViewManager.ankoComponent(component: AnkoComponent<Context>, theme: Int = 0) = ankoComponent(component, theme) {} inline fun ViewManager.ankoComponent( component: AnkoComponent<Context>, theme: Int = 0, init: View.(component: AnkoComponent<Context>) -> Unit ): View { return ankoView({ component.createView(AnkoContext.create(it)) }, theme, { init(component) }) }

I've based on @yperess comment and make it easier to insert any ankoComponent. You can easily use it.

`
    verticalLayout {
            ankoComponent(someUI)
        }

`
someUI in this case is instance of anko component you need to inject

Was this page helpful?
0 / 5 - 0 ratings