How to use kotlin.Int in databinding? - android

I wrote a layout xml like below.
But the Kotlin compiler says Cannot resolve symbol 'Int'
main_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<layout ...>
<data>
<import type="androidx.databinding.ObservableArrayMap" />
<variable
name="myList"
type="ObservableArrayMap<Int,String>" />
</data>
<!-- ...... -->
</layout>
Is it possible to use kotlin builtins in android databinding xml?

Use java Integer instead of kotlin Int.
You can not use characters <,> etc in XML. So use HTML entities.
like
ObservableArrayMap<Integer,String>

You can add data binding simple way.
Configure your app to use the data binding:
Open the app/build.gradle, Then you have to add these line of code inside the android tags in gradle and sync project
dataBinding {
enabled = true
}
Layout and Binding Expression in Data Binding:
Open the layout (XML) file activity_main and replace the root with layout tag and place all tags inside to layout tags.
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<RelativeLayout>
<layout>
Replace the traditional setContentView() to DataBindingUtil.setContentView() in Activity:
public class MainActivity extends AppCompatActivity {
ActivityMainBinding binding;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
}
}
Let’s us see how to data bind with Views and Widget using data binding
binding.tvName.setText("Monika Sharma");
binding.tvAddress.setText("251 mansarovar Jaipur | India ");
binding.tvFollowers.setText("240K");
binding.tvfollowing.setText("324K");

Related

Accessing viewbinding object in a non view class?

In my fragment I am using a bottom sheet to show some UI components
This is my fragment xml
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
>
<!-- other views -->
<include
android:id="#+id/feedbackIncluded"
layout="#layout/chat_feedback" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
I currently handle view actions or data of bottomsheet from my fragment.
I am planning to seperate the code for bottomsheet handling to another class
This is my bottom sheet 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"
app:behavior_hideable="false"
app:behavior_peekHeight="16dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<data>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/parentSheetLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#android:color/transparent">
<!-- some views -->
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
This is the class which I have created where I want to do the handling
class ChatFeedbackBottomSheet(context: Context) {
var binding: ChatFeedbackBinding? = null
init {
binding = ChatFeedbackBinding.inflate(LayoutInflater.from(context), null, false)
println(binding?.option1.text)
}
fun openSheet() {
println("------>" + binding?.option1?.text)
}
}
I can't seem to figure how do I get the correct viewbinding object in this class?
You don't need to have another class for refactoring of code, you can just take out the function and keep it inside another file, You can pass view binding variables in the function parameters and execute your code there.

Data Binding Compiler cannot find custom binding adapter provided

I have made a binding adapter available statically inside my Fragment which basically change my button appearance from "Stop" to "Play" and vice-versa.
companion object {
#BindingAdapter("playState")
fun Button.setPlayState(item: UIState) {
item.let {
if (it.isPlaying) {
setText("Stop")
setBackgroundColor(ContextCompat.getColor(context, R.color.colorStop))
} else {
setText("Play")
setBackgroundColor(ContextCompat.getColor(context, R.color.colorPlay))
}
}
}
}
Here is my layout file. I have provided a data class for it.
<?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>
<!-- stuff here -->
<variable
name="viewmodel"
type="com.mypackage.ui.ViewModel"/>
<variable
name="uistate"
type="com.mypackage.ui.UIState" />
</data>
<!-- layout, buttons, and more stuff here. Just pay attention to this following button -->
<Button
android:id="#+id/play_button"
android:layout_width="150sp"
android:layout_height="75sp"
android:layout_marginTop="20sp"
android:onClick="#{() -> viewmodel.onPlayClicked()}"
android:text="#string/play_button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/minus_layout"
app:layout_constraintVertical_bias="0.026"
app:playState="#{uistate}"/>
</layout>
UIState itself is pretty self-explanatory.
data class UIState(var isPlaying: Boolean)
and the () -> viewmodel.onPlayClicked() flips the Boolean at UIState.
After compiling, Data Binding Compiler throws this error:
Cannot find a setter for <android.widget.Button app:playState>
that accepts parameter type 'com.mypackage.ui.UIState'
I have tried:
Rebuilding the project by removing .gradle folder
Looking for answer here and here.
Removed #JvmStatic annotation at the extension function
Moved the extension function to top level instead of Fragment's companion object.
I think you missed to add kotlin plugin in your gradle
apply plugin: 'kotlin-kapt'
You don't have to use #JvmStatic because you are using Kotlin extension feature.
You need to add the view reference as a paramater to your BindingAdapter method.
#BindingAdapter("playState")
fun setPlayState(button:Button,item: UIState) {
//do your work here
}
Your namespace
xmlns:app="http://schemas.android.com/apk/res-auto"
is wrong for custom binding adapters. Please use the namespace
xmlns:app="http://schemas.android.com/tools"
since app:playState is not in the namespace you have given its not working properly

Databinding and included layouts: Cannot find setter attribute for onClick

