0

I would like to clean up my code and have my checkbox do some actions from the switch statement inside onOptionsItemSelected(). Instead, I have an onClick listener in onCreateOptionsMenu for my custom checkbox. This works, but I would like to understand how to have code inside case R.id.star_favorite: called.

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.menu_main, menu);
    checkBox = (CheckBox) menu.findItem(R.id.star_favorite).getActionView();
    checkBox.setButtonDrawable(R.drawable.favorite_checkbox);
    if(currentQuote != null) {
        currentQuoteIsFavorite = currentQuote.getFavorite();
        checkBox.setChecked(currentQuoteIsFavorite);
    }
    checkBox.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if(currentQuote != null) {
                currentQuoteIsFavorite = !currentQuoteIsFavorite;
                updateFavorite(currentQuoteIsFavorite);
            } else {
                checkBox.setChecked(false);
                Toast.makeText(getApplicationContext(), "No Quote To Save", Toast.LENGTH_SHORT).show();
            }
        }
    });
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch(item.getItemId()) {
        case R.id.star_favorite:
            //already tried putting code like updateFavorite() inside here but it's not called
            Toast.makeText(this, "Checkbox clicked", Toast.LENGTH_SHORT).show();
            if(currentQuote != null) {
                currentQuoteIsFavorite = !currentQuoteIsFavorite;
                updateFavorite(currentQuoteIsFavorite);
            } else {
                checkBox.setChecked(false);
                Toast.makeText(getApplicationContext(), "No Quote To Save", Toast.LENGTH_SHORT).show();
            }
        case R.id.share_quote:
            Log.d("onOptionsItemSelected", "case R.id.share_quote selected");
            shareQuote();
            break;
        case R.id.menu:
            Log.d("onOptionsItemSelected", "case R.id.menu selected");

            break;
    }
    return super.onOptionsItemSelected(item);
}
Peter G. Williams
  • 627
  • 1
  • 6
  • 13
  • Its not gonna work because you are trying to find an Id in your switch statement which is not initialized yet. So to listen to your optionsItemSelected clicks, you need to first initialize your checkbox so you can listen to clicks. In Conclusion, it is not possible – Yunus Kulyyev May 31 '19 at 21:46
  • but share_quote and menu work, how are these initialized? – Peter G. Williams May 31 '19 at 21:58
  • I can't see the rest of the code, but I am sure that you initialized them before that switch statement – Yunus Kulyyev May 31 '19 at 22:00
  • The "Checkbox clicked" `Toast` still gets shown, right? It's just the `if / else` block that doesn't seem to do anything? –  Jun 01 '19 at 01:31
  • No that's the thing. It is not shown. But share_quote, and menu work fine. – Peter G. Williams Jun 01 '19 at 02:19
  • What if you remove all the logic from `onCreateOptionsMenu()` except for the menu inflation? Then the `Toast` gets shown? –  Jun 01 '19 at 02:40
  • Oh, wait. It looks like you forgot a `break` statement in the `star_favorite` case.. –  Jun 01 '19 at 02:44
  • After looking at this further, each `case` label inside `onOptionsItemSelected()` should return `true` since you're handling the event yourself. But that still doesn't solve the problem. I was able to create a toggle-able toolbar icon, though. I'll post the code as an answer. Maybe it can help you. To troubleshoot, I suggest starting with code that works, and then as you add additional lines, take note if the app still works as expected. –  Jun 01 '19 at 06:37

1 Answers1

1

In this case, you don't need a Checkbox at all. You can use the android:checkable attribute to make a menu item checkable. You then update the icon in onOptionsItemSelected().

QuoteActivity.java

public class QuoteActivity extends AppCompatActivity {

    // saved state, randomized for testing
    private Random rand = new Random();
    private boolean savedAsFavorite = rand.nextBoolean();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.quote_activity);
        setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.quote_menu, menu);
        toggleItem(menu.findItem(R.id.favorite), savedAsFavorite);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.favorite:
                toggleItem(item, !item.isChecked());
                return true;
            case R.id.share:
                // do something
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

    private void toggleItem(MenuItem item, boolean isChecked) {
        item.setChecked(isChecked);
        item.setIcon(iconDrawable(isChecked));
    }

    private Drawable iconDrawable(boolean isChecked) {
        return getDrawable(isChecked ? R.drawable.favorite_enabled : R.drawable.favorite_disabled);
    }
}

quote_menu.xml

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/favorite"
        android:icon="@drawable/favorite_disabled"
        android:title="Favorite"
        app:showAsAction="ifRoom"
        android:checkable="true"
        android:checked="false"/>

    <!-- additional menu items -->

</menu>

And of course, you need two drawables for the enabled and disabled state of the favorite icon. Hope that helps!

  • This looks really good. I will try it out in a sample program then try to apply to mine. The reason I wanted to go with a CheckBox is that I HAVE to call setChecked() in another method. I load quotes from a database and whether that quote is favorite or not, I call setChecked(isFavorite). This way the star will always be checked or unchecked depending on the quote that's called. And I still need to be able to check it manually. But I think I could make a global MenuItem favoriteItem variable, set it in onCreateOptionsMenu, then later call favoriteItem.setChecked? Thanks for the help. – Peter G. Williams Jun 01 '19 at 13:38
  • One thing about this solution that bugs me is that changing the checked state or icon of a menu item will change the ripple effect into a circle that just fades out.. which is inconsistent with the normal ripple effect you get when clicking on a menu item. Not really a big deal, but I can't figure out why it's happening. –  Jun 01 '19 at 14:47
  • For some reason, I thought the ripple effect was working fine before, but then I installed a preview version of Android Studio to see if a completely different issue has been fixed, and now I noticed the ripple effect problem. So, I don't know if it was happening from the beginning or I'm just imagining things now. –  Jun 01 '19 at 14:54
  • Are you having this ripple effect with other menu items? – Peter G. Williams Jun 01 '19 at 14:56
  • Yup, it's just the one that I toggle the icon that this happens with. Quite a while I go, I implemented a similar pattern where I toggled a "pin" menu item, and I do remember having issues with the ripple effect then as well. –  Jun 01 '19 at 14:58
  • 1
    So I tried your code out in a small sample. It works great! I was also able to make a MenuItem starFavoriteItem class variable; and initialize it inside onCreateOptionsMenu. Then I can call toggleItem() on this class variable and fill in the star in the toolbar when a previously saved favorite quote is generated. I am not noticing any ripple issue that you mentioned. It works fine for now. Thanks for the help. – Peter G. Williams Jun 01 '19 at 22:07
  • That's good to hear. The ripple effect might behave differently depending on API Level or app theme, so I'll have to look into that further some time. –  Jun 01 '19 at 22:18