Kotlin casting between classes fails - android

I'm learning to code in Android with Kotlin, and have issues when casting between classes.
So I had these classes defined:
abstract class ListFragment : Fragment()
class NewListFragment : ListFragment()
and when I tried to use it when implementing a function that returns a Fragment, it throws ClassCastException. There was IDE warning about the failing cast too
override fun getItem(position: Int): Fragment {
return when(position){
0 -> NewListFragment() as Fragment
I don't know where I got wrong

Are you using the same Fragment class?
android.app.Fragment (deprecated as of Android P)
android.support.v4.app.Fragment
You seem to be casting to android.support.v4.app.Fragment, judging from the exception message. Are your imports in that file incorrect?
The warning in IntelliJ about an impossible cast only appears when it is truly impossible to cast to that specific type (that is, when their type hierarchies are completely different), which is why I think that this is likely the problem.
Additionally, you don't need to cast to a supertype. Such a conversion is already inferred, so you can remove the cast.

Related

Kotlin call function with parameter by using Any is xxx || Any is yyy not working

my variable passed into my class constructor can be either Fragment or AppCompactActivity, so I use the following code to check wther it's valid in Glide's .with function.
class XXXX(private val parent: Any)
if (parent is AppCompatActivity || parent is Fragment) {
// Load User Avatar
Glide.with(parent)
.load(dataSet[position].user.avatar)
.into(viewHolder.userAvatar)
}
when I only use parent is AppCompactActivity or parent is Fragement, the code works just fine. However, when I use an or operator, it gives me the following errors:
None of the following functions can be called with the arguments supplied.
with(Activity) defined in com.bumptech.glide.Glide
with(android.app.Fragment) defined in com.bumptech.glide.Glide
with(Context) defined in com.bumptech.glide.Glide
with(View) defined in com.bumptech.glide.Glide
with(androidx.fragment.app.Fragment) defined in com.bumptech.glide.Glide
with(FragmentActivity) defined in com.bumptech.glide.Glide
Kotlin is a static, strongly typed language, so it always requires to understand what is the type of an object and which function exactly is invoked. Well, this is only partially true, it supports virtual functions, but Glide.with() is overloaded, so it has to be resolved at compile time.
If you use parent is AppCompactActivity then the compiler is sure parent is AppCompactActivity, so when you invoke Glide.with(parent) it knows it has to use Glide.with(FragmentActivity). Similarly, if you use parent is Fragement then it knows it has to invoke Glide.with(Fragment).
If you use parent is AppCompatActivity || parent is Fragment then the compiler could assume parent is either AppCompatActivity or Fragment, but it still doesn't know, which Glide.with() function to use. This has to be decided at runtime, but the compiler has to resolve this call at compile time, which is not possible.
To fix the problem, you need to provide two separate control flow paths to use the proper with() function:
val glide = when (parent) {
is AppCompatActivity -> Glide.with(parent)
is Fragment -> Glide.with(parent)
else -> ... // throw exception?
}
glide.load(dataSet[position].user.avatar)
.into(viewHolder.userAvatar)
It looks like we execute exactly the same code for both cases, but in fact this is not true. If you ctrl+click on both with() functions, you'll see they point at different implementations.
If you do this often in your code, you can create an utility function:
fun test() {
glideWith(parent)
.load(dataSet[position].user.avatar)
.into(viewHolder.userAvatar)
}
fun glideWith(context: Any) = when (context) {
is AppCompatActivity -> Glide.with(parent)
is Fragment -> Glide.with(parent)
else -> ... // throw exception?
}

ClassCastException due to two class had same name in different aar

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.

How to create interchangeable class types for android fake testing?

I'm trying to create a fake class for my repository to test a view model.
As far as I understood, the key element here is to create two classes with a common interface so both classes would contain the same methods.
The problem is I get a Type mismatch when trying to initialize an object.
I tried to do the same in a simplified manner:
class fakeClass1 : fakeInterface {
override fun getAllData(): String {
return ""
}}}
class fakeClass2 : fakeInterface {
override fun getAllData(): String {
return ""
}}
interface fakeInterface {
fun getAllData(): String}
val fakeClass: fakeClass1 = fakeClass2()
But that didn't work either.
What am I missing?
Ok, I figured it out.
I was wrong to think that those two classes should be interchangeable.
I solved it by making the ViewModel take the common interface in its constructor instead of the actual repository class. This allows the ViewModel to take any class which implement this interface as it's repository.
I think you worked it out, but just so you're clear (this is an important, fundamental thing!)
val fakeClass: fakeClass1 = fakeClass2()
This is defining a variable called fakeClass that refers to an object with the fakeClass1 type. Then you assign an object with the fakeClass2 type.
But a fakeClass2 is not a fakeClass1, neither is a superclass of the other, so you can't treat one as the other. Your example is simple, but imagine you added coolFunction() to fakeClass1 - they'd now happen to have different structures, and trying to call that method on an object that doesn't have it would cause a crash.
The only thing those classes have in common, is that they both have the fakeInterface type - they are fakeInterfaces, and that guarantees they implement the stuff in that interface (your getAllData function in this case). So if you treat them both as that type instead:
val fakeClass: fakeInterface = fakeClass2()
you can use either one, because they're both fakeInterfaces (similar to how Ints and Doubles are different but they're both Numbers). Because fakeClass is now a fakeInterface, you can only access the functions and properties that a fakeInterface has - you can't call coolFunction() even if you happened to pass in a fakeClass1, because fakeInterface doesn't have that.
(You could cast the variable to fakeClass1, basically saying "oh by the way this object is actually this type as well", but at that point the type system can't guarantee you're correct unless you're explicitly checking fakeClass is fakeClass1, and it'll warn you if that's the case)
The Java tutorials are pretty good and they'll give you an overview about how the types each form a kind of "contract" you work with

Is there better way for handle kotlinx serialization?

I use kotlinx.serialization on Kotlin native project, I a defined Super class for my models and all of the models extends from it.
I defined a function to called toJSON() for serialize variables and fields inside model that all of class models have it.
#Serializable
open class Model {
fun toJSON(): String = JSON.stringify(this);
}
And I created a subclass
class Me : Model() {
var name:String = "Jack";
}
but when I invoke JSON.stringify(this), IDE get a Warning to me:
This declaration is experimental and its usage must be marked with '#kotlinx.serialization.ImplicitReflectionSerializer' or '#UseExperimental(kotlinx.serialization.ImplicitReflectionSerializer::class)'
I paid attention and I used #ImplicitReflectionSerializer annotation while not worked.
Where is my problem?
This is discussed here. It's the particular overload you're using which is still experimental. So your options are either to use the other overload (which takes in a serializer) or to use one of the annotations mentioned in the error message. If you look at the answer to the question I linked (and the comments following it), you'll see it talks about using #UseExperimental and where it should be used.

Kotlin type inference faild

When I use both Kotlin and Java in my project
In Java BaseActivity.class:
public abstract <T extends BaseViewModel> Class<T> bindViewModel();
And when I extend BaseActivity In Kotlin :
override fun <T : BaseViewModel<*, out IBaseView<*>>?> bindViewModel(): Class<T> {
return ArchViewModel::class.java
}
the Kotlin remind me the return is type inference faild
Type inference failed. Expected type mismatch:
required:Class<T>
found:Class<ArchViewModel>
How to fix this issue?
P.S. the ArchViewModel.class extends BaseViewModel
Type inference isn't failing. Your method signature says it can return Class<T> for any T (which extends BaseViewModel) you ask for, so it can be called e.g. as
activity.bindViewModel<SomeRandomModel>()
and must return a Class<SomeRandomModel>. Since ArchViewModel::class.java isn't a Class<SomeRandomModel>, the implementation is incorrect.
It's actually impossible to implement correctly whether in Kotlin or in Java. If return ArchViewModel.class compiles in Java, that's because you use the raw type BaseViewModel, so the compiler gives up on typechecking and doesn't report the error.
So you need to fix the method so it can be implemented. How, depends on what you actually want from it.
Alternately, you can "fix" it by casting return ArchViewModel::class.java as Class<T>. The compiler will correctly warn you that this cast is unsafe.

Categories

Resources