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.
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);
}
});
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();
}
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);
use class Context eg. MainActivity.this instead of getApplicationContext()
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
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.
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).
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();
}
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
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(.....);
}
});
}
If you show a PopupWindow in another PopupWindow, do not use the view in first POP, use the origin parent view.
pop.showAtLocation(parentView, ... );
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.
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()
You can specify the y-offset to account for the status bar from the pw.showAtLocation method...
Related
I am uploading data to a webserver. On my fragment I have a button to start the upload. There are two phases what I am trying to have the user notification done via a none-cancellable AlertDialog solution.
When I am pressing the upload button, preparation for the upload is starting I am setting up the AlertDialog and presenting it. Once the physical upload is starting, I am using the same AlertDialog, but changing the message in it to show the progress of the upload.
***** Now the issue is the following ******
When I setup the AlertDialog and call the Show method, it does not display the AlertDialog. But once the upload is started and the progress is updated I just call the setMessage method and at this point the AlertDialog appears.
The relevant codes are the followings:
The submitbutton.setOnClickLictener is in the onViewCreated()
submitbutton.setOnClickListener {
requireActivity().runOnUiThread {
SubmitAd()
}
}
I have tried here the run the SubmitAd() on the UIThread, if it helps, but it is the same without it.
SubmitAd is showing the Dialog. (Actually at this point nothing is shown.
fun SubmitAd() {
var addInApp: Boolean = false
ToBePurchased = 0
if (CheckCanUpload()) {
var AlertView = AlertDialog.Builder(requireActivity())
AlertView.setTitle("Hirdetés feltöltés")
AlertView.setMessage("A feltöltés előkészítése hosszabb ideig is eltarhat, kérjük várjon!")
AlertView.setCancelable(false)
DialogToShow = AlertView.create()
DialogToShow!!.show()
purchaseLoop = 0
UploadWithPurchase()
} else {
var AlertView = AlertDialog.Builder(requireActivity())
AlertView.setTitle("Hirdetés hiba")
AlertView.setMessage("A hirdetése hiányos. Kérjük töltse ki az összes mezőt és csatoljon fotót a hirdetéséhez!")
AlertView.setPositiveButton("Ok") { dialog, which ->
dialog.dismiss()
}
DialogToShow = AlertView.create()
DialogToShow!!.show()
}
}
In UploadWithPurchase() the Playstore purchase handling is done, but if there is no purchase at all, it is just going through a loop, which calls UploadWithPurchase() recursively until all possible purchases are checked, then it goes to the real Upload() which calls an Http request to upload the data and reports back via an interface the progress of the upload process.
The Webhelper returns the progress like this:
override fun WebHelperProgress(id: String, progress: Float) {
if (DialogToShow != null) {
DialogToShow!!.setMessage("Feltöltés folyamatban. Kérem várjon! ... ${progress.toInt()}%")
}
}
When this method is called, the AlertDialog appears.
Whatever I have tried, does not help. AlertDialog does not show up at the first call, but no clue why.
EDIT later: I have figured out that the AlertDialog is actually appears once it comes out from the recursive loop, but I do not know how to force it to be displayed before it starts the loop. That would be my aim to notify the user that a longer process is starting. It meaningless to start the process and the user does not know what is happening.
Finally I could solve it by putting the purchaseLoop to a separate Thread like this.
fun SubmitAd() {
var addInApp: Boolean = false
ToBePurchased = 0
if (CheckCanUpload()) {
var AlertView = AlertDialog.Builder(requireActivity())
AlertView.setTitle("Hirdetés feltöltés")
AlertView.setMessage("A feltöltés előkészítése hosszabb ideig is eltarhat, kérjük várjon!")
AlertView.setCancelable(false)
DialogToShow = AlertView.create()
DialogToShow!!.show()
purchaseLoop = 0
******** SOLUTION HERE ********
Thread {
UploadWithPurchase()
}.start()
*******************************
} else {
var AlertView = AlertDialog.Builder(requireActivity())
AlertView.setTitle("Hirdetés hiba")
AlertView.setMessage("A hirdetése hiányos. Kérjük töltse ki az összes mezőt és csatoljon fotót a hirdetéséhez!")
AlertView.setPositiveButton("Ok") { dialog, which ->
dialog.dismiss()
}
DialogToShow = AlertView.create()
DialogToShow!!.show()
}
}
I'm trying to create a listener for the clear button that comes from google's Place Autocomplete API. i called my clearButton() method in my fragment's onViewCreated method
clearButton()
placeAutocompleteFragment?.view?.findViewById<View>(R.id.place_autocomplete_clear_button)
?.setOnClickListener {
View.OnClickListener {
Log.d(TAG, "Cleared")
it?.findViewById<EditText>(R.id.place_autocomplete_search_input)?.setText("")
it?.visibility = View.GONE
}
}
now when i click on the clear button icon, the text doesn't get erased, nothing happens. I can still type in a new location though, but i can't clear it. my Log.d isn't getting displayed either.
I don't have android studio on this machine now to try, but I guess you can do something like
place_autocomplete_clear_button.onClick { place_autocomplete_search_input.text = "" }
where place_autocomplete_clear_button can be static import and onClick might be from anko
Figured it out. I had the method calls set up all wrong.
Here's how it should look like:
private fun clearButton() {
placeAutocompleteFragment?.view?.findViewById<View>(R.id.place_autocomplete_clear_button)?.setOnClickListener {
Log.d(TAG, "Cleared Button Clicked")
it.visibility = View.GONE
//do something
}
}
I have an Entry and a Button:
<StackLayout>
<CustomViews:ChatEntryView x:Name="ChatEntry" />
<Button Text="Send" Command="SendCommand"/>
</StackLayout>
What I wanted to achieve here is that when the user starts types something on the Entry control and then presses the button, it should not hide the keyboard (or lose the Entry Focus).
The ChatEntryView here is just actually a custom view that inherits from the Entry control and what I did inside:
1.) Added an Unfocused handler
Unfocused += ChatEntryView_Unfocused;
void ChatEntryView_Unfocused(object sender, FocusEventArgs e)
{
this.Focus();
}
2.) Tried Handling on PropertyChanged
protected override void OnPropertyChanged(string propertyName = null)
{
this.Focus();
base.OnPropertyChanged(propertyName);
}
3.) Tried Handling on PropertyChanging
protected override void OnPropertyChanging(string propertyName = null)
{
this.Focus();
base.OnPropertyChanging(propertyName);
}
But all the three methods doesn't seem to work. I was able to do a work around on IOS by making a custom renderer and it's not very neat (by actually interfacing to the Control.ShouldEndEditing on IOS).
But my problem now is on Android, as I don't exactly know how to do this on Android and there's no Control.ShouldEndEditing (the interface on Android) that I can work with.
What happens by using the handlers above is that, the keyboard for the entry view still loses focus and then immediately gets focuses again which is very odd.
The keyboard pushes down (loses focus) and then pushes up (forced focus).
I know it's too late to anwser this question, but it might be helpful for someone else, I added this code to MainActivity, it might not be a neat solution, but works for me:
private bool _lieAboutCurrentFocus;
public override bool DispatchTouchEvent(MotionEvent ev)
{
var focused = CurrentFocus;
bool customEntryRendererFocused = focused != null && focused.Parent is CustomEntryRenderer_Droid;
_lieAboutCurrentFocus = customEntryRendererFocused;
var result = base.DispatchTouchEvent(ev);
_lieAboutCurrentFocus = false;
return result;
}
public override View CurrentFocus
{
get
{
if (_lieAboutCurrentFocus)
{
return null;
}
return base.CurrentFocus;
}
}
Here is a function that gets called when an item gets selected from a ListView:
async void detail_clicked(object sender, SelectedItemChangedEventArgs e){
if (e.SelectedItem == null) {
return;
}
Detail selected = (Detail)e.SelectedItem;
order_vm.List_of_details.Add(selected);
await DisplayAlert ("Item Added",
String.Format ("{0} added to cart.", selected.detail_name), "Okay");
((ListView)sender).SelectedItem = null;
}
I added this function using the ItemSelected event handler
details_list.ItemSelected += detail_clicked;
The first time I click on the Item, the DisplayAlert pops up. After the first click, the DisplayAlert inside detail_clicked no longer pops up. But the other code inside the handler does get called.
Anyone know how to fix this issue? Is it something I am not understanding about event handlers? Is it something about await/async?
The DisplayAlert might be running on a different thread. Try wrapping Display Alert in Device.BeginInvokeOnMainThread. You can ready about that here.
Please check again without async on method and await on DisplayAlert().
Use this following code. It will helps you.
private void OnItemSelected(object sender, SelectedItemChangedEventArgs e)
{
if (e.SelectedItem == null)
{
return;
}
listView.SelectedItem = null;
DisplayAlert("Alert", e.SelectedItem.ToString(), "Ok");
}
I'm trying to implement a check box in a MaterialDialog using this library, and the check box asks the user if they don't want to see that dialog again. The dialog appears if the user's phone has NFC, but it is deactivated.
If the user presses the positive button in the dialog and has the box ticked, then it accesses a Realm object with a Boolean attribute named "NfcStatus", and sets that to true. If they press the negative button with the box ticked, then that Realm object's NfcStatus is set to false.
Here's the code of the MaterialDialog:
new MaterialDialog.Builder(context)
.title("NFC")
.content("NFC is disabled. Would you like to activate it?")
.items(R.array.checkbox) //this just has one string in it which says "Please don't show me this again"
.itemsCallbackMultiChoice(null, new MaterialDialog.ListCallbackMultiChoice() {
#Override
public boolean onSelection(MaterialDialog dialog, Integer[] which, CharSequence[] text) {
/**
* If you use alwaysCallMultiChoiceCallback(), which is discussed below,
* returning false here won't allow the newly selected check box to actually be selected.
* See the limited multi choice dialog example in the sample project for details.
**/
checkboxIsChecked = true; //TODO: checkboxIsChecked isn't being passed into onPositive or onNegative
return true;
}
})
.positiveText(R.string.accept)
.positiveColorRes(R.color.main_theme_color)
.negativeText(R.string.decline)
.negativeColorRes(R.color.main_theme_color)
.callback(new MaterialDialog.ButtonCallback() {
#Override
public void onPositive(MaterialDialog dialog) {
//this was how I was checking if checkboxIsChecked was true or false
Log.d("checkboxIsChecked", checkboxIsChecked?"true":"false"); }
if (checkboxIsChecked) {begins
if (ks.contains(KEY_NAME)) {
realmKey = ks.get(KEY_NAME);
}
realm = Realm.getInstance(context, realmKey);
RealmPhone realmPhone = realm.where(RealmPhone.class).findFirst(); realmPhone.setNfcStatus(true);
}
activity.finish();
startNfcSettingsActivity();
Toast.makeText(context, R.string.nfc_disabled_message, Toast.LENGTH_LONG).show();
}
#Override
public void onNegative(MaterialDialog dialog) {
if (checkboxIsChecked) {
if (ks.contains(KEY_NAME)) {
realmKey = ks.get(KEY_NAME);
}
realm = Realm.getInstance(context, realmKey);
RealmPhone realmPhone = realm.where(RealmPhone.class).findFirst();
realmPhone.setNfcStatus(false);
}
}
})
.cancelable(false)
.show();
The problem was that even if the check box was ticked, the checkboxIsChecked variable was still false when using it in onPositive or onNegative, so it was never being written to the Realm object. Am I doing this the wrong way?
For changing and saving the RealmObject, you need to use transactions. Related documents can be found here.
In you case, it would be something like:
realm = Realm.getInstance(context, realmKey);
// I'm not quite sure how did you create the realmPhone at the first time,
// just assume you have one realmPhone in the Realm.
RealmPhone realmPhone = realm.where(RealmPhone.class).findFirst();
realm.beginTransaction();
realmPhone.setNfcStatus(false);
realm.commitTransaction();
// Close the realm instance after using it is very important! To avoid leaks.
realm.close();
BTW, it seems code:
RealmPhone realmPhone = realm.where(RealmPhone.class).findFirst();
realmPhone.setNfcStatus(false);
is not called. If it does, a IllegalStateException should be thrown since you didn't call it in a Realm transaction. Or maybe RealmPhone is not inherited from RealmObject?