Android + Data Binding #style - android

While using the new data binding api, I found that you can't bind to the "style" attribute. Compiler complains that it can't find the style. However, if I simply set the style as is, it'll find it just fine. For example:
doesn't work:
style="#{TextUtils.isEmpty(row.getSubtitle()) ? #style/SubTitle : #style/Title}"
works:
style="#style/SubTitle"
Error:
Error:Execution failed for task ':app:compileDebugJavaWithJavac'.
java.lang.RuntimeException: Found data binding errors.
****/ data binding error ****msg:Identifiers must have user defined types from the XML file. SubTitle is missing it file:/~/test/app/src/main/res/layout/row.xml loc:48:71 - 48:78 ****\ data binding error ****

The data binding unfortunately is not supported for styles:
https://code.google.com/p/android-developer-preview/issues/detail?id=2613

Although #bwhite is correct, there may be workarounds you can do. It depends what you need to conditionally change. For instance, if you want to change the font based on the condition (which I needed to do), you can do it by making a custom binding adapter.
In other words, doing something like this:
public class FontBindingAdapter {
#BindingAdapter({"bind:font"})
public static void setFont(TextView textView, String typefaceName){
Typeface typeface = ResourcesCompat.getFont(context, R.font.myfont);
// You'd probably want to actually use `typefaceName` to determine the font to use
textView.setTypeface(typeface);
}
Then in your layout, like this:
<TextView
app:font="#{some_condition ? #string/typeface_string_name_bold: #string/typeface_string_name_bold_light}"
I used this in my code, based on a great post: https://plus.google.com/+LisaWrayZeitouni/posts/LTr5tX5M9mb

I have a found a rather elegant solution for applying styles with data binding. I use the Paris library and then create binding adapters for interested views. for example:
#BindingAdapter("bindTextViewStyle")
fun TextView.bindTextViewStyle(styleResourceId: Int) {
this.style(styleResourceId)
}
and then in XML:
<TextView
app:bindTextViewStyle="#{viewModel.priceStyleResource}"
.../>
viewModel.priceStyleResource is a MutableLiveData in my view model which is set with the style resource ID.
priceStyleResource.value = R.style.QuoteDetailsHeaderItem_Up
EXTRA NOTE
You can also probably make a generic bindStyle binding adapter directly for the View class, but in that case the attribute items specifically for textviews (textColor for example) will not get applied. So up to you to find the right balance and naming.

Related

Android BindAdapter with custom attribute with custom values (similar to visible attribute)

I've successfully made my first bind adapter and I wish to know a bit more about it.
I want to know how to make an attribute that can get only specific strings for a different state for my view.
For example every view has the visibility attribute that it can be"gone", "visible", "invisible"
<TextView
android:id="#+id/loading_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="#id/inventory_items_recycler"
app:layout_constraintEnd_toEndOf="#+id/inventory_items_recycler"
app:layout_constraintStart_toStartOf="#id/inventory_items_recycler"
app:layout_constraintBottom_toBottomOf="#id/inventory_items_recycler"
android:textSize="18sp"
android:visibility="gone"
app:item_id="#{ItemID.BLACK_GLOVES.ordinal()}"
/>
I've made a custom attribute called item_id that get a number that represent enum value. And in my binding utils I have this code:
#BindingAdapter("item_id")
public static void setItemName(TextView tv, int itemId) {
tv.setText(ItemData.get(ItemID.values()[itemId]).getName());
}
I prefer to have something similar to the visibility attribute that it value can be either "visible", "invisible" or "gone"
Bonus::
I wish android studio can auto-complete me for the possibilities that I can use.
You could pass directly the enum to your binding adapter, instead of converting it first to an int and than back to enum.
#BindingAdapter("item_id")
public static void setItemName(TextView tv, ItemID itemId) {
..
}
Then you could pass directly the enum in your xml:
app:item_id="#{ItemID.BLACK_GLOVES}"
This way you'll have a limited number of possibilities to enter and will be less likely to accidentally enter a meaningless integer.
However, binding adapters and custom attibutes are different. With a binding adapter, you still need to use the syntax of binding expression, ie: "#{ }".
android:visibility , on the other hand, is an attribute. You can also define custom attributes for your custom views and get something similar (have a limited number of input options and IDE shows you your options etc). But you shouldn't confuse that with binding adapters. These are two different concepts.
Try this:
#BindingAdapter("isGone")
#JvmStatic
fun View.setVisibility(isGone: Boolean) {
if (isGone) this.visibility = View.GONE else View.VISIBLE
}
Inside your xml:
<com.google.android.material.checkbox.MaterialCheckBox
android:id="#+id/cb_class"
style="#style/TextStyleNormal.White"
android:layout_marginStart="#dimen/margin_large"
isGone="#{isSharedDailyActivity}"// it take boolean value
app:buttonTint="#color/white"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:useMaterialThemeColors="true" />

BindingAdapter with 3 parameters throws compile error when together but works separately

I have created BindingAdapter to make clickable part inside TextView. I have used 3 parameters, one string resource with placeholder for clickable part (textWithPlaceholder), second part with clickable part (textClickablePhrase) and last one for listener (onPhraseClick). When building the app, it fails with upcoming error.
Cannot find the setter for attribute 'app:onPhraseClick' with parameter type lambda on android.widget.TextView.
The strange thing is when I try the same code without first two parameters (the strings), it can build. Even if I try to build app without last parameter (listener), it can build. So the code works but it doesn't work together.
<TextView
<!-- another attributes -->
app:textWithPlaceholder="#{#string/text_with_placeholder}"
app:textClickablePhrase="#{#string/clickable_phrase}"
app:onPhraseClick="#{() -> viewModel.onPhraseClicked()}" />
#BindingAdapter("textWithPlaceholder", "textClickablePhrase", "onPhraseClick")
#JvmStatic
fun TextView.setClickablePhrase(
textWithPlaceholder: String,
textClickablePhrase: String,
onPhraseClick: View.OnClickListener) {
// setting Spannable and click listener
}
I expect the 3 parameters should work together but they works only separated.
EDIT:
It can build even like this but I think it is not right solution because it is not defined right. All parameters are mandatory and it should be able to be defined in this way and not to use workaround to make it optional.
#BindingAdapter(value = ["textWithPlaceholder", "textClickablePhrase", "onPhraseClick"], requireAll = false)

Cannot find the setter for attribute 'android:textColorHint' with parameter type int

I am trying to use data binding to set the textColorHint on <android.support.design.widget.TextInputLayout> view as in
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="#dimen/text_input_layout_height"
android:layout_marginBottom="#dimen/text_input_margin_bottom"
android:hint="#{FieldHandlers.hasValidSpaces(account.firstName)? #string/first_name : #string/invalid_firstname}"
app:textColorHint="#{FieldHandlers.hasValidSpaces(account.firstName)? #android:color/holo_red_light : #android:color/holo_red_light}"/>
however it gives the following error message:
Error:(70, 38) Cannot find the setter for attribute 'app:textColorHint' with parameter type int on android.support.design.widget.TextInputLayout.
Is there a way I can use databinding with this property? It works if i pass in a color literal or a #color/myColor reference directly.
Not sure if you still need this answer. But that attr doesn't work because it hasn't a setter provided in TextInputLayout.class. And as thats an sdk class - you aren't allowed to change it. The problem resides here, inside constructor, while parsing TintTypedArray :
if (a.hasValue(R.styleable.TextInputLayout_android_textColorHint)) {
mDefaultTextColor = mFocusedTextColor =
a.getColorStateList(R.styleable.TextInputLayout_android_textColorHint);
}
You can try to use TextInputLayout.setHintTextAppearance() it works within binding.
WARNING : But be aware there are behaviour changes introduced within support lib version 28.
Looks like error text color and hint text color now are always the same in error state(if you will call TextInputLayout.setError())

Best practice to access property in kotlin

I comes from Java background and working first time on Kotlin. For most of the people it will be basic question, but it may help people who start working first time on Kotlin and comes from Java background
So, let say I have listadapter and I want to set list of item in that. I have two options now.
1) create a private property which stores list of items and then create a setter for it, which set the list and call notifydatasetChanged()
2) create a property with set property function and then access like instance.property
Which will be better option in Kotlin out of above two options.
A property in Kotlin is nothing else then a getter and setter for a value. If you don't want to provide a getter, you have to use a fun setData(data: List).
Otherwise it's also possible to handle everything with the property
var data = listOf()
set(data: List) {
field = data
notifydatasetChanged()
}
But eventually it's even better to use an implementation with DiffUtil.

