Разработка под Android / [Из песочницы] AlertDialog setMultiChoiceItems, баг или неочивидная особенность

в 7:38, , рубрики: alertdialog, android, setMultiChoiceItems, метки: , ,

Привет!

Вступление

Последние пару месяцев работаю над одним проектом под 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,
DialogInterface.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), то при снятии отметки, элемент все равно остается отмеченным на экране, т.к. он остается отмеченным в массиве.

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

Автор: silentnuke

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js