Override enum toString() in Kotlin? - android

How to customize toString() method for enum in Kotlin?
enum class GuideType(type: String) {
DEF_TYPE("default"),
override fun toString(): String {
return type // not working!
}
}

Default constructor params need to be either var or val to be accessible outside the init block. Also you need to add semicolor after last enum item to add any new functions or overrides.
enum class GuideType(var type: String) {
DEF_TYPE("default");
override fun toString(): String {
return type // working!
}
}

In primary constructors, you need to use var or val if you want it to be globally visible (which includes locally in class/enum methods). Otherwise it's just locally in the initialization. Consider this:
public GuideType (String type) {}
Compared to this:
private String type;
public GuideType (String type) { this.type = type; }
// Getters and setters
Without var or val, it'll produce something roughly equivalent to the first one1. You can also access it in the init block, and in class-level variable initialization. So, in order to use it in a method, prepend val:
enum class GuideType(val type: String) { ... }
Since the variable (probably) won't be changed, it should be a val. You can, of course, use var too.
Note that this applies to primary constructors. Secondary constructors work differently.
1: Kotlin will also produce a bunch of null-safety stuff including #NotNull and null checks, but the code is still the rough equivalent

You ca extend the Enum class with a method as this:
fun <K> Enum.Companion.toString(string: K): String {
return string.toString()}
Enum.toString(DEF_TYPE("default")) // 'defaut'

Related

Reference an object in a class by using a string?

I want to reference an object within this class I have below:
class HerbData {
object Dill {
const val herbName: String = "This is Dill!"
const val scientificName: String = "Anethum Graveolens"
val dullThumbnail: Int = R.drawable.dill_thumbnail_attr
}
object Peppermint {
val herbName: String = "This is Peppermint!"
}
}
Is there anyway that I can reference the object by using a string in Kotlin? Here is somewhat what I mean:
HerbData."Dill".herbname
I can't find anything on this topic for Kotlin.
Another way you could do this is with an enum class. The advantage over a map is that you have a data structure you can reference directly in code, so you could use HerbData.Dill as well as HerbData["Dill"]. And that will enable you to take advantage of compile-time checking and lint warnings, refactoring, exhaustive pattern matching, code completion etc, because the data is defined in your code
enum class HerbData(
val herbName: String,
val scientificName: String? = null,
val dullThumbnail: Int? = null
) {
Dill("This is Dill!", "Anethum Graveolens", R.drawable.dill_thumbnail_attr),
Peppermint("This is Peppermint!");
companion object {
operator fun get(name: String): HerbData? =
try { valueOf(name) } catch(e: IllegalArgumentException) { null }
}
}
fun main() {
// no guarantee these lookups exist, need to null-check them
HerbData["Peppermint"]?.herbName.run(::println)
// case-sensitive so this fails
HerbData["peppermint"]?.herbName.run(::println)
// this name is defined in the type system though! No checking required
HerbData.Peppermint.herbName.run(::println)
}
>> This is Peppermint!
null
This is Peppermint!
Enum classes have that valueOf(String) method that lets you look up a constant by name, but it throws an exception if nothing matches. I added it as a get operator function on the class, so you can use the typical getter access like a map (e.g. HerbData["Dill"]). As an alternative, you could do something a bit neater:
companion object {
// storing all the enum constants for lookups
private val values = values()
operator fun get(name: String): HerbData? =
values.find() { it.name.equals(name, ignoreCase = true) }
}
You could tweak the efficiency on this (I'm just storing the result of values() since that call creates a new array each time) but it's pretty simple - you're just storing all the enum entries and creating a lookup based on the name. That lets you be a little smarter if you need to, like making the lookup case-insensitive (which may or may not be a good thing, depending on why you're doing this)
The advantage here is that you're generating the lookup automatically - if you ever refactor the name of an enum constant, the string label will always match it (which you can get from the enum constant itself using its name property). Any "Dill" strings in your code will stay as "Dill" of course - that's the limitation of using hardcoded string lookups
The question really is, why do you want to do this? If it's pure data where no items need to be explicitly referenced in code, and it's all looked up at runtime, you should probably use a data class and a map, or something along those lines. If you do need to reference them as objects within the code at compile time (and trying to use HerbData."Dill".herbName implies you do) then an enum is a fairly easy way to let you do both
Declare a Data Class
data class HerbData (
val scientificName: String,
val dullThumbnail: Int
)
Initialize a muteable map and put data in it
val herbData = mutableMapOf<String, HerbData>()
herbData.put("Dill", HerbData("Anethum Graveolens", R.drawable.dill_thumbnail_attr))
herbData.put("Peppermint", HerbData("Mentha piperita", R.drawable.peppermint_thumbnail_attr))
You can now just
herbData["Dill"]?.scientificName
class HerbData {
interface Herb {
val herbName: String
val scientificName: String
}
object Dill : Herb {
override val herbName: String = "This is Dill!"
override val scientificName: String = "Anethum Graveolens"
}
object Peppermint: Herb {
override val herbName: String = "This is Peppermint!"
override val scientificName: String = "Mentha piperita"
}
companion object {
operator fun get(name: String): Herb? {
return HerbData::class
.nestedClasses
.find { it.simpleName == name }
?.objectInstance as? Herb
}
}
}
println(HerbData["Dill"]?.herbName) // Prints: This is Dill!
println(HerbData["Peppermint"]?.scientificName) // Prints: Mentha piperita
println(HerbData["Pepper"]?.herbName) // Prints: null

