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"
/>
Related
I have added a new binding rule like this for ImageView which takes a custom object:
#BindingAdapter({"custDrawable"})
public static void setCustDrawable(#NonNull ImageView view, HexDrawableModel model) {
view.setImageDrawable(new HexDrawable(model));
}
where HexDrawable extend Drawable, and
data class HexDrawable(val text: String, val color: Color)
So... I am not sure how to use this binding adapter in my layout file because it is expecting a class not string. Please let me know to use this binding adapter (if it is even possible).
In layout XML:
<ImageView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
custDrawable=??? />
I think you should declare them like this
#BindingAdapter("custDrawable")
public static void setCustDrawable(#NonNull ImageView view, HexDrawableModel model) {
view.setImageDrawable(new HexDrawable(model));
}
And use them like this
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
...
</data>
<ImageView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
app:custDrawable="#{yourVariableHere}/>
</layout>
use app:custDrawable={$variable}
define the variable in data section of the layout and bind it in respective java file
I need to enable my Button after check some condition, i want to call one method using #InverseBinding OR Two Way data binding and reflect the changes with return value.
my Code :
<Button
android:id="#+id/save_btn_disabled_3"
android:enabled="#={controller}"
....
/>
my Two Way data binding logic here :
#InverseBindingAdapter(attribute = "enable")
fun getEnableButton(view:View, controller:Controller): Boolean {
//some conditions
return false
}
i want to know am i going in correct direction ?, code is ok ?
please suggest me.
First Add Button Status Object
<variable
name="button"
type="com.brl.test.app.vm.ButtonStatus" />
<Button
android:id="#+id/save_btn_disabled_3"
bind:state_change="#{button.state}"
....
/>
Set your logic in buttonEnabled funtion then Enable Your Button
#BindingAdapter({"bind:state_change"})
public static void buttonEnabled(TextView view, State state) {
view.setEnabled(true);
}
You can set one variable in field like:
<variable
name="wantsToVisibleProgress"
type="boolean" />
and set it in your button like this:
<Button
android:id="#+id/constraint_select_main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/bg_button_blue_big"
android:enabled="#={wantsToVisibleProgress}"
android:gravity="center"
android:onClick="#{v -> fragment.onClick()}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/txt_evse_one">
You can set that value like this:
binding.setWantsToVisibleProgress(true);
I am following Android Archt. component to build a project. Following the guidelines I have created a custom Adapter named CataloguesAdapter extending DataBoundListAdapter as :
public class CataloguesAdapter extends DataBoundListAdapter<CatalogueEntity, CatalogueItemBinding> {
private final android.databinding.DataBindingComponent dataBindingComponent;
private final ContributorClickCallback callback;
private CatalogueItemBinding mBinding;
public CataloguesAdapter(DataBindingComponent dataBindingComponent,
ContributorClickCallback callback) {
this.dataBindingComponent = dataBindingComponent;
this.callback = callback;
}
#Override
protected CatalogueItemBinding createBinding(ViewGroup parent) {
mBinding = DataBindingUtil
.inflate(LayoutInflater.from(parent.getContext()),
R.layout.catalogue_item, parent, false,
dataBindingComponent);
//while this click event is working fine
mBinding.getRoot().setOnClickListener(v -> {
CatalogueEntity catalogueEntity = mBinding.getCatalogue();
if (catalogueEntity != null && callback != null) {
callback.onClick(catalogueEntity);
}
});
//todo:not working, this event is not firing
mBinding.deleteIcon.setOnClickListener(v-> callback.onItemDelete());
return mBinding;
}
}
I am implementing swipe to delete layout on Recycler view item. Below is the XML layout of list item:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="catalogue"
type="com.mindtree.igxbridge.traderapp.datasource.local.entity.CatalogueEntity" />
</data>
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardUseCompatPadding="true">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RelativeLayout
android:id="#+id/view_background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/colorRed">
<ImageView
android:id="#+id/delete_icon"
android:layout_width="#dimen/dimen_30_dp"
android:layout_height="#dimen/dimen_30_dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="#dimen/dimen_10_dp"
app:srcCompat="#drawable/ic_delete"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginEnd="#dimen/dimen_10_dp"
android:layout_toStartOf="#id/delete_icon"
android:text="#string/text_delete"
android:textColor="#color/Material.87.white"
android:textSize="14sp" />
</RelativeLayout>
<RelativeLayout
android:id="#+id/view_foreground"
android:layout_width="match_parent"
android:background="#FFFFFF"
android:layout_height="wrap_content">
<android.support.v7.widget.AppCompatImageView
android:id="#+id/arrow_icon"
android:layout_width="#dimen/dimen_30_dp"
android:layout_height="#dimen/dimen_30_dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="#dimen/dimen_10_dp"
app:srcCompat="#drawable/ic_arrow_right" />
</RelativeLayout>
</FrameLayout>
</android.support.v7.widget.CardView>
</layout>
Another operation like swipe left/right is working fine but clicking on Delete button event is not getting called.
I tried checking findViewbyId and register click event by that, but no luck with that too.
While CatalogueItemBinding is registered correctly, I am not able to find any other source of error.
Thanks.
Correct me if I have misunderstood your code. You used a FrameLayout to host two relative layouts one top of each other (foreground and background). The delete button is in the background and foreground has match_parent in its width attribute. Therefore, I think the delete button is getting covered by the foreground, leading to "not firing of the event".
Possible Solution
Try incorporating the delete button in the foreground. It makes sense to put UI components in the front.
I think you forget to tell your adapter class to where your XML is set or not to adapter class. just create a variable in XML which will import your adapter class have look.
<variable
name="myAdapter"
type="import your adapter class">
</variable>
Now set this variable to your adapter.
#Override
protected CatalogueItemBinding createBinding(ViewGroup parent) {
mBinding = DataBindingUtil
.inflate(LayoutInflater.from(parent.getContext()),
R.layout.catalogue_item, parent, false,
dataBindingComponent);
mBinding .setmyAdapter(this);
return mBinding;
}
}
then your click will work. Hope it will help you.
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.getVisibility() == 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)->".
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}" />