Android Tutorial Material Design 4 - Modifyable and advanced Menu

Note: You should use B4A 6.0 or above for this tutorial.

With the ACToolBar(Light|Dark) object we can get access to the Menu object of the activity. This opens the possibility for modifyable menus and some other advanced menu features.

Basics

With the ToolBar.Menu property we have access to the menu of the ToolBar. For a standalone ToolBar (which is not set as the activity ActionBar) we can just access the property everywhere in our code and do what we want to do with it just like adding/removing menu items.

If the ToolBar is our activities ActionBar we have to handle the menu in a special way because if we don't do this, B4As own menu handling will conflict with our handling.

In Android the menu is created and initalized with two methods: onCreateOptionsMenu() and onPrepareOptionsMenu(). I don't want to explain the whole menu system here you just need to know, that B4A uses these methods internally to create it's own menu and they get called "at some time" in the activity initialization process.

With the itroduction of inline Java code in B4A 4.30 we now have the possibility to add our own code to handle the application menu. The idea is to call an event to set up the menu and here is an example how this can be done:

B4X:
#If Java

public boolean _onCreateOptionsMenu(android.view.Menu menu) {
    if (processBA.subExists("activity_createmenu")) {
        processBA.raiseEvent2(null, true, "activity_createmenu", false, new de.amberhome.objects.appcompat.ACMenuWrapper(menu));
        return true;
    }
    else
        return false;
}
#End If

In this code we use the _onCreateOptionsMenu hook to check if a Sub named "Activity_CreateMenu" exists. If yes, then we call it and we provide the activities menu as a parameter. If the sub does not exist, the normal B4A menu-handling is done.

Now we need the implementation of Activity_CreateMenu:

B4X:
Sub Activity_CreateMenu(Menu As ACMenu)
    Menu.Clear

    Dim item As ACMenuItem
    Menu.Add2(10, 1, "Plus one", xml.GetDrawable("ic_plus_one_black_24dp")).ShowAsAction = item.SHOW_AS_ACTION_IF_ROOM
    Menu.Add2(20, 2, "Refresh", xml.GetDrawable("ic_refresh_black_24dp")).ShowAsAction = item.SHOW_AS_ACTION_ALWAYS

    Menu.Add(1, 3, "Overflow1", Null)
    Menu.Add(2, 4, "Overflow2", Null)
    Menu.Add(3, 5, "Overflow3", Null)

    'Sync Checkboxes with the Menu
    SetCheckBoxState(Menu)

End Sub
First we clear the menu to be sure that there are no old entries in it. Then we just use the Menu object to add some additional menus. Easy, isn't it? (For this example we sync the menu with the checkboxes with SetCheckBoxState())

It is good practice to use a Toolbar as your ActionBar. If you do this you can access the menu with ToolBar.Menu everywhere in your code to modify the menu.

Some advanced menu features

With the ACMenu and ACMenuItem objects we have access to some advanced menu features.

When you add a menu item to the menu you can specify a menu ID and a sort order. So with the sort order you can later add menu entries which are displayed above other menu items. With the ID you can access this item later and modify it.

You can create checkable menu itemes. The ACMenu.Add() methods return the added menu item so you can directly modify it and make it checkable or set it as an action item or even make it invisible.

It is even possible to add or remove menu items dynamically.

B4X:
Sub cbItemVisible_CheckedChange(Checked As Boolean)
  ToolBar.Menu.FindItem(1).Visible = Checked
End Sub

Sub cbItemCheckable_CheckedChange(Checked As Boolean)
  ToolBar.Menu.FindItem(2).Checkable = Checked
End Sub

Sub cbItemDisable_CheckedChange(Checked As Boolean)
  ToolBar.Menu.FindItem(3).Enabled = Not(Checked)
End Sub

Look at the example project for better understanding.

overflow_menu.png
 

Attachments

  • ToolBarMenuExample2_0.zip
    18.6 KB · Views: 4,162
Last edited:

lomosami

Member
Licensed User
Longtime User
Hello,
I would to add an action icon to toolbar but not appear. My code is:

