I want to use databinding with variables for colors in xml.
Here is my code:
xml:
<data>
<import type="androidx.core.content.ContextCompat"/>
<variable
name="settings"
type="..censored..Settings" />
</data>
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:textColor="#{ContextCompat.getColor(context, settings.primaryTextColor)}"
android:textColorHint="#{ContextCompat.getColor(context, settings.primaryHintColor)}"
/>
Settings:
data class Settings(val context: Context) {
var primaryTextColor: Int
var primaryHintColor: Int
init {
primaryTextColor = R.color.defaultText
primaryHintColor = R.color.defaultHint
}
However I'm getting an error
Cannot find a setter for that accepts parameter type 'int'
How do I achieve databinding colors with variables?
You can use BindingAdapter for this.
#BindingAdapter("textColor")
fun bindTextColor(textInputEditText: TextInputEditText, textColorResource: Int?) {
if (textColorResource != null) {
textInputEditText.setTextColor(ContextCompat.getColor(textInputEditText.context, textColorResource))
}
}
#BindingAdapter("textColorHint")
fun bindTextColor(textInputEditText: TextInputEditText, textColorResource: Int?) {
if (textColorResource != null) {
textInputEditText.setHintTextColor(ContextCompat.getColor(textInputEditText.context, textColorResource))
}
}
In your XML
<data>
<variable
name="settings"
type="..censored..Settings" />
</data>
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
app:textColor="#{settings.primaryTextColor}"
app:textColorHint="#{settings.primaryHintColor}"
/>
Related
I read a few posts to convert from String to Integer and tried to do the same to convert string to double using two-way binding but was unable to do it.
SampleViewModel.kt
class SampleViewModel: ViewModel() {
val weight = MutableLiveData<Double>()
fun validateFields() {
Log.i("SAMPLE_VIEW_MODEL", "validateFields: ${weight.value}")
}
}
TypeConverters.kt
object TypeConverters {
#InverseMethod("stringToDouble")
#JvmStatic
fun doubleToString(value: Double?): String {
return value?.toString() ?: ""
}
#JvmStatic
fun stringToDouble(value: String): Double? {
if (TextUtils.isEmpty(value)) {
return null
}
return value.toDoubleOrNull()
}
}
fragment_sample.xml
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="viewModel"
type="com.example.sampleapp.SampleViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
...
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/til_weight"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="#dimen/_10sdp"
android:hint="#string/hint_weight"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="#+id/tilName">
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/tiet_weight"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="numberDecimal"
android:text="#={TypeConverters.doubleToString(viewModel.weight)}"/>
</com.google.android.material.textfield.TextInputLayout>
...
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
The example above does not let me enter the proper value on UI like "56.78". I tried to follow this post but doesn't work for me. Another way is to take string value and then convert it into double and vice versa accordingly. I would like to know which is the correct way.
In your TextInputEditText, update this line
android:text="#={String.valueOf(viewModel.weight)}"
See Results
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/tiet_weight"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="numberDecimal"
android:text="#={String.valueOf(viewModel.weight)}"/>
I'm trying to call a function from my Data Binding layout, but I'm always receiving some error. I'm trying to set the text on my textView using MyUtilClass's function which I have created. here's my 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>
<import type="com.example.testapp.User"/>
<import type="com.example.testapp.MyUtilClass"/>
<variable
name="user"
type="User" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{MyUtilClass.Companion.changeText(user.firstName)}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
MyUtilClass
class MyUtilClass {
companion object {
#JvmStatic
fun changeText(text: String): String {
return text
}
}
}
User
data class User(
val firstName: String,
val lastName: String,
val age: Int,
val loggedIn: Boolean
)
MainActivity.java
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val myUser = User("John", "Doe", 25, true)
binding.user = myUser
}
}
Error:
C:\Users\Stefan\AndroidStudioProjects\TestApp\app\build\generated\source\kapt\debug\com\example\testapp\DataBinderMapperImpl.java:9:
error: cannot find symbol import
com.example.testapp.databinding.ActivityMainBindingImpl;
^ symbol: class ActivityMainBindingImpl location: package
com.example.testapp.databinding
cannot find method changeText(java.lang.String) in class
com.example.testapp.MyUtilClass.Companion Open File
Adding JvmStatic to the changeText() method in MyUtilClass automatically makes it static.
Therefore, you can access it like this in your layout 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"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="com.example.testapp.User"/>
<import type="com.example.testapp.MyUtilClass"/>
<variable
name="user"
type="User" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{MyUtilClass.changeText(user.firstName)}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
You can check this link to find out more:
Kotlin DataBinding pass static function into layout xml
This is the change I made, and it worked. I basically removed class keyword and added object instead.
object MyUtilClass {
#JvmStatic
fun changeText(text: String): String {
return text
}
}
I have a ViewModel class defined as follows:
class StockLoadTaskModel : ViewModel() {
....
....
var d: Double = 10.0
}
That is bound to the following layout:
<?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>
<import type="android.view.View" />
<import type="it.kfi.lorikeetmobile.extras.Converter" alias="Converter"/
<variable
name="viewModel"
type="it.kfi.lorikeetmobile.stock.models.StockLoadTaskModel" />
<variable
name="view"
type="it.kfi.lorikeetmobile.stock.ui.movements.StockLoadTaskFragment
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
...
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="8dp">
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/et_code"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/hint_et_item_code"
android:text="#={viewModel.itemCode}" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="8dp">
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/et_quantity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="numberDecimal"
android:text="#={Converter.doubleToString(d)}"
android:hint="#string/quantity" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="8dp">
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/et_note"
android:lines="3"
android:scrollbars="vertical"
android:overScrollMode="ifContentScrolls"
android:gravity="top"
android:inputType="textMultiLine"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/hint_et_note"
android:text="#={viewModel.selectedItem.detail.note}"/>
</com.google.android.material.textfield.TextInputLayout>
...
</LinearLayout>
And I have also the following Converter object:
object Converter {
#JvmStatic
#InverseMethod("stringToDouble")
fun doubleToString(value: Double?): String? {
if (value == null) {
return null
}
return DecimalFormat(ClientConfiguration.currentConfig.decimalFormat).format(value)
}
#JvmStatic
fun stringToDouble(value: String?): Double? {
if (value == null) {
return null
}
val v = DecimalFormat(ClientConfiguration.currentConfig.decimalFormat).parse(value)
return v.toDouble()
}
}
If I set: android:text="#={Converter.doubleToString(d)}" (two-way databinding), in the EditText with id et_quantity I get the following error:
...error: cannot find symbol
If I change it into a one-way databinding like: android:text="#{Converter.doubleToString(d)}", it works. It looks like the binding manager is not able to recognize the inverse method.
Can anybody help me? Thank you.
Why the error happens?
When you define two-way data binding like you have in your example android:text="#={Converter.doubleToString(d)}" the question is: what function/object will receive data that you get back passed from EditText as user types data in? Should data be passed to Converter.doubleToString or maybe some other static function of Converter? Maybe to the result of Converter.doubleToString(d) or to d variable?
You must be precise.
You expect it is d, the compiler expects it is the result of Converter.doubleToString(d). Actually, neither will work.
Another issue is that EditText does operate with characters. It knows nothing about double, int, float, byte, short, boolean or anything else that is not a string.
It means that in order to implement two-way data binding your source:
must return value of type String;
must be assignable.
How to fix the issue?
Android architecture components introduce us with ObservableField class. There are ready to use ObservableBoolean, ObservableChar, ObservableFloat and a few others. If you open the link from the previous sentence you should see all of the classes Observable... on the left pane.
There is no ObservableString but ObservableField accepts a generic type. So you can define a variable that is a part of data binding to be ObservableField<String>("defaultValueHere").
So what you should have is:
class StockLoadTaskModel : ViewModel() {
....
....
var d: Double = 10.0
var dataBindingVariable = ObservableField<String>(d.toString())
}
The dataBindingVariable will always return you the contents of an EditText you bound it to. You can get that value and safely convert to double.
class StockLoadTaskModel : ViewModel() {
....
....
var d: Double = 10.0
var dataBindingVariable =
object: ObservableField<String>(d.toString()) {
override fun set(value: String?) {
super.set(value)
// a value has been set
d = value.toDoubleOrNull() ?: d
}
}
}
Layout declaration will look like that for input field:
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="8dp">
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/et_quantity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="numberDecimal"
android:text="#={viewModel.dataBindingVariable}"
android:hint="#string/quantity" />
</com.google.android.material.textfield.TextInputLayout>
And there will be no need for object Converter.
There is another way of doing two-way data binding I'm not talking about here because it was already answered. Here it is.
The following code is based the project.
I modified a few code.
The android:text="#{viewmodel.name}" displays the LiveData value of the name.
The fun onLike() will change LiveData value of the name.
I think android:text="#{viewmodel.name}" will display latest value "My new" after I click the button (android:id="#+id/like_button").
But in fact, android:text="#{viewmodel.name}" keep to display "Ada", why?
SimpleViewModelSolution.kt
class SimpleViewModelSolution : ViewModel() {
private var _name = MutableLiveData("Ada") // I modified from private val _name = MutableLiveData("Ada")
val name: LiveData<String> = _name
...
fun onLike() {
_likes.value = (_likes.value ?: 0) + 1
_name = MutableLiveData("My new") // I added
}
}
solution.xml
<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="viewmodel"
type="com.example.android.databinding.basicsample.data.SimpleViewModelSolution"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="#{viewmodel.name}"
..."/>
<Button
android:id="#+id/like_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:onClick="#{() -> viewmodel.onLike()}"
android:text="#string/like"
.../>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Change your onLike() to this:
fun onLike() {
_likes.value = (_likes.value ?: 0) + 1
_name.value = "My new"
}
Also, you can declare _name as val instead of var
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.