Binary XML Inflate Error on PopupMenu.show() Android
Asked Answered
L

1

5

I'm trying to inflate a simple PopupMenu for rename/delete options when a RecylerView item is longClicked. For some reason i'm getting an XML inflate error when I call mPopup.show() after loading my xml file into the inflater.

I use similar logic elsewhere in my app to make a PopupMenu and it works fine. I've even tried loading the working PopupMenu from an unrelated part of the app into this inflater and I see the same android.view.InflateException: Binary XML file line #17: Failed to resolve attribute at index 1 error in logcat, so maybe the XML file isn't the problem?

How can I get this PopupMenu to inflate and show itself?

Fatal Exception Logcat

05-31 23:02:27.421 19597-20019/? E/AndroidRuntime: FATAL EXCEPTION: main
                                               Process: com.example.foo, PID: 19597
                                               android.view.InflateException: Binary XML file line #17: Failed to resolve attribute at index 1: TypedValue{t=0x2/d=0x7f01005d a=-1}
                                               Caused by: java.lang.UnsupportedOperationException: Failed to resolve attribute at index 1: TypedValue{t=0x2/d=0x7f01005d a=-1}
                                                   at android.content.res.TypedArray.getLayoutDimension(TypedArray.java:761)
                                                   at android.view.ViewGroup$LayoutParams.setBaseAttributes(ViewGroup.java:7060)
                                                   at android.view.ViewGroup$MarginLayoutParams.<init>(ViewGroup.java:7241)
                                                   at android.widget.FrameLayout$LayoutParams.<init>(FrameLayout.java:438)
                                                   at android.widget.FrameLayout.generateLayoutParams(FrameLayout.java:370)
                                                   at android.widget.FrameLayout.generateLayoutParams(FrameLayout.java:369)
                                                   at android.view.LayoutInflater.inflate(LayoutInflater.java:505)
                                                   at android.view.LayoutInflater.inflate(LayoutInflater.java:426)
                                                   at android.support.v7.view.menu.MenuAdapter.getView(MenuAdapter.java:93)
                                                   at android.support.v7.view.menu.MenuPopup.measureIndividualMenuWidth(MenuPopup.java:160)
                                                   at android.support.v7.view.menu.StandardMenuPopup.tryShow(StandardMenuPopup.java:153)
                                                   at android.support.v7.view.menu.StandardMenuPopup.show(StandardMenuPopup.java:187)
                                                   at android.support.v7.view.menu.MenuPopupHelper.showPopup(MenuPopupHelper.java:290)
                                                   at android.support.v7.view.menu.MenuPopupHelper.tryShow(MenuPopupHelper.java:175)
                                                   at android.support.v7.view.menu.MenuPopupHelper.show(MenuPopupHelper.java:141)
                                                   at android.support.v7.widget.PopupMenu.show(PopupMenu.java:233)
                                                   at com.example.foo.FragmentChordMenu.showChordOptionsMenu(FragmentChordMenu.java:132)
                                                   at com.example.foo.CustomChordAdapter$ChordViewHolder$2.onLongClick(CustomChordAdapter.java:138)
                                                   at android.view.View.performLongClickInternal(View.java:5687)
                                                   at android.view.View.performLongClick(View.java:5645)
                                                   at android.view.View.performLongClick(View.java:5663)
                                                   at android.view.View$CheckForLongPress.run(View.java:22234)
                                                   at android.os.Handler.handleCallback(Handler.java:751)
                                                   at android.os.Handler.dispatchMessage(Handler.java:95)
                                                   at android.os.Looper.loop(Looper.java:154)
                                                   at android.app.ActivityThread.main(ActivityThread.java:6077)
                                                   at java.lang.reflect.Method.invoke(Native Method)
                                                   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:865)
                                                   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755)

FragmentActivity

public class FragmentChordMenu extends Fragment implements CustomChordAdapter.onItemClickListener {    
    private static RecyclerView mCustomChordList;
    private static CustomChordAdapter mRecyclerViewAdapter;
    private static Context mContext;

