Niebezpieczeństwo użycia kotlin.syntetic for block in layout

Photo by Justin Chrn on Unsplash

Teraz, wraz z pojawieniem się Kotlina w rozwoju dla Androida, pojawiło się przydatne narzędzie do wiązania widoków z xml-layout do klasy. Jest to kotlinx.android.synthetic. Nie ma już potrzeby używania metody findViewById(). Wystarczy wpisać wartość id, które jest określone w xml i można użyć widoku w klasie.Jest wiele artykułów na ten temat, jak chociażby oficjalny opis. Ale mogą pojawić się nieoczywiste problemy z użyciem tagu <include> w layoucie. Stwórzmy mały przykład i spróbujmy pokazać taki przypadek.

Aplikacja testowa to lista z dwoma elementami, każdy z nich zawiera przycisk z tekstem. Na początku zostaną utworzone layouty dla elementów – item1.xml oraz item2.xml. Tło za przyciskiem ma różne kolory dla lepszej widoczności.

Skupmy się na tym, aby przyciski w elementach miały to samo id „button”. Jest to ważna rzecz.

Następnie dodajemy te elementy do main_layout z pomocą <include> tagu:

Teraz użyjmy go w klasie jako layout:

Uruchom aplikację aby otrzymać następujący wynik:

Identyfikacja problemu

Teraz zróbmy mały feature, i zmieńmy tekst przycisków w kodzie. Dla pierwszego przycisku będzie nowy tekst „Changed Text First Item”, a dla drugiego „Changed Text Second Item”. Do powiązania z przyciskami użyjemy kotlinx.synthetic z aliasem:

import kotlinx.android.synthetic.main.item1.button as button1
import kotlinx.android.synthetic.main.item2.button as button2

Oczywiście button1 jest widokiem z item1.xml, button2 z item2.xml. Następnie zmieńmy tekst w OnCreate:

Uruchommy aplikację i zobaczmy rezultat:

Oops! To nie jest to, czego oczekiwaliśmy. Tekst pierwszego przycisku został zmieniony na tekst, który został napisany dla drugiego, a tekst drugiego przycisku nie został zmieniony w ogóle.

Wyjaśnienie problemu

Postarajmy się zrozumieć, co jest przyczyną takiego wyniku. Teraz zobaczmy jak działa syntetyk pod maską. W tym celu dekompilujemy klasę Kotlina do Javy (Show Kotlin Bytecode -> Decompile for AndroidStudio). Teraz MainActivity wygląda tak:

Teraz możemy zrozumieć jak działa syntetyk. Aktywność posiada HashMap, która używa id widoku jako klucz, oraz widok-obiekt jako wartość. Jest ona wypełniana przez leniwą inicjalizację. W naszym przypadku, pierwsze wywołanie do button1 szuka wartości HashMap po kluczu id = R.id.button. Ponieważ nie ma jeszcze takiej wartości, metoda dodaje parę do mapy, gdzie id jako klucz, a button-object z item1 jako wartość. W wyniku tego tekst dla pierwszego przycisku zostanie zastosowany pomyślnie.

Wywołanie do button2, który również ma id = R.id.button, sprawdza wartość mapy dla tego klucza, znajduje nasz pierwszy Widok (przycisk z pierwszego elementu) i wypełnia go wartością dla drugiego przycisku. W rezultacie pierwszy przycisk zmieni tekst dwa razy, drugi nie zmieni go ani razu

Rozwiązanie problemu

Jak na początku, możesz użyć innego id dla każdego widoku. Ale jeśli to nie jest przydatne, możesz wrócić do findViewById:

Uruchommy aplikację i zobaczmy rezultat:

Teraz działa poprawnie!

Wniosek

Nowe funkcje języka dają wiele pożytku, ale powinieneś zrozumieć jak to działa, aby uniknąć problemów.

Dzięki za przeczytanie tego artykułu! Mam nadzieję, że będzie on dla Ciebie przydatny

.

Dodaj komentarz