How to assign raw value to enum in Dart? - flutter

In Swift, you can easily assign raw value to enum, eg:
enum Game: Int {
case lol = 1
case dnf = 2
case dota = 3
}
However, you can't assign raw value to enum in Dart:
enum Game {
lol = 1,
dnf = 2,
dota = 3,
}
It's showed error and you can only use the simplest enum:
enum Game {
lol,
dnf,
dota,
}
It's really let me down.
Any way assign raw value to Dart's enum like Swift?

Dart 2.17 support enhanced enum
enum Game {
lol(1),
dnf(2),
dota(3);
const Game(this.value);
final int value;
}
Use it like:
void main() {
const game = Game.lol;
print(game.value); // 1
}

There's an upcoming feature in Dart known as enhanced enums, and it allows for enum declarations with many of the features known from classes. For example:
enum Game {
lol,
dnf,
dota;
int get intValue => index + 1;
}
The feature is not yet released (and note that several things are not yet working), but experiments with it can be performed with a suitably fresh version of the tools by passing --enable-experiment=enhanced-enums.
The outcome is that an enum value of type Game will have a getter intValue that returns the int values mentioned in the question, so print(myGame.intValue) will print 1, 2, or 3.

You can use enum extensions for example
enum EnumAction { subtract, add }
extension EnumActionExtension on EnumAction {
String get name {
switch (this) {
case EnumAction.add:
return 'add';
case EnumAction.subtract:
return 'subtract';
}
}
}
In your case you would return an int and an int value. Enums also have int values assigned to them by default, their respective index. You could call Game.lol.index and it would return 0.

To get an int value, simply pass an enum to the enumGame function.
enum EnumGame { lol, dnf, dota }
enumGame(EnumGame enumGame) {
switch (enumGame) {
case EnumGame.lol:
return 1;
case EnumGame.dnf:
return 2;
case EnumGame.dota:
return 3;
default:
return -1;
}
}

Related

Is there any way to create Map with enum as key that ensure operator[] is not null?

enum SomeEnum { first, second }
Map<SomeEnum, String(or any type)> someMap = {
SomeEnum.first: 'first',
SomeEnum.second: 'second',
};
String variable = someMap[SomeEnum.first]; <- nullable
In codes above, someMap[SomeEnum.{anything}] defenitely can't be null because it have all possible SomeEnum as key.
But this causing error because someMap[SomeEnum.first] is nullable, can't assign to type String
How do I tell flutter that this have all possible enum values and 100% can't be null without using ! (I don't want to use it because I use this map a lot and this is a little bit annoying)
If that is your literal enum and map you don't need that map. Use SomeEnum.first.name to get the string.
If the strings are different. I would use a different approach. When using Dart 2.17.0 or higher you can use enhanced enums and simple add methods to enums like this for example
enum SomeEnum { first, second;
String getString() {
switch (this) {
case SomeEnum.first: return "first string";
case SomeEnum.second: return "second string";
}
}
int toInt() {
switch (this) {
case SomeEnum.first: return 1;
case SomeEnum.second: return 2;
}
}
}
And then use Some.first.getString() wherever you need. Or Some.first.toInt() to get ints
For lower Dart versions you can write an extension instead and use it in the same way:
extension SomeEnumExtension on SomeEnum {
String getString() {
switch (this) {
case SomeEnum.first: return "first string";
case SomeEnum.second: return "second string";
}
}
}

How do I convert an integer to an enum based on its range in Swift?

Is there a way to convert an integer to an enum based on its value range? For the sake of elegance, I prefer to implement it into an enum or auxiliary type rather than as a separate function. Ideally, it would be great if I could perform the conversion using a cast like so:
let readPosition = Position(controller.readVal())
or
let readPosition = (Position) controller.readVal()
Below is my feeble attempt at it:
// Should be sealed, yet extensible, c# has sealed, but not Swift
public enum Position: Int {
case Both = 0, Bottom = 1, Top = 2
}
// Position should not be changed to preserve compatibility.
// Different converters will be implemented by different apps
// overtime.
extension Position {
static func convert(val: Int64?) -> Position {
switch val {
case .some(1), .some(..<8):
return Position.Top
case .some(2), .some(8...):
return Position.Bottom
default:
return Position.Both
}
}
}
You already have all you need, you can now define an initializer:
extension Position {
init(_ val: Int64) {
self = Self.convert(val)
}
}
Then you can use your requested:
let readPosition = Position(controller.readVal())

