How to handle butter knife bind in both Baseactivity and sub activities?
Asked Answered
N

5

8

There is a BaseActivity with a layout and a sub Activity that extends this BaseActivity.

How do you bind views so that views in BaseActivity are binded in BaseActivity and views in Sub activity are binded there ?

Here is a sample code explaining the current scenario, Note: Sample code was taken from here

BASE ACTIVITY

public class BaseActivity extends AppCompatActivity {

    protected void onCreate(Bundle savedInstanceState, int layout) {
        super.onCreate(savedInstanceState);
        super.setContentView(layout);
        ButterKnife.bind(this);
    }


@Override
public void setContentView(int layoutResID) {

    //I added my own implementation here

}
}

SUB ACTIVITY

public class SplashActivity extends BaseActivity {

    @BindView(R.id.txtName)
    TextView txtName;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ButterKnife.bind(this);

    }
}

The above scenario produced many errors such as unable to find view with the id,

After tons of researching I found a lot of topics discussing the same,

Link 1

Link 2

Link 3

Link 4

So after going through all the links I tried the following combinations but none of them worked

  1. Call ButterKnife.bind(this) in BaseActivity but not in SplashActivity
  2. Call ButterKnife.bind(this) in both the Activity.
  3. Call ButterKnife.bind(this) in SplashActivity, with this combination I was unable to access the BaseActivity's view items as they turned out to be null.

My Question How do you bind both the BaseActivity and SplashActivity View items ?

Exact Error line

Caused by: java.lang.IllegalStateException: Required view '' with ID 2131296567 for field '' was not found. If this view is optional add '@Nullable' (fields) or '@Optional' (methods) annotation.

For your kind note, the Views exist in the layout.

Edit 1:

I have added code that overrides the setContentView();

Northnorthwest answered 22/2, 2018 at 5:6 Comment(10)
The layout of SplashActivity doesn't exist any TextView having ID "txtName", you should check spelling mistakesUnbeatable
I knew comments like this would come up so added a note in case you missed it.For your kind note, the Views exist in the layout.Northnorthwest
make your activity as abstract, and if the view is optional make nullable.Ultramontanism
@Ultramontanism As mentioned if the view is not optional and I need to access the toolbar from the base activity also would make changes in the tool bar in the base activity.Northnorthwest
@BatCat have you tried making your base activity as abstract ?Ultramontanism
@Ultramontanism Yes tried that already but did not work..Sample code ?Northnorthwest
@BatCat what are you doing in setContentView of BaseActvity class?Ultramontanism
@BatCat if you are doing something with view in setContenView of BaseActivity class then it will not work. Becuase setContentView will call before ButterKnife.bind(). So no view will be initlized. Instead of create one method like setUpActionBar() and call it after setContentView.Ultramontanism
@Ultramontanism FrameLayout parentContainerLayout= (FrameLayout) findViewById(R.id.parentContainerLayout); View inflated_child_layout = getLayoutInflater().inflate(layoutResID,(ViewGroup)parentContainerLayout,false); parentContainerLayout.addView(inflated_child_layout);Northnorthwest
Let us continue this discussion in chat.Northnorthwest
B
3

Its simple.

Have your abstract BaseActivity as:

public abstract class BaseActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(getLayoutResource());
        ButterKnife.bind(this);
    }

    protected abstract int getLayoutResource();
}

Then simply extend your activity with BaseActivity as:

public class SplashActivity extends BaseActivity {

    @BindView(R.id. txtName)
    TextView textView;

    @Override
    protected int getLayoutResource() {
        return R.layout.activity_splash;
    }
}

I prefer to have multiple Base classes in my project. You can have a BaseToolbarActivity that will extend BaseActivity as:

public abstract class BaseToolBarActivity extends BaseActivity {

    protected static final int RESOURCE_NO_MENU = 0;

    @BindView(R.id.toolbar)
    Toolbar mToolbar;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setToolBar();
    }

    public void setToolBar() {
        setSupportActionBar(mToolbar);
    }

    protected abstract int getMenuResource();

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        if (getMenuResource() == RESOURCE_NO_MENU)
            return super.onCreateOptionsMenu(menu);
        else {
            getMenuInflater().inflate(getMenuResource(), menu);
            return true;
        }
    }
}

So, if your Activity has layout Toolbar, then extend it to BaseToolbarActivity, else extend it to BaseActivity

For more such classes, you can refer my project at

https://github.com/chintansoni202/Android-Master-Project

Bacillary answered 22/2, 2018 at 6:30 Comment(8)
Sorry this did not work, I am still getting the above error.Northnorthwest
If possible,could you please try out yourself and share the code may be I could find whats wrong ?Northnorthwest
you don't have to unbind in an activityRhyne
@BatCat If you having exact same code as I have, then I am sure that my code will run.Bacillary
@TimCastelijns Yes you're right. Thanks for correction. :) I mixed it with Fragments.Bacillary
@ChintanSoni BaseActivity has a toolbar in my code, so your code might work if a BaseActivity doesn't have any layout but in my case it has and that is why the error. Hope you got it ?Northnorthwest
@BatCat You can check the updated answer with Toolbar usage.Bacillary
@ChintanSoni I just saw your code for the ToolBar but your case and my case is different. In my BaseActivity, it has a layout and a toolbar in that layout so I must at any cost pass a layout via setContentView(); Also I might access the toolbar in my sub activities So this is the problem now. Also I wondering of such a possibility if this is not possible I will skip this and move on with normal binding solution which best works.Northnorthwest
R
2

