Butterknife: Provide an example for injection in views / official io page needs clarification

Created on 10 May 2014  Â·  11Comments  Â·  Source: JakeWharton/butterknife

The github io page of butter knife mentions :

Inject a view's children into fields using ButterKnife.inject(this). If you use tags in a layout and inflate in a custom view constructor you can call this immediately after. Alternatively, custom view types inflated from XML can use it in the onLayoutInflated() callback.

But :

  • there is no onLayoutInflated() callback
  • the closest method is onFinishUpdate() but the method's invokation conditions are far from being clear. It looks like it is activated (as mentionned) when using a merge tag, otherwise the root view's onFinishUpdate() will be triggered but not the custom view's one.

It would be nice to have 1 or 2 clear examples of nested views injection, one for XML using merge tag, one without, and check what happens when those constructors get called programmatically as well.

Just make it more clear as Android docs (and code) are not and it looks far from trivial to know exactly how to make a robust view injection mechanism to handle all cases.

Most helpful comment

Just and update Injectis replaced by bindnow .
[

Version 7.0.0 _(2015-06-27)_

  • @Bind replaces @InjectView and @InjectViews.
  • ButterKnife.bind and ButterKnife.unbind replaces ButterKnife.inject
    and ButterKnife.reset, respectively.
    ...]

We need to bind views with bind method , for this question it would look like this

   LayoutInflater mInflater;

    public CustomView(Context context) {
        super(context);
        mInflater = LayoutInflater.from(context);
        init();

    }

    public CustomView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mInflater = LayoutInflater.from(context);
        init();
    }

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mInflater = LayoutInflater.from(context);
        init();
    }

    public void init() {
        View v = mInflater.inflate(R.layout.header_loan_request, this, true);
        ButterKnife.bind(this);
    }

All 11 comments

Just changed the website to say onFinishInflate (which is what I meant).

It's hard to provide comprehensive examples for every interaction because you can call ButterKnife in so many places. I also added a blurb to the site which indicates that you can call ButterKnife.inject anywhere you would otherwise call findViewById for now.

I know it's really a little detail, but then the smarter way to implement
this, for devs, would be to :
1) add butter knife in onFinishInflate
2) add a call to onFinishInflate at the end of their one-arg (Context)
constructor
3) no need to invoke it from the 2 other constructors, it is already done
by the framework.

Maybe a sample would be the best place to spread this good practice.

S.

2014-05-20 0:02 GMT+02:00 Jake Wharton [email protected]:

Reopened #138 https://github.com/JakeWharton/butterknife/issues/138.

—
Reply to this email directly or view it on GitHubhttps://github.com/JakeWharton/butterknife/issues/138#event-122670883
.

I've just come across this "issue" as well which isn't really an issue. You just need to hook up with the correct view callbacks since the views cannot be injected until they've been inflated successfully.
Just for anyone landing on this page in the future. In order to inject your custom views successfully you can do either:

public class CustomLinearLayout extends LinearLayout {

  public CustomLinearLayout(Context context) {
    super(context);
    init();
  }

  public CustomLinearLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
  }

  public CustomLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
  }

  private void init() {
    inflate(getContext(), R.layout.custom_linear_layout, this); // your layout with <merge> as the root tag
    Butterknife.inject(this);
  }

}

or:

public class CustomLinearLayout extends LinearLayout {

  public CustomLinearLayout(Context context) {
    super(context);
  }

  public CustomLinearLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
  }

  public CustomLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
  }

  @Override
  protected void onFinishInflate() {
    super.onFinishInflate();
    Butterknife.inject(this);
  }

}

Hi Jens,

The first example works fine, but not the second one.

In the latest, onFinishInflate will be triggered by the third and second
constructors automatically when the view is inflated from XML. The first
constructor, which is generally meant to be used programmatically, does not
trigger the onFinishInflate method.

Hence, in your second example, the first constructor, and only it, should
invoke the onFinishInflate hook explicitly.

And this latest form is my favorite, more elegant to my mind.

We have adopted this view design pattern in roboguice 3 and in the
injectview project (which suppresses ButterKnife one liners).
S.
Le 2015-01-17 09:44, "Jens Driller" [email protected] a écrit :

I've just come across this "issue" as well which isn't really an issue.
You just need to hook up with the correct view callbacks.
Just for anyone landing on this page in the future. In order to inject
your custom views successfully you can do either:

public class CustomLinearLayout extends LinearLayout {

public CustomLinearLayout(Context context) {
super(context);
init();
}

public CustomLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}

public CustomLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}

