Is it possible to define enum in Flutter in the way to define specific values like color and type.
Regarding to this article I do not see such things:
https://www.educative.io/blog/dart-2-language-features
code from android where this is possible:
enum class AcceptedItemStatus(#ColorRes val color: Int, #StringRes val type: Int) {
#SerializedName("New")
NEW(R.color.green, R.string.accepted_chip_new),
#SerializedName("Standby")
STAND_BY(
R.color.orange,
R.string.accepted_chip_stand_by
)
}
Dart doesn't have out-of-the-box support for adding custom properties to Enums. It's a shame because I really like that feature of Kotlin and other languages and use it a lot. However you can work around it using some packages.
Add the built_value package to your dependencies in pubspec.yaml
Add build_runner and built_value_generator to dev_dependencies
Create accepted_item_status.dart
import 'package:built_collection/built_collection.dart';
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
part 'accepted_item_status.g.dart';
class AcceptedItemStatus extends EnumClass {
// #BuiltValueEnumConst is optional, if you want to use the built_value serialization and want to control
// what value each enum item serialises to:
#BuiltValueEnumConst(wireNumber: 1)
static const AcceptedItemStatus newItem = _$newItem;
#BuiltValueEnumConst(wireNumber: 2)
static const AcceptedItemStatus standby = _$standby;
const AcceptedItemStatus._(String name) : super(name);
static BuiltSet<AcceptedItemStatus> get values => _$values;
static AcceptedItemStatus valueOf(String name) => _$valueOf(name);
// This is optional, if you want to use the automatic serializer generation:
static Serializer<AcceptedItemStatus> get serializer => _$acceptedItemStatusSerializer;
Color get colour => _colour[this] ?? (throw StateError("No colour found for $this"));
static const _colour = {
newItem: Color(0xFF123456),
standby: Color(0xFF654321),
};
}
Run the code generator to automatically generate the companion file for your enum: flutter pub run build_runner build
Et voila! Now you can use AcceptedItemStatus just like an enum, and it will have your custom properies:
final status = AcceptedItemStatus.newItem;
Color statusColour = status.colour;
Perhaps, you can use extension for enum.
import 'package:flutter/material.dart';
enum AcceptedItemStatus {
isNew, standBy
}
extension AcceptedItemStatusExtension on AcceptedItemStatus {
Color get color {
switch (this) {
case AcceptedItemStatus.isNew:
return Colors.green;
case AcceptedItemStatus.standBy:
return Colors.orange;
}
}
String get title {
switch (this) {
case AcceptedItemStatus.isNew:
return "Is New";
case AcceptedItemStatus.standBy:
return "Is Stand By";
}
}
}
Usage:
debugPrint("${AcceptedItemStatus.isNew.color}");
debugPrint("${AcceptedItemStatus.isNew.title}");
Related
I want to write a custom lint rule to ban calling function states.accept() in all classes that extends BaseViewModel where states is a BehaviorRelay object.
how can I achieve something like this.
I’ve written the check using visitMethodCall but this only can check the function name and if it’s member of BehaviorRelay,
the missing part is how to check if this function is being called in children’s of BaseViewModel.
below is the part that works: using visitMethodCall but detecting the function in whole code.
override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
val evaluator = context.evaluator
if (evaluator.isMemberInClass(method, BEHAVIOR_RELAY)) {
if (method.name == ACCEPT_FUNCTION) {
context.report(
Incident(
issue = ISSUE,
scope = node,
location = context.getNameLocation(node),
message = "View Models implements `BaseViewModel` must not update `states`"
)
)
}
}
}
applicableSuperClasses will filter only the classes that extends passed types, in my case BaseViewModel. this function works with visitClass.
Then using AbstractUastVisitor() to visit all calls in that class and find the specific function by name, also checking if it's a member function in target type.
full working code.
override fun applicableSuperClasses(): List<String>? {
return listOf(BASE_VIEW_MODEL)
}
override fun visitClass(context: JavaContext, declaration: UClass) {
val evaluator = context.evaluator
declaration.accept(object : AbstractUastVisitor() {
override fun visitCallExpression(node: UCallExpression): Boolean {
val isRelayFunction = evaluator.isMemberInClass(
node.resolve(),
BEHAVIOR_RELAY
)
if (node.methodName == ACCEPT_FUNCTION && isRelayFunction) {
context.report(
issue = ISSUE,
scope = node,
location = context.getNameLocation(node),
message = "View Models implements `BaseViewModel` must not update `states`"
)
}
return super.visitCallExpression(node)
}
})
}
I am looking at using sealed class to represent a finite set of possible values.
This is part of a codegeneration project that will write a very large number of such classes, which can each have a lot of cases. I am therefore concerned about app size. As it is very likely that several cases can have the same attributes, I am looking at using wrappers such as:
data class Foo(val title: String, ...lot of other attributes)
data class Bar(val id: Int, ...lot of other attributes)
sealed class ContentType {
class Case1(val value: Foo) : ContentType()
class Case2(val value: Bar) : ContentType()
// try to reduce app size by reusing the existing type,
// while preserving the semantic of a different case
class Case3(val value: Bar) : ContentType()
}
fun main() {
val content: ContentType = ContentType.Case1(Foo("hello"))
when(content) {
is ContentType.Case1 -> println(content.value.title)
is ContentType.Case2 -> println(content.value.id)
is ContentType.Case3 -> println(content.value.id)
}
}
Is this how I should approach this problem?
If so, how can I best make the properties of the associated value accessible from the sealed class? So that
is ContentType.Case2 -> println(content.value.id)
becomes
is ContentType.Case2 -> println(content.id)
Is this how I should approach this problem?
IMHO, yes, but with some semantic changes, listed after.
How can I best make the properties of the associated value accessible from the sealed class?
You can generate extensions or instance functions for each subclass.
e.g.
val ContentType.Case2.id: String get() = value.id
In this way, you can successfully call:
is ContentType.Case2 -> println(content.id)
How can I reduce the app size while preserving the semantic of another case?
You can do it generating only one class for all the cases which need the same types as parameters and using Kotlin contracts to handle them.
Taking your example, you could generate:
sealed class ContentType {
class Case1(val value: Foo) : ContentType()
class Case2_3(val value: Bar, val caseSuffix: Int) : ContentType()
}
As you can see, the classes Case2 and Case3 are now only one class and caseSuffix identifies which one of them it is.
You can now generate the following extensions (one for each case):
#OptIn(ExperimentalContracts::class)
fun ContentType.isCase1(): Boolean {
contract {
returns(true) implies (this#isCase1 is ContentType.Case1)
}
return this is ContentType.Case1
}
#OptIn(ExperimentalContracts::class)
fun ContentType.isCase2(): Boolean {
contract {
returns(true) implies (this#isCase2 is ContentType.Case2_3)
}
return this is ContentType.Case2_3 && caseSuffix == 2
}
#OptIn(ExperimentalContracts::class)
fun ContentType.isCase3(): Boolean {
contract {
returns(true) implies (this#isCase3 is ContentType.Case2_3)
}
return this is ContentType.Case2_3 && caseSuffix == 3
}
Since you are using contracts the client can now use them with:
when {
content.isCase1() -> println(content.title)
content.isCase2() -> println(content.id)
content.isCase3() -> println(content.id)
}
As you can see, a further optimization could be removing the property caseSuffix for cases with only one suffix to avoid unnecessary properties.
I'm trying to define a StringDef in kotlin:
#Retention(AnnotationRetention.SOURCE)
#StringDef(NORTH, SOUTH)
annotation class FilterType {
companion object {
const val NORTH = "NORTH"
const val SOUTH = "SOUTH"
}
}
I think something is wrong in the code above.
// I can send anything to this method, when using my kotlin stringDef
private fun takeString(#DirectionJava.Direction filterType: String) {
I want the kotlin equivalent of the java below:
public class DirectionJava {
public static final String NORTH = "NORTH";
public static final String SOUTH = "SOUTH";
#Retention(RetentionPolicy.SOURCE)
#StringDef({
NORTH,
SOUTH,
})
public #interface Direction {
}
}
Calling the java defined StringDef works from kotlin
// This works as expected, the Java-written string def
// restricts what I can pass to this method
private fun takeString(#DirectionJava.Direction filterType: String) {
Where have I gone wrong, how do you define a StringDef in Kotlin?
According to jetbrains issue, Lint check plugin for enumerated annotations in kotlin is under development and is not stable yet. Checking android support annotations such as #Nullable, #StringRes, #DrawableRes, #IntRange, ... (which are written in java) works fine and user defined enumerated annotations are not checked properly. So, it seems that we should define them in java then use in kotlin.
#StringDef now works if you define it inside a companion object. See:
https://stackoverflow.com/a/70672074/2465264
Also, consider using a Kotlin enum class instead since the performance issues have been fixed in ART (Android Runtime Machine), which most Android devices are now running.
Say I've got a class
#SomeAnnotation(someValue = A_CONST_VAL)
class SomeClass {
companion object {
const val A_CONST_VAL = "a constant value"
}
...
}
The above compiles fine - no issues. However, if I want to use A_CONST_VAL in a constants file...
import some.package.SomeClass.A_CONST_VAL
object SomeConstants {
const val CONSTRUCTED_CONST_VAL = "some stuff ${A_CONST_VAL}"
}
and use CONSTRUCTED_CONST_VAL as the compile-time annotation value, such that the previous class is now
#SomeAnnotation(someValue = SomeConstants.CONSTRUCTED_CONST_VAL)
class SomeClass {
companion object {
const val A_CONST_VAL = "a constant value"
}
...
}
The compiler thinks that the supplied annotation value is not a compile-time constant.
What gives?
Note: I've also tried implementing the file holding constants as Java, just to see if it worked
class SomeConstants {
public static final String CONSTRUCTED_CONST_VAL = "a constant value" + SomeClass.A_CONST_VAL;
}
still a compilation failure
I develop Android applications and often use annotations as compile time parameter checks, mostly android's support annotations.
Example in java code:
public class Test
{
#IntDef({Speed.SLOW,Speed.NORMAL,Speed.FAST})
public #interface Speed
{
public static final int SLOW = 0;
public static final int NORMAL = 1;
public static final int FAST = 2;
}
#Speed
private int speed;
public void setSpeed(#Speed int speed)
{
this.speed = speed;
}
}
I don't want to use enums because of their performance issues in Android. The automatic converter to kotlin just generates invalid code. How do I use the #IntDef annotation in kotlin?
Edit: In case you miss the comments on the question or this answer, it's worth noting that the following technique compiles,
but does not create the compile-time validation you would get in
Java (which partially defeats the purpose of doing this). Consider using an enum
class
instead.
It is actually possible to use the #IntDef support annotation by defining your values outside of the annotation class as const vals.
Using your example:
import android.support.annotation.IntDef
public class Test {
companion object {
#IntDef(SLOW, NORMAL, FAST)
#Retention(AnnotationRetention.SOURCE)
annotation class Speed
const val SLOW = 0L
const val NORMAL = 1L
const val FAST = 2L
}
#Speed
private lateinit var speed: Long
public fun setSpeed(#Speed speed: Long) {
this.speed = speed
}
}
Note that at this point the compiler seems to require the Long type for the #IntDef annotation instead of actual Ints.
There's currently no way to achieve exactly this in Kotlin, since an annotation class cannot have a body and thus you cannot declare a constant in it which would be processed by IntDef. I've created an issue in the tracker: https://youtrack.jetbrains.com/issue/KT-11392
For your problem though, I recommend you use a simple enum.
Update:
Forget #IntDef and #StringDef, Now, with ART, you can use enums instead.
From the official GoogleIO:
https://www.youtube.com/watch?v=IrMw7MEgADk&feature=youtu.be&t=857
Plus, if you're still not sure if you should use enums, you can hear a bunch of people yelling at each other in the comments of the first answer over here:
https://stackoverflow.com/a/37839539/4036390
Old answer:
Just create the #IntDef class as a java class and access it via kotlin code.
Example:
Create your type class:
public class mType {
#IntDef({typeImpl.type1, typeImpl.type2, typeImpl.type3})
#Retention(RetentionPolicy.SOURCE)
public #interface typeImpl {
int type1 = 0;
int type2 = 1;
int type3 = 2;
}
}
Put this function in any Kotlin object:
object MyObject{
fun accessType(#mType.typeImpl mType: Int) {
...
}
}
then access it:
fun somOtherFunc(){
MyObject.accessType(type1)
}
**Notice: you don't have to put the access method inside an object.
Use this:
companion object {
const val FLAG_PAGE_PROCESS = 0L//待处理
const val FLAG_PAGE_EXCEPTION = 1L//设备异常
const val FLAG_PAGE_UNCHECKED = 2L//未审核
const val FLAG_PAGE_AUDIT = 3L//统计
val FLAG_PAGE = "FLAG_PAGE"
fun newInstance(#FlagPageDef flagPage: Int): RepairFormsListFragment {
val fragment = RepairFormsListFragment()
val args = Bundle()
fragment.arguments = args
return fragment
}
#Retention(AnnotationRetention.SOURCE)
#IntDef(FLAG_PAGE_PROCESS, FLAG_PAGE_EXCEPTION, FLAG_PAGE_UNCHECKED, FLAG_PAGE_AUDIT)
annotation class FlagPageDef
}