The message in the exception tells you what to do:

Required view 'txtName' with ID 2131296567 for field 'txtName' was not found. If this view is optional add '@nullable' annotation.

Either add it to your layout or make the field binding optional with a @Nullable annotation.

See this Link

Resurrectionist answered 22/2, 2018 at 5:16 Comment(4)
I knew comments like this would come up so added a note in case you missed it.For your kind note, the Views exist in the layout.. Please read the question againNorthnorthwest
This is not a answer, but rather a comment.Bacillary
@BatCat Have you tried add Nullable with BaseActivity's views?Unbeatable
If I annotate as nullable how can I access that View ? Also when the view exists why should I declare that as a nullable ?Northnorthwest
B
2

I just realized looking at one of the comments.. I think your real problem isn't to do with the subclassing at all.. it's the layout. I think you are wanting to include the subclass layout in the superclass layout.. unfortunately you're clobbering the superclass layout in the subclass. I think you need to include the base activity layout in your subclass (probably wrapped in a merge tag in the layout).. see Re-using Layouts with include - include your baseactivity layout (toolbar etc) in your subclass layout (splash screen)..

-

(I think the below information is now irrelevant, but leaving it there in case you might be interested)

So, if I understand the problem correctly, it seems that essentially the butterknife bindng in the super class (base activity) is happening before the subclass (splashactivity) can set up its layout.

The idea of using a getLayourResourceId (overridden in the subclass) probably works for most people as it is a way of allowing the subactivity to specify its layout to the super activity just before it calls butterknife.bind.

So, assuming this is correct, another way of approaching the problem would be to delay the binding until the layout has been specified.

in other words;

public class BaseActivity extends AppCompatActivity {

    // Removed layout parameter
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // It may not make sense to set the content view if it is always
        // replaced by the subclasses, but maybe you want a default?
        setContentView(R.layout.baseactivitylayout);

... bind will be called later when onStart is called.... }

   ...

   public void onStart() {
       super.onStart();
       ButterKnife.bind(this);
   }

   ...

}

and

public class SplashActivity extends BaseActivity {

    @BindView(R.id.txtName)
    TextView txtName;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.splashLayout);
        // butterknife.bind will be called when BaseActivity's onStart is called.
    }
}

Of course it isn't always convenient (or correct) to bind from onStart, but it might help to shed light on how the other solutions work.

Good luck, CJ

Bustup answered 5/3, 2018 at 12:49 Comment(1)
Yes you are correct and that is why this ButterKnife problem is all about.I will be including a navigation drawer in my base activity so that all subclasses will have it and that is the purpose of my BaseAvtivityNorthnorthwest
S
1

in Activity A you are set the instance of activity A. remove the butterknife.bind in BaseActivity.

 super.setContentView(layout);
    ButterKnife.bind(this);

assign the View instance in childActivity with ButterKnife.bind working fine .

   super.onCreate(savedInstanceState);
    ButterKnife.bind(this);

for fragment

ButterKnife.Bind(View,this);

you are remove the butterknife in base activity. reason in that :- in base activity are no view are present butterknife assign unnecessary suppose there are more than a 10 Activity there are not easy to manage.

Slavism answered 22/2, 2018 at 5:31 Comment(3)
Please read the question again did you take a look at this => Call ButterKnife.bind(this) in SplashActivity, with this combination I was unable to access the BaseActivity's view items as they turned out to be null.Northnorthwest
@BatCat: Do you want access the BaseActivity's view from SplashActivity ? Just bind it in BaseActivity and all you need is using it directly without declaring in SplashActivityUnbeatable
@HaiHack I need to access BaseActivity's view in BaseActivity,if I do multiple bindings I am getting an error and that's exactly what the first combo is all about=> Call ButterKnife.bind(this) in BaseActivity but not in SplashActivityNorthnorthwest
P
1

Update your BaseActivity as:

public class BaseActivity extends AppCompatActivity {

    protected void onCreate(Bundle savedInstanceState, int layout) {
        super.onCreate(savedInstanceState);
        setContentView(layout);
        ButterKnife.bind(this);
    }

}

And SubActivity as:

public class SplashActivity extends BaseActivity {

    @BindView(R.id.txtName)
    TextView txtName;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState, R.layout.activity_splash);
    }
}
Pollak answered 22/2, 2018 at 9:0 Comment(2)
BaseActivity has a layout it has a toolbar so I definitely need to pass a layout in BaseActivityNorthnorthwest
My point is: We do not have any method with declaration like protected void onCreate(Bundle savedInstanceState, int layout) with second parameter int layout to override from AppCompactActivity, and hence you need to manually call that method.Pollak

© 2022 - 2024 — McMap. All rights reserved.