Generics in Kotlin: How to instantiate generic class which implements an Interface - android

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)
}

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's Array<? 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")
}
}

Related

Type mismatch. Required: Result<NewsResponse>! Found: Result<Response<NewsResponse>>?

I am developing android app and I have implemented success and failure cases in viemodel class but I am getting following mismatch Type mismatch.
Required:
Result!
Found:
Result<Response>
below my NewsViewModel where I have implemented success and failure cases when I am getting data
class NewsViewModel(private val repository: NewsRepository) : ViewModel() {
private var _newsResponse= MutableLiveData<Result<NewsResponse>>()
// Expose to the outside world
val news: LiveData<Result<NewsResponse>> = _newsResponse
#UiThread
fun getNews() {
viewModelScope.launch(Dispatchers.IO) {
try {
val response = repository.getNews()
_newsResponse.postValue(Result.success(response))
} catch (ioe: IOException) {
_newsResponse.postValue(Result.Failure("[IO] error please retry", ioe))
} catch (he: HttpException) {
_newsResponse.postValue(Result.Failure("[HTTP] error please retry", he))
}
}
}
}
below my NewsRepository.ktclass
NewsRepository(
private val apiInterface:NewsInterface
){
suspend fun getNews() = apiInterface.getNews()
}
below my Result class
sealed class Result<out T> {
data class Success<out R>(val value: R): Result<R>()
data class Failure(
val message: String?,
val throwable: Throwable?
): Result<Nothing>()
}
I want to know where I exactly I am making mistake what I have to do in order to fix that problem
below my news Interface
import com.example.newsworldwide.model.NewsResponse
import retrofit2.Response
import retrofit2.http.GET
interface NewsInterface {
#GET("ApiKey")
suspend fun getNews(): Response<NewsResponse>
}
Your NewsInterface is returning Response<NewsResponse> & in your NewsViewModel you're passing it directly to response so it becomes Result.Success<Response<NewsResponse>> at the time of posting. That's why this error.
Solution:
Get value from body() of retrofit response class.
Make it Non-nullable using(!!) as your _newsResponse live-data is accepting NewsResponse which is non-nullable. You might want to handle null case here.
So your final code would look something like this.
class NewsViewModel(private val repository: NewsRepository) : ViewModel() {
private var _newsResponse= MutableLiveData<Result<NewsResponse>>()
// Expose to the outside world
val news: LiveData<Result<NewsResponse>> = _newsResponse
#UiThread
fun getNews() {
viewModelScope.launch(Dispatchers.IO) {
try {
val response = repository.getNews().body()!! //change this line
_newsResponse.postValue(Result.Success(response))
} catch (ioe: IOException) {
_newsResponse.postValue(Result.Failure("[IO] error please retry", ioe))
} catch (he: HttpException) {
_newsResponse.postValue(Result.Failure("[HTTP] error please retry", he))
}
}
}
}

Elegant way of handling error using Retrofit + Kotlin Flow

