JakeWharton / butterknife

Bind Android views and callbacks to fields and methods.
http://jakewharton.github.io/butterknife/
Apache License 2.0
25.55k stars 4.6k forks source link

Superclass injection gets removed #31

Open oniekrenz opened 11 years ago

oniekrenz commented 11 years ago

I'm using IDEA12. Setup is: class B extends A, both classes have some @InjectView fields. A "Rebuild Project" creates: A$$ViewInjector and B$$ViewInjector, the latter with a call to A$$ViewInjector.inject(finder, target, source) I edit B and deploy to my device. Compiling starts and the call to A$$ViewInjector.inject() gets removed -> App crashes because my injected fields in B are now null.

JakeWharton commented 11 years ago

I'll have to try and reproduce. It might simple be a problem with how IDEA does incremental compilation that requires me to jump through many more hoops.

If you do a full clean build it probably still works though, right?

oniekrenz commented 11 years ago

Correct

JakeWharton commented 11 years ago

I think this is just a fundamental flaw in annotation processing-based code generation. Changing a class should cause the processor to run again for it but changing the hierarchy is too-complex a task for an incremental generation. Hopefully this doesn't happen too much. It's just something we are going to have to deal with.

SimonVT commented 11 years ago

I believe it happens any time you make changes to the subclass tho, not (just) when you change the class hierarchy.

oniekrenz commented 11 years ago

Right, it happens on any change.

JakeWharton commented 11 years ago

I can re-open, but I'm not really going to be working toward fixing this unless something obvious and easy presents itself.

donnfelker commented 11 years ago

Just as an added note: I've also noticed this happening on multiple projects of mine.

SimonVT commented 11 years ago

Haven't had this happen since I moved to Gradle.

ultimate-deej commented 11 years ago

I came across the same problem. Anyone know the reason of this behaviour? Is there a solution aside from using gradle or rebuilding the whole project?

gte619n commented 10 years ago

FWIW, I have come across this. The issue I was trying to solve was a way to abstract out a universal sliding menu for the entire application. My Activities extended an abstract BaseMenuActivity, which handed all the building of the Drawer and event listeners.

BaseMenuActivity.onCreate would call getViewId(), which was an abstract method implemented by the subclass. It would then do the view injection. It would work if there was a full rebuild, but not if there was a change in either class. I guess I will just have to rework it.

Thanks for such a nice library.

melug commented 10 years ago

Moved to maven, works like charm.

nguyentruongtho commented 10 years ago

I'm using IntelliJ & maven, it is still happening for me.

ultimate-deej commented 10 years ago

Moved to Android Studio, and all the problems are gone

lhunath commented 10 years ago

This is untrue. The issue persists and is insanely annoying. Whenever I change a class that has a superclass with injected views or a class which has a subclass with injected views, I build apps that compile perfectly fine and run into random nullpointerexceptions at runtime. This is not something you should just shrug away.

JakeWharton commented 10 years ago

The issue is open. Also pull requests welcome!

xu6148152 commented 9 years ago

great content

jaredsburrows commented 8 years ago

@JakeWharton Can we close this now? I have been using Butterknife for a long time. I can inject views such as the NavigationDrawer in a BaseClass and subclass it in SubCass. This is how I inherit the NavigationDrawer to all my Activities and Fragments.

@gte619n if you are still having this problem:

/**
 * @author <a href="mailto:jaredsburrows@gmail.com">Jared Burrows</a>
 */
@SuppressWarnings("checkstyle:visibilitymodifier")
public abstract class BaseActivity extends AppCompatActivity implements OnNavigationItemSelectedListener {

    protected DrawerLayout drawerLayout;
    protected NavigationView navigationView;
    protected AppBarLayout appBar;
    protected Toolbar toolbar;
    private static final int NAV_DRAWER_LAUNCH_DELAY = 250;
    private final Handler handler = new Handler();
    private ActionBarDrawerToggle drawerToggle;

    /**
     * @return Layout Id for the Activity.
     */
    public abstract int getLayoutResId();

    @Override
    public boolean onNavigationItemSelected(final MenuItem menuItem) {
        final int itemId = menuItem.getItemId();
        this.onNavDrawerItemClicked(itemId);
        this.navigationView.setCheckedItem(itemId);
        this.closeNavDrawer();
        return true;
    }

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        this.setContentView(this.getLayoutResId());

