I have an Int which contains a decimal number in units of tenths, so for example my int holds 308 to represent the decimal number 30.8. I want to use data binding to display this number in a TextView along with some other text. My TextView has the following text entry:
android:text="#{String.format("my value: %.1f", #{viewModel.myValue}.toFloat()/10.0f)}"
This fails to build with the following error:
[databinding] {"msg":"cannot find method toFloat() in class java.lang.Integer","file":"app/src/main/res/layout/fragment_you.xml","pos":[{"line0":163,"col0":54,"line1":163,"col1":84}]}
The value in question is defined like so:
private val _myValue = MutableLiveData<Int>()
val myValue: LiveData<Int> = _myValue
I thought this meant it was a Kotlin integer and i could call Kotlin functions on it, but apparently it's a java integer and I can't?
Why can't I call toFloat() there? How can I display this Int as a decimal number?
This might help you.
DataBindingConverter.kt
class DataBindingConverters {
companion object {
#JvmStatic
fun convertIntegerToFloat(value: Int?): Float {
return value/10.0f
}
}
}
XML import
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="com.package.DataBindingConverter" />
</data>
.....
Bind to View
<EditText
...
android:text="#={DataBindingConverter.convertIntegerToFloat(viewModel.myValue)}" />
In xml if you want to use casting you will have to do it the way you do in JAVA
Here to convert it to float
try using ->
android:text="#{String.format("my value: %.1f", Float.valueOf(#{viewModel.myValue})/10.0f)}"
.toFloat is to be done in kotlin classes. In xml still you have to use JAVA
I think, it will work.
android:text="#{String.format("my value: %.1f", #{(Float)(viewModel.myValue)}/10.0f)}"
Related
Following https://developer.android.com/topic/libraries/data-binding/two-way#converters,
I am trying to implement a data converter for two-way data binding in android.
The functionality of the converter:
Given a 10 digit phone number, add country code to the phone number.
XML code:
<data>
<import type="<package_name>.PhoneNumberStringConverter" />
<variable
name="model"
type="<package_name>.MyViewModel" />
</data>
<androidx.appcompat.widget.AppCompatEditText
android:text="#={PhoneNumberStringConverter.addExtension(model.storeDetailsEntity.storePhoneNumber)}"
... // Other irrelevant attributes are not shown
/>
Converter:
object PhoneNumberStringConverter {
#InverseMethod("addExtension")
#JvmStatic
fun removeExtension(view: EditText, oldValue: String, value: String): String {
return value.substring(3)
}
#JvmStatic
fun addExtension(view: EditText, oldValue: String, value: String): String {
return "+91$value"
}
}
When I add the converter in the XML, the build is failing.
Getting MyLayoutBindingImpl not found. Binding class generation issues.
Note:
1. Two-way data binding is working as expected, the issue is only with the converter.
Already referred:
Two-way data binding Converter
Edit:
Thanks to #Hasif Seyd's solution.
Working code:
PhoneNumberStringConverter:
object PhoneNumberStringConverter {
#JvmStatic
fun addExtension(value: String): String {
return "+91$value"
}
#InverseMethod("addExtension")
#JvmStatic
fun removeExtension(value: String): String {
return if (value.length > 3) {
value.substring(3)
} else ""
}
}
XML:
android:text="#={PhoneNumberStringConverter.removeExtension(model.storeDetailsEntity.storePhoneNumber)}"
Changed addExtension to removeExtension.
There are some issues in the code. Since you are using two way binding convertors,
first issue is you are trying to directly call the inverse binding adapter in the xml , but as per wat i see in ur convertor definition , binding adapter is removeExtension, so u have to assign that in the xml directly.
Another possible reason could be because of having parameters view and oldValue , which are not required , if you remove those two parameters from the Binding Functions , your code would compile successfully
I want to use databinding with a viewmodel as explained here
So here are excerpts:
layout:
<data class="FragmentEditPersonDataBinding">
<import type="com.unludo.interview.persons.edit.Converter"/>
<variable
name="viewmodel"
type="com.unludo.interview.persons.edit.PersonEditViewModel" />
[...]
<EditText
android:id="#+id/editBirthday"
android:inputType="date"
android:text="#={Converter.dateToString(viewmodel.birthday)}"
converter:
object Converter {
#InverseMethod("stringToDate")
#JvmStatic
fun dateToString(
view: EditText, oldValue: String,
value: Date
): String {
val sdf = SimpleDateFormat("dd/MM/yyyy", Locale.FRANCE)
return sdf.format(value)
}
#JvmStatic
fun stringToDate(
view: EditText, oldValue: String,
value: String
): Date {
val sdf = SimpleDateFormat("dd/MM/yyyy", Locale.FRANCE)
return sdf.parse(value)
}
}
viewmodel:
class PersonEditViewModel {
var birthday: Date = GregorianCalendar(1993, 5, 19).time
...
Now I get this error when I build:
e: [kapt] An exception occurred: android.databinding.tool.util.LoggedErrorException:
Found data binding errors.
****/ data binding error ****msg:cannot find method dateToString(java.util.Date)
in class com.unludo.interview.persons.edit.Converter
[...]
- 134:78 ****\ data binding error ****
I am using the latest databinding alpha, so I am wondering if there could be a bug inthe lib.
thx for any help!
--- update
If I write the converter like this then it compiles, but that does not correspond to the documentation. Any idea why?
object Converter {
#InverseMethod("stringToDate")
#JvmStatic
fun dateToString(
value: Date
): String {
val sdf = SimpleDateFormat("dd/MM/yyyy", Locale.FRANCE)
return sdf.format(value)
}
#JvmStatic
fun stringToDate(
value: String
): Date {
val sdf = SimpleDateFormat("dd/MM/yyyy", Locale.FRANCE)
return sdf.parse(value)
}
}
Bit of an old thread, but I've also been struggling with 2-way databinding, so for anyone that needs the answer to this, the issue is that Unlundo made their converter the way is documented, where there's a View, and old, and a new value. The documentation for this is not very clear, however.
The arguments in you type converters must also be present in your layout file. For the original binding in the layout, android:text="#={Converter.dateToString(viewmodel.birthday)}", there is only one argument - viewmodel.birthday, which we assume is a date. Therefore, our type converter and inverse converter only get 1 argument.
If you reuse the same converter for multiple bindings and want to be able to see what view the user changed, you can pass the view as an argument by using it's ID in the layout. This will pass in the birthday, and the view that the user was editing:
<EditText
android:id="#+id/editBirthday"
android:inputType="date"
android:text="#={Converter.dateToString(edtBirthday, viewmodel.birthday)}"
This will also mean your type converter and inverse converter both need an additional argument in the beginning for the EditText. The library does seem to be smart enough to get the type of view correct, and not just give you a View as your argument, at least.
Also, if you're struggling with the converter only firing in the direction to string, make sure you actually set the binding variable. If the variable the layout is binding from is null, it will convert default values to display, but it won't be able to bind anything back
I'm a newbie for android data binding.
I want to bind multiple SeekBars to a collection of float, such as
SeekBar1.progress <---> modelArray[0]
SeekBar2.progress <---> modelArray[1]
...
Since the progress of SeekBar is a Int type, I think it would be better to use Converter, and below is the converter code:
import android.databinding.InverseMethod
import android.util.Log
import kotlin.math.roundToInt
class Converter {
#InverseMethod("progressToFloat")
fun floatToProgress(value: Float): Int {
val result = (value * 100.0f).roundToInt()
Log.i("MODEL", "convert $value to $result(Int)")
return result
}
fun progressToFloat(value: Int): Float {
return value.toFloat()/100.0f
}
}
and the model structure looks like:
class Model {
val params by lazy {
ObservableArrayMap<Int, Float>()
}
//....
}
and my xml is 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 class="MyBinding">
<variable name="Converter" type="com.mypackage.model.Converter"></variable>
<variable
name="Converter"
type="com.mypackage.model.Converter"></variable>
<variable
name="model"
type="com.mypackage.model.Model"></variable>
</data>
...
<SeekBar
android:id="#+id/seekBar"
android:layout_width="100dp"
android:layout_height="30dp"
android:layout_gravity="center"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:progress="#={Converter.floatToProgress(model.params[0])}"/>
The problem is, every time I build it, it shows :
Found data binding errors.
****/ data binding error ****
msg:The expression converter.floatToProgress(modelParams0) cannot be inverted:
There is no inverse for method floatToProgress, you must add an #InverseMethod
annotation to the method to indicate which method should be used when using
it in two-way binding expressions
I already refer to lots of website, includes following:
https://medium.com/androiddevelopers/android-data-binding-inverse-functions-95aab4b11873
https://developer.android.com/topic/libraries/data-binding/two-way
But I still cannot find out what problem is. Could anyone give me some suggestion?
My development environment
macOS High Sierra,
Android Studio 3.2.1, with compileSdkVersion 28 and gradle 3.2.1
Update:
I also try to write Converter as following :
object Converter { // change to object
#InverseMethod("progressToFloat")
#JvmStatic fun floatToProgress(value: Float): Int { // Add static
val result = (value * 100.0f).roundToInt()
Log.i("MODEL", "convert $value to $result(Int)")
return result
}
#JvmStatic fun progressToFloat(value: Int): Float { // Add static
return value.toFloat()/100.0f
}
}
It still does not work.
Solution
Ok, I found the problem: My project does not use Kapt.
After add it to build.gradle, all works fine.
apply plugin: 'kotlin-kapt'
Also, I update my converter body as following:
object Converter {
#InverseMethod("progressToFloat")
#JvmStatic fun floatToProgress(value: Float): Int {
val result = (value * 100.0f).roundToInt()
Log.i("MODEL", "convert $value to $result(Int)")
return result
}
#JvmStatic fun progressToFloat(value: Int): Float {
val result = value.toFloat()/100.0f
Log.i("MODEL", "convert $value to $result(Float)")
return result
}
}
If the #JVMStatic removed, the project can compile successfully, but the binding does not work.
Thanks everyone who give my suggestion. I think I just ask a silly question.
Refer to:Databinding annotation processor kapt warning
Solution
Ok, I found the problem: My project does not use Kapt. After add it to build.gradle, all works fine.
apply plugin: 'kotlin-kapt'
Also, I update my converter body as following:
object Converter {
#InverseMethod("progressToFloat")
#JvmStatic fun floatToProgress(value: Float): Int {
val result = (value * 100.0f).roundToInt()
Log.i("MODEL", "convert $value to $result(Int)")
return result
}
#JvmStatic fun progressToFloat(value: Int): Float {
val result = value.toFloat()/100.0f
Log.i("MODEL", "convert $value to $result(Float)")
return result
}
}
If the #JVMStatic removed, the project can compile successfully, but the binding does not work.
Thanks everyone who give my suggestion. I think I just ask a silly question.
Refer to:Databinding annotation processor kapt warning
In my case the problem was that I was trying to make a custom TwoWay DataBinding Adapter
but my field in the ViewModel class was LiveData instead of MutableLiveData
Assume an Android project in which I have this XML for two buttons:
<layout>
<data>
<variable name="viewModel" type="com.package.package.UploadPhotoViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:orientation="vertical"
android:padding="10dp">
<com.zoosk.zoosk.ui.widgets.ProgressButton
android:id="#+id/progressButtonChooseFromLibrary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#{viewModel.choosePhotoText}"
android:onClick="#{viewModel::choosePhotoButtonClick}"
android:visibility="#{viewModel.choosePhotoVisibility}" />
<com.zoosk.zoosk.ui.widgets.ProgressButton
android:id="#+id/progressButtonTakePhoto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#{viewModel.takePhotoText}"
android:onClick="#{viewModel::takePhotoButtonClick}"
android:visibility="#{viewModel.takePhotoVisibility}" />
</LinearLayout>
</layout>
And an accompanying ViewModel:
class UploadPhotoViewModel(resources: Resources) {
var onChoosePhotoButtonClicked: ((View) -> Unit)? = null
var onTakePhotoButtonClicked: ((View) -> Unit)? = null
fun choosePhotoButtonClick(v: View) {
onChoosePhotoButtonClicked?.invoke(v)
}
fun takePhotoButtonClick(v: View) = onTakePhotoButtonClicked?.invoke(v)
}
Noting particularly the difference between how choosePhotoButtonClick and takePhotoButtonClick are declared.
When building this project, Android's databinding will work properly for choosePhotoButtonClick, but throws an error with takePhotoButtonClick, saying that there's no method that matches the expected reference signature. Assuming these methods are created the same way under the hood, this should not happen, alas there must be some difference.
What exactly is the difference to these two declaration syntaxes? The official Kotlin documentation doesn't mention anything functional, only that it can serve as an additional way to declare that method.
If you use curly braces without specifying a return type, Your function is implicitly retuning Unit (this is Kotlin for void), so your method:
fun choosePhotoButtonClick(v: View) {
onChoosePhotoButtonClicked?.invoke(v)
}
Has signature of:
fun choosePhotoButtonClick(v: View) : Unit
(IDE will hint that return type "Unit" is redundant and can be ommited).
However using equals sign to shorthand a function infers the return type of expression on right hand side, for example:
fun itemCount() = items.size
Will infer return type of Int, so its signature is:
fun itemCount() : Int
In your case of :
fun takePhotoButtonClick(v: View) = onTakePhotoButtonClicked?.invoke(v)
Function can either return a null when onTakePhotoButtonClicked is null or return value of that method (Unit), which means your takePhotoButtonClick signature is:
fun takePhotoButtonClick(v: View) : Unit?
(OP) direct solution to question:
fun takePhotoButtonClick(v: View) = onTakePhotoButtonClicked?.invoke(v) ?: Unit
Can be considered less readable than the curly brace version, but this implies a return type of Unit instead of Unit? and will be picked up correctly by databinding.
I'm trying to set TextView text color using data binding library
android:textColor="#{holder.getTitleColor(context, item)}"
where the method in Holder class is defined like below
public int getTitleColor(Context context, Item item) {
...
}
No matter if I return color int (#ColorInt) or color resource (#ColorRes) it paints the text solid white. What am I doing wrong?
I seems the int you are providing is interpreted as a hex color, even though it seem intuitive that this setter should be expecting a resource ID.
use the Context reference generated for each bindable view, and use it to convert the resource ID to the color you are pointing to, as described in the DataBinding Dev Guide:
A special variable named context is generated for use in binding expressions as needed. The value for context is the Context from the root View's getContext().
use it to set color like this:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{data.text}"
android:textColor="#{context.getColor(data.colorRes)}"
/>
Edit
for backwards compatibility you can use ContextCompat. Import needed:
<layout>
<data>
<import type="android.support.v4.content.ContextCompat"/>
<variable name="data" type="com.whatever.myapp.MyModel"/>
...
</data>
...
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{data.text}"
android:textColor="#{ContextCompat.getColor(context, data.colorRes)}"
/>
</layout>
In addition to the solution of #Mardann, here is an updated solution that also works on API lower than 23 by using ContextCompat.getColor():
<layout>
<data>
<import type="androidx.core.content.ContextCompat" />
<variable
name="data"
type="com.example.myapp.MyDataObject" />
</data>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{data.text}"
android:textColor="#{ContextCompat.getColor(context, data.colorRes)}"/>
</layout>
Make sure to import ContextCompat as shown above.
You can automagically 'context' as method parameter for ContextCompat.getColor() because it will be automatically resolved to the view's context.
create method by using BindingAdapter
#BindingAdapter({"bind:color"})
public static void setColor(TextView textView, Item item) {
textView.setTextColor(<set color of your choice>);
}
and to call it from xml
app:color="#{item}"
In my case, the color value was in a String Format(eg. "#000000")
1.String TxtColor = "#000000"
2.Import "android.graphics.Color"
<layout>
<data>
<import type="android.graphics.Color"/>
<variable name="txtColor" type="String"/>
</data>
.... other views
</layout>
3.Set to Desired View -- In my case it was TextView
........ other views
<android.support.v7.widget.AppCompatTextView
android:id="#+id/tvTitle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textcolor= "#{Color.parseColor(txtColor)}" //when we import android.graphics.Color we can access it's all methods present
tools:text="Test"/>
...... other views
4.Binding from Activity/Adapter --in my case it was Adapter
inner class ViewHolder(private val binding: BindingClass) :
RecyclerView.ViewHolder(binding.root) {
fun setData(data: DataClass, TxtColor : String?) {
binding.txtColor= TxtColor
binding.executePendingBindings()
}
}
For setting part of the string to a color - This works perfectly with Kotlin, string resources, and Databinding
Add your binding adapter (place this outside all your classes)
#BindingAdapter("app:full_text", "app:span_text", "app:span_color")
fun formatText(textView: TextView, full_text: String, span_text: String, span_color: Int) {
val firstMatchingIndex = full_text.indexOf(span_text)
val lastMatchingIndex = firstMatchingIndex + span_text.length
val spannable = SpannableString(full_text)
spannable.setSpan(ForegroundColorSpan(span_color), firstMatchingIndex, lastMatchingIndex, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)
textView.text = spannable
}
Setup string resources with variables
<string name="percentage">%1$d\%%</string>
<string name="booking_fee">Require card and collect %1$s at Booking</string>
Convert value to string in your ViewHolder (if you need to)
fun bind(percentage: Int) {
binding.percentage = context.resources.getString(R.string.percentage, percentage)
binding.executePendingBindings()
}
Apply bindings via your xml layout
<data>
<variable
name="percentage"
type="String" />
</data>
<TextView
...
app:full_text="#{#string/booking_fee(percentage)}"
app:span_color="#{#color/color_primary}"
app:span_text="#{percentage}" />
Result:
Do not use android:text="..." in your layout file
Create an extension function for textview.
#BindingAdapter("android:colorId")
fun TextView.setTextColorValue(#ColorRes colorId: Int) {
if (colorId == 0) return
setTextColor(ContextCompat.getColor(context, colorId))
}
use it in xml like this
<TextView
android:id="#+id/deliveryStatus"
android:colorId="#{model.deliveryStatusColor}" />
To set color from an integer just call:
android:textColor="#{data.color}"
You can also use binding adapter and then use color resource.
Define this method anywhere in project:
#BindingAdapter(value = "text_color") //customise your name here
public static void setTextColor(TextView view, int color) {
view.setTextColor(color);
}
And then in XML use your new attribute:
<TextView
app:text_color="#{#color/colorPrimary}"/>
Create Binding Adapter as follows, here I am passing all the strings to be colored inside {}. Replace the {blah} string with colored blah string in span.
#BindingAdapter( "spanColor")
fun formatText(view:TextView, hexColorValue:Int) {
val text = view.text
val span = SpannableStringBuilder(text)
var i = 0
var diff = 0
while (i < text.length) {
val firstIndex = text.indexOf('{', i) - diff
val secondIndex = text.indexOf('}', i) - diff
if (firstIndex < 0 || secondIndex < 0) break
span.delete(firstIndex, firstIndex + 1)
span.delete(secondIndex - 1, secondIndex)
span.setSpan(ForegroundColorSpan(hexColorValue), firstIndex, secondIndex-1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
i = secondIndex + diff + 1
diff += 2
}
view.text = span
}
In your XMl file use the attribute (app:spanColor="#{#color/colorAccent}") as
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginTop="#dimen/space_xlarge"
style="#style/DefaultSmallText"
app:spanColor="#{#color/colorAccent}"
android:text="#string/create_credential_message"/>
string.xml
<string name="create_credential_message"><![CDATA[{Username} must at least contain 8 alphanumeric characters or an email address. {Password} must be 8-20 characters long, contain uppercase, lowercase, number, & special characters.]]></string>
My solution was to use this TextView declaration in the xml:
<TextView
...
android:textColor="#{model.getResultColor(context, index)}"
...
/>
and this method in the viewModel:
fun getResultColor(index: Int): Int {
getItem(index)?.let { item ->
...
return Color.GRAY
}
object CustumBinderAdapter {
#JvmStatic
#BindingAdapter("text_color")
fun setTextColor(view: TextView, color: Int) {
when(color){
R.color.soft_green->{
view.setTextColor(Color.parseColor("#5abc6e"))
}
R.color.deep_orange->{
view.setTextColor(Color.parseColor("#e62e30"))
}
}
}
}
and in XML use like this:
app:text_color="#{yourViewModel.yourIntColorValue}"
Code:
binding.color = ContextCompat.getColor(requireContext(), R.color.text_regular)
Layout:
<variable
name="color"
type="Integer" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#{color}"/>