two way binding with null check - android

In data binding adatper, i want to check if int value in my model is not zero. Because hint is never shown, if value is 0 default then 0 is shown as text. I want to show hint if value is zero.
Below works well without checking 0 int value
<android.support.design.widget.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/port"
android:inputType="number"
android:text="#={`` + item.port}"
/>
I tried this which does not work
<android.support.design.widget.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/port"
android:inputType="number"
android:text='#={item.port != 0 ? `` + item.port : ""}'
/>
item.port is intvalue
Any suggestions to make this work with only data binding?

I think you'll need a BindingAdapter/InverseBindingAdapter or a conversion method. The easiest is probably a conversion method:
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".MainActivity">
<data>
<import type="com.example.mount.teststuff.Conversion"/>
<variable name="port" type="int"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="#+id/input"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#={Conversion.intToString(port, port)}"
android:textSize="40sp"/>
<TextView
android:id="#+id/output"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{`` + port}"
android:textSize="40sp"/>
</LinearLayout>
</layout>
And in your Conversion class you'd have something like this:
public class Conversion {
#InverseMethod("stringToInt")
public static String intToString(int oldValue, int value) {
if (value == 0) {
return null;
}
return String.valueOf(value);
}
public static int stringToInt(int oldValue, String value) {
if (value == null || value.isEmpty()) {
return 0;
}
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
return oldValue;
}
}
}
I just updated the answer to include my tested layout and code. You can look for more detail on this blog post.

<import type="Integer"/>
<android.support.design.widget.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/port"
android:inputType="number"
android:text='#={item.port}'
/>
Dont keep port variable as int as two way binding required String type Setter for port value. Instead convert String value into Integer in XML itself.
Update : Instead of checking null for hint, please change port value from your viewmodel. if port is 0 then you can replace it with empty string "" and then call notifyPropertyChange(BR.port);

Related

Html.fromHtml in DataBinding - Android

I am using from dataBinding in my project,when I have bellow xml it good work :
<TextView
android:id="#+id/txtDateCreate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{String.format(#string/DateCreate,others.created)}" />
But when I change to bellow get me crash:
<TextView
android:id="#+id/txtDateCreate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{Html.fromHtml(String.format(#string/DateCreate,others.created))}" />
Here in my string.xml :
<resources>
<string name="DateCreate">open : <![CDATA[<b><font color=#FF0000>%s</b>]]></string>
</resources>
Think you need to import html first in the xml
<data>
<import type="android.text.Html"/>
</data>
<TextView
android:id="#+id/txtDateCreate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{Html.fromHtml(String.format(#string/DateCreate,others.created))}" />
I think that the view should not have any logic/transformation to display the data. What I'd recommend to do is creating a BindingAdapter for this:
#BindingAdapter({"android:htmlText"})
public static void setHtmlTextValue(TextView textView, String htmlText) {
if (htmlText == null)
return;
Spanned result;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
result = Html.fromHtml(htmlText, Html.FROM_HTML_MODE_LEGACY);
} else {
result = Html.fromHtml(htmlText);
}
textView.setText(result);
}
Then within the layout call it as usual:
<TextView
android:id="#+id/bid_footer"
style="#style/MyApp.TextView.Footer"
android:htmlText="#{viewModel.bidFooter} />
Where viewModel.bidFooter has the logic to get the String/Spanned/Chars with the text, taking into consideration not having any direct dependence to context, activity, etc
The line below is deprecated in Android N (API level 24).
Html.fromHtml(content)
Now you're supposed to provide two params (content and flag) like below:
Html.fromHtml(content, HtmlCompat.FROM_HTML_MODE_LEGACY)
So I'll suggest you use #BindingAdapter like below:
object BindingUtils {
#JvmStatic
#BindingAdapter("loadHtml")
fun loadHtml(textView: TextView, content: String?){
if (!content.isNullOrEmpty()) {
textView.text = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
Html.fromHtml(content, HtmlCompat.FROM_HTML_MODE_LEGACY)
} else {
Html.fromHtml(content)
}
}
}
}
and in your XML file:
<data>
<import type="com.example.utils.BindingUtils"/>
</data>
<TextView
android:id="#+id/textView"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:loadHtml="#{String.format(#string/DateCreate,others.created))}"/>

Wrong Lambda expression evaluation at the time of event happens

