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}".
Related
I'm trying to use a String variable to bind it into my view.
When I use a model object with a String property, it works well. But if I use the String variable alone, it only works with one way binding.
ViewModel:
class SampleModel(var data : String = "")
var myModel : SampleModel = SampleModel()
var myVariable : String = ""
XML:
<data>
<variable
name="model"
type="MyViewModel.SampleModel" />
<variable
name="variable"
type="String" />
</data>
<!-- Two way works fine -->
<EditText
android:text="#={model.data}"/>
<!-- Only one way works -->
<EditText
android:text="#={variable}"/>
The string in the SampleModel works well with two way binding but the String variable does not.
I think it is because the imported String in xml is java.lang.String but the String in the model is kotlin.String. And I'm unable to use the kotlin.String in xml.
Is there any solution to fix this? Or is there any proper way of two way binding in Kotlin-Multiplatform projects?
It looks like you have added a wrong variable in the xml file. In your view model you have created a variable named myVariable of type String but in your xml file you are creating one more variable here :-
<variable
name="variable"
type="String" />
so these both variables are different. You don't need to import anything in your xml file just create a viewModel variable which you have already done here :-
<variable
name="model"
type="MyViewModel.SampleModel" />
and now simply use this like :- android:text="#={model. myVariable}"
UPDATE :- Here in this you need to use the String variable which i created in your viewModel because it used kotlin.String and in xml you have java.lang.String. You can simply use the variable which is created in your viewModel For eg :- android:text="#={viewModel.yourVariable}"
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. :)
I'm currently using bindings to dynamically set the texts of various text views using the android view models. At the moment the view models look something like this:
class MyViewModel(
resources: Resources,
remoteClientModel: Model = Model()
) : ObservableViewModel() {
init {
observe(remoteClientModel.liveData) {
notifyChange()
}
fun getTextViewTitle(): String = when {
someComplicatedExpression -> resources.getString(R.string.some_string, null)
else -> resources.getString(R.string.some_other_string)
}
}
And the xml layout:
<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="my.app.signature.MyViewModel"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{viewModel.textViewTitle}"
android:textAlignment="center"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
However I would like to remove the "resources: Resources" that is injected into the view model, since the resources are coupled with the Activity. The code now simply returns the string resource id instead:
fun getTextViewTitle(): Int = when {
someComplicatedExpression -> R.string.some_string
else -> R.string.some_other_string
}
Hence I've removed the activity dependency. The compiler thinks this is fine but it crashes in runtime with the following exception: android.content.res.Resources$NotFoundException: String resource ID #0x0.
This happens when trying to attach the lifeCycleOwner to the binding using:
override fun onActivityCreated(savedInstanceState: Bundle?) {
// Some more code....
binding.lifecycleOwner = activity
// Some more code....
I'm not sure how to remove the resource dependency from the view model without having it crash in runtime.
EDIT:
For clarification: The ObservableViewModel in my example is the very same one as the one found here:
https://developer.android.com/topic/libraries/data-binding/architecture
Used to perform notifyChange.
The issue here is the code is trying to call textView.setText(0) which results in an error since there is no string resource with id 0x0. This is happening because getTextViewTitle() return an Int and the view binding functionality will make it default as 0 (when initializing).
https://developer.android.com/topic/libraries/data-binding/expressions#property_reference
From the docs
Avoiding null pointer exceptions
Generated data binding code automatically checks for null values and avoid null pointer exceptions. For example, in the expression #{user.name}, if user is null, user.name is assigned its default value of null. If you reference user.age, where age is of type int, then data binding uses the default value of 0.
Maybe something like this could work,
android:text='#{viewModel.textViewTitle == 0 ? "" : #string{viewModel.textViewTitle}}'
or
android:text='#{viewModel.textViewTitle, default=""}'
To solve this simply make a context available in the view, so that you can call context.getString(...) in your view.
<data>
<import type="androidx.core.content.ContextCompat" />
<variable
name="viewModel"
type="my.application.path.SomeViewModel" />
</data>
<....
....
android:text="#{context.getString(viewModel.textResource)}"
...
/>
Just convert your int value to String to avoid this crush
android:text='#{String.valueOf(viewModel.profile.walletBalance)}'
In some cases your binding variable itself can be null
<data>
<variable
name="viewModel"
type="SomeViewModel"
/>
</data>
<TextView
android:text="#{viewModel == null ? "" : viewModel.textViewTitle}"
/>
I have a ViewModel with a List auf MutableLiveData<Data> in my Fragment Layout I set the data variable of my CustomView with one of the data elements from the List.
This works fine when it first loads but it doesn't update when I change a value in my data object.
Not really sure how to do this, until now I just used two-way data binding with EditText and MutableLiveData for example.
CustomView Layout:
<data>
<variable
name="data"
type="androidx.lifecycle.LiveData<Data>"/>
</data>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:cardBackgroundColor="#{data.color}"
app:cardCornerRadius="16dp">
Class:
var data: MutableLiveData<Data>? = null
set(value) {
binding.data = value
}
Fragment Layout:
<data>
<variable
name="viewModel"
type=".ViewModel" />
</data>
<CustomView
.
.
.
app:data="#{viewModel.data[1]}" />
The reason for the update only happening the first time the screen is loaded is that the XML is used to inflate the View and then the initial item is used and set to the CustomView.
Then when the item in the list is updated, it does not trigger an update in the CustomView.
What you might be looking for is #BindingAdapter
#BindingAdapter("enableButton")
internal fun Button.enableButton(enabled: Boolean?) = enabled?.let { isEnabled = it } ?: also { isEnabled = false }
And then using it in the following way:
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button Text"
app:enableButton="#{viewModel.observeStatus()}" /> // <- Observes Boolean
A good walk-through might be at the following link: BindingAdapter
Note: The example is only for a Boolean observation, but it can simply be changed to match whatever object is observed.
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.