Butterknife View injection
Asked Answered
M

1

22

I stumbled across a very interesting Dependency Injection library called ButterKnife. Using ButterKnife it's easily possible to inject Views into activities or fragments.

class ExampleActivity extends Activity {
  @InjectView(R.id.title) TextView title;
  @InjectView(R.id.subtitle) TextView subtitle;
  @InjectView(R.id.footer) TextView footer;

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.simple_activity);
    ButterKnife.inject(this);
    // TODO Use "injected" views...
  }
}

However if using Dependency Injection those Views must be public so that Butterknife can inject it (using private fields results in an exception fields must not be private or static).

In my past project I always made all the member fields (including the views) private as I thought this is best practice (information hiding etc.) Now I am wondering if there is a reason why one should not make all the views public? In this case I cannot use ButterKnife but I want to use it because it simplifies the code a lot.

Mellow answered 2/12, 2014 at 8:18 Comment(3)
Doesn't Guice pretty much do the same thing better but without messing up your code?Glossary
Why do you think it's messing up the code?Mellow
@G_V: Maybe, but google says not to use it. See #24194783 and #5068181.Dutcher
C
44

First off, Butter Knife is not a dependency injection library. You can think of it as a boilerplate reduction library since all it does is replace findViewById and various setXxxListener calls.

The reason that Butter Knife requires views not be private is that is actually generates code which sets the fields. The code that it generates lives in the same package as your class which is why the field must be package-private, protected, or public. If the field was private the generated code would fail to compile since it cannot access the private field.

The generated code looks something like this:

public static void inject(ExampleActivity target, ExampleActivity source) {
  target.title = (TextView) source.findViewById(R.id.title);
  target.subtitle = (TextView) source.findViewById(R.id.subtitle);
  target.footer = (TextView) source.findViewById(R.id.footer);
}

When you call ButterKnife.inject(this) it looks up this generate class and calls the inject method with your instance of ExampleActivity as both the destination for the fields and the source for findViewById calls.

Cyclosis answered 2/12, 2014 at 8:33 Comment(10)
Thank you for the clarification. So, in your opinion declaring fields package-private or even public isn't considered as bad practice? I am just wondering, because I ALWAYS declared my fields private and it feels a bit wrong for me. But at the same time I am thinking that it doesn't make much of a difference... right?Mellow
I think package-private is sufficiently limited in scope that it's not a big deal. If you can't trust other classes in your package then you have other problems :) Plus package-private is also used for exposing things to test classes so it's fairly common to see. I wouldn't do public though, you still want to hide these fields as an implementation detail.Cyclosis
It makes code that much harder to read and maintain, by exposing things that shouldn't be touched. You should always aim to write code that someone else would be able to read without spending hours to figure out what is going on. This is useless for variables that shouldn't be accessed outside of their getter or setter where you have checks and other controls.Glossary
Well there are no getters or setters and the annotation declares the contract. How that contract is fulfilled is irrelevant to the class. This is no different than a normal @Inject annotation where it might be fulfilled by reflection, code generation, or manually in a test. By not being private it also correctly conveys that the operation by which the field values are fulfilled is not local to the class. So if anything, it has semantics which are much better than private.Cyclosis
@JakeWharton what was the reason you've chosen code generation over reflection? With reflection you could inject views to private fields. Was it for performance reasons? Or am I missing something?Candice
I also remember your video about dagger and u2020 - with reflection some potentially defective code can not fail at compilation. While generated - can. Or this isn't the case for ButterKnife?Buber
@JakeWharton but performance loss should be negligible, meanwhile clogging you class interface feels very uncomfortable.Varix
@JakeWharton It seems like this problem would not exist if ButterKnife generated code inside the dependant class. Is there a reason why this is not done?Mono
javac does not allow thatCyclosis
@JakeWharton Hi, i was able to understand private, but why should the method not be static as well?Sanskritic

© 2022 - 2024 — McMap. All rights reserved.