Kotlin synthetic extension for view - android

I have a layout with some views, one of them has the id title_whalemare
import kotlinx.android.synthetic.main.controller_settings.*
import kotlinx.android.synthetic.main.view_double_text.*
class MainSettingsController : BaseMvpController<MvpView, MvpPresenter>() {
val title: TextView = title_whalemare
override fun getLayout(): Int {
return R.layout.controller_settings
}
}
I try find it with kotlin extensions, but I can't because I get the following error
None of the following candidates is applicable because of receiver type mismatch
controller_settings.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/title_whalemare"/>
</LinearLayout>
Where is my mistake?

Edit: synthetics are now deprecated and will be removed. Please see this blog post for details.
What the error is trying to tell you is that you can only access views with Extensions from either within an Activity or a Fragment. This is because it does the exact same thing to find the views with the given IDs as what you'd be doing manually, it just calls Activity.findViewById() and Fragment.getView().findViewById(), and then does the type cast to the specific subclass of View. I'm guessing that your Controller is not an Activity or Fragment.
There's another way to use Extensions, if you can pass in the root view of your layout to your controller somehow. Then you could do the following:
val title: TextView = rootView.title_whalemare
Again, this is just replacing the View.findViewById() call and the type cast.

Related

Viewbinding within Android and Exoplayer

I'm using Android Exoplayer in one of my Fragment.
Within Exoplayer I use a custom control layout"#layout/custom_player" for the controls.
I have different elements within the layout for example I have a button element"optionBtn" which I want to connect to onclicklistener from my Kotlin code. Unfortunately that doesn't go very smoothly with view binding.
This is the XML Exoplayer
<com.google.android.exoplayer2.ui.PlayerView
android:id="#+id/playerVIew"
app:resize_mode="fill"
android:animateLayoutChanges="true"
app:controller_layout_id="#layout/custom_player"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
This is the kotlin code
...
private var binding: FragmentVideoBinding? = null
private var btnsheetOptions: SheetOptionsBinding? = null
private var sheetDialog: BottomSheetDialog? = null
private var customPlayer: CustomPlayerBinding? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
btnsheetOptions = SheetOptionsBinding.inflate(inflater, null, false)
sheetDialog = BottomSheetDialog(requireContext(), R.style.BottomSheetDialogTheme)
binding = FragmentVideoBinding.inflate(inflater, container, false)
customPlayer = CustomPlayerBinding.inflate(inflater, binding!!.root, true)
return binding!!.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val simpleExoPlayer = SimpleExoPlayer.Builder(requireContext()).build()
binding!!.playerVIew.player = simpleExoPlayer
val mediaItem = MediaItem.fromUri(video.toString())
simpleExoPlayer.addMediaItem(mediaItem)
simpleExoPlayer.prepare()
simpleExoPlayer.playWhenReady = true
customPlayer!!.optionBtn.setOnClickListener {
...
}
}
override fun onDestroy() {
super.onDestroy()
binding = null
btnsheetOptions = null
sheetDialog= null
customPlayer = null
}
}
...
This way the layout is double-inflated on top of each other and one layout works with onclick listener and the other does not, which is not very useful.
Does anyone know the correct solution for this, I've been working on this for almost all afternoon.
You cannot use view binding with ExoPlayer's custom HUD layout. View Binding only works with layouts specifically inflated for the activity/fragment layouts. The custom HUD layout does NOT belong to the parent layout which the player is set in. It is inflated in a stand-alone fashion and not included in the layout (Hence the double-inflation). Since the custom layout is inflated and is not part of the original layout, you can't use view binding with all the ids contained in it.
So what can you do if View Binding does not work with the custom layout's buttons ?
You should use findViewById which is a function that belongs to Activity class.
It's very easy to use and I assume you already know how as well:
findViewById<ImageButton>(R.id.optionBtn).setOnClickListener {...}
//The next line is for usage inside a fragment class
activity?.findViewById<ImageButton>(R.id.optionBtn).setOnClickListener {...}
Make sure you give the button an ID in your layout, for example:
android:id="#id/optionBtn"
What if you COULDN'T find the (R.id.optionBtn) ?
This is a common problem, there are two R directories to be aware of.
There is android.R which is usually used as R only. And there is the app's R directory. In order to distinguish between the two and avoid Unresolved reference issue, you should import your app's resources under a different name, this is done in the import section before your class code begins, add this:
import com.example.app.R as appR
Then you can try using appR.id.optionBtn instead. There is a very low chance you'd face this particular R.idissue but follow the solution above in case it happens.
Bottom Line:
1- Viewbinding only works for the activity/fragment layout connected to their context classes, it binds the parent layout's id and all their child views with actual binding variables.
2- If you wanna reach a layout that is not part of the activity/fragment layout directly, you should use findViewById instead.
3- If you have problems using the 'R.id', you should import your app's resources under a different name. I usually use 'X' instead of 'R'. But it's all a personal preference..
One shouldn't inflate the data-binding, while also applying attribute app:controller_layout_id:
customPlayer = CustomPlayerBinding.inflate(inflater, binding!!.root, true)
One can only have that either way.
Somehow the question is pointless, unless providing custom_player.xml ...because it may be lacking some mandatory resource IDs, which would be expected to be present (there are certain limitations to what "custom" may permit, which may include: having to provide certain resource ID, even if hiding these from the user). XML markup is quite important on Android - as all the code runs against it. ExoPlayer supports overriding layout files, unless giving them a different name.
Please refer to the original layout resources, in particular their file names and resId:
https://github.com/google/ExoPlayer/tree/release-v2/library/ui/src/main/res/layout
I'd assume, that when overriding by file name, it should also be possible to data-bind.
Because, when only the include has data-binding, then the parent still cannot bind it.
The parent layout XML would need to generate a data-binding, to begin with.
With .setControllerLayoutId(), one can actually data-bind the View before assigning it:
customPlayer = CustomPlayerBinding.inflate(inflater, binding!!.root, true)
binding.playerView.setControllerLayoutId(customPlayer!!.root)
In this case app:controller_layout_id must not be set.

