Android DialogFragment - MultiChoice Dialog checkboxes do not update / require two clicks - android

I think this has only started happening with recent Android API(s), but the problem is that the first time you try to select a checkbox in a MultiChoice Dialog, it requires an additional click for the UI to update.
I'm pretty sure it's due to an Android bug as my code is very simple.
After a lot of experimenting, I've found the answer so will share it below...

The tricky part of the solution was to get the View object. Once you have the View, you can invalidate() it in order to update the UI of the checkboxes.
Here is the essentials from my DialogFragment subclass:
public class MyMultiChoiceDialogFragment extends DialogFragment {
private View mView = null;
#Override #NonNull
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(title);
builder.setMultiChoiceItems(
cursor,
isCheckedColumn
labelColumn
new DialogInterface.OnMultiChoiceClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int which, boolean isChecked) {
// Handle the checkbox de/selection
/*
* The problem is that, despite onClick being called (with the correct parameter values), the
* checkbox ticks were not updating on the UI.
* Solution is to invalidate/redraw the layout so the checkboxes also update visually
*/
mView.invalidate();
mView.forceLayout(); // Following tests, this line is also required.
}
});
AlertDialog dialog = builder.create();
/*
* This seems to be the only way to get the view.
* Save it in an instance variable so we can access it within onClick()
*/
mView = dialog.getListView();
return dialog;
}
#Override
public void onDestroyView() {
super.onDestroyView();
mView = null; // Clean up / prevent memory leak - necessary?
}
}

Related

Android AlertDialog - How do I handle extremely large message?

I'm popping up an AlertDialog when a ListView item is clicked and the string of the message is very large (nearly 20,000 characters). What winds up happening is that I click the list item and it sits for about 3-4 seconds before displaying the AlertDialog. This is problematic for many reasons, primarily that the user could click the button repeatedly and crash the app.
My first thought was to try to mimic how the Google Play app handles their open source license display (Play -> Nav Drawer -> Settings -> Open Source License info), where they pop open the AlertDialog and then it looks as though the view/text is loaded after the dialog is shown. I imagined it looking something like this:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(title);
builder.setMessage(veryLongStringMessage);
builder.setCancelable(true);
builder.setNeutralButton(android.R.string.ok, listener);
final AlertDialog alertDialog = builder.create();
alertDialog.show();
Pretty basic stuff up until this point. Then I've tried to remove the message set in builder for something like:
builder.setMessage("")
// create/show dialog as above
alertDialog.setMessage(veryLongStringMessage);
But that seems to just load the whole dialog before showing it. So I thought maybe post a runnable to go at the end of the activity calls, and that wasn't working. I tried doing this in an Async task and could not get it working that way either. I've tried this as a DialogFragment where I call
activity.getSupportFragmentManager().executePendingTransactions();
Then go on to try to set the message after I know the DialogFragment has been shown and I either wind up with an empty dialog (the new message won't show up) or it loads it all at once and I'm sitting with that 3-4 second delay.
Anyone have any good method of implementing and AlertDialog with a very large message?
This case is when I show the legal notices of Google Play Services:
The problem seems to be the Dialog.show(), it takes those seconds in order to generate the layout.
So what I have done in my case, probably not the best one but it works. I create a temporal dialog.
private void showGPSLicense() {
AlertDialog.Builder LicenseDialog = new AlertDialog.Builder(MyActivity.this);
LicenseDialog.setTitle(getString(R.string.google_maps_legalnotices));
LicenseDialog.setMessage(getString(R.string.google_maps_loading));
final Dialog loadingDialog = LicenseDialog.create();
loadingDialog.show();
//This dialog does not take much time. Meanwhile I get via AsyncTask the heavy message, replacing/dismissing the previous dialog.
(new AsyncTask<Void, Void,AlertDialog.Builder>() {
#Override
protected AlertDialog.Builder doInBackground(Void... arg0) {
String LicenseInfo = GooglePlayServicesUtil.getOpenSourceSoftwareLicenseInfo(getApplicationContext());
AlertDialog.Builder LicenseDialog = new AlertDialog.Builder(MyActivity.this);
LicenseDialog.setTitle(getString(R.string.google_maps_legalnotices));
LicenseDialog.setMessage(LicenseInfo);
return LicenseDialog;
}
protected void onPostExecute(AlertDialog.Builder result) {
Dialog dialog = result.create();
dialog.setOnShowListener(new OnShowListener() {
public void onShow(DialogInterface dialog) {
loadingDialog.dismiss();
}
});
dialog.show();
}
}).execute();
}
Tested on Nexus 5 (4.4.2)
If you are worried about many clicks you can also prevent many clicks by implementing the OnClickListener:
public abstract class PreventManyClicksListener implements OnClickListener {
private boolean clickable = true;
public abstract void preventManyClicks(View view);
public final void onClick(View view) {
if (clickable) {
clickable = false;
preventManyClicks(view);
}
}
public void setClickable() {
clickable = true;
}
}
//...
private PreventManyClicksListener preventDialog = new PreventManyClicksListener() {
#Override
public void preventManyClicks(View view) {
showGPSLicense();
setClickable();
}
};
//..
myView.setOnClickListener(preventDialog);
I think you need to use a custom view in order to be able to do this, because you can't update an AlertDialog message after it's been created. Create a custom view with a TextView with an id of textView1 and ProgressBar with id of progressBar1 then create the following class.
public class MyDialogFragment extends DialogFragment {
private TextView mTextView;
private ProgressBar mProgressBar;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.my_dialog, container, false);
mTextView = view.findViewById(R.id.textView1);
mTextView.setVisibility(View.GONE);
mProgressBar = view.findViewById(R.id.progressBar1);
mProgressBar.setVisibility(View.VISIBILE);
return view;
}
#Override
public View onStart() {
super.onStart();
mTextView.setText(getArguments().getString("text");
mProgressBar.setVisibility(View.GONE);
mTextView.setVisibility(View.VISIBILE);
}
}
You also need to pass it the text string in its arguments.
DialogFragment dialog = new MyDialogFragment();
Bundle args = new Bundle();
args.putString("text", text);
dialog.setArguments(args);
dialog.show(getFragmentManager(), "my_dialog")

