Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Error when selecting a dropdown item in Flutter

I have a problem with a Flutter dropdown. When I select one of the items it throws an error:

Another exception was thrown: 'package:flutter/src/material/dropdown.dart': Failed assertion: line 481 pos 15: 'value == null || items.where((DropdownMenuItem item) => item.value == value).length == 1': is not true.

I was searching and people tell that this error is produced because the selected element doesn't belong to the original list but after some debug I can see it does. I can't find the source of this error, so I would appreciate any help.

Here is my code

FeedCategory model

import 'package:meta/meta.dart';

class FeedCategory {
  static final dbId = "id";
  static final dbName = "name";

  int id;
  String name;

  FeedCategory({this.id, @required this.name});

  FeedCategory.fromMap(Map<String, dynamic> map)
      : this(
    id: map[dbId],
    name: map[dbName],
  );

  Map<String, dynamic> toMap() {
    return {
      dbId: id,
      dbName: name,
    };
  }

  @override
  String toString() {
    return 'FeedCategory{id: $id, name: $name}';
  }
}

Widget

import 'package:app2date/repository/repository.dart';
import 'package:app2date/model/FeedSource.dart';
import 'package:app2date/model/FeedCategory.dart';
import 'package:app2date/util/ui.dart';
import 'package:flutter/material.dart';

class ManageFeedSource extends StatefulWidget {
  ManageFeedSource({Key key, this.feedSource}) : super(key: key);

  final FeedSource feedSource;

  @override
  _ManageFeedSource createState() => new _ManageFeedSource();
}

class _ManageFeedSource extends State<ManageFeedSource> {
  FeedCategory _feedCategory;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('New Feed'),
      ),
      body: new FutureBuilder(
        future: Repository.get().getFeedCategories(),
        builder: (BuildContext context, AsyncSnapshot snapshot) {
          List<FeedCategory> categoriesList = snapshot.data;
          if (categoriesList != null) {
            return new DropdownButton<FeedCategory>(
              hint: Text('Choose category...'),
              value: _feedCategory,
              items: categoriesList.map((FeedCategory category) {
                return DropdownMenuItem<FeedCategory>(
                  value: category,
                  child: Text(category.name),
                );
              }).toList(),
              onChanged: (FeedCategory category) {
                print('Selected: $category');
                setState(() {
                  _feedCategory = category;
                });
              },
            );
          } else {
            return Container(
              decoration: new BoxDecoration(color: Colors.white),
            );
          }
        },
      ),
    );
  }

  @override
  void initState() {
    super.initState();
  }
}

Repository getFeedCategories method

Future<List<FeedCategory>> getFeedCategories() async {
  return await database.getFeedCategories();
}

Database getFeedCategories method

Future<List<FeedCategory>> getFeedCategories() async {
  var dbClient = await db;
  var query = "SELECT * FROM $feedCategoryTableName;";
  var result = await dbClient.rawQuery(query);
  List<FeedCategory> feedCategories = [];
  for (Map<String, dynamic> item in result) {
    feedCategories.add(new FeedCategory.fromMap(item));
  }
  return feedCategories;
}

The categoriesList content and the selected category (debugger) enter image description here

like image 396
Alberto Méndez Avatar asked Aug 28 '18 18:08

Alberto Méndez


3 Answers

I think I've figured out your issue. It stems from how you're using FutureBuilder.

This is what I think is going on:

  1. Your app is runs. It does the call to getFeedCategories()
  2. The future completes, and the list is built, etc with the following items:

    obj 123 ==> FeedCategory(Prueba), obj 345 ==> FeedCategory(Categories 2)

  3. You select the item from the dropdown, setState() is called

    Your _feedCategory is now equal to obj 123 ==> FeedCategory(Prueba)

  4. Widget is rebuilt. It does another call to getFeedCategories()

  5. Future completes, list is built etc with the following items

    obj 567 ==> FeedCategory(Prueba), obj 789 ==> FeedCategory(Categories 2)

  6. The Dropdown tests items.where((DropdownMenuItem item) => item.value == value).length == 1, but length == 0 because obj 123 ==> FeedCategory(Prueba) is not found.

There are a couple of solutions to your problem. One would be to add an equals operator to your FeedCategory class that compares the category and/or id.

Another would be to separate the Future used in the futurebuilder from the part that changes - you could do this either by keeping the future as a member variable (maybe instantiate it in initState?), or by making the inner part of the builder into it's own Stateful widget (in which case you could probably make ManageFeedSource a StatelessWidget). I'd recommend the last option.

Let me know if that doesn't solve your issue, but I'm pretty sure that's the reason it's doing what it's doing.

like image 65
rmtmckenzie Avatar answered Nov 20 '22 12:11

rmtmckenzie


To complete the rmtmckenzie answer, here is the code you should put in your FeedCategory object:

bool operator ==(o) => o is FeedCategory && o.name == name;
int get hashCode => name.hashCode;

Edit: A link that explains what hashCode is made for in Flutter

like image 45
Quentin Doutriaux Avatar answered Nov 20 '22 13:11

Quentin Doutriaux


i got this error when i initialized the dropdown with a value that was not actually in the dropdown list which was unclear from the error message.

like image 2
Austen Novis Avatar answered Nov 20 '22 13:11

Austen Novis