In Kotlin, can I apply lint to the constructors of all classes that inherit the class I specify?

class SomeDetector : Detector(), SourceCodeScanner {
override fun getApplicableConstructorTypes(): List<String>? {
return listOf(PARENT_CLASS)
}
override fun visitConstructor(context: JavaContext, node: UCallExpression, constructor: PsiMethod) {
// blabla...
}
}
Ok, I've even succeeded in applying lint to individual constructors for each class.
However, there are hundreds of classes I want to validate, and they all inherit a common interface.
So I want to verify the constructors of all classes inheriting the interface I specified.
The class I want to verify has an android dependency, so libraries like reflections cannot be used directly in the lint module, which is a java-library.
Can you help me to meet my requirements?
I gave your problem a go. I have checked if the value in annotation and the argument used are equal. You can tweak the code to suit your requirement. Here is a sample Detector class wherein I have provided explanations using comments. You can improve on it.
class InvalidConstructorCallDetector : Detector(), Detector.UastScanner {
// Check for call expressions
override fun getApplicableUastTypes() = listOf(UCallExpression::class.java)
override fun createUastHandler(context: JavaContext) = object : UElementHandler() {
override fun visitCallExpression(node: UCallExpression) {
// Check if call is constructor call and if the class referenced inherits your interface
if (node.isConstructorCall() &&
context.doesInherit(node, "com.example.android.YourInterface")
) {
val constructor = node.resolve()!!
// Get the first parameter. You may use a loop and check for all parameter or whatever you require
val param = constructor.parameterList.parameters[0]
// Get your annotation
val paramAnnotation =
param.getAnnotation("com.example.android.YourAnnotation")!!
// Get the value you specified in the annotation for the constructor declaration
val attributeValue = paramAnnotation.findAttributeValue("value")!!.text.toInt()
// Get the argument used for first parameter. Again, you can have this in a loop and use index
val argumentValue = node.getArgumentForParameter(0)!!.evaluate().toString().toInt()
if (attributeValue != argumentValue) // Checking for equality. Perform whatever check you want
{
context.report(
ISSUE,
node,
context.getNameLocation(node),
"Argument value($argumentValue) is invalid. Valid argument: $attributeValue"
)
}
}
}
}
// Check to see if class referenced by constructor call implements interface
private fun JavaContext.doesInherit(node: UCallExpression, typeName: String): Boolean {
for (type in node.classReference!!.getExpressionType()!!.superTypes) {
if (evaluator.typeMatches(type, typeName)) return true
}
return false
}
companion object {
val ISSUE = Issue.create(
"InvalidConstructorCall",
"Invalid arguments in constructor call",
"Only values defined in annotation in constructor declaration are allowed",
Category.CORRECTNESS,
10,
Severity.ERROR,
Implementation(
InvalidConstructorCallDetector::class.java,
EnumSet.of(Scope.JAVA_FILE)
)
)
}
}

