Android HOW TO: Jak zapobiec zamknięciu okna dialogowego po negatywnej walidacji wprowadzonych danych?

android_how_to_32

Próbowaliście kiedyś zwalidować wartości wprowadzone w oknie dialogowym, a w razie błędu poinformować o tym użytkownika, przy czym – co najważniejsze – nie zamykać tego okna, by mógł on skorygować te dane? Niestety, Android nie ułatwia takiego zadania.
Można oczywiście stworzyć własne okno i zaimplementować całą jego obsługę, ale moim zdaniem takie rozwiązanie może się sprawdzić jedynie w przypadku bardziej rozbudowanych okien dialogowych. Do prostego okienka, wymagającego wprowadzenia jednej wartości lub wybrania jednej opcji, przydałoby się łatwiejsze i szybsze rozwiązanie. Na szczęście takie istnieje, chociaż nie jest ono oczywiste.
W tym artykule przedstawię, jak tego dokonać. Użyję bardzo uproszczonego przykładu, w którym po wyświetleniu okna dialogowego naszym zadaniem będzie zaznaczenie przynajmniej jednej pozycji. W przypadku braku zaznaczenia wyświetlony zostanie komunikat o błędzie.

Najpierw przygotujmy sobie kilka zasobów, które wykorzystamy w naszym przykładzie:

strings.xml

1
2
<string name="pick_android_version">Wybierz wersję systemu Android</string>
<string name="no_selected_item">Musisz wybrać przynajmniej jedną pozycję</string>

 
arrays.xml

1
2
3
4
5
6
7
<string-array name="androidversions">
    <item >Froyo</item>
    <item >Gingerbread</item>
    <item >Honeycomb</item>
    <item >Ice Cream Sandwich</item>
    <item >Jelly Bean</item>
</string-array>

 
activity_main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:id="@+id/LinearLayout1"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".MainActivity" >

    <Button
       android:id="@+id/btnDialogValidation"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:text="Push me!" />

</LinearLayout>

 
Następnie w metodzie onCreate naszej aktywności przygotujmy sobie przycisk oraz ustawmy mu zdarzenie onClick, które wywoła nasze okno dialogowe:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
       
    Button btnDialogValidation = (Button) findViewById(R.id.btnDialogValidation);
       
    btnDialogValidation.setOnClickListener(new View.OnClickListener() {
           
        @Override
        public void onClick(View v) {
            chooseAndroidVersion();
        }
    });
}

 
Zanim jednak przejdziemy do ostatecznego rozwiązania, chciałbym przedstawić, jak najczęściej wygląda implementacja tworzenia i obsługi okna dialogowego. Pokażę jednocześnie, na czym polega omawiany w tym artykule problem. W tym celu zaimplementujmy metodę chooseAndroidVersion():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public void chooseAndroidVersion() {
       
    final List<Integer> selectedItems = new ArrayList<Integer>();
   
    AlertDialog.Builder alertBuilder = new AlertDialog.Builder(this);
    alertBuilder.setTitle(R.string.pick_android_version);
       
    alertBuilder.setMultiChoiceItems(R.array.androidversions, null, new DialogInterface.OnMultiChoiceClickListener() {
           
        @Override
        public void onClick(DialogInterface dialog, int which, boolean isChecked) {
            if (isChecked) {
                selectedItems.add(which);
            }
            else
            if (selectedItems.contains(which)) {
                selectedItems.remove(Integer.valueOf(which));
            }
           
        }
    });
       
    alertBuilder.setPositiveButton(getString(android.R.string.ok), new DialogInterface.OnClickListener() {
           
        @Override
        public void onClick(DialogInterface dialog, int which) {
               
            if (selectedItems.isEmpty()) {
                Toast.makeText(getApplicationContext(), R.string.no_selected_item, Toast.LENGTH_SHORT).show();
            }              
        }
    });
                       
    alertBuilder.create().show();
}

 
Wydaje mi się, że kod jest w miarę oczywisty i nie wymaga dodatkowych komentarzy. Nie pozostaje nam więc nic innego, jak tylko uruchomić aplikację i przetestować pierwsze rozwiązanie:

