0

I was following this article. Unfortunatelly, badge isn't drawn. Of course I changed invalidateOptionsMenu method to supportInvalidateOptionsMenu method.

I was checking the log, step by step and found out that draw method in custom Drawable named BadgeDrawable is never called. Why is that?

I'm trying to use it in AppCompatActivity using Support Design Library version 23.0.1

I saw this answer but it seems to be out of date.

Is there anyone who managed to implement badger counter for MenuItem icon inside android.support.v7.widget.Toolbar? If the way shown in the article is wrong then could you suggest other way to achieve the result as in example below?: enter image description here

Here is a setup inside my application:

LayerDrawable XML ic_filter_notifications.xml:

<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >

    <item
        android:id="@+id/ic_filter_notification"
        android:drawable="@drawable/ic_filter_grey600_18dp"
        android:gravity="center"/>

    <!-- set a place holder Drawable so android:drawable isn't null -->
    <item android:id="@+id/ic_filter_badge"
        android:drawable="@drawable/ic_filter_grey600_18dp">

    </item>

</layer-list>

Menu that I use in the Toolbar menu_activity_main.xml:

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

    <item
        android:id="@+id/menu_settings"
        android:orderInCategory="100"
        android:title="@string/menu_settings"
        cobytu:showAsAction="never"/>
    <item
        android:id="@+id/menu_logout"
        android:orderInCategory="1000000"
        android:title="@string/menu_logout"
        cobytu:showAsAction="never"/>
    <item
        android:id="@+id/action_filter"
        android:title="@string/filters"
        android:icon="@drawable/ic_filter_notifications"
        cobytu:showAsAction="always"/>
    <item
        android:id="@+id/action_location"
        android:icon="@drawable/ic_map_marker_radius_grey600_18dp"
        android:title="@string/location"
        cobytu:showAsAction="always"/>

</menu>

This is function sets the count int for new BadgeDrawable inside UsefulFunctions class - UsefulFunctions.java:

 public class UsefulFunctions {

     private Context ctx;

     public UsefulFunctions(Context context){
                ctx = context;
            }


       public static void setBadgeCount(Context context, LayerDrawable icon,
            int count) {

        BadgeDrawable badge;

        // Reuse drawable if possible
        Drawable reuse = icon.findDrawableByLayerId(R.id.ic_filter_badge);
        if (reuse != null && reuse instanceof BadgeDrawable) {
            badge = (BadgeDrawable) reuse;
        } else {
            Log.d("mTAG", UsefulFunctions.class.getName()
                    + " I'm creating new BadgeDrawable!");
            badge = new BadgeDrawable(context);
        }

        badge.setCount(count);
        icon.mutate();
        Log.d("mTAG",
                UsefulFunctions.class.getName() + " badge: " + badge.toString()
                        + " count: " + count);
        icon.setDrawableByLayerId(R.id.ic_filter_badge, badge);

    }
}

This is the important scrap of my MainActivity where I invoke BadgeDrawable creation. I also show what's inside onCreate and in onOptionsItemSelected, may be needed - MainActivity.java:

public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener,  DialogFragmentFilters.FiltersRefreshInterface{

    public String username;


    //CollapsingToolbarLayout
    private CollapsingToolbarLayout mCollapsingToolbarLayout;

    //Toolbar
    private Toolbar mToolbar;

    //TabLayout
    private TabLayout mTabLayout;
    private ViewPager mPager;
    private MyPagerAdapter mAdapter;

    //NavigationDraver
    private static final String SELECTED_ITEM_ID = "selected_item_id";
    private static final String FIRST_TIME = "first_time";
    private DrawerLayout mDrawerLayout;
    private ActionBarDrawerToggle mDrawerToggle;
    private NavigationView mNavigationView;
    private int mSelectedId;
    private boolean mUserSawDrawer = false;
    private TextView mNavHeaderNameText;
    private TextView mNavHeaderEmailText;


    private  DatabaseHandler db;
    private  UserFunctions userFunctions;
    private AlertDialog.Builder dialogBuilder;
    private AlertDialog alertDialog;

    private JSONObject jsonLocation = new JSONObject();


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        userFunctions = new UserFunctions();
        db = new DatabaseHandler(getApplicationContext());

        username = db.getUserDetails().get(DatabaseHandler.getKEY_USER_LOGIN());

        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        setupToolbar();
        setupTablayout();


        mNavigationView = (NavigationView) findViewById(R.id.navigation_view);
        mNavigationView.setNavigationItemSelectedListener(this);

        View header = LayoutInflater.from(MainActivity.this).inflate(R.layout.nav_header, null);
        mNavHeaderNameText = (TextView) header.findViewById(R.id.tv_nav_header_name);
        mNavHeaderNameText.setText("Siema "+username+"!");

        mNavHeaderEmailText = (TextView) header.findViewById(R.id.tv_nav_header_email);
        mNavHeaderEmailText.setText(db.getUserDetails().get(DatabaseHandler.getKEY_USER_EMAIL()));


        mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,mToolbar, R.string.drawer_open, R.string.drawer_close);
        mDrawerLayout.setDrawerListener(mDrawerToggle);
        mDrawerToggle.syncState();

        if (!didUserSeeDrawer()) {
            showDrawer();
            markDrawerSeen();
        } else {
            hideDrawer();
        }

        mSelectedId = savedInstanceState == null ? R.id.navigation_item_4 : savedInstanceState.getInt(SELECTED_ITEM_ID);
        navigate(mSelectedId);
        getSupportActionBar().setHomeButtonEnabled(true);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);



    }


    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
            // Handle action bar item clicks here. The action bar will
            // automatically handle clicks on the Home/Up button, so long
            // as you specify a parent activity in AndroidManifest.xml.
            int id = item.getItemId();
            showActionDialog(id);
            //noinspection SimplifiableIfStatement
        switch (id) {
        case R.id.menu_settings:
            return true;
        case R.id.menu_logout:
            dialogBuilder = new AlertDialog.Builder(new ContextThemeWrapper(
                    MainActivity.this, R.style.Theme_AppCompat_Light_Dialog));
            dialogBuilder
                    .setTitle(getResources().getString(R.string.logoutAlertTitle))
                    .setIcon(R.drawable.ic_logout).setMessage(getResources().getString(R.string.logoutAlertMessage)).setPositiveButton(getResources().getString(R.string.logoutAlertYesButton),
                            new DialogInterface.OnClickListener() {

                                @Override
                                public void onClick(DialogInterface dialog,
                                        int which) {

                                    ((RelativeLayout) findViewById(R.id.fl_mainContent))
                                            .animate()
                                            .alpha(0f)
                                            .setListener(
                                                    new AnimatorListenerAdapter() {
                                                        public void onAnimationEnd(
                                                                Animator animation) {
                                                            mDrawerLayout
                                                                    .setVisibility(View.INVISIBLE);
                                                            mDrawerLayout
                                                                    .setAlpha(1f);
                                                            mDrawerLayout
                                                                    .animate()
                                                                    .setListener(null);
                                                        }
                                                    });

                                    userFunctions.logoutUser(getApplicationContext());

                                    Intent i = new Intent(MainActivity.this, LoginActivity.class);
                                    // MainActivity.this.deleteDatabase("cobytu_db");
                                    startActivity(i);
                                    finish();
                                }
                            })
                    .setNegativeButton(getResources().getString(R.string.logoutAlertNoButton),
                            new DialogInterface.OnClickListener() {

                                @Override
                                public void onClick(DialogInterface dialog,
                                        int which) {
                                    dialog.dismiss();

                                }
                            });
            alertDialog = dialogBuilder.create();
            alertDialog.show();
            return true;
        }

            return false;
        }

    public int filterCounter = 0;

    class FetchCountTask extends AsyncTask<Void, Void, Integer> {

        @Override
        protected Integer doInBackground(Void... params) {
            // example count. This is where you'd 
            // query your data store for the actual count.
            return 5; 
        }

        @Override
        public void onPostExecute(Integer count) {
            setNotifCount(count);
        }
    }

    private void setNotifCount(int count){
        filterCounter = count;
        supportInvalidateOptionsMenu();
    }



    @Override
    public boolean onCreateOptionsMenu(Menu menu) {

        getMenuInflater().inflate(R.menu.menu_activity_main, menu);
        MenuItem item = menu.findItem(R.id.action_filter);
        LayerDrawable icon = (LayerDrawable) item.getIcon();

        // Update LayerDrawable's BadgeDrawable
        Log.d("mTag", "onCreateOptionsMenu: this: " + this.toString());
        UsefulFunctions.setBadgeCount(this, icon, filterCounter);

        return true;
    }


    @Override
    public void onRefreshFilters(int counter) {
        setupTablayout();
        Toast.makeText(MainActivity.this, "List has been refreshed! " + counter, Toast.LENGTH_SHORT).show();
        filterCounter = counter;
        new FetchCountTask.execute();
    }



}
Community
  • 1
  • 1
