Suppose I have this object:
public class Field
{
List<String> list;
public Field()
{
list = new ArrayList();
}
public boolean isOnTheList(String someText)
{
return list.contains(someText);
}
}
Now I want to use this function on a xml with binding like this.
<?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="field"
type="com.greendao.db.Field"/>
<variable
name="user"
type="com.package.User"/>
</data>
<LinearLayout
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="#{field.isOnTheList(user.name)?View.VISIBLE:View.GONE}"
tools:context=".advisor.new_visit_report.activities.FieldsSelectionActivity"> ...
The problem is that is not working. Anyone has already tried this?
If everything else is set correctly, I would suggest to edit your method to return View.VISIBLE or View.GONE directly:
public int isOnTheList(String someText) {
return list.contains(someText) ? View.VISIBLE : View.GONE;
}
Used in xml:
android:visibility="#{field.isOnTheList(user.name)}
Related
I've been using ObservableField() in my ViewModel. But now I have HTML formatted text that I want to display. Can I use an ObservableField of type Spannable for this? In the old situation I use the method "setEditText" and this works.
new situation:
var spannableText = ObservableField<Spannable>()
fun setEditTextUsingObservable() {
var complete = "<ul><li><a href='https://www.someurl.nl/hello/'>Click here!</a></li></ul>"
this.spannableText.set(complete.toSpannable())
}
old situation:
fun setEditText(textView: TextView, text: String) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
textView.text = Html.fromHtml(text, Html.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE).toSpannable()
} else {
#Suppress("DEPRECATION")
textView.text = Html.fromHtml(text).toSpannable()
}
textView.movementMethod = LinkMovementMethod.getInstance()
}
The related 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">
<data>
<variable
name="viewModel"
type="MyViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="#+id/spannableText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#{viewModel.spannableText}"/>
</LinearLayout>
</layout>
The correct solution is to use a SpannableString instead of a Spannable.
var spannableText = ObservableField<SpannableString>()
val text: SpannableString?
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
text = SpannableString(Html.fromHtml(complete, Html.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE))
} else {
#Suppress("DEPRECATION")
text = SpannableString(Html.fromHtml(complete))
}
spannableText.set(text)
Solution is in using bindadapter
ViewModel
val spannable: ObservableField<Spanned> =ObservableField(Html
.fromHtml("<ul><li><a href='https://www.someurl.nl/hello/'>Click here!</a></li></ul>"))
fun changeText(text: String) {
spannable.set(Html.fromHtml(text))
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="MyViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="#+id/spannableText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#{viewModel.spannable}"/>
</LinearLayout>
</layout>
BindAdapter
#BindingAdapter("android:text")
public static void Spanned(TextView textView, Spanned spannable) {
textView.setText(spannable);
}
now you can use changeText function to change it.
I have a layout for the toolbar spinner:
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="selectedItem"
type="int" />
</data>
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="#style/Theme.Paper.Toolbar.Popup"
app:theme="#style/Theme.Paper.Toolbar">
<Spinner
android:id="#+id/toolbar_spinner"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:theme="#style/Theme.Paper.Toolbar.Spinner"
app:popupTheme="#style/Theme.Paper.Toolbar.Spinner.Popup"
app:selectedItem="#{selectedItem}"/>
</androidx.appcompat.widget.Toolbar>
</layout>
I include that layout in fragment layouts like below:
<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="model"
type="com.contedevel.timetable.models.WeekViewModel" />
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.contedevel.timetable.activities.MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/appBar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include
android:id="#+id/toolbar_layout"
layout="#layout/toolbar_spinner"
app:selectedItem="#{model.selectedWeekPosition}"/>
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>
Here is my ViewModel:
class WeekViewModel(application: Application) : AndroidViewModel(application) {
val selectedWeekPosition = ObservableInt(0)
}
But selectedItem changes once on the start of activity only. When I try to change it in onOptionsItemSelected a nothing happens:
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
if (R.id.action_now == item?.itemId) {
week = WeekUtils.getCurrentWeek(timetable.entity)
viewModel.selectedWeekPosition.set(week - 1)
return true
}
return super.onOptionsItemSelected(item)
}
I suppose the value doesn't change due to the first layout (layout data binder) unwraps it to Int and the second layout binder gets Int instead of ObservableInt. But I still don't know how to fix it not to pass ObservableInt in the toolbar spinner binder directly.
Does anyone know how to fix it? Spinner binding adapter are below:
object SpinnerBindings {
#BindingAdapter("selectedItem")
#JvmStatic fun Spinner.setSelectedItem(position: Int) {
setSelection(position)
}
}
Pass your viewmodel to toolbar spinner layout.
Step 1: Add bind attribute to layout tag that contains include(fragment layouts) :
xmlns:bind="http://schemas.android.com/apk/res-auto"
Step 2: Change your data item in toolbar spinner layout:
<data>
<variable
name="vmodel"
type="com.contedevel.timetable.models.WeekViewModel" />
</data>
Step 3: Change your include tag like below:
<include
android:id="#+id/toolbar_layout"
layout="#layout/toolbar_spinner"
bind:vmodel="#{model}"/>
Step 4 : You have access to your viewModel in toolbar spinner layout, So:
<Spinner
android:id="#+id/toolbar_spinner"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:theme="#style/Theme.Paper.Toolbar.Spinner"
app:popupTheme="#style/Theme.Paper.Toolbar.Spinner.Popup"
app:selectedItem="#{vmodel.selectedWeekPosition}"/>
I'm trying to set a variable (buttonText) of customView from main_activity.xml but I got this error:
attribute buttonText (aka com.example.testbinding:buttonText) not
found. error: failed linking file resources.
customView.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="buttonText"
type="String" />
</data>
<LinearLayout
android:id="#+id/container"
android:orientation="vertical">
<Button
android:id="#+id/button"
android:text="#{buttonText}" />
</LinearLayout>
</layout>
main_activity.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>
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.example.testbinding.CustomView
android:id="#+id/customView"
...
app:buttonText = "Test button text"
...
>
</com.example.testbinding.CustomView>
</android.support.constraint.ConstraintLayout>
</layout>
In this post I found:
In your Custom View, inflate layout however you normally would and
provide a setter for the attribute you want to set:
My CustomView class have a public setter for buttonText property:
public class CustomView extends LinearLayout {
private CustomViewBinding binding;
private String buttonText;
public CustomView(Context context) {
super(context);
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
binding = DataBindingUtil.inflate(inflater, R.layout.custom_view, this, true);
}
public String getButtonText() {
return buttonText;
}
public void setButtonText(String buttonText) {
this.buttonText = buttonText;
}
}
Can anyone tell me what's wrong with it?
Or it impossible without BindingAdapter?
binding.setbuttonText("text_button");
instead of
public String getButtonText() {
return buttonText;
}
public void setButtonText(String buttonText) {
this.buttonText = buttonText;
}
Seems I found what was wrong.
If I use a variable declared in parent view - everything is ok. But if I use a value right in property it's don't work.
wrong:
app:buttonText = "Test button text"
app:progressValue = "50"
correct:
<variable
name="viewModel"
type="com.example.testbinding.ViewModel"/>
app:buttonText = "#{viewModel.buttonText}"
app:progressValue = "#{viewModel.progressValue}"
Of course, viewModel must have a defined setter for these properties.
Trying to set visibility of View using custom variable, but error occurs: Identifiers must have user defined types from the XML file. visible is missing it. Is it possible to set view visibility using data binding? Thanks.
<data>
<variable
name="sale"
type="java.lang.Boolean"/>
</data>
<FrameLayout android:visibility="#{sale ? visible : gone}"/>
As stated in the Android Developer Guide, you need to do it like this:
<data>
<import type="android.view.View"/>
<variable
name="sale"
type="java.lang.Boolean"/>
</data>
<FrameLayout android:visibility="#{sale ? View.GONE : View.VISIBLE}"/>
In your layout:
<data>
<variable
name="viewModel"
type="...."/>
</data>
<View
android:layout_width="10dp"
android:layout_height="10dp"
android:visibility="#{viewModel.saleVisibility, default=gone}"/>
In your ViewModel java code:
#Bindable
public int getSaleVisibility(){
return mSaleIndecator ? VISIBLE : GONE;
}
The problem is that visibility is an Integer on the View class, this means you have two ways to make this work:
Use the View.VISIBLE and View.GONE constants. https://developer.android.com/topic/libraries/data-binding/index.html#imports
Define a custom setter for visibility that takes a Boolean. https://developer.android.com/topic/libraries/data-binding/index.html#custom_setters
Possible implementation:
#BindingAdapter("android:visibility")
public static void setVisibility(View view, Boolean value) {
view.setVisibility(value ? View.VISIBLE : View.GONE);
}
Which will make <FrameLayout android:visibility="#{sale}"/> work.
Similar to Kiskae solution. Put this method in a separate file, for instance, Bindings.kt:
#BindingAdapter("android:visibility")
fun View.bindVisibility(visible: Boolean?) {
isVisible = visible == true
// visibility = if (visible == true) View.VISIBLE else View.GONE
}
Then in layout XML:
<data>
<variable
name="viewModel"
type="SomeViewModel" />
</data>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="#{viewModel.number == 1}" />
I googled but still didn't find solution for me.
This is my xml:
<?xml version="1.0" encoding="utf-8"?>
<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">
<data>
<import
alias="noteViewModel"
type="com.app.screen.createnote.CreateNoteViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="#+id/etNote"
style="#style/JWidget.EditText.Grey"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="start"
android:inputType="textMultiLine"
android:scrollbars="vertical"
android:text="#={noteViewModel.note.description}" />
</LinearLayout>
</layout>
This is my ViewModel code:
public class CreateNoteViewModel extends BaseObservable {
private Note note;
#Bindable
public Note getNote() {
return note;
}
public void setNote(Note note) {
this.note = note;
notifyPropertyChanged(BR.note);
}
}
But when I try run my app I got it:
Error:Execution failed for task ':app:compileDebugJavaWithJavac'.
java.lang.RuntimeException: Found data binding errors.
****/ data binding error ****msg:Could not find accessor com.justsafe.tmgr.justsafe.screen.createnote.CreateNoteViewModel.note
file:C:\android\work\JustSafeTmgr\app\src\main\res\layout\activity_create_note.xml
loc:44:33 - 44:62
****\ data binding error ****
P.S. In other place in my app it work, but there I get problem.
Try to change the <import> tag to a <variable> tag:
<data>
<variable
name="noteViewModel"
type="com.app.screen.createnote.CreateNoteViewModel" />
</data>
build.gradle -- app
android {
dataBinding {
enabled = true
}
}
dependencies {
def life_versions = "1.1.1"
// Lifecycle components
implementation "android.arch.lifecycle:extensions:$life_versions"
annotationProcessor "android.arch.lifecycle:compiler:$life_versions"
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="LoginViewModel"
type="com.keshav.mvvmdemokeshav.viewModel.LoginViewModel" />
</data>
<!-- TODO Your XML File-->
</layout>