Private ActionBar As ACToolBarLight
Dim xml As XmlLayoutBuilder
Dim bd As BitmapDrawable
...
bd = xml.GetDrawable("ic_plus_one_black_24dp")
ActionBar.Menu.Add2(10, 1, "find", bd).ShowAsAction = Mnitem.SHOW_AS_ACTION_ALWAYS '--> nothing appear!
'using this one appear but it is not the same (because I want to try to connect Serchview with toolbar)
Activity.AddMenuItem3("find", "Menu", bd.Bitmap, True)

Any help is welcome
Thanks
 

lomosami

Member
Licensed User
Longtime User
No my code not used inline java code as your example... I didn't know that it was the only method. Thanks for your time and repy.
 

JohnC

Expert
Licensed User
Longtime User
How can I modify the example in the first post to work with B4A 6.0 and with your new v3.2 of the AppCompat library.

The main error I think that I am getting is that your source code expects a b4a_appcompat directory I the below line, but I do not have it:

(My .jar/.xml additional libraries folder is: D:\Android\Basic4android\AddLibs)

D:\Android\Basic4android\AddLibs\b4a_appcompat, de.amberhome.objects.appcompat

But, I am just getting that it is causing the below error at runtime:

** Activity (main) Create, isFirst = true **
main_activity_create (java line: 337)
java.lang.RuntimeException: java.lang.NullPointerException
at anywheresoftware.b4a.keywords.LayoutBuilder.loadLayout(LayoutBuilder.java:166)
at anywheresoftware.b4a.objects.ActivityWrapper.LoadLayout(ActivityWrapper.java:209)
at de.amberhome.appcompat.toolbarmenuexample.main._activity_create(main.java:337)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:169)
at de.amberhome.appcompat.toolbarmenuexample.main.afterFirstLayout(main.java:102)
at de.amberhome.appcompat.toolbarmenuexample.main.access$000(main.java:17)
at de.amberhome.appcompat.toolbarmenuexample.main$WaitForLayout.run(main.java:80)
at android.os.Handler.handleCallback(Handler.java:725)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:5293)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1102)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:869)
at de.robv.android.xposed.XposedBridge.main(XposedBridge.java:132)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.NullPointerException
at de.amberhome.objects.appcompat.ACToolBarWrapper.DesignerCreateView(ACToolBarWrapper.java:74)
at anywheresoftware.b4a.objects.CustomViewWrapper.AfterDesignerScript(CustomViewWrapper.java:70)
at anywheresoftware.b4a.keywords.LayoutBuilder.loadLayout(LayoutBuilder.java:158)
... 18 more
java.lang.RuntimeException: java.lang.NullPointerException
 

corwin42

Expert
Licensed User
Longtime User
How can I modify the example in the first post to work with B4A 6.0 and with your new v3.2 of the AppCompat library.

The main error I think that I am getting is that your source code expects a b4a_appcompat directory I the below line, but I do not have it:

Remove the #AdditionalRes lines for b4a_appcompat and the #AdditionalRes that are pointing into the SDK for the support libraries. They are not needed anymore.
Then you should load the layout file once and just save it again to update the structure of it.

After this the example should compile and run properly.
 

JohnC

Expert
Licensed User
Longtime User
That did the trick!
 

JohnC

Expert
Licensed User
Longtime User
I am running into a different problem.

I am using your AcToolBarDark:

B4X:
Private ActionBar As ACToolBarDark

And I am using your Inline Java and the Activity_CreateMenu Sub to create the menu:

B4X:
Sub Activity_CreateMenu(Menu As ACMenu)

    sv.Initialize2("Search", sv.THEME_DARK)
    sv.IconifiedByDefault = False

    'Clear the menu
    Menu.Clear

    'Add some MenuItems
    'The best method to add action icons to the ActionBar/Toolbar is to load them as drawables.
    'With this method it is possible to load icons in different resolutions taking care of the device scale.
    'We use the XmlLayoutBuilder library to load the drawables.

    Dim xml As XmlLayoutBuilder
    Dim bd As BitmapDrawable
   
    bd = xml.GetDrawable("ic_search_white_24dp")

    'Add a menu item and assign the SearchView to it
    si = Menu.Add2(1, 1, "Search",  bd)
    si.SearchView = sv

    ActionBar.Menu.Add(2,0,"Menu 1",Null)
    ActionBar.Menu.Add(3,0,"Menu 2",Null)
       
