Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A better approach for handling attributes and views. #99

Merged
merged 26 commits into from
Feb 26, 2017
Merged

A better approach for handling attributes and views. #99

merged 26 commits into from
Feb 26, 2017

Conversation

zserge
Copy link
Collaborator

@zserge zserge commented Feb 26, 2017

This is what is likely to become Anvil 0.5.0 soon 🎉

Major changes:

  • Removed Anvil.Node class, using views directly. This should resolve Anvil.currentView() is null inside render method #83 and maybe other issues related to missynchronization between virtual view nodes and real views. In fact, working with views directly happens at the same speed as dealing with virtual nodes (unlike DOM vs virtual DOM in the web).
  • Replaced hundreds of lambdas in DSL classes with a single switch/case per class. This reduces number of methods (each lambda has 2 methods - a contstructor and an actual method, so we had ~2k methods in DSL.java, now we have ~500).
  • This allows to set attribute by its string name, so I've added a last-resort attribute handler which uses reflection (a-la android Property class). So if your attribute is missing in DSL - just use attr("foo", someValue). It won't be fast, but works fine for one-time constant values.
  • This in its turn opens the doors to reusable components. If a component (a RenderableView) has custom setters - one can use them using their string names.
  • Alternatively, one may register his own attribute handler to get rid of reflection. Multiple attribute handlers are chained, reflection-based is the last one, common DSL goes next, then support DSLs, then your own ones.
  • As a bonus this also resolves issues with the attributes of the same name (e.g. gravity in DSL and gravity in support DSL). Now such name clashes just work as expected.

Benchmarks show that is gives a slight performance gain (~5%) comparing to Anvil 0.5.0, but the main code part becomes much simpler and the flexibility is extremely high, so I'm merging this into master branch.

…c, removed virual nodes and using real views instead, dispatching attribute setters by attribute name string
…aries (support-core-ui), commented out recycler view hand-written dsl
@zserge zserge merged commit 20b78e7 into master Feb 26, 2017
@zserge zserge deleted the forge branch February 26, 2017 10:13
@gmarques33
Copy link
Contributor

Hi,
do you plan to release the actual master as Anvil 0.5.0 or are you planning major changes?

Btw, great work 👍

@zserge
Copy link
Collaborator Author

zserge commented Mar 2, 2017

@gmarques33 Anvil 0.5.0 is planned to be released this week, and good news is that the public API is backwards-compatible with Anvil 0.4.0. Although, internally lots of things have changed, and new added APIs such as custom attribute factories or view factories or Anvil.skip() might not be final yet.

@gmarques33
Copy link
Contributor

@zserge Cool. I look forward to trying the new changes. We are testing it in a project and I believe that 0.5.0 changes will help a lot.
If we keep using it, we can contribute with feedback and pull requests 👍

@jakst
Copy link

jakst commented May 1, 2017

I'm trying to use the bottomNavigationView introduced in this merge, but simply cannot get it to trigger my NavigationItemSelectedListener. Is this functionality not complete? The documentation on such components is currently non-existent. @zserge

class HomeView(c: Context) : RenderableView(c) {

    private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
        when (item.itemId) {
            R.id.navigation_home -> {
                println("HOME")
                return@OnNavigationItemSelectedListener true
            }
            R.id.navigation_dashboard -> {
                println("Dash")
                return@OnNavigationItemSelectedListener true
            }
            R.id.navigation_notifications -> {
                println("Notif")
                return@OnNavigationItemSelectedListener true
            }
        }
        false
    }

    override fun view() {
        frameLayout {
            size(BaseDSL.MATCH, BaseDSL.MATCH)

            bottomNavigationView {
                gravity(BaseDSL.BOTTOM)
                size(BaseDSL.MATCH, BaseDSL.WRAP)
                onNavigationItemSelected(mOnNavigationItemSelectedListener)

                bottomNavigationMenuView {
                    bottomNavigationItemView {
                        title("Home")
                        id(R.id.navigation_home)
                        icon(ContextCompat.getDrawable(context, R.drawable.ic_home_black_24dp))
                    }
                    bottomNavigationItemView {
                        title("Dash")
                        id(R.id.navigation_dashboard)
                        icon(ContextCompat.getDrawable(context, R.drawable.ic_home_black_24dp))
                    }
                    bottomNavigationItemView {
                        title("Notif")
                        id(R.id.navigation_notifications)
                        icon(ContextCompat.getDrawable(context, R.drawable.ic_home_black_24dp))
                    }
                }
            }
        }
    }
}

@zserge
Copy link
Collaborator Author

zserge commented May 1, 2017

@jakst Yes, it's another gift from Android Support Library developers, similar to #81 and friends.

Basically, they are violating the common convention of the view constructors. BottomNavigationView is a view group, but it injects child views automatically, as well as having other side effects such as binding together the menu presented and menu view. In other words, you can't create a BottomNavigationView in pure java without using XMLs.

Here's where the child view is added: https://android.googlesource.com/platform/frameworks/support/+/master/design/src/android/support/design/widget/BottomNavigationView.java#171

But this particular case is not so bar. One can specify the Menu resource in code, so I've been using the following view method in my component and it worked well:

        public void view() {
            frameLayout(() -> {
                size(MATCH, MATCH);

                bottomNavigationView(() ->  {
                    init(() -> {
                        ((BottomNavigationView) Anvil.currentView()).inflateMenu(R.menu.bottom_nav_example);
                    });

                    gravity(BOTTOM);
                    size(MATCH, WRAP);
                    onNavigationItemSelected(mOnNavigationItemSelectedListener);
                });
            });
        }

Here I'm creating a view in java, but my menu items are declared in menu XML, much like the documentation suggests. Not as good as pure Kotlin (or Java), but it seems to be the only possible way. An alternative would be to re-write BottomNavigationView as your own class that would be Java- (and thus, Anvil-) friendly.

@zserge
Copy link
Collaborator Author

zserge commented May 1, 2017

@jakst You might want to open a separate issue if you would prefer to have a separate method like menu(int resource) instead of the init(...) + inflateMenu trick. A solution would be to modify quirks file and re-generate the DSL code, so you might want to offer a pull request to make it faster.

UPD: Sorry, it looks like inflateMenu is not a reusable setter method and should only be called once. I this case perhaps the init() approach is cleaner, because it explicitly shows that the method is called once.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Anvil.currentView() is null inside render method
3 participants