MvvmCross Validation Binding visibility

I followed the instructions here to bind my view model validation to the input form.
Using MVVMCross to bind to error messages
The problem I have now is that there is a lot of extra spacing on the form due to the validation elements. How do I make these spacing problems go away? It's a bit difficult to use a Visibility converter due to the fact there is no property for each field. Same problem with Android and iOS. I suppose maybe some sort of custom visibility converter?
I think a quick fix might be to use a binding like Visible Errors['Email'] - however, you're reporting that's not working (so transferred that to https://github.com/MvvmCross/MvvmCross/issues/494 - thanks)
Since that doesn't work directly, then you you should be able to bind the Visible boolean property using something like (in Android):
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textColor="#ff0000"
android:text="My error text"
local:MvxBind="Visible ErrorExists(Errors['Email'],FallbackValue=null)"
/>
where ErrorExists is:
public class ErrorExistsValueConverter : MvxValueConverter
{
public override object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (value != null);
}
}
For iOS, if you are showing/hiding UIViews, then you would need to ensure your UI layout auto-updates - e.g. using constraints
As an alternative UI technique, you should also be able to use binding on the background color of an EditText - similar to the color binding in https://github.com/MvvmCross/MvvmCross-Tutorials/blob/master/ValueConversion/ValueConversion.UI.Droid/Resources/Layout/View_Colors.axml

Categories

Resources