Why doesn't data bing use LiveData or Observable fields?

In my mind, One-way or Two-way data bing use either LiveData or Observable fields.
The following code is from the project https://github.com/enpassio/Databinding
The attribute android:text="#={viewModel.toyBeingModified.toyName}" of the control android:id="#+id/toyNameEditText" bind to viewModel.toyBeingModified.toyName with Two-way data bing.
I'm very strange why viewModel.toyBeingModified is neither LiveData or Observable fields, could you tell me?
fragment_add_toy.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">
<data class="AddToyBinding">
<variable
name="viewModel"
type="com.enpassion.twowaydatabindingkotlin.viewmodel.AddToyViewModel" />
<import type="com.enpassion.twowaydatabindingkotlin.utils.BindingUtils"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="#dimen/margin_standard">
<androidx.cardview.widget.CardView
android:id="#+id/cardEditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="#dimen/margin_standard"
app:cardBackgroundColor="#color/skin_rose"
app:cardCornerRadius="#dimen/card_corner_radius"
app:cardElevation="#dimen/card_elevation"
app:contentPadding="#dimen/padding_standard"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
...
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/toyNameLayout"
style="#style/Widget.Enpassio.TextInputLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="#string/toy_name"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="#+id/guidelineET"
app:layout_constraintTop_toTopOf="parent"
tools:layout_editor_absoluteY="418dp">
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/toyNameEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textCapWords"
android:text="#={viewModel.toyBeingModified.toyName}"/>
</com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
...
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
AddToyViewModel.kt
class AddToyViewModel(private val mRepo: ToyRepository, private val chosenToy: ToyEntry?) : ViewModel() {
val toyBeingModified: ToyEntry
private var mIsEdit: Boolean = false
init {
if (chosenToy != null) {
//This is edit case
toyBeingModified = chosenToy.copy()
mIsEdit = true
} else {
/*This is for adding a new toy. We initialize a ToyEntry with default or null values
This is because two-way databinding in the AddToyFragment is designed to
register changes automatically, but it will need a toy object to register those changes.*/
toyBeingModified = emptyToy
mIsEdit = false
}
}
private fun insertToy(toy: ToyEntry) {
mRepo.insertToy(toy)
}
...
}
ToyEntry.kt
data class ToyEntry(
var toyName: String,
var categories: Map<String, Boolean>,
var gender: Gender = Gender.UNISEX,
var procurementType: ProcurementType? = null,
#PrimaryKey(autoGenerate = true) val toyId: Int = 0
): Parcelable{
/*This function is needed for a healthy comparison of two items,
particularly for detecting changes in the contents of the map.
Native copy method of the data class assign a map with same reference
to the copied item, so equals() method cannot detect changes in the content.*/
fun copy() : ToyEntry{
val newCategories = mutableMapOf<String, Boolean>()
newCategories.putAll(categories)
return ToyEntry(toyName, newCategories, gender, procurementType, toyId)
}
}
In fact, we use LiveData or Observable fields when we need to do something as soon as they changed, search bar can be a good example. But in this case, we don't care when the user is changing the properties of the selected toy (I haven't seen the UI but I'm assuming there is a Save button or something like that). In other words, we don't want to do anything while user is typing b, bo, boa and finally boat.
We just need that data to be once set while the viewmodel is set to binding, let the user change it to whatever and when we want to do the saving process, we want our field to be what user had entered.
In addition, if you use LiveData in your binding (as long as the lifecycleOwner is set) you're adding an observer to you LiveData which can be a point of concern for some geeks πŸ˜‚.
TL;DR
We use LiveData when we want to observe it (which is not required in the example you provided). It's an option not a must. Data binding can set/get data for nearly everything.
I would suggest to start with 1-way data binding first and as soon as this works, extend it to 2-way data binding. What you are doing wrong right now is the following:
android:text="#={viewModel.toyBeingModified.toyName}"
This line of code means that you pass a ToyEntry object to a setText() method of the TextView. That means the TextView would need to have a method with the signature: setText(entry: ToyEntry).
Of course, this method does not exist (yet). So to make this data binding work, you have to define this method yourself by creating a BindingAdapter:
#BindingAdapter("toyEntry")
fun setToyEntry(textView: TextView, toyEntry: ToyEntry) {
// in here you define what to do with the textView. For example:
textView.text = toyEntry.toyName
}
You can create this BindingAdapter in any file without the need to put it into a class.
You can give this method any name you want
The first parameter of this method is the kind of View in the xml that you want to bind the toyEntry to
The second parameter of this method os the object that you set in your xml via #{...}
Now when you write a 1-way databinding like this: binding:toyEntry="#{viewModel.toyBeingModified.toyName}"
The binding namespace can be craeted by AndroidStudio automatically. You can name this anything you want (but not android, since this is already defined)
The toyEntry is what connects this line of xml to your BindingAdapter from the previous step (it corresponds to the same string that you set in the annotation #BindingAdapter(...)
Now, the generated code knows about your binding adapter and calls its method setToyEntry when it computes this data binding. You can also delete the line android:text="#={viewModel.toyBeingModified.toyName}", because it is not used anymore.
Go from there to setup 2-way data binding. Here you also have to create #InverseBindingAdapter as explained here: https://developer.android.com/reference/android/databinding/InverseBindingAdapter
Some more comments: Depending on your gradle version, you have to enable databinding and also make sure to have all dependencies and gradle plugins setup.
More on that here: https://developer.android.com/jetpack/androidx/releases/databinding?hl=en

problems using databinding: val vs var and the use invalidateAll()

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.

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.

Kotlin Android View Binding: findViewById vs Butterknife vs Kotlin Android Extension

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

Categories

Resources