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
Related
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" />
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)
Using two-way Android Databinding, is it possible to have a generic inverse boolean converter? For example, I would like to do something like this:
<Switch android:checked="#={!viewModel.myBoolean}" />
When I run this in Android, the switch just rapidly fires back and forth. I tried to create a two way binding app:inverseChecked following some examples from George Mount, but I was not successful (just kept getting error stating cannot find event 'inverseCheckedAttrChanged' on View type 'android.widget.Switch').
As a comparison, using Aurelia this just works as you would expect for two way binding. In WPF, probably the first converter you make is some sort of InverseBooleanConverter to easily tackle these sorts of things. So, am assuming I am just missing something obvious here.
I actually didn't expect it to work at all. I assume, it's switching back and forth all the time, because the bindings don't apply inverse function of your binding expression.
That said, I tested the behavior with the current data binding library version and checked the generated sources. With the simple example of android:checked these show notes how the inverse should look like and apply it appropriately.
Also George Mount wrote a Blog post about it a short while ago: https://medium.com/google-developers/android-data-binding-inverse-functions-95aab4b11873
If you try to implement an app:inverseChecked, you'd also have to implement a #BindingAdapter("inverseChecked") as setter, #InverseBindingAdapter(attribute="inverseChecked") as getter and #BindingAdapter("inverseCheckedAttrChanged") for setting up the change listener.
The latter could look like the following:
#BindingAdapter("inverseCheckedAttrChanged")
public static void setupInverseCheckedAttrChanged(Switch view, InverseBindingListener listener) {
OnCheckedChangeListener newListener = null;
if (listener != null) {
newListener = (v,b) -> listener.onChange();
}
view.setOnCheckedChangeListener(newListener);
}
I should be able to use a #BindingAdapter in Android dataBinding so that i can override a certain attribute. I am able to do it with a cusutom attribute but with a android built-in attribute how is it accomplished?
what i have so far:
in my viewModel i have a method that is annotated with #BindingAdapter and looks like this:
#BindingAdapter({"android:text"})
public static void setText(Button view,boolean language) {//i need to pass one more variable in here for area code , its just a integer, but how ?
if("french".equals(language))//i want to test if french && area code
view.setText("si vous play");
else if ("English".equals(language)) //i want to test if french && area code
view.setText("please");
}
but i have a few problems. Lets see the xml :
<Button
android:id="#+id/mybutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{`french,844`}"/>
See my issue, i want to pass in more then one parameter to dataBinding. But how ? Do i have to make a POJO object and pass it in ? even if i did that how do i set the object from xml ?
So if someone can just tell me the following i'll be fine:
1. how to pass multiple values to a bindingAdapter and
2. How to override a built-in android attribute in any view.
If you want to set two different values in your BindingAdapter, you should use two different attributes:
<Button
android:id="#+id/mybutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{`french`}"
app:areaCode="#{`844`}"/>
Then have two different attributes in your BindingAdapter:
#BindingAdapter({"android:text", "areaCode"})
public static void setText(Button view, String language, String areaCode) {
...
}
But it would probably be better to set a different "app:language" as that would be more clear to the developer.
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.