In one of my event handler on a button, I am using lambda expression but it evaluate wrong result at the time of event happens. My flow is to check whether a view (In my case it is TextView) is visible or not in the expression. If view is visible then I am printing true else false. But my code is always giving me true response. Blow is my code:
public class MainActivity extends AppCompatActivity implements MethodReferenceHandler, ____ListenerBindingEventHandler {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
User kkr = new User();
kkr.setAge("20");
kkr.setName("Vinit Saxena");
activityMainBinding.setUser(kkr);
activityMainBinding.setEventHandler(this);
activityMainBinding.setMethodreferencehandler(this);
TextView textView = (TextView) findViewById(R.id.name_tv);
textView.setVisibility(View.GONE);
activityMainBinding.setView(textView);
}
#Override
public void onClickViaMethodReferenceHandler(View v) {
Log.i(getClass().getName(), "---->onClickViaMethodReferenceHandler");
}
#Override
public void eventHandlerViaListenerBinding() {
Log.i(getClass().getName(), "---->eventHandlerViaListenerBinding");
}
#Override
public void eventHandlerViaListenerBinding(boolean isThisTrue) {
Log.i(getClass().getName(), "---->eventHandlerViaListenerBinding - isThisTrue : " + isThisTrue);
}
}
<data>
<import type="android.view.View" />
<variable
name="user"
type="com.mds.binding.User" />
<variable
name="methodreferencehandler"
type="com.mds.binding.MethodReferenceHandler" />
<variable
name="eventHandler"
type="com.mds.binding.____ListenerBindingEventHandler" />
<variable
name="view"
type="android.view.View" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context="com.mds.binding.MainActivity">
<TextView
android:id="#+id/name_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{user.name}"
android:visibility="visible" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{user.age}" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="#{methodreferencehandler::onClickViaMethodReferenceHandler}"
android:text="Method Reference" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="#{(view)-> view.getVisibility() == View.VISIBLE ? eventHandler.eventHandlerViaListenerBinding(true) : eventHandler.eventHandlerViaListenerBinding(false)}"
android:text="Listener Binding" />
</LinearLayout>
Please help where am I doing mistake?
Thanks in advance.
You're not using the binding to its full extend. One purpose is to never use findViewById() again. So the following
TextView textView = (TextView) findViewById(R.id.name_tv);
textView.setVisibility(View.GONE);
should be done as
activityMainBinding.nameTv.setVisibility(View.GONE);
since all views with an id are provieded as a member on the binding.
As a second advantage you can even access this variable within the binding itself. So you could just remove
<variable
name="view"
type="android.view.View" />
all completely and implement the onClick listener as
android:onClick="#{(view) -> eventHandler.eventHandlerViaListenerBinding(nameTv.getVisibility() == View.VISIBLE)}"
Additionally you don't really need the ?: conditional operator.
you can try:
this: android:onClick="#{()-> eventHandler.eventHandlerViaListenerBinding(view.getVisibili‌​ty() == View.VISIBLE)}"
Because your lambda expression correspond to:
private View view;
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {eventHandler.eventHandlerViaListenerBinding(view.getVisibility() == View.VISIBLE);
}
});
In this case, onClick method has 1 argument which is clicked button. And local "view" variable is used instead of global "view" variable. if you name this variable to the other variable name, it's ok.
So you can you your lambda to "#{()->" or "#{(v)->".

Android - Conditional text value using Data Binding

I want to set the text of my TextView conditionally to either one or the other.
Android Data Binding documentation suggests that you can set the text conditionally if the text is a property of the view model. e.g.
android:text="#{user.displayName != null ? user.displayName : user.lastName}"
But is there any way to set the text from the strings.xml rather than adding it in my view model? I want something like this-
android:text="#{viewModel.expanded ? #string/collapse : #string/expand}"
The XML looks somewhat like this:
<?xml version="1.0" encoding="UTF-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:bind="http://schemas.android.com/apk/res-auto">
<data class="TravellerInfoBinding">
<import type="android.view.View" />
<variable name="viewModel" type="com.myproject.viewmodel.TravellerInfoViewModel" />
</data>
<LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal">
<ImageView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:src="#drawable/expandable_arrow_blue" />
<TextView style="#style/primary_pair_element_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{viewModel.expanded ? #string/taxes_fees_detail : #string/hide_taxes_fees_detail}"
android:textSize="12sp" />
</LinearLayout>
</layout>
And this is my View Model-
package com.myproject.viewmodel;
imports...
public class TravellerInfoViewModel extends BaseObservable {
#Bindable
private final TaxDetailsViewModel taxDetailsViewModel;
#Bindable
private boolean expanded;
Constructor....
public boolean isExpanded() {
return expanded;
}
public void setExpanded(boolean expanded) {
this.expanded = expanded;
notifyPropertyChanged(BR.expanded);
}
public void toggleExpanded() {
setExpanded(!expanded);
}
}
Actually, this works fine for me
<TextView
android:id="#+id/btnEdit"
style="#style/Common.Toolbar.Action.Text"
android:onClickListener="#{onEditClick}"
android:text="#{vm.editMode ? #string/contacts_done : #string/contacts_edit}"
tools:text="#string/contacts_edit"/>
Where vm - it's a ViewModel and editMode - it's ObservableBoolean
Here's a fix/work-around :
define a duplicate Xml definition of the layout where you want a conditional value
for each block, set one of the condition values
set the visibility of each Xml definition according to the data binding boolean value
Not the ideal solution, not very pretty .. but functionally equivalent - and works in the interim until proper solution is found.
Here's how I solved it for android:textStyle, where I had a special case requirement for showing values in bold.
<variable
name="viewModel"
type="com.demo.app.SomeViewModel"/>
...
<TextView
style="#style/RowValue"
android:visibility="#{ ! viewModel.boldRow ? View.VISIBLE : View.GONE}"
android:text="#{viewModel.currentValue}"
/>
<TextView
style="#style/RowValue"
android:visibility="#{ viewModel.boldRow ? View.VISIBLE : View.GONE}"
android:text="#{viewModel.currentValue}"
android:textStyle="bold"
/>

