5

I try to create a dropdown and to populate it with few objects which represents few servers from where the user can pick one, but when I run the app I'm getting an error saying:

The following assertion was thrown building DropdownWidget(dirty, state: _DropdownWidgetState#1f58f): There should be exactly one item with [DropdownButton]'s value: Instance of 'ServerModel'. Either zero or 2 or more [DropdownMenuItem]s were detected with the same value

Can you please help me to identify what I'm doing wrong in my code?

import 'package:flutter/material.dart';
​
class ServerSettingsPage extends StatefulWidget {
  @override
  _ServerSettingsPageState createState() => _ServerSettingsPageState();
}
​
class _ServerSettingsPageState extends State<ServerSettingsPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
      title: Text("Server Settings")),
      body: _buildUI(),
    );
  }
​
  Widget _buildUI() {
    return Padding(
      padding: const EdgeInsets.fromLTRB(0, 20, 0, 0),
      child: Center(
        child: Column(
          children: <Widget>[
            Text(
              'Select a server:',
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            DropdownWidget(),
          ],
        ),
      ),
    );
  }
}
​
class DropdownWidget extends StatefulWidget {
  DropdownWidget({Key key}) : super(key: key);
​
  @override
  _DropdownWidgetState createState() => _DropdownWidgetState();
}
​
class _DropdownWidgetState extends State<DropdownWidget> {
  ServerModel dropdownValue =
      ServerModel(name: 'Default', url: 'https://defaultServer.com/');
​
  @override
  Widget build(BuildContext context) {
    return DropdownButton<ServerModel>(
      value: dropdownValue,
      icon: Icon(Icons.arrow_downward),
      iconSize: 24,
      elevation: 16,
      style: TextStyle(color: Colors.purple[700]),
      underline: Container(
        height: 2,
        color: Colors.purple[700],
      ),
      onChanged: (ServerModel newServer) {
        setState(() {
          dropdownValue = newServer;
        });
      },
      items: <ServerModel>[
        ServerModel(name: 'Default', url: 'https:defaultServer.com/'),
        ServerModel(name: 'Alpha', url: 'https://alphaServer.com/'),
        ServerModel(name: 'Beta', url: 'https://betaServer.com/'),
      ].map<DropdownMenuItem<ServerModel>>((ServerModel server) {
        return DropdownMenuItem<ServerModel>(
          value: server,
          child: Text(server.name, style: TextStyle(fontSize: 20)),
        );
      }).toList(),
    );
  }
}

And here is the ServerModel class:

class ServerModel {
  ServerModel({this.name, this.url});
​
  ServerModel.empty() {
    this.name = null;
    this.url = null;
  }
​
  String name;
  String url;
}

Many thanks for reading this post.

2 Answers 2

7

There should be exactly one item with [DropdownButton]'s value: Instance of 'ServerModel'. Either zero or 2 or more [DropdownMenuItem]s were detected with the same value

This is happening because selected value inside the dropdown has to point to an existing list item (and obviously there shouldn't be any duplicates in that list). The way you've set it up right now is that the list of ServerModel is being generated during your widget build time and once it is built there no reference to the list inside the state of the widget. I hope my answer is clear enough, also take a look at correct code bellow:

class _DropdownWidgetState extends State<DropdownWidget> {
  List<ServerModel> serverModels = <ServerModel>[
    ServerModel(name: 'Default', url: 'https:defaultServer.com/'),
    ServerModel(name: 'Alpha', url: 'https://alphaServer.com/'),
    ServerModel(name: 'Beta', url: 'https://betaServer.com/'),
  ];
  ServerModel selectedServer;

  @override
  initState() {
    super.initState();
    selectedServer = serverModels[0];
  }

  @override
  Widget build(BuildContext context) {
    return DropdownButton<ServerModel>(
      value: selectedServer,
      icon: Icon(Icons.arrow_downward),
      iconSize: 24,
      elevation: 16,
      style: TextStyle(color: Colors.purple[700]),
      underline: Container(
        height: 2,
        color: Colors.purple[700],
      ),
      onChanged: (ServerModel newServer) {
        setState(() {
          selectedServer = newServer;
        });
      },
      items: serverModels.map((ServerModel map) {
        return new DropdownMenuItem<ServerModel>(
            value: map, child: Text(map.name));
      }).toList(),
    );
  }
}

Tested, working interactive answer on dartpad: https://dartpad.dev/153bad9baac64382e27bc41cdc8131c9

0
5

You're facing an equality problem.

In Dart, non-primitive types like SizedBox, List, and in your case, ServerModel are compared to each other using referential equality, meaning that they are equal to each other if they have the same reference. That is, they are the same instance.

So this code will print false:

print(ServerModel(name: 'Default', url: 'https://defaultServer.com/') == ServerModel(name: 'Default', url: 'https://defaultServer.com/'));
// TL;DR
print(ServerModel(xyz) == ServerModel(xyz)); // false

The solution would be to override the equality operator for your class ServerModel.

class ServerModel {
  ServerModel({this.name, this.url});

 ServerModel.empty() {
    this.name = null;
    this.url = null;
  }
   String name;
   String url;

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) return true;
    return other is ServerModel && other.name == name && other.url == url;
  }

  @override
  int get hashCode => name.hashCode ^ url.hashCode;
}

Now it should work.

PRO TIP: Use equatable to automatically generate equality and hashcode.

1
  • PRO TIP: use autoequal to generate equatable props))
    – sunnyday
    Commented Mar 6, 2021 at 8:51

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.