I ran into a strange issue in android development, a ClassCastException happened in running time when show my XTextView.
I have 2 modules and imported via aar, say module_A and module_B. Each of them has a XTextView, and a layout named search_result, and other stuff not relate so I omit here, and their structure looked like this:
module_A
src...
java
XTextView.java
res...
search_result.xml
module_B
src...
java
XTextView.java
res...
search_result.xml
Also these two XTextView has different package:
com.module_A.XTextView
com.module_B.XTextView
And module_A did not dependent on module_B, module_B did not dependent on module_A.
Then I set module_A.search_result.xml to AResultAdapter, whom is a recylcerView adapter to display the result elements with following code:
package com.module_A.search
AResultAdapter {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ResultItemHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.search_result, parent, false)
return ResultItemHolder(view)
}
class ResultItemHolder(parent:View):RecyclerView.ViewHolder(parent){
val result:XTextView = parent.findViewById(R.id.x_text_view) // crash here
// other implement
}
}
And I use this adapter in SearchActivity in module_A, and when I need to show search result, I just startActivity from any activity in app modules.
Then a lassCastException happened in parent.findViewById(R.id.x_text_view), said:
java.lang.ClassCastException: module_B.XTextView cannot be cast to module_A.XTextView
Why did I get module_B.XTextView from module_A.search_result.xml. I assume it came from they have same layout and class name. But I can not modify the aar, so how can I work around and prevent render wrong layout?
Appreciate any help.
Related
Hi and thanks for reading in advance,
I have built a small project testing an adapter to populate a main activity view with a custom xml template [this template holds some fields that are populated by some simple test data and images inside the project]
The build is fine afaik, I have debugged it as best I can to yield no errors and I cant actually find the error as i follow the build in debug so it's likely my noob-like experience with kotlin, android studio and maybe the later development approach changes.
My best guess after following it through a few (way more ha) times, at this stage it seemed to be going wrong during the android part of the build versus any prep and adapter code so it could be some settings I havent invoked maybe...
Also probably importantly: I saw it pull in the recyclerView during debug(prior to any customisation from the imported xml layout im using), the visual then disappears, but i do see all the code being populated by the functions ive created in MainActivity and the adapter does seem to return to MainActivity with the respective data constructed.
I enclose the project zipped as there is no discernable error code or post runtime exit code that i can find [thats not to say there isn't one, I'm newoob to the IDE :( ]
I'm targeting an Android 7.0 600*1024 mdpi using API 24 on a 7" (LH flip)portrait orientation x86 emulator
I'm using Android Studio 2021.1.1 patch 3 if that's important to know too
Thanks again for anyone who can take a peek at it to help me along, im proper stuck, been so for couple days now, thinking of a complete redo again :/
:)
I enclose a link to the zipped file below [due to no errors in debug to provide here]
[Edit: Cripes I forgot to mention I'm also using CircleImageViewer, not sure if that causing an issue but it doesn't appear to be ]
[Final edit: As can be seen below this was answered by Tyler V who understood my problem better than I could pose the question - please see below for a complete resolution/ refactor of code by them that works correctly - thanks again to Tyler V]
project on googledrive
I took a look at you app, the error it is throwing (look in the Logcat tab!) is this
Process: com.example.kayakthing, PID: 4289
java.lang.IndexOutOfBoundsException: Index: 5, Size: 5
at java.util.ArrayList.get(ArrayList.java:411)
at com.example.kayakthing.HiresAdapter.onBindViewHolder(HiresAdapter.kt:34)
at com.example.kayakthing.HiresAdapter.onBindViewHolder(HiresAdapter.kt:11)
Problem
The reason it is throwing this is because you are supplying different length arrays to your adapter. Take a look at the adapter code below - in your current code the lengths of nameList and clientDetailsList are both 7 but the length of hireImageList is 5. Since you define getItemCount based on the length of nameList - the adapter crashes when trying to show values for positions past the end of the hireImageList size.
override fun onBindViewHolder(holder: HiresViewHolder, position: Int) {
holder.tvClientName.text = nameList[position]
holder.tvClientDetails.text = clientDetailsList[position]
holder.imageView.setImageResource(hireImageList[position])
}
override fun getItemCount(): Int {
return nameList.size
}
Solution
I recommend you 1) learn to read the Logcat tab to find errors, and 2) define a data class to hold the three items you need in your adapter, so you can pass in a single list of that data class instead of three lists. That way there is no way to pass in lists with mis-matched lengths.
For example, use something like this and have the adapter take a single List<HiresAdapter.Data> instead of three lists.
class HiresAdapter(
private var adapterData: ArrayList<Data>,
private var context: Context) : RecyclerView.Adapter<HiresAdapter.HiresViewHolder>() {
// Define a data class in the adapter to hold the data
// needed for each row
data class Data(val name: String, val details: String, val image: Int)
class HiresViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var tvClientName : TextView = itemView.findViewById(R.id.tvClientName)
var tvClientDetails : TextView = itemView.findViewById(R.id.tvClientDetail)
var imageView : CircleImageView = itemView.findViewById(R.id.hireImageView)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) : HiresViewHolder {
this.context = parent.context
val view : View = LayoutInflater.from(this.context).inflate(R.layout.rv_card_design,parent, false)
return HiresViewHolder(view)
}
override fun onBindViewHolder(holder: HiresViewHolder, position: Int) {
holder.tvClientName.text = adapterData[position].name
holder.tvClientDetails.text = adapterData[position].details
holder.imageView.setImageResource(adapterData[position].image)
}
override fun getItemCount(): Int {
return adapterData.size
}
}
and populate it like this
val adapterData = ArrayList<HiresAdapter.Data>()
adapterData.add(HiresAdapter.Data("Geoff", "Geoff", R.drawable.solo))
adapterData.add(HiresAdapter.Data("Frogme", "Frogme", R.drawable.tandem))
adapterData.add(HiresAdapter.Data("Jenny", "Jenny", R.drawable.quattro))
adapterData.add(HiresAdapter.Data("Benny", "Benny", R.drawable.sup))
adapterData.add(HiresAdapter.Data("Sylvia", "Sylvia", R.drawable.croc))
adapter = HiresAdapter(adapterData, this#MainActivity)
I have an AppCompatAutoEditTextView in my layout. This view is populated from an enum class called FlavorType using ArrayAdapter.
Now, I want to implement 2-way DataBinding, with the text attribute being bound to a MutableLiveData. For this to work, I implemented the following Converter:
object Converter {
#InverseMethod(value = "flavor_str_to_enum")
fun flavor_enum_to_str(view: AppCompatAutoCompleteTextView, value: FlavorType): String {
return when (value) {
FlavorType.SWEET -> view.context.getString(R.string.label_flavor_sweet)
FlavorType.SAVORY -> view.context.getString(R.string.label_flavor_savory)
FlavorType.NONE -> view.context.getString(R.string.label_none)
else -> view.context.getString(R.string.label_none)
}
}
fun flavor_str_to_enum(view: AppCompatAutoCompleteTextView, value: String): FlavorType {
return when (value) {
view.context.getString(R.string.label_flavor_sweet) -> FlavorType.SWEET
view.context.getString(R.string.label_flavor_savory) -> FlavorType.SAVORY
view.context.getString(R.string.label_none) -> FlavorType.NONE
else -> FlavorType.NONE
}
}
}
After this, in my XML for my view, I added the following line:
android:text="#={Converter.INSTANCE.flavor_enum_to_str(viewmodel.flavor)}
where, flavor is of type MutableLiveData. Also, I added import tags for my Converter class at the top of the XML.
Now, for some reason unknown to me, just by adding the above line in my xml, my build starts failing with a cannot find symbol error that the BindingImpl associated with my XML was not found. I looked in the generated code folders and yes, the Fragment...BindingImpl for my xml is not there while for other layouts they are present. Just by removing the above line of code, everything starts working again. I have tried everything, invalidating, restarting, etc. But, this issue still seems to persist.
Can anyone provide insights into, what could be happening or what I might be doing wrong?
EDIT:
Running gradle build with --stacktrace, I get the following error:
[databinding] {"msg":"cannot find method flavor_enum_to_str(dev.example.myapp.model.FlavorType) in class dev.example.myapp.utils.Converter","file":"app\\src\\main\\res\\layout\\fragment_addrecipe.xml","pos":[{"line0":119,"col0":41,"line1":119,"col1":86}]}
This leads me to believe that it's trying to look up the above mentioned function signature, but instead it finds:
flavor_enum_to_str(AppCompatAutoCompleteTextView, FlavorType)
I followed the documentation and implemented things the way it described.
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.
Good day!
Using Kotlin 1.1.51 in Android Studio 3.0, targeting Android API 26 to create RecyclerView with next ViewHolder, but receiving error on building the project:
Type mismatch: inferred type is View! but TextView was expected
So I can't find TextView directly to ViewHolder variable, but found around way - find View and after that cast with as TextView as you can see in the code for holder.textView. Doesn't look so good, so are there solutions how to prevent this error or is it a bug?
The code of RecyclerView.Adapter:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.custom_item_view, parent, false)
return VH(view)
}
override fun onBindViewHolder(holder: VH, position: Int) {
val event: TimelineEvent = items[position]
// does not work because of error in VH class
holder.timeView.text = event.time
// works
(holder.textView as TextView).text = event.text
}
class VH(itemView: View) : RecyclerView.ViewHolder(itemView) {
// error: Type mismatch: inferred type is View! but TextView was expected
val timeView: TextView = itemView.findViewById(R.id.timeline_item_time)
// works fine
val textView: View = itemView.findViewById(R.id.timeline_item_text)
}
It seems like you're not on API level 26 or newer yet. That's when findViewById was changed so that it returns a generic T instead of the base View class, which enables you to use it from Kotlin in these ways.
You can either manually cast the result of the findViewById call as #AlexTa suggested in the other answer, or you can update your support library version to 26 or later - the latest currently is 27.0.0. These new versions are available from Google's Maven repository.
You just need to cast found view as expected type, to do so:
val timeView: TextView = itemView.findViewById(R.id.timeline_item_time) as TextView
or
val timeView: TextView = itemView.findViewById<TextView>(R.id.timeline_item_time)
This question is several years old, but maybe my answer will help someone who comes across this question after making a simple error like I just did.
I came across a similar error (mine was inferred type is View but ImageView was expected) and while I was reading the answers here, I realized that I had two layouts generated by the primary/detail template in Android Studio. I had changed an element (id/item_detail) from TextView to ImageView in res/layout/fragment_item_detail.xml, but I only did it for the regular layout, and not for the layout in res/layout-sw600dp/fragment_item_detail.xml
Since the same id had different types in the two layouts, the binding couldn't cast the associated view to ImageView. Instead, it fell back to the common parent, which is View.
Changing both elements to ImageView (which is what I wanted anyway) fixed the error.
So, one thing you need to do to avoid this sort of error is to make sure that layout elements with the same id in the same-named layout files have the same class across all layout folders.
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