Using Enum in if-else in Swift

I have the following for loops with if-else condition. However, it is not quite intituitve for others to read the code at a first glance. therefore I am thinking to use Enum here.
for row in 0..<result.count {
for column in 0..<result[row].count {
if column == 0 {
// add hotels here
} else if column == 1 {
// add motels here
}
}
}
I am trying to use the following enum, but I am confused how to put into if-else condition.
enum ResortColumn {
case hotel
case motel
}
If you want to compare your enum type to an integer, you need to declare it an Int and compare its rawValue.
enum ResortColumn: Int {
case hotel
case motel
}
if column == ResultColumn.hotel.rawValue { // etc.
See the second "enum Planet" example on this page, and read the rest for a full understanding of Swift enums:
https://docs.swift.org/swift-book/LanguageGuide/Enumerations.html
enum ResortColumn: Int {
case hotel
case motel
}
for row in 0 ..< result.count {
for column in 0 ..< result[row].count {
let resortColumn = ResortColumn(rawValue: column)
switch resortColumn {
case .hotel:
// add hotels here
case .motel:
// add motels here
}
}
}
This is the best what I can come up with the context you gave. In this case, it does really matter what is behind the result variable

How to reduce Swift enum conversion code

I have a variety of enums such as below.
enum PaperOrientation : Int { case portrait, landscape }
enum MetricType : Int { case inches, metric }
I made the enums of type Int, so that the values of instances could be saved as numbers with CoreData.
When retrieving the values from CoreData to use in the program, I end up with very similar conversion routines, like those shown below.
Typically, I want some default value - such as for the case where it is a new enum for the latest version of the program, and a value for that variable may not actually have been saved in CoreData. For example, the MetricType was added for the second rev of the program. Retrieving a paper created in rev 1 will not have a metric value saved. For the nil value, I want to use a default value the paper was originally assumed to have.
class ConversionRoutine {
class func orientationFor(_ num: NSNumber?) -> PaperOrientation {
if let iVal = num?.intValue {
if let val = PaperOrientation(rawValue: iVal) {
return val
}
}
return PaperOrientation(rawValue: 0)!
}
class func metricTypeFor(_ num: NSNumber?) -> MetricType {
if let iVal = num?.intValue {
if let val = MetricType(rawValue: iVal) {
return val
}
}
return MetricType(rawValue: 0)!
}
}
Is there a way to reduce the redundancy?
I present a way below that works pretty well. But welcome more refinements or improvements.
The Swift 4 example below uses a Defaultable protocol based on RawRepresentable. The first step is creating a defaultValue that can be used when the initializer fails. Note that the Defaultable protocol is not limited to Int enums. A String enum could also use it.
protocol Defaultable : RawRepresentable {
static var defaultValue : Self { get }
}
protocol IntDefaultable : Defaultable where RawValue == Int {
}
extension IntDefaultable {
static func value(for intValue : Int) -> Self {
return Self.init(rawValue: intValue) ?? Self.defaultValue
}
static func value(for num : NSNumber?) -> Self {
if let iVal = num?.intValue {
return self.value(for: iVal)
}
return Self.defaultValue
}
}
After the Defaultable protocol is defined, I can create an IntDefaultable protocol that will be used for Int enums.
In an extension to IntDefaultable, I can create the generic code to handle the conversion. First, I create a function that takes an Int. Then I create a function that takes an NSNumber optional.
Next, look at how one of the enums is built:
enum MetricType : Int, Codable, IntDefaultable { case inches, metric
static var defaultValue: MetricType = .inches
}
I also decided to declare the enum Codable, which may be useful. When I add the IntDefaultable protocol, it becomes fairly easy to add the defaultValue line of code with code completion - go to the new line and type “def”-tab, then “ = .”, and then choose one of the values from the popup. Note that often I want to pick the first enum value, but the default value could be any one.
The last thing is calling the conversion routine for getting a value from CoreData
let units = MetricType.value(for: self.metricType) // where self.metricType is the NSManagedObject member.
You can add an initializer in enum.
enum PaperOrientation : Int {
case portrait, landscape
init(number: NSNumber) {
self = PaperOrientation(rawValue: number.intValue) ?? .portrait
}
}

Access an enum value by its hashvalue?

enum have a property named 'hashValue' which is its index inside the enum.
Now my question is, is it possible to access its value by using a number? Ex: let variable:AnEnum = 0
If you want to map enum values to integers, you should do so directly with raw values. For example (from the Swift Programming Language: Enumerations):
enum Planet: Int {
case Mercury = 1, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}
let possiblePlanet = Planet(rawValue: 7)
I don't believe there's any documentation promising that an enum's hashValue is anything in particular (if you have a link, I've be very interested). In the absence of that, you should be explicit in your assignment of raw values.
enum Opponent: String {
case Player
case Computer
static func fromHashValue(hashValue: Int) -> Opponent {
if hashValue == 0 {
return .Player
} else {
return .Computer
}
}
}
Explanation:
Since there is no way to get back an enum value from its hashValue, you have to do it manually. It's not pretty, but it works. You essentially create a function that allows you to pass in the index of the value you want and manually return that enum value back to the caller. This could get nasty with an enum with tons of cases, but it works like a charm for me.
Swift 4, iOS 12:
Simply make your enum with explicitly setting raw type (like Int in below example):
enum OrderStatus: Int {
case noOrder
case orderInProgress
case orderCompleted
case orderCancelled
}
Usage:
var orderStatus: OrderStatus = .noOrder // default value
print(orderStatus.rawValue) // it will print 0
orderStatus = .orderCompleted
print(orderStatus.rawValue) // it will print 2
Swift 4.2 🔸(Based on previous answer by #Stephen paul)
This answer uses switch instead of if/else clauses. And returns optional as your not garantueed that the hash provided will match.
enum CellType:String{
case primary,secondary,tierary
/**
* NOTE: Since there is no way to get back an enum value from its hashValue, you have to do it manually.
* EXAMPLE: CellType.fromHashValue(hashValue: 1)?.rawValue//👉primary
*/
static func fromHashValue(hashValue: Int) -> CellType? {
switch hashValue {
case 0:
return .primary
case 1:
return .secondary
case 2:
return .tierary
default:
return nil
}
}
}
Your requirement is to have this line of code working, where 0 is the hashValue of the enum variable (note that starting with Xcode 10, 0 is never a valid hashValue...):
let variable:AnEnum = 0
This is simply done by making your enum ExpressibleByIntegerLiteral and CaseIterable:
extension AnEnum: CaseIterable, ExpressibleByIntegerLiteral {
typealias IntegerLiteralType = Int
public init(integerLiteral value: IntegerLiteralType) {
self = AnEnum.allCases.first { $0.hashValue == value }!
}
}
The CaseIterable protocol is natively available with Swift 4.2, and you can implement it yourself for older swift versions (Swift 3.x, 4.x) using the code from https://stackoverflow.com/a/49588446/1033581.
Actually, with Swift 4.2, the hashValue is not the index inside the enum anymore.
Edit: Just found a safer way to achieve that. You can use the CaseIterable (Swift 4.2) and pick the desired case in the allCases collection.
enum Foo: CaseIterable {
case bar
case baz
init?(withIndex index: Int) {
guard Foo.allCases.indices ~= index else { return nil }
self = Foo.allCases[index]
}
}
Foo(withIndex: 0) // bar
Foo(withIndex: 1) // baz
Foo(withIndex: 2) // nil
Note: I'm leaving this little trick here, because playing with unsafe pointer is fun, but please, do not use this method to create a case with an index.
It relies on Swift memory representation which might change without notice, and is really unsafe because using a wrong index produce a runtime error.
That being said, in Swift, a case is represented using an Int in raw memory. So you can use this to build a case using unsafe pointers.
enum Foo {
case bar
case baz
init(withIndex index: UInt8) {
var temp: Foo = .bar
withUnsafeMutablePointer(to: &temp) { pointer in
let ptr = UnsafeMutableRawPointer(pointer).bindMemory(to: UInt8.self, capacity: 1)
ptr.pointee = index
}
self = temp
}
}
Foo(withIndex: 0) // bar
Foo(withIndex: 1) // baz
Foo(withIndex: 2) // runtime error !