End Sub

Everything works as expected, except on devices with a hardware "Menu" key. Pressing the hardware menu key does NOT pop up a Menu - either the toolbar menu, or a typical menu at the bottom of the screen (which is typically associated with the hardware menu button).

I could simple catch the KeyCodes.KEYCODE_MENU in the Activity_Keypress event and programmically show the toolbars menu, but there is no Actionbar.Menu.Show method.

So how can I pop up the menu when the hardware button is pressed?
 

JohnC

Expert
Licensed User
Longtime User
Figured it out - I had to add the overflow like this:

B4X:
Menu.Add(2,0,"Menu 1",Null)
Menu.Add(3,0,"Menu 2",Null)
 

miguelconde

Member
Licensed User
Longtime User
I have this error runing the example. "ToolBarMenuExample2_0".
All the other tutorial (1,2,3) examples are running fine.

I am using Android SDK Buid-Tools and Plataform Tools version 24.0.1
, SDK Tools version 25.1.7 , android support repository 35.

I am testing on a galaxy tab gt-p1000N with android 2.3.3

Installing file.
** Activity (main) Pause, UserClosed = false **
PackageAdded: package:de.amberhome.appcompat.toolbarmenuexample
** Activity (main) Create, isFirst = true **
main_activity_create (java line: 340)
java.lang.RuntimeException: java.lang.NullPointerException
at anywheresoftware.b4a.keywords.LayoutBuilder.loadLayout(LayoutBuilder.java:166)
at anywheresoftware.b4a.objects.PanelWrapper.LoadLayout(PanelWrapper.java:134)
at de.amberhome.appcompat.toolbarmenuexample.main._activity_create(main.java:340)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:169)
at de.amberhome.appcompat.toolbarmenuexample.main.afterFirstLayout(main.java:102)
at de.amberhome.appcompat.toolbarmenuexample.main.access$000(main.java:17)
at de.amberhome.appcompat.toolbarmenuexample.main$WaitForLayout.run(main.java:80)
at android.os.Handler.handleCallback(Handler.java:587)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:123)
at android.app.ActivityThread.main(ActivityThread.java:3687)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:842)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:600)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.NullPointerException
at de.amberhome.objects.appcompat.ACCheckBoxWrapper.DesignerCreateView(ACCheckBoxWrapper.java:61)
at anywheresoftware.b4a.objects.CustomViewWrapper.AfterDesignerScript(CustomViewWrapper.java:70)
at anywheresoftware.b4a.keywords.LayoutBuilder.loadLayout(LayoutBuilder.java:158)
... 17 more
java.lang.RuntimeException: java.lang.NullPointerException
main_setcheckboxstate (java line: 474)
java.lang.RuntimeException: Object should first be initialized (ACCheckBox).
at anywheresoftware.b4a.AbsObjectWrapper.getObject(AbsObjectWrapper.java:50)
at anywheresoftware.b4a.objects.CompoundButtonWrapper.setChecked(CompoundButtonWrapper.java:55)
at de.amberhome.appcompat.toolbarmenuexample.main._setcheckboxstate(main.java:474)
at de.amberhome.appcompat.toolbarmenuexample.main._activity_createmenu(main.java:362)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:169)
at de.amberhome.appcompat.toolbarmenuexample.main._onCreateOptionsMenu(main.java:495)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at anywheresoftware.b4a.BA.runHook(BA.java:142)
at de.amberhome.appcompat.toolbarmenuexample.main.onCreateOptionsMenu(main.java:134)
at android.app.Activity.onCreatePanelMenu(Activity.java:2158)
at android.support.v4.app.FragmentActivity.onCreatePanelMenu(FragmentActivity.java:358)
at android.support.v7.view.WindowCallbackWrapper.onCreatePanelMenu(WindowCallbackWrapper.java:88)
at android.support.v7.app.AppCompatDelegateImplBase$AppCompatWindowCallbackBase.onCreatePanelMenu(AppCompatDelegateImplBase.java:270)
at android.support.v7.view.WindowCallbackWrapper.onCreatePanelMenu(WindowCallbackWrapper.java:88)
at android.support.v7.app.ToolbarActionBar.populateOptionsMenu(ToolbarActionBar.java:454)
at android.support.v7.app.ToolbarActionBar$1.run(ToolbarActionBar.java:61)
at android.os.Handler.handleCallback(Handler.java:587)
at android.os.Handler.dispatchMessage(Handler.java:92)
at anywheresoftware.b4a.Msgbox.waitForMessage(Msgbox.java:198)
at anywheresoftware.b4a.Msgbox.msgbox(Msgbox.java:137)
at anywheresoftware.b4a.BA.ShowErrorMsgbox(BA.java:228)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:207)
at de.amberhome.appcompat.toolbarmenuexample.main.afterFirstLayout(main.java:102)
at de.amberhome.appcompat.toolbarmenuexample.main.access$000(main.java:17)
at de.amberhome.appcompat.toolbarmenuexample.main$WaitForLayout.run(main.java:80)
at android.os.Handler.handleCallback(Handler.java:587)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:123)
at android.app.ActivityThread.main(ActivityThread.java:3687)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:842)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:600)
at dalvik.system.NativeStart.main(Native Method)
java.lang.RuntimeException: Object should first be initialized (ACCheckBox).
** Service (notificationservice) Start **
 