I have a favorite way of doing network request on Android (using Retrofit). It looks like this:
// NetworkApi.kt
interface NetworkApi {
#GET("users")
suspend fun getUsers(): List<User>
}
And in my ViewModel:
// MyViewModel.kt
class MyViewModel(private val networkApi: NetworkApi): ViewModel() {
val usersLiveData = flow {
emit(networkApi.getUsers())
}.asLiveData()
}
Finally, in my Activity/Fragment:
//MyActivity.kt
class MyActivity: AppCompatActivity() {
private viewModel: MyViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel.usersLiveData.observe(this) {
// Update the UI here
}
}
}
The reason I like this way is because it natively works with Kotlin flow, which is very easy to use, and has a lot of useful operations (flatMap, etc).
However, I am not sure how to elegantly handle network errors using this method. One approach that I can think of is to use Response<T> as the return type of the network API, like this:
// NetworkApi.kt
interface NetworkApi {
#GET("users")
suspend fun getUsers(): Response<List<User>>
}
Then in my view model, I can have an if-else to check the isSuccessful of the response, and get the real result using the .body() API if it is successful. But it will be problematic when I do some transformation in my view model. E.g.
// MyViewModel.kt
class MyViewModel(private val networkApi: NetworkApi): ViewModel() {
val usersLiveData = flow {
val response = networkApi.getUsers()
if (response.isSuccessful) {
emit(response.body()) // response.body() will be List<User>
} else {
// What should I do here?
}
}.map { // it: List<User>
// transform Users to some other class
it?.map { oneUser -> OtherClass(oneUser.userName) }
}.asLiveData()
Note the comment "What should I do here?". I don't know what to do in that case. I could wrap the responseBody (in this case, a list of Users) with some "status" (or simply just pass through the response itself). But that means that I pretty much have to use an if-else to check the status at every step through the flow transformation chain, all the way up to the UI. If the chain is really long (e.g. I have 10 map or flatMapConcat on the chain), it is really annoying to do it in every step.
What is the best way to handle network errors in this case, please?
You should have a sealed class to handle for different type of event. For example, Success, Error or Loading. Here is some of the example that fits your usecases.
enum class ApiStatus{
SUCCESS,
ERROR,
LOADING
} // for your case might be simplify to use only sealed class
sealed class ApiResult <out T> (val status: ApiStatus, val data: T?, val message:String?) {
data class Success<out R>(val _data: R?): ApiResult<R>(
status = ApiStatus.SUCCESS,
data = _data,
message = null
)
data class Error(val exception: String): ApiResult<Nothing>(
status = ApiStatus.ERROR,
data = null,
message = exception
)
data class Loading<out R>(val _data: R?, val isLoading: Boolean): ApiResult<R>(
status = ApiStatus.LOADING,
data = _data,
message = null
)
}
Then, in your ViewModel,
class MyViewModel(private val networkApi: NetworkApi): ViewModel() {
// this should be returned as a function, not a variable
val usersLiveData = flow {
emit(ApiResult.Loading(true)) // 1. Loading State
val response = networkApi.getUsers()
if (response.isSuccessful) {
emit(ApiResult.Success(response.body())) // 2. Success State
} else {
val errorMsg = response.errorBody()?.string()
response.errorBody()?.close() // remember to close it after getting the stream of error body
emit(ApiResult.Error(errorMsg)) // 3. Error State
}
}.map { // it: List<User>
// transform Users to some other class
it?.map { oneUser -> OtherClass(oneUser.userName) }
}.asLiveData()
In your view (Activity/Fragment), observe these state.
viewModel.usersLiveData.observe(this) { result ->
// Update the UI here
when(result.status) {
ApiResult.Success -> {
val data = result.data <-- return List<User>
}
ApiResult.Error -> {
val errorMsg = result.message <-- return errorBody().string()
}
ApiResult.Loading -> {
// here will actually set the state as Loading
// you may put your loading indicator here.
}
}
}
//this class represent load statement management operation
/*
What is a sealed class
A sealed class is an abstract class with a restricted class hierarchy.
Classes that inherit from it have to be in the same file as the sealed class.
This provides more control over the inheritance. They are restricted but also allow freedom in state representation.
Sealed classes can nest data classes, classes, objects, and also other sealed classes.
The autocomplete feature shines when dealing with other sealed classes.
This is because the IDE can detect the branches within these classes.
*/
ٍٍٍٍٍ
sealed class APIResponse<out T>{
class Success<T>(response: Response<T>): APIResponse<T>() {
val data = response.body()
}
class Failure<T>(response: Response<T>): APIResponse<T>() {
val message:String = response.errorBody().toString()
}
class Exception<T>(throwable: Throwable): APIResponse<T>() {
val message:String? = throwable.localizedMessage
}
}
create extention file called APIResponsrEX.kt
and create extextion method
fun <T> APIResponse<T>.onSuccess(onResult :APIResponse.Success<T>.() -> Unit) : APIResponse<T>{
if (this is APIResponse.Success) onResult(this)
return this
}
fun <T> APIResponse<T>.onFailure(onResult: APIResponse.Failure<*>.() -> Unit) : APIResponse<T>{
if (this is APIResponse.Failure<*>)
onResult(this)
return this
}
fun <T> APIResponse<T>.onException(onResult: APIResponse.Exception<*>.() -> Unit) : APIResponse<T>{
if (this is APIResponse.Exception<*>) onResult(this)
return this
}
merge it with Retrofit
inline fun <T> Call<T>.request(crossinline onResult: (response: APIResponse<T>) -> Unit) {
enqueue(object : retrofit2.Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
if (response.isSuccessful) {
// success
onResult(APIResponse.Success(response))
} else {
//failure
onResult(APIResponse.Failure(response))
}
}
override fun onFailure(call: Call<T>, throwable: Throwable) {
onResult(APIResponse.Exception(throwable))
}
})
}

How to abstract type convertion function properly?