Dialog error when open from a Adapter + ListView

I have a little problem with a Dialog.
It's a ListView of Videos with thumbnails that load the videos with an Adapter. The ListView register an OnItemClickListener and inside the OnClickItem method I try to raise the Dialog.
I've tried with various types of Dialog but nothing happened. A simplified piece of code it's here:
public class ListOfVideos extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.list_of_videos);
init_phone_video_grid();
}
private void init_phone_video_grid() {
// Here's some code for the video reading
// The ListView
videolist = (ListView) findViewById(R.id.PhoneVideoList);
videolist.setAdapter(new VideoAdapter(getApplicationContext()));
videolist.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
// Here's some code for the video reading
/** ============= Here's the problem ================ **/
AlertDialog.Builder builder = new AlertDialog.Builder(v.getContext());
builder.setMessage("Example Message")
.setTitle("This is the title!!")
.setCancelable(false)
.setNeutralButton("Ok",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
AlertDialog alert = builder.create();
alert.show();
System.out.println("[debug]" + "Open File " + filename);
}
});
}
The list of videos load perfectly. But when I click on an Item:
The Dialog does not show
I got a error message in the LogCat, that state: "show() Dialog's window is null!"
The println debug message, appears ok in the LogCat
I have searched for that message error, but there's not much information.
I think the problem could be on the Context that receive the Builder, but I'm stuck on this point.
Any advice will be apreciated
That error message is saying that the Context given to the AlertDialog.Builder has no attached window, which Dialogs need as a UI anchor, basically. An Activity is what should be used for such a Context, as it will have the required window.
Without seeing VideoAdapter's code, the root cause is presumably new VideoAdapter(getApplicationContext()), which is handing your VideoAdapter the application Context to build Views with. That likely means that the v passed into onItemClick() is one such View, and v.getContext() is returning that application Context in new AlertDialog.Builder(v.getContext()).
That application Context does not have a window but your Activity does, as mentioned. Furthermore, the Activity is actually what you want to give to VideoAdapter to build Views with anyway, to ensure that they are created with the correct theme and styling. Change that relevant line to:
videolist.setAdapter(new VideoAdapter(ListOfVideos.this));
That alone might solve the issue, depending on what VideoAdapter does internally. However, it's arguably better to specify the Activity again in the AlertDialog.Builder constructor call, just so there's no question:
AlertDialog.Builder builder = new AlertDialog.Builder(ListOfVideos.this);
As a final note, whenever a Context is needed for any UI component, you usually want to use the immediately available Activity.
Here is a example of How to create dialog box..
String message = "Hello";
AlertDialog.Builder alt_bld = new AlertDialog.Builder(
CurrentActi.this);
alt_bld.setTitle("Alert")
.setMessage(message)
.setCancelable(false)
.setPositiveButton("Yes",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
//here right the code that you want perform onClick
dialog.cancel();
}
})
.setNegativeButton("No", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
AlertDialog alert = alt_bld.create();
alert.setTitle("Alert");
alert.show();
May be it will help you..

Android Custom List Dialog

Could someone point out a working example of a custom dialog that takes an ArrayAdapter as input and shows a selectable list.
I have tried to create a Dialog using an AlertDialog Builder as such...
final ArrayAdapter<MyObject> myAdapter = getMyobjects();
final AlertDialog.Builder builder = new AlertDialog.Builder(this).setTitle("Pick an item").setAdapter(myAdapter,
new android.content.DialogInterface.OnClickListener() {
public void onClick(final DialogInterface dialog, final int item) {
Toast.makeText(Islands.this, myAdapter.getItem(item).toString(), Toast.LENGTH_SHORT).show();
}
});
final AlertDialog alert = builder.create();
return alert;
My problem is that my dialog is not updating then i called
#Override
protected void onPrepareDialog(final int id, final Dialog dialog) {
switch (id) {
case DIALOG_GET_AVAIL_DESTS:
((AlertDialog) dialog).getListView().setAdapter( getDestinations());
break;
}
}
However the onClick listener listens to the initial set of items...
Indeed AlertDialog is implements Facade design pattern with this class behind :
http://www.netmite.com/android/mydroid/frameworks/base/core/java/com/android/internal/app/AlertController.java
And the whole code is such a mess...
I took 3 hours to try to do that, and I am going to build a dialog from scratch, using android.R.layout as a basis.
Steff
You have to make a call to
invalidateViews()
on your listview - that will cause it to redraw the view with the updates.
Since you are using onPrepareDialog(int id, Dialog dialog), I am guessing you're initially setting up the dialog in onCreateDialog(int id).
Doing so cause the system to save the dialog you initially create. In order to achieve the desired functionality, when the dialog is dismissed, tell the system to discard it by calling android.app.Activity.removeDialog(int id).
Any subsequent invocations will have your dialog regenerated through the onCreateDialog(int id) method, causing the set of items to be updated.

How actualize setMultiChoiceItems items values in onPrepareDialog?

I show dialog of checkboxes (list retrieved from DB) to allow user select, which rows remove. Because android dialog caching, I need to refresh count and names of checkboxes.
In my onCreateDialog:
dialog = new AlertDialog.Builder( this )
.setTitle( "Remove Items" )
.setMultiChoiceItems( items, _selections, new OnMultiChoiceClickListener(){public void onClick (DialogInterface dialog, int which, boolean isChecked){}} )
.setPositiveButton("Smazat", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
deleteRow(_selections);
} })
.setNegativeButton("Storno", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
} })
.create();
How refresh values (items,_selections) in onPrepareDialog?
I tried invalidate views, hoping that force android to load items againg(dont work neither), but I think its bad choice as well as removing dialog and recreating.
protected void onPrepareDialog(final int id, final Dialog dialog) {
switch (id) {
case REMOVE_DIALOG_ID:
ListView lv = ((AlertDialog) dialog).getListView();
lv.invalidateViews();
break;
}
Thanks for any ideas!
When you create a list of items using AlertDialog.Builder, it internally takes that and creates a ListAdapater that is dependent on the type of data you passed. Since "items" in your example doesn't look like a resource ID, I'm assuming it's either a CharSequence[] or a Cursor. If you provide more information about what "items" is, I can provide a more concrete example.
For CharSequence[] (like String[]) data, Builder creates an ArrayAdapter instance.
For Cursor data, Builder creates a CursorAdapter
You will need to obtain a reference to this ListAdapter using getListView().getAdapter() on the AlertDialog instance.
For a Cursor, you can get away with calling notifyDataSetChanged() after you have called requery() to update the data set.
Since you can't "update" an array with new data (changing the pointer to a new instance is not the same thing...the instance that the adapter is pointing to stays unchanged), this case is a little more work. You will need to call the add(), clear(), etc. methods of the adapter to remove invalid items and add the updates ones. With the adapters data set fully updated, you may now call notifyDataSetChanged().
Hope that Helps!
I spent lot of time to search for same solution and eventually fixed my problem with simple stuff after trying to use onPrepareDialog too
I use the removeDialog(int) function of the Activity. When a dialog is dismissed, the Activity basically stores the state of the dialog (for performance reasons I would imagine). Calling removeDialog(int) on the dialog forces the activity to unload all references for the dialog and dismisses it from the screen if it's being shown.
did this when my activity lost focus simply add:
public void onStop() {
removeDialog(Id_Dial);
return;
}
My technique is to create an adapter with empty data in onCreateDialog and completely replace the adapter during onPreparDialog.
Example:
#Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case DialogMergeRegion:
title = ...
return new AlertDialog.Builder(BaseDataTabView.this)
.setTitle(title)
.setMultiChoiceItems(new CharSequence[0], null,
new OnMultiChoiceClickListener() {
#Override
public void onClick(DialogInterface dialog, int which, boolean isChecked) {
manageSelectionList(which, isChecked);
}
})
//...
.create();
}
}
#Override
protected void onPrepareDialog(int id, Dialog dialog) {
super.onPrepareDialog(id, dialog);
switch (id) {
case DialogMergeRegion: {
List<String> regionNames = ...// get the data
ListAdapter mergeAdapter = new ArrayAdapter<String>(this, android.R.layout.select_dialog_multichoice, regionNames);
AlertDialog ad = (AlertDialog) dialog;
ad.getListView().setAdapter(mergeAdapter);
break;
}
}
In your onPrepareDialog method, instead of using invalidateViews(), you should try getting the adapter of the ListView and try calling either notifyDataSetChanged() or notifyDataSetInvalidated(). Does that help?

Android: onItemClick only returns first selected Item

I'm using and ArrayAdapter to populate a ListView. After selecting and item, it displays a confirmation Y/N dialog. If the user's choice is negative, then he should be able to select another item showing the same dialog. And so on.
Here's my code:
lView.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(final AdapterView<?> parent, final View v, final int index, final long id) {
Toast.makeText("Selected file"+ mFiles.get(index).getfileName(),
Toast.LENGTH_SHORT).show();
SelectedFile = mFiles.get(index);
showDialog(DIALOG_CONFIRMIMPORT_ID);
}
});
The weird thing is that while the "Toast" shows the clicked item every time, only the first selected item since the Activity is initiated is being passed to "SelectedFile". No matter how many times you click a diferent item, "SelectedFile" always assumes the same value, the value of the first clicked item, outside of this code.
Heres's my Dialog code:
Protected Dialog onCreateDialog(int id) {
switch(id) {
case DIALOG_CONFIRMIMPORT_ID:
{
AlertDialog.Builder builder = new AlertDialog.Builder(this);
String message = String.format(getString(R.string.importstudentfileconfirm),SelectedFile.getfileName());
builder.setMessage(message)
.setCancelable(false)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// Activity.this.finish();
// startActivity(new Intent(Activity.this, LOL.class));
}
})
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
SelectedFile = null;
dismissDialog(DIALOG_CONFIRMIMPORT_ID);
mFiles.notifyAll();
}
});
AlertDialog alert = builder.create();
return alert;
}
}
return null;
}
Thank you very much for any help!
I'm guessing this has something to do with the fact that the onCreateDialog method is only called the first time the dialog is created. So the first time you see the dialog it will have the correct filename.
After onCreateDialog is called, onPrepareDialog(...) is called. onPrepareDialog, allows you to change the dialog after it has been created, but before it gets displayed.
Remember that underneath everything, Android isn't creating a new Dialog for you every time you want to show the DIALOG_CONFIRMIMPORT_ID dialog. It is too computationally expensive to instantiate a new dialog every time. Instead, it creates it once, which causes onCreatDialog to be called, followed by the onPrepareDialog. Every other time the dialog is shown, it only calls onPrepareDialog.
Check out the following article on the Android Developer site. It explains things pretty clearly.
http://developer.android.com/guide/topics/ui/dialogs.html#ShowingADialog
So try using onCreateDialog just for initialization of stuff that won't change between showings of the dialog, then use the onPrepareDialog method to dynamically update the contents of the dialog (i.e. getting the new filename)
Cheers!

Categories

Resources