Databinding Episode I; Hidden Danger
Databinding Эпизод I; Скрытая угроза
Иногда в повседневной разработке под Android сталкиваешься со странными, если не сказать мистическими багами. (стандартное вступление)
Наш QA заметил странный баг, который трое из наших разработчиков не могли заметить глядя на экран в упор. При переходе на экран очень быстро “промаргивала” часть экрана, которая затем скрывалась. Очень хороший FPS у тестировщика :) Отловив в предложенном видео кадр с “промаргиванием” я усомнился в своей способности видеть этот мир, по крайней мере в сравнении с некоторыми людьми. Что ж, нужно править.
Забравшись в верстку обнаруживаю в общем-то безобидный кусок xml-ля:
...
<ru.ivi.uikit.UiKitGridLayout
...
android:visibility="@{authState.isUserAuthorized ? View.GONE : View.VISIBLE, default=gone}"
...
Эта часть лэйаута должна была быть скрыта по умолчанию, а затем либо появляться, либо нет, в зависимости от статуса авторизации. Но почему-то по умолчанию она показывалась и затем скрывалась как и должна для авторизованного юзера.
Похоже где-то в этой строке есть ошибка. Может быть “, default=gone”? Тут нам не подскажет документация, придется лезть в устройство дата-байндинга:
UserAuthorizedState authState = mAuthState;
int authStateIsUserAuthorizedViewGONEViewVISIBLE = 0;
boolean authStateIsUserAuthorized = false;
if ((dirtyFlags & 0x3L) != 0) {
if (authState != null) {
// read authState.isUserAuthorized
authStateIsUserAuthorized = authState.isUserAuthorized;
}
if((dirtyFlags & 0x3L) != 0) {
if(authStateIsUserAuthorized) {
dirtyFlags |= 0x8L;
}
else {
dirtyFlags |= 0x4L;
}
}
// read authState.isUserAuthorized ? View.GONE : View.VISIBLE
authStateIsUserAuthorizedViewGONEViewVISIBLE = ((authStateIsUserAuthorized) ? (android.view.View.GONE) : (android.view.View.VISIBLE));
}
// batch finished
if ((dirtyFlags & 0x3L) != 0) {
// api target 1
this.motivationToRegistration.setVisibility(authStateIsUserAuthorizedViewGONEViewVISIBLE);
}
Если проследить за логикой этого сгенерированного кода, то видим, что authStateIsUserAuthorized по умолчанию false. Если байндинг еще не произошел, то есть authState==null, то значение переменной не меняется. В результате переменная authStateIsUserAuthorizedViewGONEViewVISIBLE содержит View.VISIBLE. А как же то что мы указали в коде “, default=gone”?
Нет никакого default! Вот документация и там его нет https://developer.android.com/topic/libraries/data-binding/expressions смотрите сами! Он есть только для строк и только в одном из ответом на stackoverflow https://stackoverflow.com/questions/39241191/error-with-default-value-in-databinding?rq=1
Такие дела.
Теперь уберем из xml слово default и сравним сгенерированный код - ничего не поменялось. Давайте просто сделаем вид, что default это наша фантазия о том как должен выглядеть databinding. А пока что заменим код в xml на что-то более надежное:
android:visibility="@{authState==null||authState.isUserAuthorized ? View.GONE : View.VISIBLE}"
Теперь никаким супер-зрением наш лэйаут не увидеть.
Вывод такой: будьте осторожны с кодом внутри xml и databinding и еще осторожнее с Android SDK ༼ʘ̚ل͜ʘ̚༽
UPD: default - работает, но только как параметр, который подставится в xml при inflate’е. Далее может вызваться байндинг с null-данными и затереть default значение.