3

I have following code.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'My App',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: SimpleScreen());
  }
}

class SimpleScreen extends StatefulWidget {
  @override
  _SimpleScreenState createState() => _SimpleScreenState();
}

class _SimpleScreenState extends State<SimpleScreen> {
  ItemCls currentValue = ItemCls(name: 'one', price: 1);

  List<DropdownMenuItem> _menuItems = <DropdownMenuItem>[
    DropdownMenuItem(
        child: new Container(
          child: new Text("Item#1"),
          width: 200.0,
        ),
        value: ItemCls(name: 'one', price: 1)),
    DropdownMenuItem(
        child: new Container(
          child: new Text("Item#2"),
          width: 200.0,
        ),
        value: ItemCls(name: 'two', price: 2))
  ];

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        body: Center(
      child: DropdownButton(
        value: currentValue,
        items: _menuItems,
        onChanged: onChanged,
        style: Theme.of(context).textTheme.title,
      ),
    ));
  }

  void onChanged(value) {
    setState(() {
      currentValue = value;
    });
   // print(value);
  }
}

class ItemCls {
  final String name;
  final double price;

  const ItemCls({
    @required this.name,
    @required this.price,
  })  : assert(name != null),
        assert(price != null);
}

That fails with

The following assertion was thrown building SimpleScreen(dirty, dependencies: [_LocalizationsScope-[GlobalKey#b1ff3], _InheritedTheme], state: _SimpleScreenState#d473f): 'package:flutter/src/material/dropdown.dart': Failed assertion: line 620 pos 15: 'items == null || items.isEmpty || value == null || items.where((DropdownMenuItem item) => item.value == value).length == 1': is not true.

2 Answers2

3

The value in each of your DropDownMenuItem's needs to be unique. Dart ensures uniqueness in two ways: defining the == operator and calling hashCode on the object. You need to add both to the ItemCls class:

class ItemCls {
  final String name;
  final double price;

  const ItemCls({
    @required this.name,
    @required this.price,
  })  : assert(name != null),
        assert(price != null);

  bool operator ==(dynamic other) {
    return other is ItemCls && 
           this.name == other.name && 
           this.price == other.price;
  }

  @override
  int get hashCode {
    // Hash code algorithm derived from https://www.sitepoint.com/how-to-implement-javas-hashcode-correctly/
    int hashCode = 1;
    hashCode = (23 * hashCode) + this.name.hashCode;
    hashCode = (23 * hashCode) + this.price.hashCode;
    return hashCode;
  }
}
Abion47
  • 22,211
  • 4
  • 65
  • 88
  • This answer is more relevant and useful in general for similar problems. I only suggest one replacement. Use the following code to override the hashCode getter instead of the given one: int get hashCode => Object.hash(name,price); (Source: https://api.flutter.dev/flutter/package-collection_collection/ListEquality/hash.html) – irshukhan Oct 11 '22 at 14:48
  • @irshukhan That wouldn't be the source, the source would be https://api.flutter.dev/flutter/dart-core/Object/hash.html But yes, it would be a better general solution. – Abion47 Oct 11 '22 at 15:33
1

Reason
instance ItemCls currentValue = ItemCls(name: 'one', price: 1); not equal value: ItemCls(name: 'one', price: 1)),
they have different address and treat as different value
So DropdownButton think default value ItemCls(name: 'one', price: 1) did not exist in selection list

Solution
Use List<ItemCls> to contain your selection and point currentValue to first value of List

ItemCls currentValue;
  static List<ItemCls> itemList = [
    ItemCls(name: 'one', price: 1),
    ItemCls(name: 'two', price: 2)
  ];
  ...
 DropdownMenuItem<ItemCls>(
    child: new Container(
      child: new Text("Item#1"),
      width: 200.0,
    ),
    value: itemList[0]),
...
void initState() {
// TODO: implement initState
currentValue = itemList[0];
}

working demo

enter image description here

full code

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'My App',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: SimpleScreen());
  }
}

class SimpleScreen extends StatefulWidget {
  @override
  _SimpleScreenState createState() => _SimpleScreenState();
}

class _SimpleScreenState extends State<SimpleScreen> {
  ItemCls currentValue;
  static List<ItemCls> itemList = [
    ItemCls(name: 'one', price: 1),
    ItemCls(name: 'two', price: 2)
  ];

  List<DropdownMenuItem<ItemCls>> _menuItems = <DropdownMenuItem<ItemCls>>[
    DropdownMenuItem<ItemCls>(
        child: new Container(
          child: new Text("Item#1"),
          width: 200.0,
        ),
        value: itemList[0]),
    DropdownMenuItem<ItemCls>(
        child: new Container(
          child: new Text("Item#2"),
          width: 200.0,
        ),
        value: itemList[1])
  ];

  @override
  void initState() {
    // TODO: implement initState
    currentValue = itemList[0];
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        body: Center(
      child: DropdownButton<ItemCls>(
        value: currentValue,
        items: _menuItems,
        onChanged: onChanged,
        style: Theme.of(context).textTheme.title,
      ),
    ));
  }

  void onChanged(value) {
    setState(() {
      currentValue = value;
    });
    // print(value);
  }
}

class ItemCls {
  final String name;
  final double price;

  const ItemCls({
    @required this.name,
    @required this.price,
  })  : assert(name != null),
        assert(price != null);
}
chunhunghan
  • 51,087
  • 5
  • 102
  • 120