How to make bold text with Android Data Binding Library

Pretty basic, I want to make a title of a message bold based on whether the text it is read or not. I can't seem to find a solution for this.
Here is my XML code:
<TextView
android:text="#{message.title}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginBottom="5dp"
android:layout_marginTop="5dp"
android:layout_toLeftOf="#+id/timestamp"
android:textSize="18sp"
android:textStyle='#{message.isRead() ? "bold" : "normal"}'
android:textColor='#{message.isRead() ? 0xff313131 : 0xff0662ab}' />
Th colorchange is working great, only the bold text is giving me some problems.
Error:Execution failed for task ':app:compileDebugJavaWithJavac'.
java.lang.RuntimeException: Found data binding errors.
****/ data binding error ****msg:Cannot find the setter for attribute 'android:textStyle' with parameter type java.lang.String on android.widget.TextView.
file:D:......xml
loc:39:41 - 39:79
****\ data binding error ****
An easy way
public class TextViewBindingAdapter {
#BindingAdapter("isBold")
public static void setBold(TextView view, boolean isBold) {
if (isBold) {
view.setTypeface(null, Typeface.BOLD);
} else {
view.setTypeface(null, Typeface.NORMAL);
}
}
}
XML:
<TextView
android:id="#+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:isBold="#{item.bold}"/>
I ended up using the following code, it implements DataBinding.
public abstract class BindingAdapter {
#android.databinding.BindingAdapter("android:typeface")
public static void setTypeface(TextView v, String style) {
switch (style) {
case "bold":
v.setTypeface(null, Typeface.BOLD);
break;
default:
v.setTypeface(null, Typeface.NORMAL);
break;
}
}
}
And the XML
<TextView
android:text="#{bericht.titel}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginBottom="5dp"
android:layout_marginTop="5dp"
android:layout_toLeftOf="#+id/timestamp"
android:textSize="18sp"
android:textColor='#{bericht.isGelezen() ? 0xff313131 : 0xff0662ab}'
android:typeface='#{bericht.isGelezen() ? "normal" : "bold"}' />
You can do this without creating an adapter.
Import Typeface to your XML
<data>
<import type="android.graphics.Typeface" />
...
</data>
Use the attribute android:typeface with Typeface.defaultFromStyle:
android:typeface="#{Typeface.defaultFromStyle(message.isRead() ? Typeface.BOLD : Typeface.NORMAL)}"
Saif Bechan is correct in his answer, I made a slight change to facilitate databinding from a view model.
public abstract class BindingAdapter {
#android.databinding.BindingAdapter("app:textStyle")
public static void setTextStyle(TextView v, int style) {
v.setTypeface(null, style);
}
}
Then you bring in the app namespace to your XML
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
...
</layout>
Create a binding in the view model
#Bindable
public int getValueFormat() {
String message = getMyObject().getValue();
if (message == MyObject.DEFAULT_VALUE)
return Typeface.ITALIC;
return Typeface.NORMAL;
}
Now you can bind this directly
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:textStyle="#{viewModel.valueFormat}"
android:text="#{viewModel.value}" />
you just has to import Typeface to the xml file and make your check like this
<variable
name="vm"
type="com.example.VM" />
<import type="android.graphics.Typeface" />
<TextView
android:id="#+id/username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="#{vm.isBold ? Typeface.BOLD : Typeface.NORMAL}"/>
So, I had the same problem this week, I was trying to use the view.setTypeface() method, but I wanted to keep the font style in my case. It turns out that the bold style worked when I passed null in the setTypeFace method, but...passing null can make changes in the font style (was not looking at the same). When I tried to use the same font style and set the view to bold it was not working.
To achieve what I wanted, since I was not succeeding in using this method I just change the font style when the bold is required, see my code:
#BindingAdapter("boldOrRegular")
#JvmStatic
fun TextView.boldOrRegular(isBold: Boolean) {
typeface = if (isBold) {
ResourcesCompat.getFont(context, R.font.myfont_bold)
} else {
ResourcesCompat.getFont(context, R.font.myfont_regular)
}
}
I hope that this can help someone :)
You need to create getMessageStyle into your message Object :
public String getMessageStyle() {
return isRead() ? "bold" : "normal";
}
then use as below into your code :
<TextView
android:text="#{message.title}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginBottom="5dp"
android:layout_marginTop="5dp"
android:layout_toLeftOf="#+id/timestamp"
android:textSize="18sp"
android:textStyle="#{message.getMessageStyle()}"
android:textColor="#{message.isRead() ? 0xff313131 : 0xff0662ab}" />