Kotlin: enums with associated values; function inside 'enum entry' gets `unresolved reference` error

I need to create a list of events with a string as name and a list of pairs as properties, some events properties are the static value, some need dynamically changed, so I create specific functions inside the enum entry to update it but complied with error unresolved reference:
Actually, what I want to implement is a list of enums with associated values,
something like these articles mentioned:
KT-4075 Allow setters overloading for properties, or
Kotlin: single property with multiple setters of different types, or
Using Kotlin’s sealed class to approximate Swift’s enum with associated data
Because I have more than 100 events, 95% of them are static, only several of them need to be updated during runtime, so sealed class might not suit my situation:
enum class Event(val eventName: String, vararg eventProperties: Pair<String, String?>) {
LOGIN_CLICKED("Login", ("View" to "button clicked")),
LOGIN_SUCCEED("Login", ("Type" to "succeed")),
LOGIN_ERROR("Login") {
fun errorMessage(errorMessage: String) {
eventProperties = listOf("ErrorType" to errorMessage)
}
},
// ... some other events
LIST_ITEM_CLICKED("LIST") {
fun listItemName(itemName: String) {
eventProperties = listOf("View" to itemName)
}
};
var eventProperties: List<Pair<String, String?>>? = listOf(*eventProperties)
// Although this approach can fix my problem, but I don't prefer it,
// because these functions are only meaningful to specific enum item,
// I don't want them be opened to all enum items.
//
// fun errorMessage(errorMessage: String) {
// eventProperties = listOf("ErrorType" to errorMessage)
// }
// fun listItemName(itemName: String) {
// eventProperties = listOf("View" to itemName)
// }
}
fun main(args: Array<String>) {
// unresolved reference
println(Event.LOGIN_ERROR.eventProperties)
Event.LOGIN_ERROR.errorMessage("error password")
println(Event.LOGIN_ERROR.eventProperties)
}
Because I have more than 100 events, 95% of them are static, only several of them need to be updated during runtime, so sealed class might not suit my situation
Why wouldn't it? If you are bothered with slightly longer declarations:
object LoginClicked : Event("Login", mapOf("View" to "button clicked"))
\\ vs
LOGIN_CLICKED("Login", mapOf("View" to "button clicked"))
you can create a helper enum class for them:
sealed class Event(val eventName: String, val eventProperties: Map<String, String?>) {
enum class Basic(val eventName: String, val eventProperties: Map<String, String?>) {
LOGIN_CLICKED("Login", mapOf("View" to "button clicked")),
LOGIN_SUCCEED("Login", mapOf("Type" to "succeed")),
...
}
class BasicEvent(b: Basic) : Event(b.eventName, b.eventProperties)
class LoginError(errorMessage: String) : Event("Login", mapOf("ErrorType" to errorMessage))
...
}

How to get class of generic type parameter in Kotlin

