I have RecyclerView ,where each item represents, CheckBox and EditText
when clicks on CheckBox the text of EditText should strike through,
I have ObservableBoolean which is article.complete
I used it in app:checkBoxChangeListener="#{article.complete}"
app:itemComplete="#{article.complete}"
it works unless I scroll RecyclerView, then clicking on CheckBox another item’s text is strike through
#BindingAdapter("itemComplete")
public static void bindItemComplete(EditText itemInput, boolean complete){
itemInput.setPaintFlags(complete ?
(itemInput.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG) : 0);
}
Article.java
public class Article{
public final ObservableBoolean complete = new ObservableBoolean();
}
xml file :
<?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">
<data>
<import type="android.view.View" />
<variable
name="viewModel"
type="se.ica.handla.articles.ArticleListViewModel" />
<variable
name="article"
type="se.ica.handla.models.articles.Article" />
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
<EditText
android:id="#+id/editText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
app:itemComplete="#{article.complete}"
/>
<CheckBox
android:id="#+id/checkBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="#={article.complete}" />
</android.support.constraint.ConstraintLayout>
</layout>
As written in my comments, I recommend you to use Two-way-Databinding.
You can completely delete this BindingAdapter:
#BindingAdapter(value = {"checkBoxChangeListener", "article"}, requireAll = false)
public static void bindCheckBox(CheckBox view, final ObservableBoolean checked, Article article) {
if (view.getTag(R.id.binded) == null) {
//Here you are setting the attributes to your *view* and
//decouple it from your article. It does not reference it,
//the properties (isChecked) isnow on the view.
//So when your view gets recycled when you scroll,
//it still has the property you set the last time -
//and not from your current article, which is displayed now in the view.
view.setTag(R.id.binded, true);
view.setOnCheckedChangeListener((buttonView, isChecked) -> checked.set(isChecked));}
}
}
As you already found out, your xml should look like this now, using Two-Way Databinding:
<EditText
android:id="#+id/editText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
app:itemComplete="#{article.complete}"
/>
<CheckBox
android:id="#+id/checkBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="#={article.complete}" //#={} for Two-Way Databinding
/>
Related
I am learning MVVM with data binding in Android and got stuck on maintaining UI visibility state of items in the recycler view. Each item sell has 2 text view - title(visible) & description(hidden). Onclick of title, I want to show/hide description. To maintain its UI state, I have a boolean field in the POJO (shared with DB as an entity).
OnClick of title, I am trying to set this field. How can I make the change trigger changing the visibility and maintain it during the scroll?
Here is the Github link for the code and below, the code snippet that I want to work.
Following MVVM architecture with repository to hit network and update DB and observe data via LiveData.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="2dp">
<TextView
android:id="#+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/title"
android:layout_margin="8dp"
android:text="#{posts.desc}"
android:textSize="14sp"
android:visibility="#{(posts.isDescVisible()) ? View.VISIBLE : View.GONE}"
tools:text="This is an answer provided by the creator to test the UI layout" />
<TextView
android:id="#+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_margin="2dp"
android:ellipsize="end"
android:maxLines="1"
android:onClick="#{()-> posts.setDescVisible(!posts.isDescVisible())}"
android:text="#{posts.title}"
android:textSize="16sp"
android:textStyle="bold"
tools:text="This is a question" />
</RelativeLayout>
<data>
<import type="android.view.View" />
<variable
name="posts"
type="com.tyagiabhinav.loremipsum.model.db.Posts" />
</data>
</layout>
This works well for me
import androidx.databinding.BaseObservable;
public class Posts extends BaseObservable {
private int id;
private final String title;
public final String desc;
private boolean descVisible;
public Posts(String title, String desc) {
this.title = title;
this.desc = desc;
}
public void setId(int id) {
this.id = id;
}
public int getId() {
return id;
}
public String getTitle() {
return title;
}
public String getDesc() {
return desc;
}
public void setDescVisible(boolean descVisible) {
this.descVisible = descVisible;
notifyPropertyChanged(BR.descVisible);
}
#Bindable
public boolean isDescVisible() {
return descVisible;
}
}
And the xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="2dp">
<TextView
android:id="#+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/title"
android:layout_margin="8dp"
android:text="#{posts.desc}"
android:textSize="14sp"
***android:visibility="#{posts.descVisible ? View.VISIBLE : View.GONE}"***
tools:text="This is an answer provided by the creator to test the UI layout" />
<TextView
android:id="#+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_margin="2dp"
android:ellipsize="end"
android:maxLines="1"
android:onClick="#{()-> posts.setDescVisible(!posts.descVisible)}"
android:text="#{posts.title}"
android:textSize="16sp"
android:textStyle="bold"
tools:text="This is a question" />
</RelativeLayout>
<data>
<import type="android.view.View" />
<variable
name="posts"
type="com.tyagiabhinav.loremipsum.model.db.Posts" />
</data>
</layout>
I want to show error on the Edittext, if input is not correct. I am doing this on the click of the button inside my activity class. Right now I am not getting anything, Please show me what is the correct way to achieve 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">
<variable
name="activity" type="com.example.SigninActivity" />
</data>
<RelativeLayout
<EditText
android:id="#+id/ed_login"
android:layout_width="match_parent
android:layout_height="match_parent"
android:digits="0123456789"
app:errorText='#{activity.errorMsg != null ? activity.errorMsg : ""}'/>
Binding Adapter
#BindingAdapter("errorText")
fun setError(editText: EditText, str: String?) {
if(!str.isNullOrEmpty()) {
editText.
setError((HtmlCompat.fromHtml(
"<font color='red'>" + str + "</font>",
HtmlCompat.FROM_HTML_MODE_LEGACY)))
}
}
Activity class
var errorMsg: MutableLiveData<String> = MutableLiveData()
override fun onClick(view: View) {
val mobileNo = dataBinding.etLoginMobnum.text.toString()
if (!TextUtils.isEmpty(mobileNo) && mobileNo.length != 11) {
errorMsg.value = "Enter Valid Number"
}
to show an error on your EditText you may use TextInputEditText within TextInputLayout, like the following
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/textInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:errorEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/textInputEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Your number" />
</com.google.android.material.textfield.TextInputLayout>
with app:errorEnabled="true" in the TextInputLayout you can achieve what you want
Using it in code:
show error to the user
myBinding.textInputLayout.setError("Enter Valid Number")
to remove it
myBinding.textInputLayout.setError(null)
to use it with databinding you can do the following
<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="myData" type="com.example.CustomDataObject"/>
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/textInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:errorEnabled="true"
android:errorText="#{myData.errorMsg}">
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/textInputEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Your number" />
</com.google.android.material.textfield.TextInputLayout>
</RelativeLayout>
</layout>
Binding Adapter
#BindingAdapter({"android:errorText"})
fun setError(tInputLayout: TextInputLayout, str: String) {
if (!str.isNullOrEmpty()) {
tInputLayout.setError("Enter Valid Number")
} else {
tInputLayout.setError(null)
}
}
In Activity
myBinding.setMyData(myDataObject)
note: in <data> scope in the XML you should declare your data objects with <variable> tag, not your activities.
make sure you are using material library in your gradle
implementation 'com.google.android.material:material:1.1.0'
Take a look here for more information about DataBinding
I have met a strange problem, It didn't work when I set layout used for adapter item corners by shape resource file.
This is my shape resource file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<corners android:radius="8dp" />
</shape>
This is my adapter item layout
<?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>
<import type="android.view.View.OnClickListener" />
<import type="android.view.View" />
<import type="com.zhixin.wedeep.homepage.data.model.BriefComposition" />
<variable
name="clickListener"
type="OnClickListener" />
<variable
name="composition"
type="BriefComposition" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="108dp"
android:layout_height="150dp"
android:background="#drawable/homepage_shape_recommend_composition_background"
android:gravity="center"
android:onClick="#{clickListener}"
android:orientation="vertical">
<TextView
android:id="#+id/text_view_label_is_new"
android:layout_width="36dp"
android:layout_height="22dp"
android:text="#string/homepage_new"
android:textColor="#color/color_white"
android:textSize="#dimen/font_size_10"
android:visibility='#{composition.tag == "NEW" ? View.VISIBLE : View.INVISIBLE}'
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="新上" />
<ImageView
android:id="#+id/image_view_cover"
imageFromUrl="#{composition.cover}"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:src="#drawable/gt3_new_bind_logo" />
<LinearLayout
android:id="#+id/linear_layout_text_area"
android:layout_width="match_parent"
android:layout_height="42dp"
android:orientation="vertical"
android:paddingStart="5dp"
app:layout_constraintBottom_toBottomOf="parent"
tools:ignore="RtlSymmetry">
<TextView
android:id="#+id/text_view_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:text="#{composition.title}"
android:textColor="#color/color_white"
android:textSize="#dimen/font_size_13"
tools:text="助眠脑波" />
<TextView
android:id="#+id/text_view_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="1dp"
android:text="#{composition.duration}"
android:textColor="#color/color_white"
android:textSize="#dimen/font_size_10"
tools:text="5-30min" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
This is my RecylerView for display the above item layout
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler_view_recommendation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginStart="15dp"
android:layout_marginEnd="15dp"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="3"
tools:itemCount="6"
tools:listitem="#layout/homepage_item_recommend_composition" />
This is my RecylerView Adapter
class RecommendCompositionsAdapter : ListAdapter<BriefComposition, RecommendCompositionsAdapter.ViewHolder>(BriefCompositionDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(HomepageItemRecommendCompositionBinding.inflate(
LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val briefComposition = getItem(position)
briefComposition?.let {
holder.apply {
itemView.tag = it
bind(createOnClickListener(it.id), it)
}
}
}
private fun createOnClickListener(id: String): View.OnClickListener {
return View.OnClickListener {
ARouter.getInstance().build(RouterConstant.PATH_AUDIO_PLAYER).withString(AudioPlayerActivity.KEY_ID, id).navigation()
}
}
class ViewHolder(private val binding: HomepageItemRecommendCompositionBinding)
: RecyclerView.ViewHolder(binding.root) {
fun bind(listener: View.OnClickListener, item: BriefComposition
) {
binding.apply {
clickListener = listener
composition = item
// from View
executePendingBindings()
}
}
}
}
Any ideas for this odd problem? Thank you in advance!
It looks like even if you are setting the background of your ConstraintLayout, it is overlapped by the ImageView and hence you are not getting the shape that your ConstraintLayout has. I would recommend using a CardView instead of the ConstraintLayout. Set the drawable as the background of your CardView and put everything inside of your CardView as you have done it for the ConstraintLayout.
I hope that helps!
ObservableField<Marker> value inside ViewModel class value is changed using EditText in layout however value is not propagated to TextView tv_summary.
This is the layout
<?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">
<data>
<import type="com.example.tutorial5livedata_mvvm_room_recyclerview.util.BindingUtils"/>
<variable
name="viewModel"
type="com.example.tutorial5livedata_mvvm_room_recyclerview.viewmodel.AddMarkerViewModel"/>
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Title"
android:textColor="#FC7100"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="#+id/guideline_left"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="#+id/et_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginTop="8dp"
android:ems="10"
android:hint="Title"
android:inputType="textPersonName"
android:text="#={viewModel.markerObservableField.title}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="#+id/guideline_left"
app:layout_constraintTop_toBottomOf="#+id/tv_title" />
<TextView
android:id="#+id/tv_latitude"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Latitude"
android:textColor="#FC7100"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="#+id/guideline_left"
app:layout_constraintTop_toBottomOf="#+id/et_title" />
<EditText
android:id="#+id/et_latitude"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginTop="8dp"
android:ems="10"
android:hint="Latitude"
android:text="#={viewModel.markerObservableField.latitude}"
android:inputType="number"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="#+id/guideline_left"
app:layout_constraintTop_toBottomOf="#+id/tv_latitude" />
<TextView
android:id="#+id/tv_summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text='#{viewModel.markerObservableField.title + " " + viewModel.markerObservableField.latitude}'
app:layout_constraintStart_toStartOf="#+id/guideline_left"
app:layout_constraintTop_toBottomOf="#+id/et_latitude" />
<android.support.constraint.Guideline
android:id="#+id/guideline_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="8dp" />
</android.support.constraint.ConstraintLayout>
</layout>
ViewModel class
public class AddMarkerViewModel extends AndroidViewModel {
private MarkerRepository mMarkerRepository;
public ObservableField<Marker> markerObservableField = new ObservableField<>();
public AddMarkerViewModel(#NonNull Application application) {
super(application);
AppDatabase appDatabase = AppDatabase.getInstance(application.getApplicationContext());
mMarkerRepository = MarkerRepository
.getsInstance(MarkerLocalDataSource.getInstance(appDatabase.markerDao(), new AppExecutors()));
if (markerObservableField.get() == null) {
Marker marker = new Marker();
marker.setTitle("New Title");
markerObservableField.set(marker);
}
}
public long addMarker(Marker marker) {
return mMarkerRepository.addMarker(marker);
}
}
onCreate method of Activity for adding marker to set values
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_add_marker);
mAddMarkerViewModel = ViewModelProviders.of(this).get(AddMarkerViewModel.class);
mBinding.setViewModel(mAddMarkerViewModel);
}
In order to listen to property changes, you will need to extend BaseObservable.
I think the problem here is that property change does not fire event, because you listen to Field change, that is marker object itself, that stays same.
Marker field Latitude is not observable, that means it's impossible to detect it's change.
You have two options.
If you want to detect changes, you can create observable field for Latitude.
public ObservableField<String> latitudeObservableField = new ObservableField<>();
You can listen to field Changes and update marker object.
latitudeObservableField.addOnPropertyChangedCallback(() -> {
// Update marker object
})
Another approach would be to make Marker extend BaseObservable, like explained in attached reference.
Please check out official documentation on observable objects.
I have a model CricketModel
public class CricketModel extends BaseObservable{
private String score;
#Bindable
public String getScore(){
return score
}
public void setScore(String s){
score=s;
notifyPropertyChanged(BR.score);
}
}
When I call API I get JSONArray, I convert them to CricketModel and add it to a CricketModel list. And pass this list to adapter to display them in the Recycler view.
This is my item_cricket.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="cm"
type="com.panasonic.arbohub.cricket.model.CricketModel" />
</data>
<LinearLayout xmlns:app="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.v7.widget.CardView
android:id="#+id/cv_main"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="#dimen/dp_8"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"
card_view:cardCornerRadius="5dp"
card_view:cardElevation="3dp">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/tv_two_s_score"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/tv_one_p_score"
android:layout_marginEnd="#dimen/dp_16"
android:layout_marginTop="#dimen/dp_8"
android:layout_toStartOf="#+id/rl_team_two"
android:textSize="#dimen/sp_12"
android:textStyle="bold"
android:text="#{cm.score}"
android:visibility="visible" />
</FrameLayout>
</android.support.v7.widget.CardView>
</LinearLayout>
</layout>
This is my ViewHolder
private class CricketViewHolder extends RecyclerView.ViewHolder {
ItemCricketBinding itemCricketBinding;
public CricketViewHolder(ItemCricketBinding binding) {
super(binding.getRoot());
itemCricketBinding = binding;
}
public void bind(CricketModel cm){
itemCricketBinding.setCm(cm);
itemCricketBinding.executePendingBindings();
}
}
In my CricketAdapter.java
holder.bind(cricketModelList.get(position));
API updates once in 1 mi, I'm replacing the cricketModelList with new data that is coming from the API but data is not reflecting in the UI.
Have followed this- https://medium.com/google-developers/android-data-binding-recyclerview-db7c40d9f0e4
Update:
After api has been called. I added notifyDataSetChanged() onClickOfthe item view, then new data is reflecting. So model has the new data but its not notifying the UI.
you should set "itemCricketBinding.lifecycleOwner" in bind method, not the best practice and I'm not sure about the performance but if you wonder why that not work, That was the reason.