    private FloatingActionButton mFAB;
    private View mPopupView;
    private PopupWindow mCustomChordMenu;

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        mRecyclerViewAdapter = new CustomChordAdapter(this);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
        mContext = getActivity().getApplicationContext();   //stores application context for later use in fragment without risk
                                                            //of detachment

        View v = inflater.inflate(R.layout.menu_fragment_chord, container, false);
        LayoutInflater layoutInflater = (LayoutInflater)getActivity().getBaseContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        ...

        mFAB = (FloatingActionButton) v.findViewById(R.id.addChord);
        mFAB.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                mCustomChordMenu.showAtLocation(mPopupView, Gravity.CENTER, 10, 10);
                mCustomChordList = (RecyclerView) mPopupView.findViewById(R.id.rv_userChords);
                LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
                mCustomChordList.setLayoutManager(layoutManager);
                mCustomChordList.setAdapter(mRecyclerViewAdapter);
            }
        });

        return v;
    }

    public static void showChordOptionsMenu(final int position){
        View anchorView = mCustomChordList.findViewHolderForAdapterPosition(position).itemView;
        PopupMenu mPopup = new PopupMenu(mContext, anchorView);
        mPopup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem item) {
                switch (item.getItemId()){
                    case R.id.delete:
                        mRecyclerViewAdapter.deleteChord(position);
                        return true;
                    case R.id.rename:
                        Log.d("FragmentChordMenu: ", "Rename clicked");
                }
                return true;
            }
        });

        MenuInflater popupInflater = mPopup.getMenuInflater();
        popupInflater.inflate(R.menu.popup_delete_chord, mPopup.getMenu());
        mPopup.show();              //ERROR HERE
    }

    ...
}

PopupMenu XML

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content">
    <item
        android:id="@+id/rename"
        android:title="@string/rename"/>

    <item
        android:id="@+id/delete"
        android:title="@string/delete"/>
</menu>

FragmentActivity XML

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:app="http://schemas.android.com/apk/res-auto"
                xmlns:android.support.design="http://schemas.android.com/tools"
                android:id="@+id/chordMenu"
                android:layout_width="match_parent"
                android:layout_height="match_parent">

    <ScrollView
        android:id="@+id/scrollview"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:id="@+id/chordButtons"
                android:orientation="vertical"  >
            </LinearLayout>

            <android.support.design.widget.FloatingActionButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:layout_margin="16dp"
                android:clickable="true"
                app:fabSize="mini"
                android:id="@+id/addChord"
                app:borderWidth="0dp"
                app:useCompatPadding="false"
                android:src="@drawable/ic_add_black_24dp"/>
        </LinearLayout>

    </ScrollView>