private void init() {
inflate(getContext(), R.layout.custom_linear_layout, this); // your layout with as the root tag
Butterknife.inject(this);
}

}

or:

public class CustomLinearLayout extends LinearLayout {

public CustomLinearLayout(Context context) {
super(context);
}

public CustomLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}

public CustomLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

@Override
protected void onFinishInflate() {
super.onFinishInflate();
Butterknife.inject(this);
}

}

—
Reply to this email directly or view it on GitHub
https://github.com/JakeWharton/butterknife/issues/138#issuecomment-70369393
.

Hi Stephanenicolas,

For Jenzz's second example, would this modified version work?

public class CustomLinearLayout extends LinearLayout {

  public CustomLinearLayout(Context context) {
    this(context, null);
  }

  public CustomLinearLayout(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
  }

  public CustomLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
  }

  @Override
  protected void onFinishInflate() {
    super.onFinishInflate();
    Butterknife.inject(this);
  }
}

Hi @ayinozendy ,

Not, it wont:

onFinishInflate: Called after a view and all of its children has been inflated from XML.

That means:


_inflating View from XML_ THEN _2nd or 3rd constructor are used_ AND _onFinishInflate is called_


The opposite is not true. So, if you call the 2nd constructor from the 1st, it does NOT mean that the view is inflated from XML. Thus, onFinishInflate won't be called, you need to call it.

Just and update Injectis replaced by bindnow .
[

Version 7.0.0 _(2015-06-27)_

  • @Bind replaces @InjectView and @InjectViews.
  • ButterKnife.bind and ButterKnife.unbind replaces ButterKnife.inject
    and ButterKnife.reset, respectively.
    ...]

We need to bind views with bind method , for this question it would look like this

   LayoutInflater mInflater;

    public CustomView(Context context) {
        super(context);
        mInflater = LayoutInflater.from(context);
        init();

    }

    public CustomView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mInflater = LayoutInflater.from(context);
        init();
    }

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mInflater = LayoutInflater.from(context);
        init();
    }

    public void init() {
        View v = mInflater.inflate(R.layout.header_loan_request, this, true);
        ButterKnife.bind(this);
    }

I guess it becomes important again since we have prefetching in RecyclerView.
If someone uses composite custom view (http://lucasr.org/2014/05/12/custom-layouts-on-android/) as a RecyclerView item and this custom view uses ButterKnife to bind child views - you may encounter unexpected trouble.
I was calling ButterKnife.bind() in init() method like above and unbind() in onDetachedFromWindow callback. Since RecyclerView reuses views, they are attached/detached and constructor (and init() method) is not called so my views values were null.
It would be awesome to have some guidelines here, when to call bind/unbind in case of custom views.

When would you call findViewById? That's when you call bind.

On Thu, Dec 22, 2016, 7:43 AM mzgreen notifications@github.com wrote:

I guess it becomes important again since we have prefetching in
RecyclerView.
If someone uses composite custom view (
http://lucasr.org/2014/05/12/custom-layouts-on-android/) as a
RecyclerView item and this custom view uses ButterKnife to bind child views

  • you may encounter unexpected trouble.
    I was calling ButterKnife.bind() in init() method like above and unbind()
    in onDetachedFromWindow callback. Since RecyclerView reuses views, they
    are attached/detached and constructor (and init() method) is not called
    so my views values were null.
    It would be awesome to have some guidelines here, when to call bind/unbind
    in case of custom views.

—
You are receiving this because you modified the open/close state.
Reply to this email directly, view it on GitHub
https://github.com/JakeWharton/butterknife/issues/138#issuecomment-268792108,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAEEEW6vWpz8McmupFAd-lHJHnVhlBVSks5rKnBZgaJpZM4B5vU-
.

Yup that one is clear. The question is if we should and where to call unbind? I guess it should be called since it keeps a reference to the view/activity/fragment that we inject into.

If you need to null out views and null out listeners then you need to call
unbind. For a RecyclerView ViewHolder I can't see it being needed.

On Thu, Dec 22, 2016 at 10:14 AM mzgreen notifications@github.com wrote:

Yup that one is clear. The question is if we should and where to call
unbind? I guess it should be called since it keeps a reference to the
view/activity/fragment that we inject into.

—
You are receiving this because you modified the open/close state.
Reply to this email directly, view it on GitHub
https://github.com/JakeWharton/butterknife/issues/138#issuecomment-268819801,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAEEEasasrFlILg_eg8jn8EPS2CYaL9hks5rKpPRgaJpZM4B5vU-
.

Was this page helpful?
0 / 5 - 0 ratings