Android - Using databinding to assign a layout? - android

I'm trying to use data binding to assign a 'layout' attribute within an tag. I'm passing in a boolean value that tells me if the app is in dark mode. I use this variable to determine if I should assign a white button layout or a black button layout.
I've tried to just do this intuitively since I've seen people use data binding booleans all the time to assign values in xml.
<include
android:id="#+id/buy_with_google"
layout="#{ isDarkMode ? #layout/buy_with_googlepay_button_white : #layout/buy_with_googlepay_button_black}"
Error: ****/ data binding error ****msg:included value (#{ isDarkMode ? #layout/buy_with_googlepay_button_white : #layout/buy_with_googlepay_button_black}) must start with #layout/. file:[Redacted File Path] ****\ data binding error ****```
Does databinding just not work in a way that allows me to use logic to assign whole layouts like that?

This is not possible as the include is executed at compile time and doesn't know about your viewModel or any runtime parameters.

Related

Epoxy requires every model attribute to implement equals and hashCode error

I have created a small test app for this issue here: (https://github.com/Winghin2517/EpoxyExample2).
I would like to pass a list of objects into the epoxy controller so that I can generate a graph. I have however encountered this error when building the app:
error: Epoxy Processor Exception: Type in Iterable does not implement
hashCode. Type: kwaai.com.exampleepoxy_hashcodeequals.GraphData (View
Prop {view='HeaderView', name='setLineChart',
type=java.util.LinkedList})
Epoxy requires every model attribute to implement equals and hashCode
so that changes in the model can be tracked. If you want the attribute
to be excluded, use the option 'DoNotHash'. If you want to ignore this
warning use the option 'IgnoreRequireHashCode'
I think it is because I using the #ModelProp on a List of Objects (LinkedList of GraphDataFeed) and not on a primitive type as per the example app from Epoxy.
#ModelProp
public void setLineChart(LinkedList<GraphData> graphDataFeed) { }
So I folllowed the options and modified my #ModelProp to reflect this:
#ModelProp(options = ModelProp.Option.IgnoreRequireHashCode)
After the change the app builds and runs correctly. You can see the graph below.
However, I do not want to ignore the attribute as I understand Epoxy uses Diffing to update the models in the recyclerview: https://github.com/airbnb/epoxy/wiki/Diffing
Ignoring the attribute might mean that my models do not get updated correctly in the recyclerview.
In the guidance material here (https://github.com/airbnb/epoxy/wiki/Epoxy-Models#annotations), I see it says:
A model's state is determined by its equals and hashCode
implementations, which is based on the value of all of the model's
properties.
This state is used in diffing to determine when a model has changed so
Epoxy can update the view.
These methods are generated so you don't have to created them
manually.
Why are these methods not generated for me then and if they are not generated, how do I generate these methods myself to get rid of the error?
Your GraphData class needs to implement equals and hashcode. It says this right in the error message you copied
Type in Iterable does not implement hashCode. Type: kwaai.com.exampleepoxy_hashcodeequals.GraphData

Null Android data-binding

While studying android data-binding, my colleague told me that the android data-binding can be null in few cases, also when one layout includes another layout with data-binding, the generated data-binding file annotate the binding of another layout as #Nullable. My question is can data-binding be null and if yes when?
Data binding is a blueprint.
A class that is created at compile time when it sees the "layout" tag.
The blueprint class will be named LayoutNameBinding Pascal Case.
Just like any other class, it is non-existent until you reserve memory for it and new it up.
So when you use the data binding utility on the onCreate it creates the class and you can store that in a local variable for using later.
example:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.activity = this
binding.iBindingRecyclerView = this
binding.navHeader?.activity = this
setupNavigationDrawer()
}
"layout root xml files that have other layout root xml files nested in them or named include layouts" will be added as classes inside the parent NameOfLayoutBinding class. These will not be null as they are auto generated at compile time so when you new up the parent, the children will exist.
So a databinding will not be null if you are referring to the auto generated class and if you are newing it up in your onCreate method appropriately.
Now a failed binding event due to a null object can happen if you didn't pass in the variable that you are binding to, but that is not the question you asked.
Hope that helps, if you meant something different, please digress.
Finally, after so many years found an explaination for this. In case you are manually executing the DataBinding.inflate function then the returned DataBinding won't be null.
For auto generated bindings (i.e. when you include a layout and binding is generated and assigned to a variable in the parent layout binding), the data binding can be null based on some situation.
E.g.: Let's say we have layout as follow:
Portrait Mode layout: /src/res/layout/activity.xml
<LinearLayout ...>
<include
android:id="#+id/main_content"
layout="#layout/main_content_layout"
/>
</LinearLayout>
And for Lanscape mode: /src/res/layout-land/activity.xml
<LinearLayout ...>
<include
android:id="#+id/sidebar"
layout="#layout/sidebar_layout"
/>
<include
android:id="#+id/main_content"
layout="#layout/main_content_layout"
/>
</LinearLayout>
Now here as the multiple layout files are for same purpose but with different configuration (lanscape mode and portrait mode) the Android DataBinding will generate ActivityBinding.java file. Now here the developer would need to access binding for both sidebar and main content using object of ActivityBinding.java class. As the sidebar is not present in portrait mode layout file, the binding file won't have any reference. Hence the reference for sidebar binding would be kept as Nullable.
Hence, for the layout files with same name for different configuration and with different view hierarchy, the inner binding object generated can have null value, due to which the data binding may have Nullable binding fields.

Why does "enabled" and "pressed" attributes work for ImageButton?

Where to see a list of all attributes of all view types in Android?
I got an impression, that ImageButton does not have enabled and pressed attributes. At least, they didn't work when I set them in XML. Also I found a lot of "guides" on how to make these button either disabled and/or pressed.
Simultaneously, when I bound them with data binding
<ImageButton
android:id="#+id/locate_button"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_toRightOf="#id/bookmark_button"
android:enabled="#{activity.locateEnabled}"
android:pressed="#{activity.locatePressed}"
android:onClick="#{activity.onLocateClick}"
android:src="#drawable/locate_selector"
android:background="#null"
/>
they just worked. Both pressed and enabled. At the same moment pressed is even reported as unknown property by Android Studion spell checked!
So, what is it?
1) by-design behaviour and I just don't understand something (what?)
2) sugar from data binding library
3) hacking
4) ????
How to know, how poratble is this feature?
The answer is both 1 and 2. Data binding will allow you use an attribute to call any setter on a View. You can look at the data binding guide's section on attribute setters.
When you set the android:enabled attribute, you are using Android data binding's the automatic setters to calls setEnabled(). Android data binding sees the attribute name (enabled) and looks for a setter with that name and finds setEnabled(). The same is true for android:pressed -- there is a setPressed() method. Yay for convention!
android:onClick is a real attribute, but data binding doesn't use it. There is no setOnClick() method, either. Instead, there is a Binding Adapter that sets an OnClickListener to call your onLocateClick() method. There is a bit of magic involved in this that relies on the Annotation Processor that data binding uses to examine your code, but suffice it to say that it does this at compile time instead of using runtime reflection.
All event listeners should have Binding Adapters written for them with the same name as the event (as opposed to the listener name). So, you can also set android:onLongClick, for example. You may also be interested in the lambda syntax for events.
I'm not sure what you mean by "portable." Data Binding is usable at least back to Gingerbread (we claim Froyo, but really, who targets Froyo?), but you won't be able to transfer it to iOS or anything like that. All of Android data binding is done with a small runtime library and generated code. You don't have to worry about specific versions of Android.