I'm trying to set an OnClickListener for an <include>d layout, but receive a data binding error at compile time stating that data binding "Cannot find the setter for attribute 'android:onClick' with parameter type android.view.View.OnClickListener".
Context here is that I'm using data binding to inflate the included layout, so that I can pass values into it from a viewModel that I've bound to the including layout.
I've tried various syntax for the data binding expression:
#{viewModel::onClickFunction}
#{viewModel.onClickFunction}
#{() -> viewModel.onClickFunction()}
#{(view) -> viewModel.onClickFunction()}
#{_ -> viewModel.onClickFunction()}
I've tried all of the above with onClickFunction as a function, and also as an OnClickListener object.
Other related questions on Stack Overflow seem to solve this issue by cleaning the project to regenerate the databinding files, but that hasn't worked for me.
Relevant code snippets below:
viewModel
class MyViewModel() {
val onClickFunctionAsAListener: OnClickListener = object:OnClickListener{
override fun onClick(v: View?) {
//Do something
}
}
fun onClickFunction() {
//Do something
}
}
Including layout
<layout>
<data>
<variable name="viewModel" type="full.package.name.MyViewModel"/>
</data>
<LinearLayout>
<include
layout="#layout/included_layout"
android:onClick="#{viewModel.onClickListener}"
app:customAttribute="#{`someText`}/>
</LinearLayout>
</layout>
Included layout
<layout>
<data>
<variable name="customAttribute" type="String"/>
</data>
<TextView
layout="#layout/included_layout"
android:text="#{customAttribute}"/>
</layout>
It seems that you can't actually assign an OnClick handler to an <include> tag directly. I managed to get it to work by adding another variable to IncludedLayouts data binding class, and then assigning the OnClickListener to IncudedLayouts root view in XML.
After the changes, my files looked like this:
viewModel
class MyViewModel() {
val onClickFunction: OnClickListener = object:OnClickListener{
override fun onClick(v: View?) {
//Do something
}
}
}
Including layout
<layout>
<data>
<variable name="viewModel" type="full.package.name.MyViewModel"/>
</data>
<LinearLayout>
<include
layout="#layout/included_layout"
app:onClick="#{viewModel.onClickListener}"
app:customAttribute="#{`someText`}/>
</LinearLayout>
</layout>
Included layout
<layout>
<data>
<variable name="customAttribute" type="String"/>
<variable name="onClick" type="android.view.View.OnClickListener"/>
</data>
<TextView
layout="#layout/included_layout"
android:text="#{customAttribute}"
android:onClick="#{onClick}"/>
</layout>
include tag does not support onClick method directly. While the selected answer is correct, instead of passing onClickLister to include layout (or having custom #BindingAdapter, which is also another solution), I would just wrap my include inside a ViewGroup and onClick on ViewGroup.
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="#{()-> viewModel.yourFunction()}">
<include layout="#layout/custom_layout"/>
It's a workaround, but works as charm.

Why generated ViewDataBinding class annotates property for "include" tag as Nullable

I'm using Android Data Binding library to bind an xml layout that has an <include>
layout.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="model"
type="com.example.MyViewModel" />
</data>
...
<include
layout="#layout/someOtherLayout"
android:id="#+id/includedLayout" />
...
</layout>
In the generated Databinding class for the xml I see this property:
#Nullable
public final com.example.databinding.SomeOtherLayoutBinding includedLayout;
Why is it annotated as #Nullable? The <include> is in the layout and as I see it, it is obviously non-null. What am I missing?
It forces me to use non-null assertion operator !! in Kotlin code when accessing the fields of the included layout and I'm wondering if it is safe or if there is something I'm not considering here
val binder = DataBindingUtil.bind(view)
val someView = binder.includedLayout!!.someView
According to the Documentation on View Binding, when you have multiple layouts for configuration changes, if the view is only present in some configurations, the binding class will be marked as nullable.
View Binding Docs
For the latest version of databinding compiler (3.1.0) to resolve that issue with nullable bindings of included layouts you could set
android.databinding.enableV2=true
in gradle.properties file inside your project.
After that you need invoke rebuild. After that all included layout bindings will be marked with #NonNull annotation.

Referencing properties of Observable class in Android Data Binding layout

What is the type of the Observable class property which getter is annotated as #Bindable in the Android Data Binding framework?
For example, let the Observable class be defined as follows:
class Localization() : BaseObservable() {
var translation: (key: String) -> String by Delegates.observable(defaultTranslation) { _, _, _ ->
notifyPropertyChanged(BR.translation)
}
#Bindable get
}
The layout XML will be then something like this:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="translation"
type="WHAT IS THE TYPE OF TRANSLATION?" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{translation.invoke(stringKey)}" />
</FrameLayout>
</layout>
The question is, what to put in the type attribute of variable "translation".
I've tried:
type="kotlin.jvm.functions.Function1<String, String>"
It compiles, but the TextView is not updated when translation property changes.
I can achieve the desired behavior by introducing localization variable in the layout XML and then calling localization.translation.invoke() in the binding expression. I am just not comfortable with this and want to know if I can reference translation directly.
The Localization extends BaseObservable while Function1 is not observable at all. So using the Localization gives you an interface for observing the changes to the properties.
If you bind the translation, it's a simple field that gets set. If you want to update it, you'd have to call setTranslation() again.

Categories

Resources