Hey! I’m having some trouble using DataBinding lib...
# android
d
Hey! I’m having some trouble using DataBinding library. I have a binding method for View visibility:
Copy code
@BindingAdapter("goneUnless")
fun goneUnless(view: View, visible: Boolean) {
    view.visibility = if (visible) VISIBLE else GONE
}
In the layout, it looks like this:
Copy code
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?
r
What happens when you explicitly set the visibility to gone while having the binding adapter
Or force livedata to emit false first
Either way wrong channel for this question lol
w
@Doru N. So even if you make
visible
parameter nullable, you get
true
initially?
Anyway you can check if
user.IsAdult
is null and handle this properly in the layout
d
@rkeazor user LiveData is not of type Boolean, only isAdult property is. Forcing LiveData to emit an empty User instance, only for the isAdult property to be false, does not look good enough to me.
@wasyl you mean writing in layout:
user != null ? user.isAdult : false
? If so, it would not scale for multiple conditions.. say
user.isAdult && user.company.isLarge
etc
Eventually, I created a
ViewsBehavior
class, which handles these checks in Kotlin, and import it into the layout.
w
That’s because you shouldn’t be putthing this much logic in the xml anyway 😉 My general rule is no logic in the xml, your viewmodel/whatever should only expose value that’s directly passed in the binding
👍 4
r
oh ok maybe I didn't understand what you were saying earlier? You could have just made a seperate livedata object. Set the view to false initally that tie a observer to User Livedata , and when it changes update the view visibility.. Its a pretty simple issue to solve. You might be over thinking it
@wasyl, I don't think there is anything wrong with logic in the XML as long as the logic is only pertaining to the view. Like you shouldn't make network calls from XML. but something like view visibility isn't that bad.
w
It’s always a balance. My experience is that exposing live data that’s always directly passed in binding is not much overhead and puts logic in the view model that can be unit tested. Any logic in data binding can only be tested using Robolectric/Espresso 🤷‍♂️ I was just sharing my thoughts, for me
user.isAdult && user.company.isLarge
is relatively serious logic that doesn’t belong in the xml (and is problematic, hence this entire thread)
👍 1
r
Well not exactly. You can always unit test live data objects. And android specific things can only be tested with espresso and Robolectric... There really isnt much difference . And with Android nitrogen now you can achieve headless UI tests
w
You can always unit test live data objects
That’s what I wrote. But you can’t directly unit test databinding
android specific things can only be tested with espresso and Robolectric
Enjoy running the tests couple of orders or magnitude slower than regular unit tests 🤷‍♂️
There really isnt much difference
Yes there is, massive one
And with Android nitrogen now you can achieve headless UI tests
Still 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.
1
r
@wasyl I think your missinterpreting what Im saying lol. Let say he didn't write this using databinding (user != null ? user.isAdult : false), and instead wrote this in a normal way. He would than need a function to toggle the Views Visiblilty right? Like fun showView(isVisible: Boolean){ view.visibility = if (show) Visible else Gone } , guess what this function will still need to be tested with either Robolectric, Espresso , Or a complete mocking of the view. There is no way around this because of how the android framework works... In best practice you should always use espresso to test View Logic. I never said it was the same or better than Unit tests. But in terms of UI logic its always the same issue and resolution
Now lets talk pre databinding era, these 3 liner function such as void showView(Boolean show), void setViewText(String text) ...Where all over peoples code base. Guess what else was. so was Robolectric or Espresso Test. But your argument suggest that Databinding increase the need for this. No , no it didn't. When databinding first got released in 2016 , they stated in google IO "It was way of removing the 3 liner view setting methods". And thats exactly what it does, no more , no less
Now lets talk about testing
When your testing, you don't have to test the frameworks inner workings. So essentially all he needs to test is wether the LiveData objects are being set at the right time with the right values. He doesn't need to test wether those values make the view hide. Because thats the inner workings databinding. But in good practice you should still do Espresso tests .. Like thats just good practice. You could even write end to end espresso tests... And between that and your unit tests , your codebase is covered when it comes to testing...
w
Well, seems you also misintepreted what I said 😉 I’m not against data binding, very far from it. I’m against putting
user.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.
👍 1
r
lol and im saying no you don't lol. The connection between LiveData and your XML doesn't need to be unit tested. Only thing that needs to be tested is if the livedata objects are being set correctly.
when you write your espresso test to test your flow, that will be covered
This is how databinding was intended .
w
we’re not gonna agree, then ¯\_(ツ)_/¯
r
all this was stated when databinding was first released lol
w
I’m gonna keep putting my view logic in the view model so that it’s unit-testable, you keep in the xml where you test it with espresso or whatever
r
ViewLogic? give me an example?
w
Sure, for example
view X should be visible only when user is an adult and the company is large
r
ok fine lets take this
Lets say your not using databinding right. If your using Viewmodel and livedata , than the best practice is to tie a observer for the livedata in your activity or fragment
viewModel.viewVisibility.observe (lifecycleOwner, Observer { show -> showViewMethod(show) }
w
Lets say your not using databinding right
Why? That’s not the working assumption in any of the discussion here
r
if your doing it with databinding.are you saying your just going to have a liveData that explicitly just represents the view visibility
how would you approach it
?
Untitled
something to that affect right. either than or use Live Data Transformations... But guess what your still just testing the same thing. Testing if the live data object got set at the right time with the right value lol
w
iveData that explicitly just represents the view visibility
Yes
Copy code
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 xml
r
its still the same thing lol
w
Testing if the live data object got set at the right time with the right value lol
I 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
r
You should always test viewmodels without robolectric, i agree
but databinding doesn't force you to add robolectric tests
the viewModel is just being observed.. the Viewmodel knows nothing about databinding
w
man.
but databinding doesn’t force you to add robolectric tests
Putting logic in your xml bindings does
r
lol no it doesn't . how can it,the viewModel knows nothing about your XML
so why would you add robolectric to your viewmodel
w
i’m not saying anything about view model
Okay so my question:
you’d say it’s okay to e.g. pass
user
to databinding
r
sure why not
w
and then have
android:visible=@{user.isAdult && user.company.isLarge}
in the xml
r
ye
p
w
how and where do you test this?
There are 4 scenarios you need to test, for 4 combinations of
user.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?
r
In your viewmodel you just unit tests that user is being set correctly. Than you should espresso test that reflect your acceptance critera. between those two tests, your codebase is completely covered. You don't need to test the like between the livedata and databinding. Databinding framework got its own tests lol
w
Than you should espresso test that reflect your acceptance critera
That’s my point exactly. You need to have those 4 cases tested with Espresso, is that right?
Somewhere in your espresso tests pass 4 different
user
instances with those 4 different cases, and verify the view’s visibility is set properly
r
Lol no you don't ... when you write your espresso test, the UI portion should be cover. lets say your getting Info from an API. than you should have the api responses mocked. if the api returns a User that is not an adult than that test case should be covered in your UI. because its part of the Acceptance Criteria. And this is true whether you did with the logic in the xml or not
its always the same thing lol
w
You say
lol
a lot but you don’t answer my question 😕 You just said you would test this with espresso
Which test would fail if by accident, I put
user.isAdult && !user.company.isLarge
in the xml?
r
because your missing the big picture. the correlation between xml and livedata is always the same. Reguardless if you put the logic in the XML or you set it explicitly in the viewmodel. UI stuff will always need Espresso or some kind of mock/isolation framework... setting a livedata object can always be done as a unit tests.
adding that logic doesn't increase the need for it. its still the same thing
w
Simple question, which test would fail? I have answer for my approach, the unit test for view model would fail. Which test would fail with your approach?
r
UI wise, both my espresso test and your espresso test should fail lol ., Livedata wise, that means im setting the wrong value in livedata, so the test that make sure im setting the right value should fail
w
Livedata wise, that means im setting the wrong value in livedata
Your live data is just the
user
, so it wouldn’t fail. It has nothing to do with the condition
r
than just my espresso test
that fails
and so those your Espresso test
w
So this expression is tested by Espresso. This means you use espress to test logic which in my approach is tested in unit tests. My entire point is that it’s favorable to put as much logic as possible in view model, so that it’s unit tested. That way I don’t have to run emulator and entire connected test just to see that this condition is wrong
feedback loop is much faster
r
You use espresso to test UI logic,
w
No, you should use espresso to test the UI
r
its still the same thing, both our espresso test still fail
My espresso test fails your espresso test fails. only difference is your unit test also failed lol
w
No, UI is Android framework thing, UI logic is not. UI logic is what’s mostly the same between platforms, for example. UI logic should be unit testable because it’s not described in terms of the framework, but in terms of the data
My espresso test fails your espresso test fails. only difference is your unit test also failed lol
Yes, 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
r
hahah, sure but lets see if the logic lives up to your expectation . Lets now say the actual liveData Object your using in xml is observing the wong value. now when do you find out 🙃
one hint.
w
Of course, that’s why I’m not saying I don’t need espresso tests at all. But I’ll find 90% of bugs in unit tests, you’ll find 50% of bugs in unit tests
r
in ESPRESSO LOL
w
I don’t think this discussion is productive so I’ll just stop here. You keep arguing for
lol espresso
tests, I’ll keep my ui logic separate and unit testable. Maybe someone else will benefit from the discussion.
r
that's a bit farfetched lol. The only thing different your doing is explicitly setting the livedata object . I could even argue the point that since you will have more livedata objects than me, than you have more opportunity to create more logic bugs
w
Argue whatever. I’ll take my larger coverage of logic with unit tests any day
r
and i'll take the android documentation any day. The reason why I argue this so heavly because I have seen both instances in production code bases. And one thing I have noticed is that when people explicitly keep all UI driven logic outside of XML, they tend to have a significant increase in BindingAdapters, LiveData objects so on and so forth. Just to compensate for value representation in there XML. On the other hand I have also seen people go overboard in XML. so im not saying your not completely incorrect. It can be def be taken to an extreme. but the other way also has drawbacks. But as far as testability goes, I don't think that's the real issue. I even think there is a way to mock out the databinding component if you really need the sanity check of unit tests...
in retrospect, logic in the xml isn't that bad, if done correctly lol.
d
wow, 86 thread replies 😄, all in all, the only ‘problem’ using DataBinding with boolean conditions, which I didn’t have in mind was that having negated conditions, like
!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.
I extracted this logic into a Kotlin object, and imported it into the layout, so I can manage it easily and add unit tests on it.
👍 2