Search code examples
kotlinanko

Why is createView never called?


My ultimate goal is to be able move my content widgets into a custom view and instantiate that view in an Anko layout in my MainView. I thought I had this working at one point, but I can't reproduce it.

When I run with the following code, the content in the createView of the MainContextView is never displayed and I never see the message "creating main context view", but I do see the message "main content view".

I start by creating a MainContextView

class MainContextView(context: Context) : ViewGroup(context), AnkoComponent<Context> {

    lateinit var textBox: EditText
    lateinit var button: Button
    lateinit var clickCount: TextView

    override fun createView(ui: AnkoContext<Context>) = with(ui) {
        println("creating main context view")
        verticalLayout {
            themedEditText {
                hint = "hi from main context"
            }
            button = themedButton {
                text = "ok"
            }
            textBox = themedEditText {
                hint = "hi"
            }
            clickCount = themedTextView {
               text = "0"
            }
        }
    }

    override fun onLayout(p0: Boolean, p1: Int, p2: Int, p3: Int, p4: Int) {
        println("onLayout called")
    }

}

and call it from my main view

class MainView : AnkoComponent<MainActivity> {

    lateinit var mainCtx: MainContextView
    lateinit var textBox: EditText
    lateinit var button: Button
    lateinit var clickCount: TextView
    lateinit var mainMenu: Menu
    lateinit var settingItem: MenuItem
    lateinit var otherItem: MenuItem
    lateinit var floatingActionButton: FloatingActionButton

    override fun createView(ui: AnkoContext<MainActivity>) = with(ui) {
        coordinatorLayout {
            verticalLayout {
                themedAppBarLayout {
                    themedToolbar(theme = R.style.Base_ThemeOverlay_AppCompat_Dark_ActionBar) {
                        title = resources.getString(R.string.app_name)
                        popupTheme = R.style.AppTheme
                        mainMenu = menu
                        settingItem = mainMenu.add("My Settings")
                        otherItem = mainMenu.add("My Other")
                    }
                }.lparams(width = matchParent, height = wrapContent)

                // ************************************
                // HERE IS THE CALL TO THE CONTEXT VIEW
                mainCtx = mainContextView { println("main content view ") }
               // *************************************

            }.lparams(width = matchParent, height = wrapContent) {
            }
            floatingActionButton = floatingActionButton {
                imageResource = android.R.drawable.ic_dialog_email
            }.lparams {
                margin = dip(10)
                gravity = Gravity.BOTTOM or Gravity.END
            }
        }
    }
}

The MainView is called set as the content view from the MainActivity

class MainActivity : AppCompatActivity() {

    private lateinit var presenter: MainPresenter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val mainView = MainView()
        mainView.setContentView(this)
        presenter = MainPresenter(mainView)

    }

}

And finally the ViewManger extensions

inline fun ViewManager.mainContextView(theme: Int = 0) = mainContextView(theme) {}

inline fun ViewManager.mainContextView(theme: Int = 0, init: MainContextView.() -> Unit): MainContextView {
    return ankoView({ MainContextView(it) }, theme, init)
}

Solution

  • I found my problem, it had to do with my subView being defined as a subclass of View/ViewGroup, but not really implementing any of the methods.

    As it turns out that was not really the right direction. The solution is based on a comment on this issue

    Here are some of the key parts of the solution, I also created a Gist with the full code.

    The extension function, creates an instance of the "SubView" class and then calls the createView() method in the ankoView() method. It also passes the created instance into the closure that is passed to the extension function, which is key as it allows access to the widgets contained within the view.

    inline fun ViewManager.mainContentView(theme: Int = 0) = mainContentView(theme) {}
    
    inline fun ViewManager.mainContentView(theme: Int = 0, init: View.(mainContentView: MainContentView) -> Unit): View {
        val mainContentView = MainContentView()
        return ankoView({ mainContentView.createView(AnkoContext.create(it)) }, theme, { init(mainContentView)} )
    }
    

    The "content view" creates the layout and holds a reference to the widgets.

    class MainContentView :  AnkoComponent<Context> {
    
        lateinit var textBox: EditText
        lateinit var button: Button
        lateinit var clickCount: TextView
    
        override fun createView(ui: AnkoContext<Context>) = with(ui) {
            verticalLayout {
                button = themedButton {
                    text = "ok"
                }
                textBox = themedEditText {
                    hint = "hi"
                }
                clickCount = themedTextView {
                    text = "0"
                }
            }
        }
    }
    

    In the main view I have fields to refer to the widgets in the "subView" which I then initialize in the closure passed to the mainContentView instance.

    lateinit var textBox: EditText
    lateinit var button: Button
    lateinit var clickCount: TextView
    private lateinit var mainMenu: Menu
    lateinit var settingItem: MenuItem
    lateinit var otherItem: MenuItem
    private lateinit var floatingActionButton: FloatingActionButton
    
    
    override fun createView(ui: AnkoContext<MainActivity>) = with(ui) {
        coordinatorLayout {
            verticalLayout {
                themedAppBarLayout {
                    themedToolbar(theme = R.style.Base_ThemeOverlay_AppCompat_Dark_ActionBar) {
                        title = resources.getString(R.string.app_name)
                        popupTheme = R.style.AppTheme
                        mainMenu = menu
                        settingItem = mainMenu.add("My Settings")
                        otherItem = mainMenu.add("My Other")
                    }
                }.lparams(width = matchParent, height = wrapContent)
                mainContentView {
                    button = it.button
                    textBox = it.textBox
                    clickCount = it.clickCount
                }.lparams(width = matchParent, height = wrapContent)
            }.lparams(width = matchParent, height = wrapContent)
            floatingActionButton = floatingActionButton {
                imageResource = android.R.drawable.ic_dialog_email
            }.lparams {
                margin = dip(10)
                gravity = Gravity.BOTTOM or Gravity.END
            }
        }
    }