Visibility Converters in Xamarin Android

I am creating a android application using MvvmCross, in which I have to show and hide some controls in listview depending upon the value. For that I have created a visibility converter in PCL like this
public class VisibilityValueConverter : MvxValueConverter<bool, MvxVisibility>
{
protected override MvxVisibility Convert(bool value, Type targetType, object parameter, CultureInfo culture)
{
return (value == true) ? MvxVisibility.Visible : MvxVisibility.Collapsed;
}
}
and I am using this value converter in my layout page like this
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="20dp"
local:MvxBind="Text QuestionText"
android:layout_marginTop="15dp" />
<RadioGroup
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/radioGroup1"
android:layout_marginTop="5dp">
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/radioButton1"
local:MvxBind="Text OptionA" />
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
local:MvxBind="Text OptionB"
android:id="#+id/radioButton2" />
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
local:MvxBind="Text OptionC"
android:id="#+id/radioButton3" />
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
local:MvxBind="Text OptionD"
android:id="#+id/radioButton4" />
</RadioGroup>
<EditText
android:layout_width="fill_parent"
android:layout_height="159.0dp"
android:textSize="20dp"
android:layout_marginTop="2dp"
local:MvxBind="Visibility TexboxVisible,Converter=Visibility" />
</LinearLayout>
But it's not working. It's not hitting the breakpoints in PCL value converter.
I have also tried to use MvxVisibility plugin but it's also not working.
I think I am doing something wrong. Can someone help and let me know how to use visibilty converters inside listview in android.
ViewModel
public class Question
{
public string Type { get; set; }
public bool RadioVisible { get; set; }
public bool TexboxVisible { get; set; }
}
private List<Question> _questionList;
public List<Question> QuestionList
{
get { return _questionList; }
set
{
_questionList = value;
RaisePropertyChanged(() => QuestionList);
}
}
private async void ShowQuestionsList(int assignmentId)
{
QuestionList = await _service.GetQuestionListByAssignmentAsync(assignmentId);
if (QuestionList != null)
{
foreach (Question q in QuestionList)
{
if (q.Type != null)
{
if (q.Type == "S")
{
q.RadioVisible = false;
q.TexboxVisible = true;
}
else if (q.Type == "O")
{
q.RadioVisible = true;
q.TexboxVisible = false;
}
}
}
}
}
My breakpoint in my Testconverter is fired as it should. My code:
public class TestMethodValueConverter : MvxValueConverter<bool, MvxVisibility>
{
protected override MvxVisibility Convert(bool value, Type targetType, object parameter, CultureInfo culture)
{
return value ? MvxVisibility.Visible : MvxVisibility.Collapsed;
}
}
And the View-Xaml:
local:MvxBind="Visibility MyBoolProperty, Converter=TestMethod"
But there is another problem. The android view elements can't change the visibility with the MvxVisibility enum. They need a Android.Vioews.ViewStates value.
So you need to add the converter in the Android project. Thats why we use the MvxVisibility-Plugin.
Edit
Your viewmodels should all inherit from MvxViewModel and the properties, which are used for binding need to implement the property-changed call RaisePropertyChanged(() => Property);. Otherwise nobody knows about changes. Thats the first point.
But the Converter should work at the first time without that (as far as I know). So I don't see anything other which can go wrong.. so try to create a simple clean project only with that problem and one single View-Element to reproduce what can go wrong..

Categories

Resources