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() }
Related
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")
}
}
I tried to make a MethodCall type judgement function using kotlin, but it return a type mismatch for me, how should I fix with it?
import java.util.Objects
import io.flutter.plugin.common.MethodCall
class Utils private constructor() {
companion object {
fun getDoubleArgument(call: MethodCall, argument: String?): Double {
return try {
if (call.argument<Any>(argument) is Double) {
Objects.requireNonNull(call.argument<Double>(argument))!! // The call.argument return Double? type, but when I add !! assert, it report Unnecessary non-null assertion (!!).
} else if (call.argument<Any>(argument) is Long) {
val l = Objects.requireNonNull(call.argument<Long>(argument))
l!!.toDouble()
} else if ...
}
I don't use Flutter, so please excuse any errors.
You don't need Objects.requireNonNull() in Kotlin, because !! already does the exact same thing as requireNonNull(), except that it throws KotlinNullPointerException instead of NullPointerException. The reason it's not working is that Kotlin doesn't know that the Java method is guaranteed to return a non-null value.
class Utils private constructor() {
companion object {
fun getDoubleArgument(call: MethodCall, argument: String?): Double {
return try {
if (call.argument<Any>(argument) is Double) {
call.argument<Double>(argument)!!
} else if (call.argument<Any>(argument) is Long) {
call.argument<Long>(argument)!!.toDouble()
} else if ...
}
It would be better to get the argument one time and using that value. Then you can rely on Kotlin smart-casting to simplify the code:
class Utils private constructor() {
companion object {
fun getDoubleArgument(call: MethodCall, argument: String?): Double {
val arg = call.argument<Any>(argument)
return try {
if (arg is Double) {
arg
} else if (arg is Long) {
arg.toDouble()
} // ...
else if (arg == null) {
0.0
} else {
error("unsupported argument type")
}
}
You can use a when statement instead of a chain of else if to make it easier to read:
class Utils private constructor() {
companion object {
fun getDoubleArgument(call: MethodCall, argument: String?): Double {
val arg = call.argument<Any>(argument)
return try {
when (arg) {
is Double -> arg
is Long -> arg.toDouble()
// ...
null -> 0.0
else -> error("unsupported argument type")
}
}
}
I have created a Generic Fragment class to handle all type of responses from server. I want to do some sort of DataTableProvider<*> to hanle any type of response.
How could I achieve this.
class TestFragmentActivity : AppCompatActivity(), DataTableProvider<Any> {
protected val mTabPatientsFragment = TabPatientsFragment()
protected val mTabObservationsFragment = TabObservationsFragment()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test_fragment)
replaceFragment()
}
private fun replaceFragment(){
supportFragmentManager.beginTransaction().replace(R.id.frame_container,
mTabPatientsFragment).commit()
}
override fun getDataTableListener(mTableFragment: DataTableFragment<Any>): DataTableListener<Any> {
val dataTableId = mTableFragment.dataTableId
if (dataTableId.equals("observations"))
return mTabObservationsFragment
else return mTabPatientsFragment
}
override fun getDataTableConfig(mTableFragment: DataTableFragment<Any>): DataTableConfig {
val dataTableId = mTableFragment.dataTableId
val config = DataTableConfig()
config.noRecordCell = R.layout.cell_no_record
config.showSearch = false
config.showAddButton = false
if (dataTableId.equals("observations"))
{
config.cellResourceId = R.layout.home_observation_cell
} else config.cellResourceId = R.layout.home_patient_cell
return config
}
}
getDataTableListener callback in above fragment has error type mismatch required DataTableListener found TabObservationFragment
TabObservationFragment
class TabObservationFragment : AppBaseFragment(),DataTableListener<Observation>
TabPatientFragment
class TabPatientFragment : AppBaseFragment(),DataTableListener<Patient>
How could I set it to work for all type of responses.
I tried DataTableListener<*> but could not achieve
The error states
projections are not allowed for immediate arguments of a supertype
How could I use DataTableProvider<*> to work for all type of responses
Edit
I have couple of fragment with fragmentViewpager inside TestFragmentActivity .
I have got a structure that helps to implement pagination ,search and implement everything in a fragment. But according to that structure DataTableProvider must be handle in activity and basis of tableId I updated callback of getDataTableListener and getDataTableListener
The above callback should return some type of
Is there a way to achieve callback like below
override fun getDataTableConfig(mTableFragment: DataTableFragment<*>?): DataTableConfig?
override fun getDataTableListener(mTableFragment: DataTableFragment<*>?): DataTableListener<*>?
Quick answer, use "out" modifier:
fun getDataTableListener(mTableFragment: DataTableFragment<Any>): DataTableListener<out Any>
Long answer:
What you are looking for is variance, which can you read about in official kotlin docs:
https://kotlinlang.org/docs/reference/generics.html
Because for example List interface looks like this:
public interface List<out E>
You can do assigement like this:
val list: List<Any> = listOf(1,2,3)
But it is not possible to do:
val mutableList : MutableList<Any> = listOf(1,2,3)
Because MutableList doesn't have "out" modifier. It makes sense, because MutableList can be changed, to MutableList you could add for example String, but it already points to List.
In your example you can use "out" modifier, if, and only if, your DataTableListener doesn't use generic type as input. For example:
interface DataTableListener<T>{
fun assignValue(t:T)
fun getValue():T
}
With interface like above, you still could use "out" modifier in your function, but you won't be able to execute "assignValue" function.
Whole example:
class Patient
class Observation
class DataTableFragment<T>
interface DataTableListener<T> {
fun assignValue(t: T)
fun getValue(): T
}
class TabObservationFragment : DataTableListener<Observation> {
override fun getValue(): Observation {
TODO("Not yet implemented")
}
override fun assignValue(t: Observation) {
TODO("Not yet implemented")
}
}
class TabPatientFragment : DataTableListener<Patient> {
override fun getValue(): Patient {
}
override fun assignValue(t: Patient) {
TODO("Not yet implemented")
}
}
val mTabObservationsFragment = TabObservationFragment()
val mTabPatientsFragment = TabPatientFragment()
fun getDataTableListener(mTableFragment: DataTableFragment<Any>): DataTableListener<out Any> {
val test = "observations"
if (test == "observations")
return mTabObservationsFragment
else return mTabPatientsFragment
}
fun getIt() {
val listener = getDataTableListener(DataTableFragment())
listener.assignValue("test")
}
I can transform a list with map in one line like this:
override suspend fun getAllTxtFile(): List<TxtFileModel> {
return someDao.getAllTxtFile().map { with(txtFileDataMapper) { it.fromEntityToDomain() } }
}
But I don't know how to do it for single class object:
override suspend fun getTxtFile(txtFileName: String?): TxtFileModel {
val txtFile = someDao.getTxtFile(txtFileName)
val txtFileModel = with(txtFileDataMapper) { txtFile.fromEntityToDomain() }
return txtFileModel
}
So, the difference is that for list I can return a value in one line but for a single object only in 3 lines.
That is not big deal but I wonder if exist some anologue for .map but for a single object. I want something like .mapSingleObject:
override suspend fun getTxtFile(txtFileName: String?): TxtFileModel {
return someDao.getTxtFile(txtFileName).mapSingleObject{ with(txtFileDataMapper) { it.fromEntityToDomain() } }
}
You can use some functions from Kotlin standard library, such as let, run, or with.
If function fromEntityToDomain() is a TxtFile extension function defined within the txtFileDataMapper's class, you can use functions mentioned above:
override suspend fun getTxtFile(txtFileName: String?): TxtFileModel? =
someDao.getTxtFile(txtFileName)?.let {
with(txtFileDataMapper) { it.fromEntityToDomain() }
}
I'm trying to create a new Matcher for espresso in order to be able to select a list item. This works fine for a simple class like Office. See this example.
private fun withOffice(title: String): Matcher<Any> {
return object : BoundedMatcher<Any, Office>(Office::class.java) {
override fun describeTo(description: Description?) {
description?.appendText("with title '$title'");
}
public override fun matchesSafely(office: Office): Boolean {
return office.name == title
}
}
}
However things get more difficult when bringing in generics, like in this class.
class KeyTranslationPair<F, S> extends Pair<F, S>
Trying to create a Matcher like this
private fun withCompanyType(companyType: CompanyType): Matcher<Any> {
return object : BoundedMatcher<Any, KeyTranslationPair<CompanyType, String>>(KeyTranslationPair<CompanyType, String>::class.java) {
override fun describeTo(description: Description?) {
description?.appendText("with companyType '$companyType'");
}
public override fun matchesSafely(keyTranslationPair: KeyTranslationPair<CompanyType, String>): Boolean {
return keyTranslationPair.key == companyType
}
}
}
results in the following error
My assumption is that kotlin get things mixed up with the java type system.
Maybe someone has an idea here.
That's because KeyTranslationPair<CompanyType,Strnig> is not a class, when is saying class means KeyTranslationPair::class.java, so, you can do it like :
return object : BoundedMatcher<Any, KeyTranslationPair<*,*>>(KeyTranslationPair::class.java)
And you are saying that you don't know what's inside of the KeyTranslationPair, and since it's a Generic you'll have to change the matchesSafely to :
override fun matchesSafely(item: KeyTranslationPair<*, *>?): Boolean {
return item?.key == companyType
}
And also you can check if Key is an instance of CompanyType doing :
override fun matchesSafely(item: KeyTranslationPair<*, *>?): Boolean {
if(item?.key is CompanyType){
return item.key == companyType
}
return false
}
Hope it helps.