I have a lot of methods that look like that:
override suspend fun getBalance(): Result<BigDecimal> = withContext(Dispatchers.IO) {
Log.d(TAG, "Fetching balance from data store")
val balance = balancePreferencesFlow.firstOrNull()
?: return#withContext Result.Error(CacheIsInvalidException)
return#withContext when (balance) {
is Result.Success -> {
if ((balance.data.timestamp + ttl) <= getCurrentTime()) {
deleteBalance()
Result.Error(CacheIsInvalidException)
} else {
resultOf { balance.data.toDomainType() }
}
}
is Result.Error -> balance
}
}
There I am collecting a Flow of some type from DataStore, then if it is a Success Result(with data parameter of type T), I should get its timestamp(it is a data class field), and if the condition is true delete invalid data and if it's false return the converted Result.
The convertion functions look somehow like that:
fun BigDecimal.toPersistenceType(): Balance = Balance(
balanceAmount = this,
timestamp = getCurrentTime()
)
fun Balance.toDomainType(): BigDecimal = this.balanceAmount
I've tried to make an abstract method in this way, but I don't completely understand how I should pass a lambda to it.
suspend inline fun <reified T : Any, reified V : Any> getPreferencesDataStoreCache(
preferencesFlow: Flow<Result<V>>,
ttl: Long,
deleteCachedData: () -> Unit,
getTimestamp: () -> Long,
convertData: () -> T
): Result<T> {
val preferencesResult = preferencesFlow.firstOrNull()
return when (preferencesResult) {
is Result.Success -> {
if ((getTimestamp() + ttl) <= getCurrentTime()) {
deleteCachedData()
Result.Error(CacheIsInvalidException)
} else {
resultOf { preferencesResult.data.convertData() }
}
}
is Result.Error -> preferencesResult
else -> Result.Error(CacheIsInvalidException)
}
}
And a lambda for convertion should look like an extension method.
The Result class:
sealed class Result<out T : Any> {
data class Success<out Type : Any>(val data: Type) : Result<Type>()
data class Error(val exception: Exception) : Result<Nothing>()
}
First of all, I see here some cache work, that from my point should be placed in one interface.
interface Cache {
val timestamp: Long
fun clear()
}
You can make timestamp property nullable to return null if your cache is still empty - it's up to you.
Then universal method you need I assume to place inside Result class as it seems to be only its own work.
sealed class Result<out T : Any> {
data class Success<out Type : Any>(val data: Type) : Result<Type>()
data class Error(val exception: Exception) : Result<Nothing>()
fun <R : Any> convertIfValid(cache: Cache, ttl: Long, converter: (T) -> R) : Result<R> =
when (this) {
is Success -> {
if (cache.timestamp + ttl <= getCurrentTime()) {
cache.clear()
Error(CacheIsInvalidException())
} else {
Success(converter(data))
}
}
is Error -> this
}
}
May be it would be better to place getCurrentTime method in some injected entity too, but it's not important in this post.
By the way, as you can see here in when I didn't place else state as it is unnecessary for sealed classes.
From your code I can make an example of cache implementation only for balance:
class BalanceCache : Cache {
var balanceValue = Balance()
override val timestamp: Long
get() = balanceValue.timestamp
override fun clear() {
deleteBalance()
}
}
If you need more examples from me, please give me more details about your code where you want to use it.

Cannot use 'T' as reified type parameter