Last edited:

corwin42

Expert
Licensed User
Longtime User
I have this error runing the example. "ToolBarMenuExample2_0".
All the other tutorial (1,2,3) examples are running fine.

I am using Android SDK Buid-Tools and Plataform Tools version 24.0.1
, SDK Tools version 25.1.7 , android support repository 35.

I am testing on a galaxy tab gt-p1000N with android 2.3.3

Installing file.
** Activity (main) Pause, UserClosed = false **
PackageAdded: package:de.amberhome.appcompat.toolbarmenuexample
** Activity (main) Create, isFirst = true **
main_activity_create (java line: 340)
java.lang.RuntimeException: java.lang.NullPointerException
[...]

Hmm, seems to be that onCreateOptionsMenu is called before Activity_Create. I can't reproduce it here since the oldest Android Version I can test on is 2.3.7 and there I can't see this problem.
Try to change the call to SetCheckboxState in Activity_CreateMenu to an CallSubDelayed() call. This should fix the problem.

how i can change the logobitmap left margin?. It is very separate from the left edge
This question is not related to this tutorial. Create a new thread in the questions forum.
 

RauchG

Active Member
Licensed User
Longtime User
Now the debug ok. The lib DesignSupport is missing

Unfortunately stops it here

B4X:
** Activity (main) Create, isFirst = true **
main_activity_create (B4A line: 50)
Activity.LoadLayout("main")
java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
    at anywheresoftware.b4a.keywords.LayoutBuilder.loadLayout(LayoutBuilder.java:166)
    at anywheresoftware.b4a.objects.ActivityWrapper.LoadLayout(ActivityWrapper.java:209)
    at de.amberhome.appcompat.toolbarmenuexample.main._activity_create(main.java:367)
    at java.lang.reflect.Method.invoke(Native Method)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:169)
    at de.amberhome.appcompat.toolbarmenuexample.main.afterFirstLayout(main.java:102)
    at de.amberhome.appcompat.toolbarmenuexample.main.access$000(main.java:17)
    at de.amberhome.appcompat.toolbarmenuexample.main$WaitForLayout.run(main.java:80)
    at android.os.Handler.handleCallback(Handler.java:739)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:152)
    at android.app.ActivityThread.main(ActivityThread.java:5497)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
Caused by: java.lang.reflect.InvocationTargetException
    at java.lang.reflect.Method.invoke(Native Method)
    at anywheresoftware.b4a.objects.CustomViewWrapper.AfterDesignerScript(CustomViewWrapper.java:64)
    at anywheresoftware.b4a.keywords.LayoutBuilder.loadLayout(LayoutBuilder.java:158)
    ... 14 more
Caused by: java.lang.NoClassDefFoundError: Failed resolution of: Lde/amberhome/objects/appcompat/R$style;
    at de.amberhome.objects.appcompat.ACToolbarLightWrapper.innerInitialize(ACToolbarLightWrapper.java:26)
    at anywheresoftware.b4a.objects.ViewWrapper.Initialize(ViewWrapper.java:65)
    at de.amberhome.objects.appcompat.ACToolBarWrapper.Initialize(ACToolBarWrapper.java:45)
    at de.amberhome.objects.appcompat.ACToolBarWrapper._initialize(ACToolBarWrapper.java:77)
    ... 17 more
