hi i am new in mvvm structure and i am learning new things over here. Currently i have bind default string with my view i have multi color text so i choose to use HTML.fomHTml nut i am unable to use can anyone help to solve this binding issue below my testing code ....
data>
<import type="android.text.Html"/>
<variable
name="loginviewmodel"
type="app.road2xtech.neighbourhood.view.viewmodel.LoginViewModel" />
</data>
<TextView
android:id="#+id/textViewAccount"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="56dp"
android:fontFamily="#font/raleway_regular"
android:gravity="center"
android:text="#={Html.fromHtml(loginviewmodel.accountString)}"
android:textColor="#android:color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
it will give me below error
The expression 'android.text.Html.fromHtml(loginviewmodelAccountString)' cannot be inverted, so it cannot be used in a two-way binding
Details: There is no inverse for method fromHtml, you must add an #InverseMethod annotation to the method to indicate which method should be used when using it in two-way binding expressions
This is called two-way data binding.
android:text="#={Html.fromHtml(loginviewmodel.accountString)}"
The InverseMethod annotation may be applied to any method used in two-way data binding. So to sum up in your XMl if you don't need two-way binding then kindly use this.
android:text="#{Html.fromHtml(loginviewmodel.accountString)}"
This will work. :)
Related
I am trying to find out how to bind both the list items, and the selected value/index of an Android Spinner (I am pretty new to Android / Kotlin)
I have the following
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="com.example.app.Modes" />
<variable
name="viewModel"
type="com.example.app.MainActivityViewModel" />
</data>
....
<Spinner
android:layout_row="17"
android:layout_column="2"
android:id="#+id/spinner1"
android:layout_width="1200px"
android:entries="#{viewModel.devicesDescriptions}"
app:selectedValue="#={viewModel.devicePosition}"
android:layout_height="wrap_content"
android:background="#android:drawable/btn_dropdown"
android:spinnerMode="dropdown"/>
and in the View Model
val devicesDescriptions = ObservableArrayList<String>()
var devices = listOf<MidiDeviceInfo>()
fun setFoundDevices(d: MutableList<MidiDeviceInfo>) {
devices = d
for (dev in devices)
devicesDescriptions.add(dev.toString())
}
By trial and error I could set just strings to the Spinner items (the MidiDeviceInfo would have been better, but string will do)
However, I cannot get a binding to get the selectedItem to work.
I have tried many things, but with the above, I have the error
Found data binding error(s):
[databinding] {"msg":"Cannot find a getter for \u003candroid.widget.Spinner app:selectedValue\u003e that accepts parameter type \u0027java.lang.String\u0027\n\nIf a binding adapter provides the getter, check that the adapter is annotated correctly and that the parameter type matches.","file":"app\\src\\main\\res\\layout\\activity_main.xml","pos":[{"line0":334,"col0":4,"line1":343,"col1":39}]}
Anyone know a way to do this?
Try using android:selectedItemPosition="#={viewModel.devicePosition}" instead of app:selectedValue="#={viewModel.devicePosition}".
This is actually 2 questions.
I noticed that databinding doesn't work if in the Person data class I set the name parameter to be val instead of var. The code will break with the following error:
error: cannot find symbol
import com.example.android.aboutme.databinding.ActivityMainBindingImpl;
^
symbol: class ActivityMainBindingImpl
location: package com.example.android.aboutme.databinding
Why does it happen?
Why do I need to call invalidateAll() in doneClick()? The documentation says that it "Invalidates all binding expressions and requests a new rebind to refresh UI". Isn't the purpose of databinding to connect data and views in such a way that an update to the data immediately updates the views?
MainActivity:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
val person = Person("Bob")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.person = person
binding.apply {
btnDone.setOnClickListener { doneClick(it) }
}
}
private fun doneClick(view: View) {
binding.apply {
person?.nickname = etNickname.text.toString()
invalidateAll()
etNickname.visibility = View.GONE
tvNickname.visibility = View.VISIBLE
btnDone.visibility = View.GONE
}
hideKeybord(view)
}
private fun hideKeybord(view: View) {
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(view.windowToken, 0)
}
}
Person:
class Person(var name: String, var nickname: String? = null)
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">
<data>
<variable
name="person"
type="com.example.android.aboutme.Person" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingStart="#dimen/padding"
android:paddingEnd="#dimen/padding">
<TextView
android:id="#+id/tv_name"
style="#style/NameStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#={person.name}"
android:textAlignment="center" />
<EditText
android:id="#+id/et_nickname"
style="#style/NameStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="#string/what_is_your_nickname"
android:inputType="textPersonName"
android:textAlignment="center" />
<Button
android:id="#+id/btn_done"
style="#style/Widget.AppCompat.Button.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="#dimen/layout_margin"
android:fontFamily="#font/roboto"
android:text="#string/done" />
<TextView
android:id="#+id/tv_nickname"
style="#style/NameStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#={person.nickname}"
android:textAlignment="center"
android:visibility="gone" />
<ImageView
android:id="#+id/star_image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/layout_margin"
android:contentDescription="#string/yellow_star"
app:srcCompat="#android:drawable/btn_star_big_on" />
<ScrollView
android:id="#+id/bio_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="#dimen/layout_margin">
<TextView
android:id="#+id/bio_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:lineSpacingMultiplier="#dimen/line_spacing_multiplier"
android:text="#string/bio"
android:textAppearance="#style/NameStyle" />
</ScrollView>
</LinearLayout>
</layout>
Qustion 1:
I noticed that databinding doesn't work if in the Person data class I set the name parameter to be val instead of var.
Why does it happen?
Because you're using two-way databinding.
In your layout you have this:
<TextView
android:id="#+id/tv_name"
style="#style/NameStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#={person.name}"
android:textAlignment="center" />
The #= in android:text="#={person.name}", specifically, tells databinding "I want to set the TextView's text to the person's name value and I want to update the person's name when the TextView text changes".
When you use the #= databinding will look for a setter for the attribute you're assingning. In this case, it's looking for a setter for the name attribute on the Person class. In Kotlin, this means having a property named name that is a var.
If you do not intend to update the person's name attribute when the TextView changes (which I assume you don't, you'd generally do that with an EditText), then change that line to just # (android:text="#{person.name}"). Then you can make name a val because you're only reading from it for databinding.
Question 2:
Why do I need to call invalidateAll() in doneClick()?
You actually don't ...
The documentation says that it "Invalidates all binding expressions and requests a new rebind to refresh UI". Isn't the purpose of databinding to connect data and views in such a way that an update to the data immediately updates the views?
Yes, but: databinding is not magic. If the UI is to update it must be told to do so and changing your data does not magically tell databinding that it has to update. Something has to tell databinding that a) it's time to update and b) what it needs to update.
So what you have right now with invalidateAll() is the shotgun approach. You updated the one nickname field and then you yelled at databinding "hey, update everything!", so it rebinds all views based on the current state of Person which of course includes "nickname" so that view gets updated.
What you want to do is update only the fields that are bound to nickname because that is the one thing that changed and, preferably, you want to do it automatically when nickname changes. For that, you need to observe the state of the nickname field and react to it changing.
You can do this in a few ways:
Use LiveData
In this approach you have the fields of the model you want to bind be LiveData objects (val nickname = MutableLiveData<String>()) and you add a LifeCycleOwner to the binding so it can observe the LiveData objects.
Databinding is set up to use LiveData so your xml does not need to change. But now the properties are observable and when you update the name on Person (person?.nickname?.value = "New Nickname") databinding will be notified automatically and will update the state of the associated view.
You will not have to call invalidateAll().
Use Observable Fields
This is conceptually the same as #1 but this came before LiveData was introduced. Nowadays you can consider this deprecated and use the LiveData approach, but I'll mention it for completeness.
Again, instead of having a regular property of type String you wrap that property in an observable data structure (val nickname = ObservableString()) that will notify databinding when the value has changed. Again, databinding is set up to work with this so you don't have to change your XML.
Use Observable Objects
With this option, you make your Person class (or preferably a ViewModel) extend Observable and manage notifying databinding yourself as the fields change. You would go this route if you have special logic that has to happen when updating some fields and a simple "set and notify" is not enough. This option is far more complicated and I'll leave it as an exercise to the reader to read the docs to see how this option works. For the vast majority of cases you should be able to do what you need with option #1.
Parting thought on this line:
person?.nickname = etNickname.text.toString()
If you set up databinding correctly, this should not be necessary. :)
If you set up etNickname to use two-way binding and make person.nickname properly observable, the person.nickname attribute will automatically update to the text value in etNickname when it changes!
That is the beauty of databinding.
Hope that helps!
Val = Inmutable
Var = mutable
Full answer
Val and Var in Kotlin
It's because the properties have no built-in mechanisms to notify the UI that they've changed. So you have to invoke it manually. A solution for this problem is using LiveData or MutableLiveData.
I have quick question about my code. I'm writing an app using Android MVVM with LiveData. I want to create loading layout which is will be included in many views. Main goal is to have ability of passing live data representing if layout should be visible and what text info should be displayed with progress bar.
So far I created loading indicator layout, and definded two variables "indicatorVisibility" and "progressText". In attached code one of values is commented out. I created also BindingAdapters to set visibility and text on controls.
This is my layout with progress bar
<data>
<variable
name="indicatorVisibility"
type="android.arch.lifecycle.LiveData"/>
<!--<variable-->
<!--name="progressText"-->
<!--type="android.arch.lifecycle.LiveData"/>-->
</data>
<android.support.constraint.ConstraintLayout
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/layout_loading_background"
>
<ProgressBar
android:id="#+id/progressBar2"
style="?android:attr/progressBarStyle"
android:layout_width="#dimen/layout_loading_progress_bar_size"
android:layout_height="#dimen/layout_loading_progress_bar_size"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#+id/textView7"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!--android:text="#{progressText}"-->
<TextView
android:id="#+id/textView7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="TextView"
android:textColor="#color/layout_loading_text"
android:textSize="#dimen/layout_loading_text_size"
app:layout_constraintBottom_toBottomOf="#+id/progressBar2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="#+id/progressBar2"
app:layout_constraintTop_toTopOf="#+id/progressBar2" />
</android.support.constraint.ConstraintLayout>
This is how i include it in fragment layout
<include layout="#layout/layout_loading_info"
app:indicatorVisibility="#{viewModel.isBusy}"
/>
And those are my bind adapters:
#BindingAdapter("android:visibility")
fun getVisibility(view: View, liveData: LiveData<Boolean>){
liveData.observe(view.getLifecycleOwner(), Observer {
view.visibility = if(it == true) View.VISIBLE else View.INVISIBLE
})
}
#BindingAdapter("app:text")
fun getText(view: TextView, liveData : LiveData<Int>)
{
liveData.observe(view.getLifecycleOwner(), Observer {
it?.let{
view.text = view.context.resources.getString(it)
}
})
}
So far I tried passing simple types like Integer and it works. The problem lays in LiveData. Even when I don't use variables inside included layout I get error (error message tells nothing).
I saw similar stack task [here] : Applying databinding adapter to include tag but they passed the whole viewModel, which is not a flexible enough solution for me.
I think you use the wrong name-space; for data-binding that should be bind:
<include
layout="#layout/layout_loading_info"
bind:indicatorVisibility="#{viewModel.isBusy}"/>
The data-type is LiveData<Boolean>; therefore you'd need to import LiveData and Boolean, in order to use them in a variable definition. The data-binding should look about like this:
<data class=".databinding.LiveDataBinding">
<import type="android.arch.lifecycle.LiveData"/>
<import type="java.lang.Boolean"/>
<variable name="indicatorVisibility" type="LiveData<Boolean>"/>
</data>
bind:viewModel="#{viewModel}" might in general be better than binding single values.
Via data-binding I am setting the visibility of a text field. The visibility is depending on a string being null or empty or nothing of the both.
<?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="com.example.viewModel"/>
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
<TextView
android:id="#+id/textField1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#{viewModel.data.text}"
android:visibility="#{(viewModel.data.text == null || viewModel.data.text.empty) ? View.GONE : View.VISIBLE}"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</android.support.constraint.ConstraintLayout>
Is it possible to create an import in the data element so that I can use the isNullOrBlank() function from the kotlin.text.Strings.kt class?
I was hoping to be able to use it like this: android:visibility="#{(viewModel.data.title.isNullOrBlank() ? View.GONE : View.VISIBLE}"
Android data binding still generates the Java code from the XML instead of the Kotlin code, once data binding will be migrated to generating Kotlin code instead of Java I believe we will be able to use Kotlin extension function in the XML, which will be really cool.
I am sure that gonna happen real soon as Google is pushing to Kotlin heavily. But for now, you have below
TextUtils.isEmpty() as mentioned by #Uli Don't forget to write an import.
The reason why you can't you use StringKt.isNullOrBlack in xml:
Below is the code from Kotlin String.kt
#kotlin.internal.InlineOnly
public inline fun CharSequence?.isNullOrEmpty(): Boolean {
contract {
returns(false) implies (this#isNullOrEmpty != null)
}
return this == null || this.length == 0
}
As you can see it is annotated with #kotlin.internal.InlineOnly which says the Java generated code of this method will be private.
InlineOnly means that the Java method corresponding to this Kotlin
function is marked private so that Java code cannot access it (which
is the only way to call an inline function without actual inlining
it).
It means it can't be called from Java and as data binding generated code is in JAVA it can't be used in data binding either. Thumb rule is what you can access from JAVA you can use that in data binding if not just use the old Java way I would say.
TextUtils.isEmpty() should do what you want.
I read the tutorial about data binding just like the below sample from android developers
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{user.firstName}"
android:onClick="#{handlers::onClickFriend}"/>
But as I implement the data binding function on TextInputLayout xml
<android.support.design.widget.TextInputLayout
android:id="#+id/user_id_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/spacing_normal"
android:layout_marginBottom="#dimen/spacing_normal">
<android.support.design.widget.TextInputEditText
android:id="#+id/input_user_id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:lines="1"
android:text="#={viewmodel.userName}"
android:hint="#string/prompt_username" />
</android.support.design.widget.TextInputLayout>
and declare the variable in viewmodel
public final ObservableField<String> userName = new ObservableField<>();
While I call the userName.get(), it always gives me null.
However, I only change the data binding syntax from
android:text="#{viewmodel.password}"
to
android:text="#={viewmodel.password}"
my userName.get() finally return the user input value.
Why it happened?
From what I understand, the android:text="#={viewmodel.userName}" is used for two-way data binding (where data can come from the program or from the layout), and where you can get updated from data in real time, by using Observable.
On the other hand, the android:text="#{viewmodel.userName}" used for the simple value, where you set the value manually using simple data type (like String, etc).
Thus because you are using Observable, you must use the #=.