Doru N.
09/10/2019, 10:25 AM@BindingAdapter("goneUnless")
fun goneUnless(view: View, visible: Boolean) {
view.visibility = if (visible) VISIBLE else GONE
}
In the layout, it looks like this: app:goneUnless="@{!user.isAdult}"
The value supplied to this binding method is a LiveData which, obviously, its initial value is null.
The problem is that user property being initially null, databinding evaluates user.isAdult to false -> negated -> turns to true, but I would like the View to be gone at first, and only change visibility after I assign a value to user LiveData.
Any thoughts on handling this in a more generic manner?rkeazor
09/10/2019, 11:15 AMrkeazor
09/10/2019, 11:16 AMrkeazor
09/10/2019, 11:18 AMwasyl
09/10/2019, 11:27 AMvisible
parameter nullable, you get true
initially?wasyl
09/10/2019, 11:28 AMuser.IsAdult
is null and handle this properly in the layoutDoru N.
09/10/2019, 11:31 AMDoru N.
09/10/2019, 11:33 AMuser != null ? user.isAdult : false
? If so, it would not scale for multiple conditions.. say user.isAdult && user.company.isLarge
etcDoru N.
09/10/2019, 11:38 AMViewsBehavior
class, which handles these checks in Kotlin, and import it into the layout.wasyl
09/10/2019, 12:04 PMrkeazor
09/10/2019, 11:57 PMrkeazor
09/10/2019, 11:58 PMwasyl
09/11/2019, 12:06 AMuser.isAdult && user.company.isLarge
is relatively serious logic that doesn’t belong in the xml (and is problematic, hence this entire thread)rkeazor
09/11/2019, 1:26 AMwasyl
09/11/2019, 8:51 AMYou can always unit test live data objectsThat’s what I wrote. But you can’t directly unit test databinding
android specific things can only be tested with espresso and RobolectricEnjoy running the tests couple of orders or magnitude slower than regular unit tests 🤷♂️
There really isnt much differenceYes there is, massive one
And with Android nitrogen now you can achieve headless UI testsStill using Robolectric. When scaling to thousands of tests, Robolectric vs pure unit tests make world of a difference So yes, you’re always free to put whatever logic in xml you feel is right. Again, I shared my experience and my approach. You’re free to disagree, but saying there’s no difference or suggesting it’s equally good to test bindings using Robolectric is not true. Unit tests scale well, keeping view logic in view model is clean and maintainable. Having to cover logic that resides in your xml layouts is slow to exectue and a chore to maintain.
rkeazor
09/11/2019, 11:23 AMrkeazor
09/11/2019, 11:28 AMrkeazor
09/11/2019, 11:29 AMrkeazor
09/11/2019, 11:36 AMwasyl
09/11/2019, 11:42 AMuser.isAdult && user.company.isLarge
in the xml. Xml should only have android:visible=@{model.someLiveData}
. The user.isAdult && user.company.isLarge
part (the logic) should be in the view model or whatever component that’s unit testable without Robolectric.
And then yes, you still want some test which confirms the binding is correct, but then you only need to test for 2 scenarios (someLiveData
value is true or false) in such Robolectric/Espresso test. If you put entire user.isAdult && user.company.isLarge
condition in xml, then you need that logic tested with Robolectric, and that’s suboptimal.rkeazor
09/11/2019, 11:44 AMrkeazor
09/11/2019, 11:44 AMrkeazor
09/11/2019, 11:46 AMwasyl
09/11/2019, 11:47 AMrkeazor
09/11/2019, 11:47 AMwasyl
09/11/2019, 11:47 AMrkeazor
09/11/2019, 11:48 AMwasyl
09/11/2019, 11:50 AMview X should be visible only when user is an adult and the company is large
rkeazor
09/11/2019, 11:50 AMrkeazor
09/11/2019, 11:51 AMrkeazor
09/11/2019, 11:53 AMwasyl
09/11/2019, 11:53 AMLets say your not using databinding rightWhy? That’s not the working assumption in any of the discussion here
rkeazor
09/11/2019, 11:55 AMrkeazor
09/11/2019, 11:55 AMrkeazor
09/11/2019, 11:55 AMrkeazor
09/11/2019, 12:01 PMrkeazor
09/11/2019, 12:02 PMwasyl
09/11/2019, 12:04 PMiveData that explicitly just represents the view visibilityYes
class SomeViewModel(val user: User) {
val xViewVisibility = MutableLiveData<Boolean>()
init {
xViewVisibility.value = user.isAdult && user.company.isLarge
}
}
xml:
...
<!-- databinding layout with `SomeViewModel` variable -->
<TextView
id="@+id/viewX"
android:visible="@{model.xViewVisible}" />
Then I have unit tests (no robolectric) for the SomeViewModel
class that checks if live data has proper value. I’m also pretty confident xml binding just works, so I don’t need unit tests for the view. Any issues will likely be found by larger test suites, since the view would be in an obviously wrong state if it didn’t pass proper binding values. But even if, I only have to test this view for very simple set of live data and check that the values are passed 1:1 and not some custom logic inside xmlrkeazor
09/11/2019, 12:05 PMwasyl
09/11/2019, 12:05 PMTesting if the live data object got set at the right time with the right value lolI mean, I already mentioned that couple of times — testing view model is a unit test, without robolectric. It’s several times faster than a robolectric test, tens times faster than an espresso test
rkeazor
09/11/2019, 12:05 PMrkeazor
09/11/2019, 12:06 PMrkeazor
09/11/2019, 12:06 PMwasyl
09/11/2019, 12:07 PMbut databinding doesn’t force you to add robolectric testsPutting logic in your xml bindings does
rkeazor
09/11/2019, 12:08 PMrkeazor
09/11/2019, 12:08 PMwasyl
09/11/2019, 12:08 PMwasyl
09/11/2019, 12:08 PMwasyl
09/11/2019, 12:08 PMuser
to databindingrkeazor
09/11/2019, 12:09 PMwasyl
09/11/2019, 12:09 PMandroid:visible=@{user.isAdult && user.company.isLarge}
in the xmlrkeazor
09/11/2019, 12:09 PMrkeazor
09/11/2019, 12:09 PMwasyl
09/11/2019, 12:09 PMwasyl
09/11/2019, 12:10 PMuser.IsAdult
and user.company.isLarge
(true/false, true/true, false/true and false/false). 4 cases that should be covered with tests, that only for true/true case the view is visible, for 3 others it’s not. Where is the place to test this?rkeazor
09/11/2019, 12:11 PMwasyl
09/11/2019, 12:11 PMThan you should espresso test that reflect your acceptance criteraThat’s my point exactly. You need to have those 4 cases tested with Espresso, is that right?
wasyl
09/11/2019, 12:12 PMuser
instances with those 4 different cases, and verify the view’s visibility is set properlyrkeazor
09/11/2019, 12:14 PMrkeazor
09/11/2019, 12:14 PMwasyl
09/11/2019, 12:15 PMlol
a lot but you don’t answer my question 😕 You just said you would test this with espressowasyl
09/11/2019, 12:15 PMuser.isAdult && !user.company.isLarge
in the xml?rkeazor
09/11/2019, 12:18 PMrkeazor
09/11/2019, 12:19 PMwasyl
09/11/2019, 12:19 PMrkeazor
09/11/2019, 12:21 PMwasyl
09/11/2019, 12:22 PMLivedata wise, that means im setting the wrong value in livedataYour live data is just the
user
, so it wouldn’t fail. It has nothing to do with the conditionrkeazor
09/11/2019, 12:22 PMrkeazor
09/11/2019, 12:23 PMrkeazor
09/11/2019, 12:24 PMwasyl
09/11/2019, 12:24 PMwasyl
09/11/2019, 12:25 PMrkeazor
09/11/2019, 12:25 PMwasyl
09/11/2019, 12:25 PMrkeazor
09/11/2019, 12:25 PMrkeazor
09/11/2019, 12:26 PMwasyl
09/11/2019, 12:26 PMwasyl
09/11/2019, 12:27 PMMy espresso test fails your espresso test fails. only difference is your unit test also failed lolYes, lol. I found the bug in half a second when my unit tests ran. You found yours in couple of minutes where you started the emulator and ran connected tests. Also with thousands of such cases across the projects, my entire unit tests suite completes and catches these errors in two minutes. Your espresso tests run for half an hour
rkeazor
09/11/2019, 12:29 PMrkeazor
09/11/2019, 12:29 PMwasyl
09/11/2019, 12:29 PMrkeazor
09/11/2019, 12:29 PMwasyl
09/11/2019, 12:32 PMlol espresso
tests, I’ll keep my ui logic separate and unit testable.
Maybe someone else will benefit from the discussion.rkeazor
09/11/2019, 12:33 PMwasyl
09/11/2019, 12:34 PMrkeazor
09/11/2019, 12:45 PMrkeazor
09/11/2019, 12:47 PMDoru N.
09/11/2019, 2:34 PM!user.isAdult
could result in unexpected result when data is null, erroneously turning to true
. I had to make a separate check for null data, have a fallback for that case, and only after having non-null data, consider the boolean condition.Doru N.
09/11/2019, 2:37 PM