</RelativeLayout>
Lipson answered 1/6, 2017 at 3:36 Comment(15)
mContext = getActivity().getApplicationContext(); - I would bet that that's your problem. You're using the v7 appcompat PopupMenu, and the application Context won't have the right theme resources set on it. Just use getActivity(). You really don't need to keep a Context field, btw. You can call getActivity() wherever you need one.Shrovetide
@MikeM. how can I use getActivity() in a non-static context? showChordOptionsMenu() is a static method, hence the issue.Lipson
Ah, I didn't notice it was. Yeah, you really don't want to keep all those static members in your Fragment class. Normally, you'd wanna add a Context parameter to the method to pass one in, but that method is using other static members with Contexts, so it's a moot point here. You'd have to redesign how you're communicating with that Fragment. Anyhoo, the current issue is likely the wrong Context, so just removing the getApplicationContext() call in the code you have now will let you know if that is indeed the issue.Shrovetide
@MikeM. Good suggestion, however, i'm getting a new runtime error now. android.view.WindowManager$BadTokenException: Unable to add window -- token android.view.ViewRootImpl$W@f847ac3 is not valid; is your activity running? I think i might be trying to secure the wrong context, I think I should instead be trying to get the context of the PopupWindow this transaction occurs in. Do you know how I would do this? Or rather, what the source of the issue is?Lipson
Oh, you're trying to show a PopupMenu from a PopupWindow? I don't think that's gonna work. I don't believe you can anchor a popup to another popup.Shrovetide
Uh oh. I'll ask a new question about this. Hopefully its doable, I want to avoid major code rework.Lipson
Just use a Dialog instead of the PopupWindow.Shrovetide
That's undesirable for a few reasons, the biggest being that I don't need the action buttons or the interaction-with-parent-view methods that the Dialog class provides.Lipson
Dialogs don't have any buttons - or any kind of View - by default. I think you're thinking of AlertDialog. You can make it be just a floating RecyclerView, if you want. I don't know what you mean by "interaction-with-parent-view methods".Shrovetide
I suppose I was thinking about this answer "If you want to add more control and feedback between your View then use a Dialog. If you, like me, want master control over everything, I would suggest a PopupWindow since it has fewer user-evident default methods to override." from here #4710861. Are you sure that a PopupMenu works when anchored to a dialog? If there really isn't much trade off I'll use that instead.Lipson
Sure, I've done it before. Of course, there's a way you can find out for certain yourself. :-) Looks like you'd have to change all of about three lines of code.Shrovetide
Thanks. If you post the suggested changes for the ~3 relevant lines I will award you the accepted answer.Lipson
You can actually do it rather simply with AlertDialog; just don't setup any buttons. Remove/comment out the mCustomChordMenu.showAtLocation() line, and insert - just as an example - new AlertDialog.Builder(getActivity()).setView(mPopupView).show();.Shrovetide
Amazing, works. Thanks. If you want rep post this so I can accept it.Lipson
Cool, glad it worked. Will do. It'll be a little bit before I get a chance to put an answer together, though. Cheers!Shrovetide
S
7

The cause of the immediate issue here - the InflateException - was using the application Context for an appcompat-v7 PopupMenu.

mContext = getActivity().getApplicationContext();
...
PopupMenu mPopup = new PopupMenu(mContext, anchorView);

That Context won't have the correct theme resources set for a v7 widget, leading to the InflateException. The Activity does have the appropriate theme, though, and using that instead solves that particular issue.

mContext = getActivity();

After fixing that, however, a WindowManager$BadTokenException came up due to the PopupMenu being passed an anchor View from a PopupWindow. Popups must be anchored to a View in a top-level Window, and the Popup* classes are basically simple Views, thus the Exception.

A simple solution for this is to replace the PopupWindow with a Dialog, which does have a Window. For example:

AlertDialog dlg = new AlertDialog.Builder(getActivity()).setView(mPopupView).show();

Lastly, I would suggest that you modify your setup to remove the need for the static members in your Fragment class. Those static fields are likely to cause memory leaks, and your IDE may very well be warning you of that now. A listener interface similar to the one you have in CustomChordAdapter would suffice.

Shrovetide answered 3/6, 2017 at 3:2 Comment(3)
I'd like to learn more about the last part of your answer; do you have a link to any resources about preventing memory leaks in static members?Lipson
Hmm, not really. I mean, there's pretty much only two ways to prevent leaks with them: 1.) Don't use them. 2.) Make sure you set the references to null as soon as you're done with them. If you're just looking for general info on static leaks and such, there's been volumes written on it. Here are some on-site posts, but you might find more thorough explanations on some blogs or tutorials or whatnot: stackoverflow.com/a/11908685, stackoverflow.com/a/641473, stackoverflow.com/a/28091135. That last one has a link to an old Android Developers Blog page on it.Shrovetide
Saved my day, thank you so much. I had a similar issue when trying to inflate a layout in my adapter, and after having read your comment I realized I created this adapter with application's Context instead of Activity's.Spineless

© 2022 - 2024 — McMap. All rights reserved.