Caused by: java.lang.ClassNotFoundException: Didn't find class "de.amberhome.objects.appcompat.R$style" on path: DexPathList[[zip file "/data/app/de.amberhome.appcompat.toolbarmenuexample-2/base.apk"],nativeLibraryDirectories=[/data/app/de.amberhome.appcompat.toolbarmenuexample-2/lib/arm64, /vendor/lib64, /system/lib64]]
    at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:511)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:469)
    ... 21 more
    Suppressed: java.lang.ClassNotFoundException: de.amberhome.objects.appcompat.R$style
        at java.lang.Class.classForName(Native Method)
        at java.lang.BootClassLoader.findClass(ClassLoader.java:781)
        at java.lang.BootClassLoader.loadClass(ClassLoader.java:841)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:504)
        ... 22 more
    Caused by: java.lang.NoClassDefFoundError: Class not found using the boot class loader; no stack trace available
 

corwin42

Expert
Licensed User
Longtime User
Now the debug ok. The lib DesignSupport is missing

Unfortunately stops it here

B4X:
** Activity (main) Create, isFirst = true **
main_activity_create (B4A line: 50)
Activity.LoadLayout("main")
java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
    at anywheresoftware.b4a.keywords.LayoutBuilder.loadLayout(LayoutBuilder.java:166)
    at anywheresoftware.b4a.objects.ActivityWrapper.LoadLayout(ActivityWrapper.java:209)
    at de.amberhome.appcompat.toolbarmenuexample.main._activity_create(main.java:367)
    at java.lang.reflect.Method.invoke(Native Method)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:169)
    at de.amberhome.appcompat.toolbarmenuexample.main.afterFirstLayout(main.java:102)
    at de.amberhome.appcompat.toolbarmenuexample.main.access$000(main.java:17)
    at de.amberhome.appcompat.toolbarmenuexample.main$WaitForLayout.run(main.java:80)
    at android.os.Handler.handleCallback(Handler.java:739)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:152)
    at android.app.ActivityThread.main(ActivityThread.java:5497)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
Caused by: java.lang.reflect.InvocationTargetException
    at java.lang.reflect.Method.invoke(Native Method)
    at anywheresoftware.b4a.objects.CustomViewWrapper.AfterDesignerScript(CustomViewWrapper.java:64)
    at anywheresoftware.b4a.keywords.LayoutBuilder.loadLayout(LayoutBuilder.java:158)
    ... 14 more
Caused by: java.lang.NoClassDefFoundError: Failed resolution of: Lde/amberhome/objects/appcompat/R$style;
    at de.amberhome.objects.appcompat.ACToolbarLightWrapper.innerInitialize(ACToolbarLightWrapper.java:26)
    at anywheresoftware.b4a.objects.ViewWrapper.Initialize(ViewWrapper.java:65)
    at de.amberhome.objects.appcompat.ACToolBarWrapper.Initialize(ACToolBarWrapper.java:45)
    at de.amberhome.objects.appcompat.ACToolBarWrapper._initialize(ACToolBarWrapper.java:77)
    ... 17 more
Caused by: java.lang.ClassNotFoundException: Didn't find class "de.amberhome.objects.appcompat.R$style" on path: DexPathList[[zip file "/data/app/de.amberhome.appcompat.toolbarmenuexample-2/base.apk"],nativeLibraryDirectories=[/data/app/de.amberhome.appcompat.toolbarmenuexample-2/lib/arm64, /vendor/lib64, /system/lib64]]
    at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:511)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:469)
    ... 21 more
    Suppressed: java.lang.ClassNotFoundException: de.amberhome.objects.appcompat.R$style
        at java.lang.Class.classForName(Native Method)
        at java.lang.BootClassLoader.findClass(ClassLoader.java:781)
        at java.lang.BootClassLoader.loadClass(ClassLoader.java:841)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:504)
        ... 22 more
    Caused by: java.lang.NoClassDefFoundError: Class not found using the boot class loader; no stack trace available
Are you using B4A 6.0? And have you installed the aar file of the AppCompat library?
 
Last edited:
Top