Jack Lynx
  • 216
  • 2
  • 12
  • 1
    post your relevant code – pskink Nov 08 '15 at 14:19
  • @pskink - I've posted what I have. Please have a look into description again. – Jack Lynx Nov 08 '15 at 15:48
  • start with the simple `Drawable` like [this](http://pastebin.com/2nqrBn0k) if it works try to change its look to match your needs – pskink Nov 08 '15 at 16:01
  • @pskink - I tried what you suggested and **still** the `draw` function is never called: `@Override public void draw(Canvas canvas) { Log.d("mTAG", getClass().getName() + ": I'M INSIDE draw !"); canvas.drawColor(0x5500ff00); String text = Integer.toString(cnt); paint.getTextBounds(text, 0, text.length(), textBounds); canvas.drawText(text, 0, -textBounds.top / 2, paint); }` – Jack Lynx Nov 08 '15 at 16:22
  • try this http://pastebin.com/N0MKuEuT, tested on 4.4 with support lib – pskink Nov 08 '15 at 16:26
  • @pskink again, no result. The `draw` function inside `D` class was not touched. – Jack Lynx Nov 08 '15 at 16:38
  • what api level are you running? – pskink Nov 08 '15 at 16:38
  • I'm testing on 5.1.1 (Xperia Z3 Compact) Now trying on 4.1.2 (Samsung S III Mini) and there's also no result. – Jack Lynx Nov 08 '15 at 16:47
  • btw your `Toolbar mToolbar` is not setup – pskink Nov 08 '15 at 17:29
  • It doesn't matter, I guess. – Jack Lynx Nov 08 '15 at 17:36
  • it does, call `setSupportActionBar` – pskink Nov 08 '15 at 17:37
  • sorry, I have mToolbar setup inside `setupToolbar()` function: `private void setupToolbar() { mToolbar = (Toolbar) findViewById(R.id.app_bar); setSupportActionBar(mToolbar); // Show menu icon final ActionBar ab = getSupportActionBar(); ab.setHomeAsUpIndicator(R.drawable.ic_launcher); ab.setDisplayHomeAsUpEnabled(true); }` – Jack Lynx Nov 08 '15 at 17:37
  • i think that nobody will help you if you are posting only parts of your code – pskink Nov 08 '15 at 17:44
  • I know that the problem is with `draw` function inside cutrom Drawable class. I tested my setup inside MainActivity and it works, problem is inside `BadgeDrawable` class. Please tell me which part of code would you like to see? – Jack Lynx Nov 08 '15 at 17:56
  • first check `MenuItem item = menu.findItem(R.id.action_filter); item.setIcon(d);` – pskink Nov 08 '15 at 18:00
  • The result is that icon is not changed and in log I can see only that constructor of class `D` was called and nothing else. – Jack Lynx Nov 08 '15 at 18:12
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/94544/discussion-between-lynx-and-pskink). – Jack Lynx Nov 08 '15 at 18:38
  • see [here](https://www.dropbox.com/s/gfuvdvcepfbkjoz/src.zip?dl=0) for a working sources – pskink Nov 08 '15 at 21:59
  • and? did you try them? – pskink Nov 09 '15 at 00:12
  • Yes, your code works as we all want. Now I'm looking into my code to examine what's wrong. It's all the same by my code is not working... – Jack Lynx Nov 09 '15 at 09:49
  • see again [here](https://www.dropbox.com/s/gfuvdvcepfbkjoz/src.zip?dl=0) for a more realistic solution, notice how menu.xml looks like now – pskink Nov 09 '15 at 10:34
  • maybe it sounds strange, but it seems that .setIcon function brings no result for me. Maybe I should use DrawableCompat instead Drawable? – Jack Lynx Nov 09 '15 at 10:39
  • so how come `setIcon` works for me? see `setupBadge` method – pskink Nov 09 '15 at 10:40
  • just get my sources and forget about that artificial `layer-list` drawables created for each badge – pskink Nov 09 '15 at 10:46
  • It's also strange for me. Is `setupBadge` meant to be inside MainActivity from new code that you posted? If so, there's old MainActivity there. – Jack Lynx Nov 09 '15 at 10:47
  • If you have TeamViewer, maybe it's a good idea to take a glance on my code... – Jack Lynx Nov 09 '15 at 10:50
  • old? what do you mean? my MainActivity.java is `3360` bytes long and created `2015-11-09 11:26`, path: `src/main/java/org/pskink/badge/MainActivity.java`, md5sum: `87f3c0ee532d7da52f5c30cab5d4c432`, no TeamViewer – pskink Nov 09 '15 at 10:56
  • I configured it in the way you presented inside my project and it doesn't work. I have Navigation drawer, TabLayout, AppBarLayout, PagerAdapter, and CoordinatorLayout inside my MainActivity .xml. Maybe this causes some troubles. – Jack Lynx Nov 09 '15 at 11:06
  • so try adding your components to my sources one by one – pskink Nov 09 '15 at 11:08
  • also what happens if you call `item.setIcon(res.getDrawable(android.R.drawable.ic_....))` ? – pskink Nov 09 '15 at 11:18
  • `item.setIcon(res.getDrawable(android.R.drawable.ic_....))` gives no result – Jack Lynx Nov 09 '15 at 11:49
  • `Log.d` the value of `menu.getClass()` and `item.getClass()` what do you see? – pskink Nov 09 '15 at 12:01
  • `Log.d("mTAG", getClass().getName() + " onCreateOptionsMenu: menu.getClass(): " + menu.getClass() + "; item.getClass(): " + item.getClass());` gives `com.lynx.home.MainActivity onCreateOptionsMenu: menu.getClass(): class android.support.v7.internal.view.menu.MenuBuilder; item.getClass(): class android.support.v7.internal.view.menu.MenuItemImpl` – Jack Lynx Nov 09 '15 at 12:08
  • ok i have the same classes, if `setIcon` does not work, did you try: `setTitle[Condensed]` ? – pskink Nov 09 '15 at 12:12
  • Not sure what is Condensed by neither: `@Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_activity_main, menu); MenuItem item = menu.findItem(R.id.action_location); item.setTitleCondensed("TEST"); return super.onCreateOptionsMenu(menu); }` nor when using `item.setTitle("TEST");` don't change title of any item in Menu. (I even removed icons for MenuItems to have only title displayed in Toolbar, but see [this screen](https://www.dropbox.com/s/dm19z1ge4wz5vhq/Przechwytywanie.JPG?dl=0) - only initial titles areshown, no "TEST" there). – Jack Lynx Nov 09 '15 at 12:31
  • @pskink Just found out that when I moved `item.setTitle("TEST");` to onOptionsItemSelected then it worked. When I tried to use setupBadge() on menu item from onOptionsItemSelected(MenuItem item) I received an error: `java.lang.NullPointerException: Attempt to invoke virtual method 'void android.graphics.drawable.Drawable.setCallback(android.graphics.drawable.Drawable$Callback)' on a null object reference at android.graphics.drawable.LayerDrawable.(LayerDrawable.java:112) at android.graphics.drawable.LayerDrawable.(LayerDrawable.java:94)` – Jack Lynx Nov 09 '15 at 12:56
  • It shows that error on this line inside BadgeDrawable class: `super(new Drawable[]{dr});` – Jack Lynx Nov 09 '15 at 13:03
  • did you then try in `onCreateOptionsMenu` just inflate the menu and call `setupBadge` in `onPrepareOptionsMenu`? – pskink Nov 09 '15 at 13:37
  • Yes, only then it WORKS! :) – Jack Lynx Nov 09 '15 at 13:54
  • great, but this is very strange ;-) – pskink Nov 09 '15 at 14:00
  • It really is, actually I cannot explain why usage of same code in two different projects can vary as this. Anyway, @pskink - thank you for your time, really appreciate that. – Jack Lynx Nov 09 '15 at 14:04
  • if it works inside `onPrepareOptionsMenu` be careful with calling `setupBadge` as `onPrepareOptionsMenu` can be called **many many times**, unlike `onCreateOptionsMenu` which is called only once, so the best approach is using some boolean flag indicating if `setupBadge` was already called or not, or inside `setupBadge` check if `getIcon()` is instance of `BadgeDrawable`, in that case do nothing – pskink Nov 09 '15 at 14:12

0 Answers0