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)->".
Related
I have a TabLayout, and it have 3 tab items inside it (Accepted, Rejected, and Pending). Each of them have a recyclerlist for different status (Use 1 Adapter for it). In Tab Pending I have a button to delete the item, but the button doesnt respond to any click input. I already set the clickListener in the adapter and fragment but it still doesn't work.
I may miss something in the process, so I really need any help. Thank You.
This is the xml for list inside recycle view
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/tools">
<data>
<variable
name="overrideHistory"
type="example.com.absensiapp.model.OverrideHistoryRespModel" />
<variable
name="onClick"
type="example.com.absensiapp.view.listener.OverrideHistoryListener" />
</data>
<androidx.appcompat.widget.AppCompatImageButton
android:id="#+id/btn_delete"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="330dp"
android:layout_marginTop="10dp"
android:src="#drawable/ic_delete"
android:backgroundTint="#color/red"
android:tint="#color/white"
android:background="#drawable/circle_button"
card_view:layout_constraintEnd_toEndOf="parent"
card_view:layout_constraintTop_toTopOf="parent"
android:onClick="#{()->onClick.onClickDeleteButton(overrideHistory.id)}"/>
</androidx.cardview.widget.CardView>
This is how i set the listener interface in adapter
public void setOnClick(OverrideHistoryListener listener) {
this.overrideHistoryListener = listener;
}
This is how I set the listener in Fragment for the pending tab
public class HistoryPendingFragment extends Fragment implements OverrideHistoryListener {
overrideHistoryListBinding.setOnClick(this);
}
For the action onClick
public void onClickDeleteButton(String overrideId) {
overrideViewModel.deletePendingOverride(overrideId);
}
This is the interface for listener
public interface OverrideHistoryListener {
void onClickDeleteButton(String overrideId);
}
android:onClick="#{overrideHistory::onClickDeleteButton}"
public void onClickDeleteButton(view: View) {
overrideViewModel.deletePendingOverride(overrideId);
}
Found the answer, I forgot to initialize the Listener in the onBindViewHolder so it looks like this
public void onBindViewHolder(#NonNull OverrideHistoryAdapter.ViewHolder holder, int position) {
holder.overrideHistoryListBinding.setOnClick(this);
}
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);
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.
trying to understand data binding, not getting it though. Any help appreciated.
what i need is all together my custom method call "setOnClick(User user)" on click of the button. I just want understand Binding Adapters Concepts.
Execution failed for task ':databinding:compileDebugJavaWithJavac'.
android.databinding.tool.util.LoggedErrorException: Found data binding errors.
****/ data binding error ****msg:cannot find method setOnClick() in class com.locale.databinding.MyAdapter file:/Users/svernekar003/Documents/GitHub/RxDagger2Demo/databinding/src/main/res/layout/activity_main.xml loc:61:35 - 61:61 ****\ data binding error ****
sample code.
activity_main.xml
<?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="user"
type="com.locale.databinding.User"></variable>
<variable
name="myClickHandler"
type="com.locale.databinding.MyAdapter"></variable>
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.locale.svernekar.databinding.MainActivity">
<TextView
android:id="#+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:text="#{user.name}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/last_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"
android:text="#{user.lastName}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="#+id/name"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.502" />
<ViewStub
android:id="#+id/view_stub"
android:layout_width="20dp"
android:layout_height="20dp"
android:inflatedId="#+id/inflate_id"
android:layout="#layout/view_stub_sample" />
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"
android:onClick="#{()->myClickHandler.setOnClick(user)}"
android:text="Change Data"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="#+id/last_name" />
</android.support.constraint.ConstraintLayout>
MyAdapter.java
public class MyAdapter {
private User user;
#BindingAdapter("android:onClick")
public static void setOnClick(User user) {
Log.d("Onclick", "after do long time");
}
#BindingAdapter("android:src")
public static void setImageResource(ImageView imageView, int resource) {
imageView.setImageResource(resource);
}
public int getId() {
return android.R.drawable.ic_dialog_dialer;
}
}
Use this
android:onClick="#{user}"
Because you have created BindingAdapter
#BindingAdapter("android:onClick")
public static void setOnClick(User user) {
...
}
Reason
You have created #BindingAdapter of attribute android:onClick so it will work like above.
You're missing an understanding of callbacks here. I suggest doing Google's databinding code lab here:
https://codelabs.developers.google.com/codelabs/android-databinding/index.html
However, we can walk through the solution together.
First, to create a BindingAdapter, you need to specify a view type in the method. So, it should look something like this:
#BindingAdapter("android:onClick")
fun setOnClick(view: View, user: User) {
Log.d("Onclick", "after do long time")
}
Now, the thing about BindingAdapters is, they are set either when the page loads or when their bound value changes. So, if User is a LiveData, this will work, BUT it won't call when the user clicks- it'll call when the user value is updated. BindingAdapters call when their bound value updates, no matter what the name is. So, the above code is the same as this block:
#BindingAdapter("app:userUpdated")
fun setUserUpdated(view: View, user: User) {
Log.d("UserUpdated", "after user value is updated")
}
Your onClick binding isn't setting a click listener in this case- it's setting a binding to the "user" field. So, there will be two bindingadapters for onClick- one if you bind a User, and one (system default) if you bind a ClickListener. The XML you have binds to the clicklistener, not the user, so your adapter will never be executed. But even if it was bound correctly, it wouldn't bind on click, because it's not bound that way. To get it to bind on click for the user, you'd need to do something like this:
#BindingAdapter("android:onClick")
fun setOnClick(view: View, user: User) {
view.setOnClickListener(View.OnClickListener { Log.d("Onclick", "after do long time") })
}
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"
/>