Ekran naszej aplikacji stanowi rozciągnięty na całą wielkość wyświetlacza przycisk, który po wciśnięciu wyświetli okno dialogowe:

Jeśli nie zaznaczymy żadnej pozycji i wciśniemy OK, otrzymamy komunikat o niepoprawnym zachowaniu. Okno dialogowe zostajnie zamknięte.

Takie zachowanie aplikacji wymusza na użytkowniku powtórzenie całej sekwencji czynności od początku, co w niektórych przypadkach może być całkiem frustrujące.

Przebudujmy teraz naszą aplikację tak, aby okienko wyboru jednak nie zostało zamknięte w przypadku błędnego wyniku walidacji. Zmian nie będzie dużo. Pierwsze, co musimy zrobić, to wyczyścić kod zdarzenia onClick dla przycisku OK. Nie będzie on nam już potrzebny w tym miejscu, ponieważ obsługa walidacji zostanie przeniesiona w inne miejsce. Następnie za pomocą obiektu alertBuilder tworzymy obiekt alert klasy AlertDialog. Teraz dla tego obiektu obsłużymy zdarzenie onShow, w którym odnajdujemy przycisk OK i w jego zdarzeniu onClick implementujemy walidację zaznaczenia pozycji listy. Kod naszej metody chooseAndroidVersion() będzie teraz wyglądał następująco:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public void chooseAndroidVersion() {
       
    final List<Integer> selectedItems = new ArrayList<Integer>();
   
    AlertDialog.Builder alertBuilder = new AlertDialog.Builder(this);
    alertBuilder.setTitle(R.string.pick_android_version);
       
    alertBuilder.setMultiChoiceItems(R.array.androidversions, null, new DialogInterface.OnMultiChoiceClickListener() {
           
        @Override
        public void onClick(DialogInterface dialog, int which, boolean isChecked) {
            if (isChecked) {
                selectedItems.add(which);
            }
            else
            if (selectedItems.contains(which)) {
                selectedItems.remove(Integer.valueOf(which));
            }
               
        }
    });
       
    alertBuilder.setPositiveButton(getString(android.R.string.ok), new DialogInterface.OnClickListener() {
           
        @Override
        public void onClick(DialogInterface dialog, int which) {                                               
        }
    });
       
    final AlertDialog alert = alertBuilder.create();
    alert.setOnShowListener(new DialogInterface.OnShowListener() {
           
        @Override
        public void onShow(DialogInterface dialog) {
               
            Button positiveButton = alert.getButton(AlertDialog.BUTTON_POSITIVE);
            positiveButton.setOnClickListener(new View.OnClickListener() {
                   
                @Override
                public void onClick(View v) {
                    if (selectedItems.isEmpty()) {
                        Toast.makeText(getApplicationContext(), R.string.no_selected_item, Toast.LENGTH_SHORT).show();
                    }
                    else {
                        alert.dismiss();
                    }
                }
            });            
        }
    });
    alert.show();                              
}

 
Należy pamiętać, że w tym przypadku sami musimy zamknąć okno dialogowe, wywołując metodę dismiss() z klasy AlertDialog. W przeciwnym razie okno nie zostanie zamknięte, nawet przy poprawnym przebiegu walidacji.

Zobaczmy, jak będzie wyglądał efekt wprowadzonych zmian. W tym celu ponownie uruchamiamy aplikację, klikamy przycisk, nie zaznaczamy żadnej pozycji i klikamy OK.

Jak widać, ponownie dostajemy komunikat o braku wybranych pozycji. Jednak w tym przypadku okno dialogowe nie zostaje zamknięte. Czekamy na poprawne wprowadzenie danych. W ten sposób rozwiązaliśmy problem będący tematem tego artykułu. W kolejnych krokach możemy przekazać do aktywności wybrane przez użytkownika pozycje w celu ich dalszej obsługi. Mam nadzieję, że przedstawione rozwiązanie okaże się przydatne w waszych projektach.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *