Привет, Хабр!

Вступление


Последние пару месяцев работаю над одним проектом под Android. Но речь сейчас пойдет не о нем, о нем я постараюсь обязательно написать, но всему свое время

За все время работы над проектом, случалось (и случается) много интересного. Сегодня я хочу рассказать одну небольшую историю.

Начало истории


Как-то вечером, совсем перед новогодними праздниками, работая в офисе и попивая любимый чаек, писал я очередную часть функционала для проекта.

Понадобилось мне создать обычный диалог, с возможностью множественного выбора элементов из списка, с возможностью сразу отметить\снять отметку со всех элементов, сохранить выбранные элементы и при последующим отображением диалога сразу их отметить.
Сказано — сделано.

Собственно сам код:
protected ArrayList<String> items;
protected ArrayList<String> selectedItems;

protected void showMyDialog() {

	int count = items.size();
	boolean[] checkedItems = new boolean[count];

	for (int i = 0; i < count; i++)
		checkedItems[i] = selectedItems.contains(items.get(i));

	AlertDialog.Builder builder = new AlertDialog.Builder(this);
	builder.setMultiChoiceItems(items.toArray(new String[items.size()]),
			checkedItems, new DialogInterface.OnMultiChoiceClickListener() {
				@Override
				public void onClick(DialogInterface dialog, int which,
						boolean isChecked) {

					ListView list = ((AlertDialog) dialog).getListView();
					if (isChecked) {
						if (which == 0) {
							for (int i = 0; i < list.getCount(); ++i)
								list.setItemChecked(i, true);
							selectedItems.clear();
							selectedItems.addAll(items);
						} else
							selectedItems.add(items.get(which));
					} else {
						if (which == 0) {
							for (int i = 0; i < list.getCount(); ++i)
								list.setItemChecked(i, false);
							selectedItems.clear();
						} else
							selectedItems.remove(items.get(which));
					}
				}
			});

	AlertDialog dialog = builder.create();
	dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
	dialog.show();
}


Казалось бы, проблема решена, можно протестировать и идти с чистой совестью отдыхать(ну ладно, поиграть в WOT=) ).

Кнопка отметить\снять отметку со всех элементов работала. Но потом я заметил, если отметить вначале самому пару элементов, потом кнопкой отметить все элементы и сразу же снять отметку со всех, то те пару элементов, которые отмечал сам, остаются отмеченными.
Немного поэкспериментировав, решил, что пора идти отдыхать и продолжить разбираться утром. Перед уходом поднял этот вопрос на stackoverflow.

Утром придя на работу, просмотрев свою тему и не увидев никаких дельных советов, продолжил разбор дальше.
После не продолжительного анализа происходящего, стало понятно, что проблема воспроизводится, только если в функцию
setMultiChoiceItems(CharSequence[] items, boolean[] checkedItems, OnMultiChoiceClickListener listener)
передавать массив checkedItems, если вместо него передавал null, проблема не возникала.
Поднял снова вопрос на stackoverflow, но к сожалению никто так ничего и не посоветовал.

На дворе уже было 29 декабря, да и по проекту еще было много задач, поэтому переписав немного код, без передачи массива, проблема была решена.
Сообщив о найденной проблеме в багтрекер гугла и продолжив работу дальше, я и забыл про эту историю.

Наши дни


Сегодня, просматривая почту, увидел, что кто-то откликнулся на мой багрепорт. После просмотра стало понятно, что еще пару человек столкнулись с этой же проблемой.

Разобравшись немного с делами, решил ещё раз взглянуть на проблему.
В голову пришла идея, хотя мне казалось, что она уже приходила и я её проверял, я проверил еще раз.
Суть идеи заключалась в том, чтобы массив checkedItems сделать полем класса, и в слушателе работать и с ним тоже.
В итоге, метод onClick принял вид:

public void onClick(DialogInterface dialog, int which,
		boolean isChecked) {

	ListView list = ((AlertDialog) dialog).getListView();
	if (isChecked) {
		if (which == 0) {
			for (int i = 0; i < list.getCount(); ++i)
			{
				list.setItemChecked(i, true);
				checkedItems[i]=true;
			}
			selectedItems.clear();
			selectedItems.addAll(items);
		} else
			selectedItems.add(items.get(which));
	} else {
		if (which == 0) {
			for (int i = 0; i < list.getCount(); ++i)
			{
				list.setItemChecked(i, false);
				checkedItems[i]=false;
			}
			selectedItems.clear();
		} else
			selectedItems.remove(items.get(which));
	}
}


И таки да, это решило первоначальную проблему.

Итог.


Можно сделать вывод, хоть он мне и не нравится:
Если в массиве checkedItems состояние элемента указано как не отмеченное (false), то при отметки он отмечается на экране (но не отмечается в массиве) и при снятии отметки, отметка снимается.
Но если в массиве checkedItems состояние элемента указано как отмеченное (true), то при снятии отметки, элемент все равно остается отмеченным на экране, т.к. он остается отмеченным в массиве.

Вот такая получилась история, надеюсь было интересно и вы не зря потратили время, впрочем как и я.
Спасибо за внимание, если есть вопросы, с радостью отвечу.
В следующей статье хочу рассказать уже о самом проекте, должно быть интересно.