56

I'm trying to create a popup window that only appears the first time the application starts. I want it to display some text and have a button to close the popup. However, I'm having troubles getting the PopupWindow to even work. I've tried two different ways of doing it:

First I have an XML file which declares the layout of the popup called popup.xml (a textview inside a linearlayout) and I've added this in the OnCreate() of my main Activity:

PopupWindow pw = new PopupWindow(findViewById(R.id.popup), 100, 100, true);
    pw.showAtLocation(findViewById(R.id.main), Gravity.CENTER, 0, 0);

Second I did the exact same with this code:

final LayoutInflater inflater = (LayoutInflater)this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    PopupWindow pw = new PopupWindow(inflater.inflate(R.layout.popup, (ViewGroup) findViewById(R.layout.main) ), 100, 100, true);
    pw.showAtLocation(findViewById(R.id.main_page_layout), Gravity.CENTER, 0, 0);

The first throws a NullPointerException and the second throws a BadTokenException and says "Unable to add window -- token null is not valid"

What in the world am I doing wrong? I'm extremely novice so please bear with me.

Amplify91
  • 2,690
  • 5
  • 24
  • 32

14 Answers14

188

To avoid BadTokenException, you need to defer showing the popup until after all the lifecycle methods are called (-> activity window is displayed):

 findViewById(R.id.main_page_layout).post(new Runnable() {
   public void run() {
     pw.showAtLocation(findViewById(R.id.main_page_layout), Gravity.CENTER, 0, 0);
   }
});
kordzik
  • 1,922
  • 1
  • 12
  • 5
  • 1
    So what method should I create it in if not one of the lifecycle methods? It seems like onStart() or onResume() should work since onCreate() doesn't. – Amplify91 Feb 02 '11 at 22:10
  • 5
    No it won't. It has to be run after all the lifecycle methods finish. Run the above code in onCreate or onStart and it will execute pw.showAtLocation on the UI thread after all the init life cycle methods are called and everything is set up (that's the purpose of the post method - read its javadoc for more details). This should work fine – kordzik Feb 04 '11 at 11:55
  • 1
    this answer is awesome! thanks a lot. my month long search final ends here :) – Sudarshan Bhat Dec 26 '11 at 11:44
  • 3
    And in the onAttachedToWindow() method ? – Arnaud Apr 18 '13 at 17:40
  • 1
    hi i am using layout.postDelayed(new Runnable() { @Override public void run() { // TODO Auto-generated method stub mWifiListing.showAsDropDown(mWIfiObj, convertDipToPixels(30), 0); } }, 1000); i am still getting the same error.can you help me out with this – dheeraj Jan 02 '14 at 09:22
  • hi @kordzik please see the comment – dheeraj Jan 02 '14 at 09:28
  • 1
    This is also useful for restoring an popup on screen rotate – Rodney Aug 15 '15 at 11:51
35

Solution provided by Kordzik will not work if you launch 2 activities consecutively:

startActivity(ActivityWithPopup.class);
startActivity(ActivityThatShouldBeAboveTheActivivtyWithPopup.class);

If you add popup that way in a case like this, you will get the same crash because ActivityWithPopup won't be attached to Window in this case.

More universal solusion is onAttachedToWindow and onDetachedFromWindow.

And also there is no need for postDelayed(Runnable, 100). Because this 100 millis does not guaranties anything

@Override
public void onAttachedToWindow() {
    super.onAttachedToWindow();
    Log.d(TAG, "onAttachedToWindow");

    showPopup();
}

@Override
public void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    Log.d(TAG, "onDetachedFromWindow");

    popup.dismiss();
}
Danylo Volokh
  • 4,149
  • 2
  • 33
  • 40
  • 1
    This should be the answer ! Thanks ! – Jonathan ANTOINE Feb 10 '17 at 19:56
  • I have been fighting this error for a week with dozens of other answers never consistently working. This one did it. Thank you sir. – seekingStillness Jan 10 '19 at 01:55
  • Perfect answer. Thanks – Visal Varghese Nov 11 '20 at 13:33
  • It seems that if a token is null the view is not attached to a window. This can be verified in the source code of the `View` class here (https://android.googlesource.com/platform/frameworks/base/+/a175a5b/core/java/android/view/View.java#9742). The variable `mAttachInfo`, which contains the window token, is only set during the attache of a view to a window. – Bruno Bieri Jun 07 '23 at 06:43
18

The accepted answer did not work for me. I still received BadTokenException. So I just called the Runnable from a Handler with delay as such:

new Handler().postDelayed(new Runnable() {
    public void run() {
        showPopup();
    }
}, 100);
Johnny Five
  • 987
  • 1
  • 14
  • 29
Todd Painton
  • 701
  • 7
  • 20
10

use class Context eg. MainActivity.this instead of getApplicationContext()

bipin
  • 1,091
  • 4
  • 12
  • 30
4

There are two scenarios when this exception could occur. One is mentioned by kordzik. Other scenario is mentioned here: http://blackriver.to/2012/08/android-annoying-exception-unable-to-add-window-is-your-activity-running/

Make sure you handle both of them

TheMan
  • 703
  • 8
  • 11
4

the solution is to set the spinner mode to dialog as below:

android:spinnerMode="dialog"

or

Spinner(Context context, int mode)
tnxs RamallahDroid

See This.

Community
  • 1
  • 1
saber
  • 41
  • 3
  • This worked perfectly for me. I had a Spinner inside of a PopupWindow, and the issue would show up on Android 5 (worked fine on newer). This solved it, although the look of the spinner is changed. – Rafał Feb 24 '18 at 16:45
1

Depending on the use case, for types of pop-up to display a message, setting the pop-up type to TYPE_TOAST using setWindowLayoutType() avoids the issue, as this type of pop-up is not dependent on the underlying activity.

Edit: One of the side effects: no interaction in the popup window for API <= 18, as the touchable / focusable events would be removed by the system. ( http://www.jianshu.com/p/634cd056b90c )

I end up with using TYPE_PHONE (as the app happens to have the permission SYSTEM_ALERT_WINDOW, otherwise this won't work too).

headuck
  • 2,763
  • 16
  • 19
1

You can check the rootview if it has the token. You can get the parent layout defined from your activity xml, mRootView

if (mRootView != null && mRootView.getWindowToken() != null) {
    popupWindow.showAtLocation();
}
Cheng
  • 775
  • 2
  • 12
  • 28
0

You can also try to use this check:

  public void showPopupProgress (){
    new Handler().post(new Runnable() {
        @Override
        public void run() {
            if (getWindow().getDecorView().getWindowVisibility() == View.GONE) {
                showPopupProgress();
                return;
            }
            popup.showAtLocation(.....);
        }
    });
}
Roman Nazarevych
  • 7,513
  • 4
  • 62
  • 67
0

If you show a PopupWindow in another PopupWindow, do not use the view in first POP, use the origin parent view.

pop.showAtLocation(parentView, ... );
YanXing Ou
  • 49
  • 2
0

Check that findViewById returns something - you might be calling it too early, before the layout is built

Also you may want to post logcat output for the exceptions you're getting

Asahi
  • 13,378
  • 12
  • 67
  • 87
  • I'm calling it in the onCreate() method, I'm not sure where else to call it from. I updated with the logcat output of the first set of code. – Amplify91 Nov 15 '10 at 20:59
  • can you post your onCreate method? Make sure you call `findViewById` after inflating and setting layout with `setContentView` – Asahi Nov 15 '10 at 21:05
  • @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //Admob Ads AdView ad = (AdView) findViewById(R.id.ad); ad.setAdListener(new AdMobListener()); Then I have my PopupWindow code from above. – Amplify91 Nov 15 '10 at 21:58
0

I had the same problem (BadTokenException) with AlertDialog on dialog.show(). I was making an AlertDialog by following some example. In my case the reason of that problem was a string dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_TOAST)

Everything became working after I removed it.

Maria
  • 199
  • 3
  • 15
0

Maybe it's time for a newer solution. This methods checks 5 times every 50ms if the parent view for the PopupWindow has a token. I use it inside my customized PopupWindow.

private fun tryToShowTooltip(tooltipLayout: View) {
    Flowable.fromCallable { parentView.windowToken != null }
            .map { hasWindowToken ->
                if (hasWindowToken) {
                    return@map hasWindowToken
                }
                throw RetryException()
            }
            .retryWhen { errors: Flowable<Throwable> ->
                errors.zipWith(
                        Flowable.range(1, RETRY_COUNT),
                        BiFunction<Throwable, Int, Int> { error: Throwable, retryCount: Int ->
                            if (retryCount >= RETRY_COUNT) {
                                throw error
                            } else {
                                retryCount
                            }
                        })
                        .flatMap { retryCount: Int ->
                            Flowable.timer(retryCount * MIN_TIME_OUT_MS, TimeUnit.MILLISECONDS)
                        }
            }
            .onErrorReturn {
                false
            }
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe({ hasWindowToken ->
                if (hasWindowToken && !isShowing) {
                    showAtLocation(tooltipLayout, Gravity.NO_GRAVITY, 100, 100)
                }
            }, { t: Throwable? ->
                //error logging
            })
}

with

companion object {

    private const val RETRY_COUNT = 5
    private const val MIN_TIME_OUT_MS = 50L
}

class RetryException : Throwable()
Camino2007
  • 796
  • 10
  • 17
-3

You can specify the y-offset to account for the status bar from the pw.showAtLocation method...

cipherz
  • 543
  • 6
  • 10