Issue
My problem is I cannot instantiate a generic class when it implements an Interface.
The instantiation code is below;
class MainClass {
fun mainMethod() {
val access = EADBAccess<AppUserModel>(AppUserModel::class.java)
}
}
in this main class I gen an error. The error is
The other Respective classes are below.
EADBModelI Interface
interface EADBModelI {
var id: String
}
AppUserModel Class
class AppUserModel : EADBModelI {
override var id: String
get() = id
set(value) { id = value }
var name: String
get() = name
set(value) { name = value}
}
EADBAccess Class
Class EADBAccess<in T : EADBModelI>(private val typeParameterClass: Class<T>) {
fun getSingleDocument(source: Source = Source.DEFAULT, docRef: DocumentReference, handler: ResultHandlerI<T>) {
docRef.get(source).addOnCompleteListener { taskResult ->
if (taskResult.isSuccessful) {
val snapshot = taskResult.result
if (snapshot!!.exists()) {
val model : T = snapshot.toObject(typeParameterClass)
model!!.id = snapshot.reference.id
handler.onSuccess(model)
}
} else {
handler.onFailure(taskResult.exception)
}
}
}
}
ResultHandlerI Interface
interface ResultHandlerI<T> {
fun onSuccess(data: T)
fun onFailure(e: Exception)
}
Solution
I copied your code and made it executable (see 'Runnable code' at the bottom of this answer). When I ran it, I got an error:
Type parameter T is declared as 'in' but occurs in 'invariant'
position in type ResultHandlerI<T>
Error location
Where does this happen? Well first, type parameter T is defined in the class EADBAccess. T is marked as in.
class EADBAccess<in T : EADBModelI>
The error occurs when T is also used as in parameter handler of fun getSingleDocument:
fun getSingleDocument(source: String, docRef: String, handler: ResultHandlerI<T>) {
// ...
}
tl;dr
The quick fix is to remove in.
class EADBAccess<T : EADBModelI>
And now when I run the code it compiles, runs, and prints:
success: AppUserModel(id='docRef', name='source')
Explanation
The Kotlin documentation Generics: in, out, where goes into details.
[...] Kotlin provides a [...] variance annotation:
in. It makes a type parameter contravariant, meaning it can only be consumed and never produced.
Array<in String>corresponds to Java'sArray<? super String>.
So if <in T : EADBModelI> is used, then T will be some unknown implementation of the EADBModelI interface. But that's not clear enough - ResultHandlerI needs to know an invariant T, not a variable range.
While on one hand T is an input (and so in T makes sense), in effect, T is also an output, as it is being used to define the type of ResultHandlerI.
Defining <T : EADBModelI> makes T invariant - at runtime it will be a single, specific implementation of EADBModelI (which in your example is AppUserModel). This implementation of T can be used as both an input, and an output.
See this answer for more explanation
Function parameters which themselves allow input are logically equivalent to return values for a function, which are obviously in "out" position.
Runnable code
fun main() {
val access = EADBAccess<AppUserModel>(AppUserModel::class.java)
access.getSingleDocument("source", "docRef", PrintResult())
}
interface EADBModelI {
var id: String
}
class AppUserModel : EADBModelI {
override var id: String = ""
var name: String = ""
override fun toString() = "AppUserModel(id='$id', name='$name')"
}
class EADBAccess<in T : EADBModelI>(private val typeParameterClass: Class<T>) {
fun getSingleDocument(source: String, docRef: String, handler: ResultHandlerI<T>) {
// simplified example
val model = AppUserModel()
model.id = docRef
model.name = source
try {
val result: T = typeParameterClass.cast(model)
handler.onSuccess(result)
} catch (e: Exception) {
handler.onFailure(e)
}
}
}
interface ResultHandlerI<T> {
fun onSuccess(data: T)
fun onFailure(e: Exception)
}
/** Dummy result handler, prints result to console */
class PrintResult<T> : ResultHandlerI<T> {
override fun onSuccess(data: T) {
println("success: $data")
}
override fun onFailure(e: Exception) {
println("failure")
}
}
Answered By - aSemy

0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.