        this.drawerLayout = (DrawerLayout) this.findViewById(R.id.drawer_layout);
        this.appBar = (AppBarLayout) this.findViewById(R.id.app_bar_layout);
        this.toolbar = (Toolbar) this.findViewById(R.id.tool_bar);
        this.navigationView = (NavigationView) this.findViewById(R.id.navigation_view);
    }

    // Must call this for drawer toggle to work correctly
    @Override
    protected void onPostCreate(final Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);

        // Setup Toolbar
        try {
            if (this.toolbar != null) {
                this.setToolbarNavigationIcon(R.drawable.ic_menu_white_24dp);
                this.setSupportActionBar(this.toolbar);
            }
        } catch (final Throwable e) {
            LogUtils.logE(TAG, "Sadly, Robolectric blows up when this method is called.", e);
        }

        // Setup DrawerLayout
        if (this.drawerLayout != null && this.toolbar != null) {
            this.drawerToggle = new ActionBarDrawerToggle(this, this.drawerLayout, this.toolbar, R.string.nav_open,
                    R.string.nav_close);
            this.drawerLayout.addDrawerListener(this.drawerToggle);
            // Sync the toggle state after onRestoreInstanceState has occurred.
            this.drawerToggle.syncState();
        }

        // Setup NavigationView
        if (this.navigationView != null) {
            this.navigationView.setNavigationItemSelectedListener(this);
            this.navigationView.setCheckedItem(R.id.menu_nav_home);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        this.handler.removeCallbacksAndMessages(null);
    }

    @Override
    public void onConfigurationChanged(final Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        this.drawerToggle.onConfigurationChanged(newConfig);
    }

    @Override
    public boolean onCreateOptionsMenu(final Menu menu) {
        this.getMenuInflater().inflate(R.menu.menu_activity_main, menu);

        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(final MenuItem item) {
        // Pass the event to ActionBarDrawerToggle, if it returns
        // true, then it has handled the app icon touch event
        if (this.drawerLayout != null && this.drawerToggle.onOptionsItemSelected(item)) {
            return true;
        }

        switch (item.getItemId()) {
            case android.R.id.home:
                if (this.drawerLayout != null) {
                    this.drawerLayout.openDrawer(GravityCompat.START);
                } else {
                    this.finish();
                }
                return true;

            default:
                return super.onOptionsItemSelected(item);
        }
    }

    @Override
    public void onBackPressed() {
        if (this.isNavDrawerOpen()) {
            this.closeNavDrawer();
        } else {
            super.onBackPressed();
        }
    }

    public void setToolbarTitle(final CharSequence title) {
        if (this.toolbar != null) {
            this.toolbar.setTitle(title);
        }
    }

    public void setToolbarSubtitle(final CharSequence subtitle) {
        if (this.toolbar != null) {
            this.toolbar.setSubtitle(subtitle);
        }
    }

    public void setToolbarNavigationIcon(final int resId) {
        if (this.toolbar != null) {
            this.toolbar.setNavigationIcon(resId);
        }
    }

    protected boolean isNavDrawerOpen() {
        return this.drawerLayout != null && this.drawerLayout.isDrawerOpen(GravityCompat.START);
    }

    protected void openNavDrawer() {
        if (this.drawerLayout != null) {
            this.drawerLayout.openDrawer(GravityCompat.START);
        }
    }

    protected void closeNavDrawer() {
        if (this.drawerLayout != null) {
            this.drawerLayout.closeDrawer(GravityCompat.START);
        }
    }

    private void onNavDrawerItemClicked(final int id) {
        this.handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                goToNavDrawerItem(id);
            }
        }, NAV_DRAWER_LAUNCH_DELAY);
    }

    private void goToNavDrawerItem(final int id) {
        if (id == R.id.menu_app_settings) {
            this.startActivity(new Intent(this, SettingsActivity.class));
        } else {
            this.closeNavDrawer();
        }
    }
}
cypressf commented 8 years ago

How does this issue relate to https://code.google.com/p/android/issues/detail?id=200043? If google fixes that bug, will it also fix this issue?

JakeWharton commented 8 years ago

No idea. Maybe. It depends on what information is given to the processor in the incremental compilation.

On Wed, Jul 6, 2016 at 4:29 PM Cypress Frankenfeld notifications@github.com wrote:

How does this issue relate to https://code.google.com/p/android/issues/detail?id=200043? If google fixes that bug, will it also fix this issue?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/JakeWharton/butterknife/issues/31#issuecomment-230896478, or mute the thread https://github.com/notifications/unsubscribe/AAEEEUG3d8zRfhtBSEpxy8Ahgd88RKCBks5qTBAvgaJpZM4An8m2 .