Errors with android:id fields in layouts not anymore recognized - android

Following an upgrade of android-studio, kotlin plugins and sourceCompatibility to Java 8 in an android project, many layout ids are not any more recognized (but strangely not all).
It looks like field name with underscores are not recognized in binding.
For instance:
whith the following declaration in the layout
<TextView
android:id="#+id/alert_hist_item__equipement"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5px"
android:text="#string/Device"
android:textStyle="bold"
/>
in the code
alert_hist_item__equipement.text = "x"
is not recognized but
alertHistItemEquipement.text = "x"
is recognized.
If I change the field names in the layouts to equivalent camel case names (either only in the code, with the names in the layout still having underscores; or both in code and layout) the fields are recognized.
It is so painful for many reasons:
I have dozens of layouts all with id names containing underscore
I find names with underscore easier to read (and my team too)
it was never specified that android:id fields should follow certain rules
is this possible to change that behavior ? Could it be a bug ?

Please add following id 'kotlin-android-extensions' to you app level gradle.
plugins {
id 'kotlin-android-extensions'
}
According to Kotlin documentation they are deprecated but they are still working for me.
As and alternate they provide us with data and view binding. For you View Binding should work!
here is the example:
add following to your app level gradle:
buildFeatures{
viewBinding true
}
then in you activity declare as following (Assuming you activity name is ManuelYguel)
class ManuelYguel : AppCompatActivity() {
private lateinit var binding: ActivityManuelYguelBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityManuelYguelBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.alertHistItemEquipement.text = "x"
}
}

It is a sad and unfortunate constraint that comes from the new autogenerated binding class. This is specified in https://developer.android.com/topic/libraries/data-binding/expressions
it says:
Note: The binding class converts IDs to camel case.

Related

How can I dynamically update widget text using "latest" best Kotlin Android pratcices?

I created a new Kotlin project in the latest version of Android Studio, using the empty activity project template, which only contains a TextView with "Hello, World!" in its main layout. Then:
I enabled viewBinding in the module build.gradle.
Defined binding in MainActivity.kt (as shown in the last listing).
Gave that TextView an identifier txt_hello as follows:
<?xml version="1.0" encoding="utf-8"?>
<...>
<TextView
android:id="#+id/txtHello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</...>
Then I went on to MainActivity.kt file to define some code that modifies the text attribute of the txt_hellow TextView widget as follows:
package com.example.myapplication
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
// I added this:
import com.example.myapplication.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// I removed this:
// setContentView(R.layout.activity_main)
// I added this:
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.txtHello.setOnClickListener {
binding.txtHello.setText("lel")
}
}
}
But, I get the following error:
String literal in setText can not be translated. Use Android resources instead.
How can I update widgets using the viewBinding approach?
First I think that's a warning that you shouldn't use a hardcoded string in the text view, because it's a bad practice and cannot be translated.
So you have to create some string values in your strings.xml, for example:
<string name="text_hello">Hello</string>
And use that string value like this
binding.txtHello.text = getText(R.string.text_hello)
That error is because you can't use binding.txtHello.text = "lel" The reason for this is that there are two definitions of setText- one for a string and one for an int (a text resource). This means the code that converts setters from Java to Kotlin doesn't know which to call when you try to assign to the text variable, so it throws this error. Instead, use binding.txtHell.setText("lel"). You need to do this whenever there are multiple functions named setXXX for a given XXX written in Java.

Android Kotlin - viewBinding Import unresolved reference on views

I followed multiple tutorials on how to implement this new nonsense.
I removed
apply plugin: `kotlin-android-extensions`
from gradle, added:
android {
...
buildFeatures {
viewBinding true
}
}
and then in activity (not MainActivity but another one because it's the first one that creates error on rebuild):
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
totorials are showing that the views have to be used this way now:
binding.whatEverView
but I still have unresolved reference on those views.
Now I'm wondering if the correct class gets automatically imported which is:
import de.blaa.blaaaa.databinding.ActivityMainBinding
None of the tutorials are showing what actual class supposed to be imported so is this the correct one? What am I missing?
Check that the id of "whateEverView" doesn't have a leading underscore or something.
In my case, I found that my field had an id of "_what_ever_view" in the layout xml. Changing it to "what_ever_view" fixed it.
Not sure if the problem is limited to leading underscores, or even if it's a specific version of the plug-in, but it does appear to be a bug. In my case, the issue field appears totally valid during code editing (code completion knows about the field), but the "unresolved" issue appears during build time.

Should we use butterknife or findViewById() with Kotlin Android project as we can directly access views by id

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.

How do not use findViewById?

Usually we do not use findViewById (R.id.listView) in kotlin because Android studio do it for us automatically (we do not need to find view).
But this example shows that we need to you it (in this line of code):
val listView = findViewById<ListView>(R.id.listView) as ListView.
Why do we use this line in this example? How to not use it?
If you're using findViewById from Kotlin, you should never need a cast (from API level 26 and up). You should use it one of these two ways:
val myTV1 = findViewById<TextView>(R.id.myTextView)
val myTV2: TextView = findViewById(R.id.myTextView)
And then you can access its properties via these variables:
myTV1.text = "testing"
This is a perfectly valid way of getting View references and using them in Kotlin as it is.
However, if you also have Kotlin Android Extensions enabled in the project (by the apply plugin: 'kotlin-android-extensions' line in your module level build.gradle file), you also can refer to your Views by their IDs via the synthetic properties it provides, just make sure you have the correct imports, for example:
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
myTextView.text = "testing"
}
}
Note that Kotlin Android Extensions is entirely optional to use, and if you do use it, findViewById of course is still available if for whatever reason you want to mix the two methods.
In general, when you need a view from a layout file, you can import the following:
kotlinx.android.synthetic.main.<layout filename>.<id of view>
If you need all of the views from a layout file, you can use:
kotlinx.android.synthetic.main.<layout filename>.*
It's only applicable for Kotlin
Step 1:
Add this Kotlin Android Extensions in our code
Gradle Scripts -> build.gradle(Module)
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
NOTE: Now you can directly access the view id without using findViewbyId, in case if you face any red line press alt+enter, or import this in KT file.
import kotlinx.android.synthetic.main.activity_your_layout.*

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