/**
this "T::class.java" report an error :Cannot use 'T' as reified type parameter. Use a class instead!
so how can i fix it or what can i do to realize this way?please.
**/
see the next kotlin code
data class PostHttpResultBean<T>(private var errno:Int,private var error:String,private var data:String):IHttpResultEntity<T>{
override val errorCode: Int
get() = errno
override val errorMessage: String
get() = error
override val isSuccess: Boolean
get() = errno==0
override val result:T
get() = RSAUtil.dataDecrypt(RSAUtil.getKeyPassword(), data,T::class.java)!!
class RSAUtil {
companion object {
fun <T> dataDecrypt(password: String, data: String, java: Class<T>): T? {
val content = Base64.decode(data.toByteArray(), Base64.NO_WRAP)
try {
var deString = decrypt(content, password)
if (!deString.isEmpty()){
val first = deString.substring(0, deString.lastIndexOf(":") + 1)
deString = "$first$deString}"
return Gson().fromJson(deString,java)
}
return null
} catch (e: Exception) {
e.printStackTrace()
}
return null
}
}
}
You should change dataDecrypt like that:
inline fun <reified T> dataDecrypt(password: String, data: String): T? {
...
try {
...
if (!deString.isEmpty()){
...
return Gson().fromJson(deString, T::class.java)
}
...
}
}
And on the call site the T type will be inferred from result:
override val result:T
get() = RSAUtil.dataDecrypt(RSAUtil.getKeyPassword(), data)!!
You can read more about inline functions and reified types here and I strongly recommend to do so. I would also point out that your code is ill-formatted, it is advised to use ?: instead of !! in nullability checks and companion objects are discouraged in Kotlin, you could define functions outside of class and use (or import) them as if they were static.

How can I generalize functions on an enum class in Kotlin?

How can I create a class which could be more reusable with enum classes, as I might have few more classes later on? My point is to make it more reusable, flexible and global for other usage.
enum class PaymentMethodType(val type: String) {
PAYPAL("Paypal"),
VISA("Visa"),
MASTERCARD("MasterCard"),
VISA_DEBIT("VISA Debit"),
LPQ_CREDIT("Lpq Credit");
companion object {
private val TAG: String = this::class.java.simpleName
fun fromString(name: String): PaymentMethodType? {
return getEnumFromString(PaymentMethodType::class.java, name)
}
private inline fun <reified T : Enum<T>> getEnumFromString(c: Class<T>?, string: String?): T? {
if (c != null && string != null) {
try {
return enumValueOf<T>(
string.trim()
.toUpperCase(Locale.getDefault()).replace(" ", "_")
)
} catch (e: IllegalArgumentException) {
Log.e(TAG, e.message)
}
}
return null
}
}
}
You can generalize your getEnumFromString function by creating an interface and having your companion object implementing it. An extension on this interface will let you call the function directly on the companion of your enum class.
This will do the trick:
interface EnumWithKey<T : Enum<T>, K> {
val T.key: K
}
/* The reified type parameter lets you call the function without explicitly
* passing the Class-object.
*/
inline fun <reified T : Enum<T>, K> EnumWithKey<T, K>.getByKey(key: K): T? {
return enumValues<T>().find { it.key == key }
}
Now you can create your PaymentMethodType like this:
enum class PaymentMethodType(val type: String) {
PAYPAL("Paypal"),
VISA("Visa"),
MASTERCARD("MasterCard"),
VISA_DEBIT("VISA Debit"),
LPQ_CREDIT("Lpq Credit");
companion object : EnumWithKey<PaymentMethodType, String> {
// Just define what the key is
override val PaymentMethodType.key
get() = type
}
}
And voila, now you can do this:
println(PaymentMethodType.getByKey("Paypal")) // Prints PAYPAL
The EnumWithKey interface can now be reused by just having the companion object of an enum implementing it.
Well? How about this code?
enum class PaymentMethodType(val type: String) {
PAYPAL("Paypal"),
VISA("Visa"),
MASTERCARD("MasterCard"),
VISA_DEBIT("VISA Debit"),
LPQ_CREDIT("Lpq Credit");
companion object {
private val TAG: String = PaymentMethodType::class.simpleName
fun fromString(name: String?): PaymentMethodType? {
val maybeType = PaymentMethodType.values().firstOrNull { it.type == name }
if (maybeType == null) {
Log.e(TAG, "No corresponding PaymentMethodType for $name")
}
return maybeType
}
}
}
Just made getEnumFromString method simpler like this way.
Moreover, if you want to make your PaymentMethodType more "reusable, flexible and global", add some abstract method onto your PaymentMethodType or consider using Sealed class in this case. We can guess that many payment methods require their own protocols, and implementing it by enum requires an externalised when or if-else branch to do so. For example, the code should be looks like this:
fun paymentProcessor(payment: PaymentMethodType): Boolean {
return when (payment) {
PAYPAL -> { processPaypalPayment() }
VISA -> { processVisaPayment() }
// ...
}
}
which is not bad unless numbers of payment methods are limited but not quite desirable. We can remove this insidious if or when keyword like this way(retaining enum class approach):
enum class PaymentMethodType(val type: String) {
PAYPAL("Paypal") {
override fun processPayment(): Boolean {
TODO("Not implemented.")
}
},
VISA("Visa") {
override fun processPayment(): Boolean {
TODO("Not implemented.")
}
},
// ... more types ...
;
abstract fun processPayment(): Boolean
// ...
}
With either approach, we can eliminate when keyword in paymentProcessor method I demonstrated like this:
fun paymentProcessor(payment: PaymentMethodType): Boolean {
return payment.processPayment()
}
I don't explain sealed class approach since the code is not much different compare to enum class approach in this case. The official document may help.
Hope this helps.
Get all enum values with PaymentMethodType.values(), then use find() to get the one you need:
fun fromString(type: String): PaymentMethodType? = PaymentMethodType.values().find { it.type.toLowerCase() == type.toLowerCase() }

Categories

Resources