1

I have a device class in my application where one property needs to be computed and final plus some properties that can be set in the constructor or have default values. Here's the code:

class Device {
  String idx;
  String id; 
  String name; 
  String icon; 
  int order; 

  // have to create the idx before initializing the object because I can't
  // figure out how to do that here.
  Device(
      {required this.idx,
      required this.id,
      required this.name,
      this.icon = 'none',
      this.order = -1});

  // Creates a device from a JSON string
  factory Device.fromJson(Map<String, dynamic> jsonData) {
    return Device(
        idx: jsonData['idx'],
        id: jsonData['id'],
        name: jsonData['name'],
        icon: jsonData['icon'],
        order: jsonData['order']);
  }

  // Returns the device as a String
  static Map<String, dynamic> toMap(Device device) => {
        'idx': device.idx,
        'id': device.id,
        'name': device.name,
        'icon': device.icon,
        'order': device.order
      };
}

Basically I'm trying to set a unique index for the object so in my object list I can clearly identify a specific device. I'm using the Uuid package to generate a UUID for idx.

The only way I can make this work today is to create the idx in my other code that creates the object and pass it in. I read a lot of articles here that talk about different ways to solve this problem and I know I have to make the idx value a constant but I can't figure out how to do that and call the Uuid library.

I know it would look something like this:

Device(
      {this.idx = const <<some calculation/expression>>,
      required this.id,
      required this.name,
      this.icon = 'none',
      this.order = -1});

removing the required modifier and putting a const before the value assignment. Nothing I've tried lets me call the Uuid method. Can someone help me understand how to do this?

Updating the code based on the answer from @jamesdlin:

import 'package:uuid/uuid.dart';

const uuid = Uuid();

class Device {
  String idx;
  String id;
  String name;
  String icon;
  int order;

  Device(
      {String? idx,
      required this.id,
      required this.name,
      this.icon = 'none',
      this.order = -1})
      : idx = idx ?? uuid.v1();

  // Creates a device object from a JSON string
  factory Device.fromJson(Map<String, dynamic> jsonData) {
    return Device(
        idx: jsonData['idx'],
        id: jsonData['id'],
        name: jsonData['name'],
        icon: jsonData['icon'],
        order: jsonData['order']);
  }

  // Returns the device object as a String
  static Map<String, dynamic> toMap(Device device) => {
        'idx': device.idx,
        'id': device.id,
        'name': device.name,
        'icon': device.icon,
        'order': device.order
      };
}

This works, but I don't ever have a use case where I want the idx set manually, so how to I accomplish that? I could leave it like this, but I really want to better understand how to do exactly what I need.

johnwargo
  • 601
  • 2
  • 7
  • 22
  • I may be mistaken but I don't think this is necessary. Why don't you create the UUID elsewhere then add it to later when you call Device c1 = Device(uudi,...add values); – murage kibicho Apr 03 '22 at 15:17
  • simply because I don't want to have to do that. That's the way it works today, I generate the UUID and pass it into the constructor. Why can't the Device object set that value itself? Why does it need to rely upon something external to get the value? – johnwargo Apr 03 '22 at 15:56
  • I don't have to use the UUID package, I can just generate a string value based on the current time for example (so the `idx` is unique) but I can't even get that to work. – johnwargo Apr 03 '22 at 15:57
  • 1
    Why can't you just do `String idx = Uuid().v1();`? – jamesdlin Apr 03 '22 at 15:59
  • I've tried that and doing so breaks the `fromJson` factory – johnwargo Apr 03 '22 at 16:15

1 Answers1

2

The only way I can make this work today is to create the idx in my other code that creates the object and pass it in.

If you want the object to be able to generate its own UUID, you just can do:

const uuid = Uuid();

class Device {
  String idx = uuid.v1(); // Or whatever UUID version you want.
  ...

or you if you want the caller to have the option to pass in a UUID string, you can use the typical technique of using null to achieve non-const default function arguments:

const uuid = Uuid();

class Device
  String idx;

  Device({String? idx, ...})
    : idx = idx ?? uuid.v1(),
      ...

Note that attempting to make a const initializer for a UUID makes no sense. const means that the object is a compile-time constant, and furthermore, const objects are canonicalized, so a hypothetical const expression that generated a UUID would end up producing the same String for every Device, which would be the opposite of unique.


Update for your updated question

This works, but I don't ever have a use case where I want the idx set manually, so how to I accomplish that? I could leave it like this, but I really want to better understand how to do exactly what I need.

I don't understand what you mean since you quite obviously do have a use case for setting idx manually (your Device.fromJson factory constructor). If you instead mean that you don't have a use case for code from outside the Device class to manually set idx, then you can add a private constructor with the idx parameter and a public one without:

class Device {
  String idx;
  String id;
  String name;
  String icon;
  int order;

  Device({
    required String id,
    required String name,
    String icon = 'none',
    int order = -1,
  }) : this._(
          idx: uuid.v1(),
          id: id,
          name: name,
          icon: icon,
          order: order,
        );

  Device._({
    required this.idx,
    required this.id,
    required this.name,
    required this.icon,
    required this.order,
  });

  // Creates a device object from a JSON string
  factory Device.fromJson(Map<String, dynamic> jsonData) {
    return Device._(
        idx: jsonData['idx'],
        id: jsonData['id'],
        name: jsonData['name'],
        icon: jsonData['icon'],
        order: jsonData['order']);
  }
}

or, since idx isn't final, .fromJson could assign it a value:

  factory Device.fromJson(Map<String, dynamic> jsonData) {
    return Device(
        id: jsonData['id'],
        name: jsonData['name'],
        icon: jsonData['icon'],
        order: jsonData['order'])
      ..idx = jsonData['idx'];
  }
jamesdlin
  • 81,374
  • 13
  • 159
  • 204
  • If I do that, what do I do with the `fromJason` method? Dart gives me an error on the ` idx: jsonData['idx'],` line `The named parameter 'idx' isn't defined.` I'd show the whole code, but SO won't let me paste it in (too long) – johnwargo Apr 03 '22 at 16:26
  • @johnwargo Why won't the second approach I suggested work for that case? – jamesdlin Apr 03 '22 at 16:37
  • @johnwargo Additionally, since `idx` isn't `final` (although maybe it should be), there isn't anything that prevents the `fromJson` factory constructor from simply assigning a different value for the `idx` member after it constructs the `Device` object. – jamesdlin Apr 03 '22 at 18:11