Using same Android binding class from 2 different layouts

I am trying to display data from the same class in two different layouts using Android's data binding. The layouts are used to inflate the views in a ListView. I already have it functioning for one, and I was hoping to use the same adapter class since it's easy enough to specify which layout resource to use.
The problem arises in the automatically generated databinding classes; since there are two layout files, it generates two of them, say, LayoutOneBinding and LayoutTwoBinding, and when I use
DataBindingUtil.bind(inflatedView)
I get one of the two, and they have no common superclass that I can assign the result to and still be able to use the contained data. So, is there any way to reuse the data binding class across two different layouts?
Each layout file has a separate <variable>, but it is named the same and contains the same type of data.
There is a way to reuse binding in case you have same variable names in both bindings. Every data binding extends ViewDataBinding. So, you have a super class which you can accept.
Here, you cannot directly set the variable like dataBinding.variable1 = someValue. But, there is an alternate way i.e. use of #setVariable function.
So in your adapter, your code would be something like as follow:
dataBinding.setVariable(BR.variable1, someValue)
Ref: https://www.vogella.com/tutorials/AndroidDatabinding/article.html#implement-the-recyclerview-with-data-binding

Is it possible to get an attribute value of a widget as declarated in the android layout.xml file?

Is there a way in an android application to retrieve the attribute value of a widget declared in a layout.xml?
for example in my layout.xml file i have :
<CheckBox
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/clean_filesWithoutVideo"
android:checked="#{uiprefs.switchButtonEditable}"
android:id="#+id/checkbox_updateable" />
I would like to retrieve the literal value for android:checked, eg I want to retrieve "#{uiprefs.switchButtonEditable}" at runtime.
I searched in android.content.res.Resources class using getResources() from an Activity without success :
I cant get the xml file as stream.
Parsing attributes throught the Resources.getLayout() doesn't restitute the attribute value.
Maybe this value is hidden somewhere in the CheckBox instance but I can't find it using inspection at debug time...
Note : The value I want to retrive are especially databinding literals. Maybe I could retrieve this through the databinding API ?
Every UI component has it’s own getters and setters through which you can retrieve attributes that are currently set or set what you want to. If the attributes are common among the views, for example height/width, the getter will be available in View class.
In your case, you can get checked attribute through:
CheckBox checkbox = (CheckBox) view.findViewById(R.id.checkbox_updateable);
boolean initialState = checkbox.isChecked();
In Android Studio, you can get the methods available for a particular view. Enter var name (say checkbox) then enter dot (.) and it will show you all available methods. Start typing that you think you are looking for, it will show you all relevant methods.
UPDATE
I think AttributeSet is what you are looking for. You can do this :
XmlPullParser parser = resources.getXml(myResource);
AttributeSet attributes = Xml.asAttributeSet(parser);
From doc:
The implementation returned here, unlike using the implementation on
top of a generic XmlPullParser, is highly optimized by retrieving
pre-computed information that was generated by aapt when compiling
your resources. For example, the getAttributeFloatValue(int, float)
method returns a floating point number previous stored in the compiled
resource instead of parsing at runtime the string originally in the
XML file.
This interface also provides additional information
contained in the compiled XML resource that is not available in a
normal XML file, such as getAttributeNameResource(int) which returns
the resource identifier associated with a particular XML attribute
name.

Categories

Resources