I would like get the class property from a generic type T.
I've decided to extend to Any but I'm getting an error.
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-any/index.html#extension-properties
I have the following code:
class FirebaseDBRepo<T : Any>(val child:String) {
private var callback: FirebaseDatabaseRepositoryCallback<T>? = null
private val ref: DatabaseReference
private val listener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
//T::class.java is showing the error cannot use t as reified type parameter use class instead
val gameDS = dataSnapshot.getValue(T::class.java)
callback!!.onSuccess(gameDS!!)
}
override fun onCancelled(databaseError: DatabaseError) {
}
}
init {
ref = FirebaseDatabase.getInstance().reference.child(child)
}
fun addListener(callback: FirebaseDatabaseRepositoryCallback<T>) {
this.callback = callback
ref.addValueEventListener(listener)
}
fun removeListener() {
ref.removeEventListener(listener)
}
}
You can only get the class on reified variables. The same thing happens in java, but with a slightly different message:
public <T> void x(){
T t = T.class.newInstance();
}
In Java, you'd solve this like:
public <T> void x(Class<T> cls){
T t = cls.newInstance();
}
The same applies to Kotlin, and any calls. You'd need to get a class instance in most cases. However, Kotlin supports reified generics using a keyword, but only on inline generic functions. You could pass a class, but in functions, it's really easy just using the reified keyword.
As in you can't declare a class with reified generics, which means this is invalid:
class SomeClass<reified T>
But it is valid for inline functions, meaning you can do:
inline fun <reified T> someFunction()
So you have two options. But since you extend a listener, the first option of adding the generics to the function isn't an option. You can't override a non-generic method with generics. It won't compile.
Which leaves the second option, which unfortunately is rather hackish; passing the class to the constructor. So it should look like this:
class FirebaseDBRepo<T : Any>(val child: String, private val cls: Class<T>) {
Now, I don't use Firebase, so I have no clue what classes you'd pass, so for this next example, I just use String.
Kotlin supports some type minimization without going over to raw types. This:
val t = FirebaseDBRepo<String>("", String::class.java)
Could be shortened to this:
val t = FirebaseDBRepo("", String::class.java)
The inferred type in both cases is FirebaseDBRepo<String>.
Since you are running on the JVM, type erasure is a thing.
This means (in simplified terms), that during compilation, the generics are simply ignored. Therefore, you cannot get the class of T, as the JVM doesn't even know what you mean by "T".
Kotlin uses a clever trick to come around this limitation in some cases. When you are using inline functions, the compiler does not call the function you defined, but instead, copies the whole body to the location where you called it. This can only be done for inline functions. Not classes.
There is a workaround tough: Just add private val classT: Class<T>
to the constructor and use the parameter instead!
Maybe it is too late but you could get the memory address from the generic class.
try to use:
object: GenericTypeIndicator<"T>() {}
to get the memory address from ur generic value.
It looks then so:
val gameDS = dataSnapshot.getValue(object: GenericTypeIndicator<"T">(){}
But you need to give your genericType without the ""
Maybe it is a solution for you.

In which situation val/var is necessary in Kotlin constructor parameter?

Right code:
class MainActHandler(val weakActivity: WeakReference<Activity>): Handler() {
override fun handleMessage(msg: Message?) {
val trueAct = weakActivity.get() ?: return
if (msg?.what == ConversationMgr.MSG_WHAT_NEW_SENTENCE){
val sentence = msg.obj as String?
trueAct.conversation.text = sentence
}
super.handleMessage(msg)
}
}
cannot be resolved code:
class MainActHandler(weakActivity: WeakReference<Activity>): Handler() {
override fun handleMessage(msg: Message?) {
val trueAct = weakActivity.get() ?: return
if (msg?.what == ConversationMgr.MSG_WHAT_NEW_SENTENCE){
val sentence = msg.obj as String?
trueAct.conversation.text = sentence
}
super.handleMessage(msg)
}
}
cannot be resolved code screenshot
The only difference is the "val" has been deleted and cannot be resolve.
Which might be important is that it's a inner class.
BUT
This one class without "val/var" in constructor parameter is working:
class BookInfo(convrMgr: ConversationMgr, id: String, queue: RequestQueue, queueTag:String) {
val TAG = "BookInfo"
var title: String? = ""
init {
val url = "https://api.douban.com/v2/book/$id"
// Request a string response from the provided URL.
val stringRequest = StringRequest(Request.Method.GET, url,
Response.Listener<String> { response ->
Log.d(TAG + " Response", response.substring(0))
// Parse JSON from String value
val parser = Parser()
val jsonObj: JsonObject =
parser.parse(StringBuilder(response.substring(0))) as JsonObject
// Initial book title of book properties.
title = jsonObj.string("title")
Log.d(TAG + " Book title", title)
convrMgr.addNewMsg(title)
},
Response.ErrorListener { error -> Log.e(TAG + " Error", error.toString()) })
// Set the tag on the request.
stringRequest.tag = queueTag
// Add the request to the RequestQueue.
queue.add(stringRequest)
}
}
And if I add var/val before "queue: RequestQueue", I'll get suggestion:
"Constructor parameter is never used as a property less. This inspection reports primary constructor parameters that can have 'val' or 'var' removed. Unnecessary usage of 'val' and 'var' in primary constructor consumes unnecessary memory."
I am just confused about it.
When you write val/var within the constructor, it declares a property inside the class. When you do not write it, it is simply a parameter passed to the primary constructor, where you can access the parameters within the init block or use it to initialize other properties. For example,
class User(val id: Long, email: String) {
val hasEmail = email.isNotBlank() //email can be accessed here
init {
//email can be accessed here
}
fun getEmail(){
//email can't be accessed here
}
}
Constructor parameter is never used as a property
This suggestion is saying that you do not use this property in place apart from the initialization. So, it suggests you to remove this property from the class.
Constructor parameters must use var or val when they are used as a property elsewhere in the class. They do not need to be properties if they are only used for class initialization.
In the example below, the parameter must be a property (var or val) because it is used in a method:
class A(val number: Int) {
fun foo() = number
}
In this other example, the parameter is only used to initialize the class, so it does not need to be a property:
class B(number: Int): A(number) {
init {
System.out.println("number: $number")
}
}
This might be a late answer but the magic lies under the hood:
Based on #BakaWaii's answer:
Putting var/val will make the variable a property of the class and not putting it will make it a parameter of only the constructor function.
So what does it mean, to understand lets look into some code:
class Test(a: Int){}
Now Lets see the decompiled java code:
public final class Test {
public Test(int a) {
}
}
So now if I try to access a using the object of Test() like the below code:
Test t = new Test(10);
t.a //Error
It will give me error. Unresolved reference: a. Why because a is a parameter of the constructor only.
Now if we put var/val in the paramater like below:
class Test(var a: Int){}
The decompliked Java code will become:
public final class Test {
private int a;
public final int getA() {
return this.a;
}
public final void setA(int var1) {
this.a = var1;
}
public Test(int a) {
this.a = a;
}
}
Thus it will not only give you a class property but also give you getter/setters for setting the values.
Now the next question arises if the field a is private how can it be accessed. Simple answer in Java you cannot, i.e. if you are calling the KT class from a Java you will not be able to assign value of a like Test(1).a = 10 but will have to use Test(1).setA(5).
But as kotlin internally handles getters/setters Test(1).a = 5 will be ok.
For #Parcelize to work you need to open up the super's properties and override them in the child:
abstract class Goal(open var number: Int, open var name: String) : Parcelable
#Parcelize
class OperationalGoal(override var number: Int, override var name: String, var description: String) : Goal(number, name)```
In very simple terms, use var or val in class constructor parameters when you want to use that variable, say, inside a method within that class. Thus you're effectively turning them into properties and not just mere constructor or method parameters.
class User(var name: String, age: Int) {
var str = "John"
var num = 18
fun setName(){
name = str // due to using var on our class constructor parameter, we can access the constructor variable *name* inside this setter method. *name* is a property parameter thanks to the var keyword.
}
fun setAge(){
age = num // this will result in a compiler error, because *age* is just a parameter, notice that var wasn't used in the *age* parameter within the class constructor, which means we can't access it like we did with *name*
}
}
Run this Kotlin Playground code to get a clearer idea of what's going on.

Categories

Resources