I'm trying to implement RecyclerView with GridLayoutManager and I'm stuck.
I'm receiving error:
If a binding adapter provides the setter, check that the adapter is annotated correctly and that the parameter type matches.
Open File"
When I'm trying to compile. "Open file" provides me to xml recycler view declaration.
This is my BindingAdapter:
#BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView,
data: List<MarsPropertyData>?) {
val adapter = recyclerView.adapter as PhotoGridAdapter
adapter.submitList(data)
}
How I'm calling the above:
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/photos_grid"
android:clipToPadding="false"
app:layoutManager=
"androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2"
tools:itemCount="16"
tools:listitem="#layout/grid_view_item"
app:listData="#{viewModel.properties}"
/>
And this is my properties variable:
val properties: LiveData<List<MarsPropertyData>>
I'm understand that IDE complains about data types, my properties var it is LiveData<List<MarsPropertyData>> and inside BindingAdapter there's just normal List, but I saw example from google in which logic was made in same way and it worked fine.
Yes, I have added 'kotlin-kapt' plugin, I have another BindingAdapter which works fine.
I experienced a similar problem while working on a google codelab whose setup is very similar to yours.
In my case, I inadvertently defined the "bindRecyclerView" bindingAdapter function within the curly braces of a previous bindingAdapter function. This resulted in the "bindRecyclerView" function being eclipsed by the previous function and hence its setter cannot be seen from other files. When I removed it from within the previous function, everything worked fine.
Related
I'm able to access my layout views(like button, TextView, EditText etc) directly inside the activity by their ids defined in layout xml file in Kotlin android project.
So, do we need to use findviewbyId(), or butterknife lib in kotlin android project?
StudentActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val studentViewModel = getStudentViewModel()
updateButton.setOnClickListener {
val name = nameEditText.text.toString()
val age = ageEditText.text.toString()
val subject = subjectEditText.text.toString()
studentViewModel.updateStudentRecord(
Student(
name,
Integer.parseInt(age),
subject
)
)
}
}
}```
ButterKnife is an old solution for view binding. It has less boilerplate code than old findviewbyId way but because of annotation processors it impacts build time speed and doesn't provide Null safety and Type safety. A better solution is kotlinx.android.synthetic that you used in your example but it has some problems too. For example, if you set your content view to a layout, then type an id that only exists in a different layout, the IDE lets you autocomplete and add the new import statement. Unless the developer specifically checks to make sure their import statements only import the correct views, there is no safe way to verify that this won’t cause a runtime issue. As everything is global, one has to be careful to make sure that they only use views they are expecting and ignore the autocomplete. DataBinding and ViewBinding are the best solutions for now. They are similar at first look. Both generate binding classes that you can use to reference views directly with support Null safety and Type safety, but there are differences:
DataBinding approach needs you to add <layout> tag to your XML layout in order to enable the data binding process
ViewBinding doesn’t support layout variables or layout expressions, so it can’t be used to bind layouts with data in XML
ViewBinding is faster than DataBinding in build time because it does n't use annotation processors.
I think you will not use anymore, just if you want? But I believe that is not because of the Synthetic Accessors, it's because of the Data Bindings and the announced this year, View Binding
Nope, here is the the magic of kotlin. Just use your id from the layout file (xml) and directly use it. Like:
button.setOnClickListener {}
and so on. hope it will help.
In my data binding layouts, I set long click listeners via:
android:onLongClick="#{ ..binding expression.. }"
The code runs as expected, but the android:onLongClick attribute is flagged as 'unknown' in the xml file. Additionally, there is no auto-complete for it.
The binding adapter for this attribute is included with the data binding library in ViewBindingAdapter.java.
As said here you can use: android:onLongClick="#{() -> handler.onLongClicked()}"
but if you want to remove warning you can use below code instead of above:
app:onLongClickListener="#{() -> handler.onLongClicked()}"
if you use app:onLongClickListener data binding will find setOnLongClickListener in the View class and will use that method
There is a difference between onLongClickListener and onLongClick : We have a method in view called setOnLongClickListener but we do not have a method like this: setOnLongClick and when you use an attribute atr in data binding that has a method like setAtr data binding will find and use that method automatically not needing any adapter. Thus onLongClickListener do not need any adapter (if there is an adapter it will be used instead of setOnLongClickListener) but onLongClick always needs adapter.
Thanks to Bahman for the helpful answer. Here's some more details and options.
If one uses the native xml binding app:onLongClickListener then the viewModel must return Boolean or android compilation crashes with cannot generate view binders java.lang.StackOverflowError
So this crashes the compiler: app:onLongClickListener="#{() -> viewModel.onLongClickRowNoReturn()}" assuming the viewModel method does not return Boolean. If it returns a boolean it works. The Boolean is required by View.OnLongClickListener see View.OnLongClickListener
Alternatively we can use our own custom adapter
#BindingAdapter("onLongClick")
fun setOnLongClickListener(view: View, listener: Runnable) {
view.setOnLongClickListener { listener.run(); true }
}
XML: app:onLongClick="#{() -> viewModel.onLongClickRowNoReturn()}"
Here's the docs as Bahman also linked, though they are not very informative regarding this issue.
My viewModel example:
override fun onLongClickRow():Boolean {
Toast.makeText(context, "LongClick", Toast.LENGTH_SHORT).show()
return true
}
override fun onLongClickRowNoReturn() {
Toast.makeText(context, "LongClick without return", Toast.LENGTH_SHORT).show()
}
onLongClick and onLongClickListener do the exact same thing because there is a BindingMethod that connects onLongClick to setOnLongClickListener in ViewBindingAdapter.java.
It seems like the IDE just complains about any android: prefixed attributes that don't exist in the framework. This is why it doesn't complain about the app: versions. However, they are not always freely interchangeable because for example android:text does some performance optimizations under the hood whereas app:text would just call setText directly.
I am trying to learn data binding with Kotlin and I was able to implement it successfully for edit text and text views. After that I am trying to use it for Image Views. I am currently trying to give an option to users to choose their profile picture by clicking on the imageview. This code works properly but when I try to set the image to the view using data binding adapter , I get the following error.
Found data binding errors.
****/ data binding error ****msg:Cannot find the getter for attribute 'android:userImage' with value type java.lang.String on de.hdodenhof.circleimageview.CircleImageView. file:/home/parangat-pt-p10/AndroidStudioProjects/ReUsableAndroid/reusable_android/app/src/main/res/layout/activity_signup.xml loc:25:12 - 31:48 ****\ data binding error ****
Below is my code for the same.
Layout code of ImageView
<de.hdodenhof.circleimageview.CircleImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center_horizontal"
android:userImage="#{signup.userImage}"
android:id="#+id/iv_user"
android:src="#drawable/profile"/>
Model class code
class Signup {
var userImage=""
var firstName=""
var lastName=""
var phoneNumber=""
var postCode=""
var country=""
var email=""
var password=""
var confirmPassword=""
var isAcceptTerms=false
#BindingAdapter("android:userImage")
fun loadImage(view: CircleImageView, imageUrl: String) {
userImage=imageUrl
Glide.with(view.context).load(imageUrl).into(view)
}
}
And this is what I am doing after user selected image
override fun onSingleImageSelected(uri: Uri?) {
signupBinding.signup?.loadImage(iv_user,uri.toString())
}
Since this is written in kotlin, therefore there is no need to define the getter and setter methods but the error states that no getter method found.
As suggested by Enzokie, I created the binding Adapter in separate file like below
#BindingAdapter("userImage")
fun loadImage(view: CircleImageView, imageUrl: String) {
Glide.with(view.context).load(imageUrl).into(view)
}
But I still have the same issue.
Correct Approach
Use either Observable / Live Data.
Make a binding adapter class individually and don't mess-up things in model.
Yes tutorials do that, because they are just teaching you.
Just make one common binding adapter (like android:src) for whole app.
No need to use custom namespace, until when you need it. So you can use android:src instead of android:userImage.
No need to use CircleImageView in BindingAdapter, make common adapter with ImageView because CircleImageView is child of ImageView.
Final code
If you need to manually change fields like signup.userImage = "someUrl" then use Bindable and notify, other wise no need of both.
If you use ObservableField instead of extending BaseObservable class, then you don't need to use Bindable and notify.
Signup.class
class Signup : BaseObservable() {
#get:Bindable
var userImage: String = ""
set(value) {
field = value
notifyPropertyChanged(BR.userImage)
}
}
DataBindingAdapter.kt
// binding adapter for setting url/uri on ImageView
#BindingAdapter("android:src")
fun setImageUrl(view: ImageView, url: String) {
Glide.with(view.context).load(url).into(view)
}
layout.xml
<de.hdodenhof.circleimageview.CircleImageView
...
android:src="#{signup.userImage}"/>
Now you can set binding.signup.userImage = "Url", it will refract on UI automatically.
That's all!
Reason of Fail
When you use data binding, and you want UI automatic update after setting fields. then your model should be one of below :
Either extend BaseObservable
Or fields must be Observable fields
Or using LiveData
In your case, initially your URL is empty (""). Now when you set image after some time programmatically, then UI is not notified because you are not using any observing option like I said above.
Bit more info
The difference between both is, Live data is Android Lifecycle Aware (Activity/ Fragments/ Services).
LiveData is an observable data holder class. Unlike a regular
observable, LiveData is lifecycle-aware, meaning it respects the
lifecycle of other app components, such as activities, fragments, or
services. This awareness ensures LiveData only updates app component
observers that are in an active lifecycle state.
Use this
#BindingAdapter({"bind:userImage"})
Instead of this
#BindingAdapter("android:userImage")
And in CircleImageView
<de.hdodenhof.circleimageview.CircleImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center_horizontal"
app:userImage="#{signup.userImage}"
android:id="#+id/iv_user"
android:src="#drawable/profile"/>
here is the good article for Loading images with data binding
try by removing android TAG
#BindingAdapter("userImage")
fun loadImage(view: CircleImageView, imageUrl: String) {
userImage=imageUrl
Glide.with(view.context).load(imageUrl).into(view)
}
AND
<de.hdodenhof.circleimageview.CircleImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center_horizontal"
app:userImage="#{signup.userImage}"
android:id="#+id/iv_user"
android:src="#drawable/profile"/>
Alongwith the answer given by Khemraj make sure you have marked Binding Adapter as #JvmStatic and it should be added above #BindingAdapter annotation. I wasted a whole day as I had added jvmstatic after Binding Adapter annotation.
object ImageBindingAdapter {
#JvmStatic
#BindingAdapter("android:src")
fun setImage(imageView: ImageView, uri: String) {
Glide.with(imageView.context).load(uri).placeholder(R.drawable.no_image).error(R.drawable.no_image).centerCrop().into(imageView)
}}
I'm trying to figure out the best way to do Android View Binding in Kotlin. It seems like there are a few of options out there:
findViewById
val button: Button by lazy { findViewById<Button>(R.id.button) }
Butterknife
https://github.com/JakeWharton/butterknife
#BindView(R.id.button) lateinit var button: Button
Kotlin Android Extensions
https://kotlinlang.org/docs/tutorials/android-plugin.html
import kotlinx.android.synthetic.main.activity_main.*
I'm pretty familiar with findViewById and Butterknife in java land, but what are the pros and cons of each view binding approach in Kotlin?
Does Kotlin Android Extensions play well with the RecyclerView + ViewHolder pattern?
Also how does Kotlin Android Extensions handle view binding for nested views via include?
ex: For an Activity using activity_main.xml, how would View custom1 be accessed?
activity_main.xml
<...>
<include layout="#layout/custom" android:id="#+id/custom" />
</>
custom.xml
<...>
<View android:id="#+id/custom1" ... />
<View android:id="#+id/custom2" ... />
</>
There are a lot of ways to access views in Android. A quick overview:
My advise would be:
findViewById: old school. Avoid.
ButterKnife: old school, but less boilerplate and some added functionality. Still lacks compile time safety. Avoid if possible.
Kotlin Synthetic: really a elegant cached version of findViewbyId. Better performance and way less boilerplate but still no (real) compile time safety. Will be no longer supported from Kotlin 1.8. Avoid if possible.
ViewBinding: Google's recommendation nowadays. It's faster than databinding and prevents logic errors inside XML (hard to debug). I use this option for all new projects.
Data Binding: most versatile option, since it allows code inside XML. Still used on a lot of existing projects. But can slow down build times (uses annotation processor just like ButterKnife) and a lot of logic inside XML has become a bit of anti pattern.
See also: https://www.youtube.com/watch?v=Qxj2eBmXLHg
Funny to note that Jake Wharton (original author of ButterKnife) has now joined Google and works on ViewBinding.
kotlin-android-extensions is better for Kotlin. ButterKnife is also good but kotlin-android-extensions is a better and smart choice here.
Reason : Kotlin uses synthetic properties and those are called on demand using caching function(Hence slight fast Activity/Fragment loading) while ButterKnife binds all view at a time on ButterKnife.bind()(that consumes slight more time). With Kotlin you don't even need to use annotation for binding the views.
Yes it also plays good with RecyclerView + ViewHolder pattern, you just need to import kotlinx.android.synthetic.main.layout_main.view.*(if layout_main.xml is Activity/Fragment layout file name).
You do not need to do any extra effort for layout imported using include. Just use id of imported views.
Have a look at following official documentation notes:
Kotlin Android Extensions is a plugin for the Kotlin compiler, and it does two things:
Adds a hidden caching function and a field inside each Kotlin Activity. The method is pretty small so it doesn't increase the size of APK much.
Replaces each synthetic property call with a function call.
How this works is that when invoking a synthetic property, where the receiver is a Kotlin Activity/Fragment class that is in module sources, the caching function is invoked. For instance, given
class MyActivity : Activity()
fun MyActivity.a() {
this.textView.setText(“”)
}
a hidden caching function is generated inside MyActivity, so we can use the caching mechanism.
However in the following case:
fun Activity.b() {
this.textView.setText(“”)
}
We wouldn't know if this function would be invoked on only Activities from our sources or on plain Java Activities also. As such, we don’t use caching there, even if MyActivity instance from the previous example is the receiver.
Link to above documentation page
I hope it helps.
I can't flag this question as a duplicate, as you're asking multiple things that have been answered / discussed under different questions.
What are the pros and cons of each view binding approach in Kotlin?
This has been discussed here.
How does Kotlin Android Extensions handle view binding for nested views via include? ex: For an Activity using activity_main.xml, how would View custom1 be accessed?
All Kotlin Android Extensions does is call findViewById for you. See here.
Does Kotlin Android Extensions play well with the RecyclerView + ViewHolder pattern?
Yes, it does. However, you have to use save the Views you get from it into properties, as there is no cache for them like in Activities or Fragments. See here.
If you still have unanswered questions, feel free to ask for clarification.
Take care of using
val button: Button by lazy { findViewById<Button>(R.id.button) }
I already confront the problem when the view is destroyed, and as the instance of your fragment survive(I think in the case of acitivities it doesn't apply), they hold the lazy property referencing to the old view.
Example:
You have an static value in the layout, let say android:text="foo"
//calling first time
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
button.setText("bar")
// button is called for the first time,
// then button is the view created recently and shows "bar"
}
Then the fragment get destroyed because you replace it, but then ou comeback and it regenerated callin onCreateView again.
//calling second after destroyed
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
button.setText(Date().time.toString())
//button is already set, then you are setting the value the to old view reference
// and in your new button the value won't be assigned
// The text showed in the button will be "foo"
}
Now there is a fourth option which is called View Binding, available with Android Studio 3.6 Carnary 11
Quoting from docs.
View Binding
View binding is a feature that allows you to more easily write code
that interacts with views. Once view binding is enabled in a module,
it generates a binding class for each XML layout file present in that
module. An instance of a binding class contains direct references to
all views that have an ID in the corresponding layout.
In most cases, view binding replaces findViewById.
Differences from findViewById
View binding has important advantages over using findViewById:
Null safety: Since view binding creates direct references to views, there's no risk of a null pointer exception due to an invalid
view ID. Additionally, when a view is only present in some
configurations of a layout, the field containing its reference in the
binding class is marked with #Nullable.
Type safety: The fields in each binding class have types matching the views they reference in the XML file. This means that
there's no risk of a class cast exception.
Differences from the data binding library
View binding and the data binding library both generate binding
classes that you can use to reference views directly. However, there
are notable differences:
The data binding library processes only data binding layouts created using the <layout> tag.
View binding doesn't support layout variables or layout expressions, so it can't be used to bind layouts with data in XML.
Usage
To take advantage of View binding in a module of your project, add the
following line to its build.gradle file:
android {
viewBinding.enabled = true
}
For example, given a layout file called result_profile.xml:
<LinearLayout ... >
<TextView android:id="#+id/name" />
<ImageView android:cropToPadding="true" />
<Button android:id="#+id/button"
android:background="#drawable/rounded_button" />
</LinearLayout>
In this example, you can call ResultProfileBinding.inflate() in an
activity:
private lateinit var binding: ResultProfileBinding
#Override
fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
binding = ResultProfileBinding.inflate(layoutInflater)
setContentView(binding.root)
}
The instance of the binding class can now be used to reference any of
the views:
binding.name.text = viewModel.name
binding.button.setOnClickListener { viewModel.userClicked() }
if you using datainding library. you should databinding view binding.
because it is explict more then kotlin-extensions
p.s findviewbyid is very inconvenience and boilerplate code
I am working on DataBinding with BindingAdapter. Here is my custom method.
#BindingAdapter("{bind:fadevisible}")
public static void setFadeVisible(LinearLayout view, int visible) {
Log.e("Bindings", "setFadeVisible: ");
}
And in xml file i am calling it like
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:fadevisible="#{1}"/>
But it is showing error
Error:Execution failed for task ':app:compileDebugJavaWithJavac'.
java.lang.RuntimeException: Found data binding errors.
****/ data binding error ****msg:Cannot find the setter for attribute 'app:fadevisible' with parameter type int on android.widget.LinearLayout.
file:\app\src\main\res-main\layout\activity_detail.xml
loc:236:31 - 236:54
****\ data binding error ****
I have checked this and this thread but somehow it is not helping me, as you can see i am passing int from xml and in BindingAdapter also i have mentioned LinearLayout with int value.
Even i have another method, where just parameters are different and its working fine
#BindingAdapter({"bind:image_round"})
public static void loadRoundImage(ImageView imageView, String url)
Make sure in app level gradle, you have apply plugin: 'kotlin-kapt'
Your #BindingAdapter definition looks a little bit odd to me
#BindingAdapter("{bind:fadevisible}")
This is not the same like
#BindingAdapter({"bind:fadevisible"})
or
#BindingAdapter("bind:fadevisible")
which should work perfectly fine.
I had this problem with binding to ImageView and unlike your case, the definition of my binding adapter was correct but still, the IDE kept giving me this error message. After spending many hours on searching for the cause, I figured that the namespace that I use in xml layout file needs to be exactly what I declared in #BindingAdapter.
So, if my xml is like below:
<ImageView
android:id="#+id/logo"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_alignParentRight="true"
android:layout_marginRight="16dp"
android:layout_marginTop="16dp"
app:image_url="#{item.logoUrl}"
/>
Then my binding method should be as below:
#BindingAdapter({"app:image_url"})
public static void loadImage(ImageView view, String logoUrl) {
if (logoUrl == null) {
view.setImageResource(R.drawable.ic_place_holder);
} else {
Glide.with(getContext()).load(logoUrl).crossFade().into(view);
}
}
Note that binding method annotation indicates the namespace in it , i.e. #BindingAdapter({"app:image_url"}) exactly as it is used in layout file app:image_url="#{item.logoUrl}"
So unlike what is said in most tutorials, don't use #BindingAdapter({"bind:image_url"}) in your binding method and app:image_url="#{item.logoUrl}" in your xml file.
You try
#BindingAdapter("bind:fadevisible")
I had initially set defined my customBindidingAdapter as private:
#BindingAdapter("setPriorityColor")
private static void getPriorityColor(TextView textView, int priority) {
}
In my particular case, my BindingAdapter had two parameters, with requireAll, and I had neglected to put one of them on the element in my layout XML. So, like this: (Kotlin, I know)
#BindingAdapter("app:arg1", "app:arg2", requireAll = true)
fun MyAdapter(view: ImageView, x: String, y: Int) {
// ...
}
<Element app:arg1="#{"foo"}"/>
The error was roughly Cannot find the setter for attribute "app:arg1" with parameter String which is perfectly true, there is no such adapter; there's only one for two args.
One hint that this was happening was that Android Studio indicated that MyAdapter was an unused function by coloring it grey.
Obviously a more eloquent error message like "there is no adapter for app:arg1 of type String but there is one for..." (when one of the attribute names matches) would be appreciated, but I won't hold my breath.
Add on to the answers if you are working on multiple modules then where you have
#BindingAdapter("fadevisible")
That module should have the following code in the module -> build.gradle.
dataBinding {
enabled = true
}
Enjoy Happy coding. :)
Apart from #BindingAdapter improvements
(mine were working fine in one build and not in another),
upgrading the Build gradle version to the latest one worked for me.