Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Either zero or 2 or more items were detected with the same value, and if the first item is not selected the other selections disappear from the button #138

Closed
Gendows opened this issue Mar 31, 2023 · 21 comments
Labels
guidance Question that needs advice or information.

Comments

@Gendows
Copy link

Gendows commented Mar 31, 2023

hello Eng, Ahmed, it's cheerful to know that you are from Egypt too :)

I'm using your package to add multiselect to the dropdownbutton i have on code below,

What i'm trying to achieve
displaying contact types from firestore to categories company contacts
image
every type has a title (which it can be changed to Multilanguage's titles) and a cons id in english (not changeable).

i want to display the title in drop down menu and store the selected ID's in a list.
tried custom class with two strings (id, title) but storing the value as (id, title) wasn't successful with his error every time for giving more than value (id, title) as item's value Stack Overflow post
items.where((DropdownMenuItem<T> item) { return item.value == value; }).length == 1': There should be exactly one item with [DropdownButton]'s value: عميل. Either zero or 2 or more [DropdownMenuItem]s were detected with the same value)
as a work around I'm trying to store only values in a selected list and only displaying it's title on the button, after that on profile page or whatever will search for the title on firestore collection via it's id

but even there are some issues if the first item is selected it's displaying the selected items, but if not, there is an error
Exception caught by rendering library: 'package:flutter/src/rendering/stack.dart': Failed assertion: line 749 pos 12: 'child != null': is not true.
or
Exception caught by rendering library: 'package:flutter/src/rendering/stack.dart': Failed assertion: line 748 pos 12: 'i == index': is not true.

and the id's are shown on the button, how can i change them to the title instead of the
(item value)?
some screens from the app
2
3
4

other thing how can i define the (title) value instead of item value for search fn? because it searching for the id not the displaying items title
5
6

full code:
`

Row contactsType(AppLocalizations t) {
    return Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <
        Widget>[
      Expanded(
        flex: 1,
        child: StreamBuilder<QuerySnapshot>(
            stream:
                _fireStore.collection('conttyp').orderBy('title').snapshots(),
            builder: (context, snapshot) {
              if (!snapshot.hasData) {
                return const Center(
                  child: CircularProgressIndicator(
                    backgroundColor: Colors.amber,
                  ),
                );
              } else {
                return DropdownButtonFormField2<String>(
                  isExpanded: true,
                  hint: Text(
                    t.category,
                    style: const TextStyle(fontFamily: 'Zahey'),
                  ),

                  items: snapshot.data!.docs.map((DocumentSnapshot ctype) {
                    return DropdownMenuItem<String>(
                      value: ctype.id,
                      enabled: false,
                      child: StatefulBuilder(
                        builder: (context, menuSetState) {
                          final isSelected = selectedTypes.contains(ctype.id);
                          return InkWell(
                            onTap: () {
                              isSelected
                                  ? selectedTypes.remove(ctype.id)
                                  : selectedTypes.add(ctype.id);
                              print(selectedTypes);
                              menuSetState(() {});
                              setState(() {});
                            },
                            child: Container(
                              height: double.infinity,
                              padding:
                                  const EdgeInsets.symmetric(horizontal: 16.0),
                              child: isSelected
                                  ? Row(
                                      children: [
                                        const Icon(Icons.check_box_outlined,
                                            color: Colors.amber),
                                        const SizedBox(width: 10),
                                        Text(
                                          ctype['title'],
                                          softWrap: true,
                                          style: const TextStyle(
                                              fontWeight: FontWeight.bold,
                                              color: Colors.amber),
                                        ),
                                      ],
                                    )
                                  : Row(
                                      children: [
                                        const Icon(
                                            Icons.check_box_outline_blank),
                                        const SizedBox(width: 10),
                                        Text(
                                          ctype['title'],
                                          softWrap: true,
                                        ),
                                      ],
                                    ),
                            ),
                          );
                        },
                      ),
                    );
                  }).toList(),
                  value: selectedTypes.isEmpty ? null : selectedTypes.last,
                  onChanged: (value) {},
                  selectedItemBuilder: (context) {
                    return selectedTypes.map(
                      (item) {
                        return Container(
                          padding: const EdgeInsets.symmetric(horizontal: 16.0),
                          child: Text(
                            selectedTypes.join(', '),
                            style: const TextStyle(
                              overflow: TextOverflow.ellipsis,
                            ),
                            maxLines: 1,
                          ),
                        );
                      },
                    ).toList();
                  },
                  buttonStyleData: const ButtonStyleData(
                    height: 40,
                    width: 200,
                  ),
                  dropdownStyleData: const DropdownStyleData(
                    maxHeight: 200,
                  ),
                  menuItemStyleData: const MenuItemStyleData(
                    height: 40,
                  ),
                  dropdownSearchData: DropdownSearchData(
                    searchController: typeEditingController,
                    searchInnerWidgetHeight: 50,
                    searchInnerWidget: Container(
                      height: 50,
                      padding: const EdgeInsets.only(
                        top: 8,
                        bottom: 4,
                        right: 8,
                        left: 8,
                      ),
                      child: TextFormField(
                        expands: true,
                        maxLines: null,
                        controller: typeEditingController,
                        decoration: InputDecoration(
                          isDense: true,
                          contentPadding: const EdgeInsets.symmetric(
                            horizontal: 10,
                            vertical: 8,
                          ),
                          hintText: 'بحث...',
                          border: OutlineInputBorder(
                            borderRadius: BorderRadius.circular(8),
                          ),
                        ),
                      ),
                    ),
                    searchMatchFn: (item, searchValue) {
                      return (item.value!.contains(searchValue));
                    },
                  ),
                  //This to clear the search value when you close the menu
                  onMenuStateChange: (isOpen) {
                    if (!isOpen) {
                      usersEditingController.clear();
                    }
                  },
                );
              }
            }),
      ),
    ]);
  }

`

@AhmedLSayed9 AhmedLSayed9 added the guidance Question that needs advice or information. label Mar 31, 2023
@AhmedLSayed9
Copy link
Owner

AhmedLSayed9 commented Mar 31, 2023

Hello @Gendows !

For your question at SO, you should use id for your value instead of title:

value: selectedTypeID.isEmpty ? null : selectedTypeID.last

If you want to use custom class as your value, you'll need to override operator == & hashCode to make your class immutable (comparable), or maybe use a package like Equatable/Freezed to easily achieve same thing.

@Gendows
Copy link
Author

Gendows commented Mar 31, 2023

I am already using the id not the title . See the code provided... :)

@AhmedLSayed9
Copy link
Owner

For the snippet above, you're using id as value for your menu items but you're using your custom class for your selected value (same as what you posted on SO), instead do:

value: selectedTypes.isEmpty ? null : selectedTypes.last.id,

@Gendows
Copy link
Author

Gendows commented Mar 31, 2023

There is no id in selectedTypes it only stores one value [client, contractor, ...etc]

And i'm not currently using any classes on this code
Please see the final code attached at the bottom of this thread..

@AhmedLSayed9
Copy link
Owner

Oh sorry the multiple questions confused me.
so your issue Either zero or 2 or more items is not the case any more?

@Gendows
Copy link
Author

Gendows commented Mar 31, 2023

Sorry for confusing you while explaining

No. The other two are the issue now.

The main points are

the button only shows selected item when the first one is selected

Displaying selected items text instead of value on button

Search by at items text not the id

@AhmedLSayed9
Copy link
Owner

To start with, you need to use your custom class as your value. That will make displaying selected items and search by items text easily done

@Gendows
Copy link
Author

Gendows commented Apr 1, 2023

There is no custom class 😕

I retrieve the list from firestore by stream builder
Everything is in the provided code 🙂

@Gendows
Copy link
Author

Gendows commented Apr 1, 2023

If you want you can use TeamViewer or anydisk app to view and debug the code yourself

@AhmedLSayed9
Copy link
Owner

tried custom class with two strings (id, title) but storing the value as (id, title)

I'm talking about this. You can do:

items: snapshot.data!.docs.map((DocumentSnapshot item) {
   final SomeItem item = SomeItem(id: item.id, title: item.get('title'));

and SomeItem should override ==/hashCode

You can ofc use separated lists for id and title but that would make it harder

@Gendows
Copy link
Author

Gendows commented Apr 1, 2023

if that will work it will be better,

this is the custom class i used before which gave me the error (zero or two or more...)

import 'dart:convert';

class ConTyp {
  final String id;
  final String title;

  ConTyp({
    required this.title,
    required this.id,
  });

  Map<String, dynamic> toMap() {
    final result = <String, dynamic>{};

    result.addAll({'title': title});
    result.addAll({'id': id});

    return result;
  }

  factory ConTyp.fromMap(Map<String, dynamic> map) {
    return ConTyp(
      title: map['title'] ?? '',
      id: map['id'] ?? '',
    );
  }

  String toJson() => json.encode(toMap());

  factory ConTyp.fromJson(String source) => ConTyp.fromMap(json.decode(source));
  }
}

```

@Gendows
Copy link
Author

Gendows commented Apr 1, 2023

i will add override string to my class as

  @override
  String toString() {
    return "ContactType (id: $id, title: $title)";
  }

@AhmedLSayed9
Copy link
Owner

AhmedLSayed9 commented Apr 1, 2023

because you need to override ==/hashCode, add this to your ConTyp:

@override
  bool operator ==(dynamic other) {
    return identical(this, other) ||
        (other.runtimeType == runtimeType &&
            other is ConTyp &&
            (identical(other.id, id) || other.id == id) &&
            (identical(other.title, title) || other.title == title));
  }

  @override
  int get hashCode => Object.hash(runtimeType, id, title);

Then:

Your list will be like: List<ConTyp> selectedItems = []; and you'll use ConTyp as your value.

Your selectedItemBuilder text will be like: selectedItems.map((i) => i.title).join(', ')

Your searchMatchFn will be like:

searchMatchFn: (item, searchValue) {
                      return (item.value.title.contains(searchValue));
                    },

@Gendows
Copy link
Author

Gendows commented Apr 1, 2023

image

@AhmedLSayed9
Copy link
Owner

Added by mistake, delete that line.

@Gendows
Copy link
Author

Gendows commented Apr 1, 2023

image

it's repeating values if clicked twice
the selected items not changing
selected items

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:projects/utilities/styles.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:projects/utilities/widgets/app_bar.dart';
import 'package:projects/utilities/widgets/drawer.dart';

class AddContactScreen extends StatefulWidget {
  const AddContactScreen({super.key});

  @override
  State<AddContactScreen> createState() => _AddContactScreenState();
}

class _AddContactScreenState extends State<AddContactScreen> {
// firebase
  final _fireStore = FirebaseFirestore.instance;
  final _auth = FirebaseAuth.instance;
  late User loggedInUser;

  // form variable
  String? fname;
  String? userid;
  String? lname;
  String? mobile1;
  String? mobile2;
  String? notes;
  String? typTitle;
  List<String> selectedTypes = [];
  List<String> usersTypes = [];
  final TextEditingController typeEditingController = TextEditingController();
  final TextEditingController usersEditingController = TextEditingController();
  bool isHvAccChecked = false;
  @override
  void dispose() {
    typeEditingController.dispose();
    usersEditingController.dispose();
    super.dispose();
  }

  final _formKey = GlobalKey<FormState>();

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

  void getCurrentUser() async {
    try {
      final user = _auth.currentUser;
      if (user != null) {
        loggedInUser = user;
        print(loggedInUser.email);
      }
    } catch (e) {
      print(e);
    }
  }

  @override
  Widget build(BuildContext context) {
    final t = AppLocalizations.of(context)!;
    return Scaffold(
      appBar: RQAppBar(appBarText: t.newcontact),
      drawer: const RqDrawer(),
      drawerEdgeDragWidth: 50,
      body: Container(
        decoration: kAppBackGround,
        height: double.infinity,
        child: SingleChildScrollView(
          child: Column(
            children: [
              Form(
                key: _formKey,
                child: Container(
                  margin: const EdgeInsets.all(15),
                  child: AutofillGroup(
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        haveAccChk(t),
                        if (isHvAccChecked) ...[chooseUser(t)],
                        nameFields(t),
                        contactsType(t),
                        mobileNumber(t),
                      ],
                    ),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

// choose user name
  Row chooseUser(AppLocalizations t) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: <Widget>[
        // Mobile 1
        Expanded(
          flex: 1,
          child: StreamBuilder(
              stream: _fireStore.collection('conttyp').snapshots(),
              builder: (context, snapshot) {
                if (!snapshot.hasData) {
                  return const Center(
                    child: CircularProgressIndicator(
                      backgroundColor: Colors.amber,
                    ),
                  );
                } else {
                  return StreamBuilder(
                      stream: _fireStore.collection('users').snapshots(),
                      builder: (context, snapshot) {
                        if (!snapshot.hasData) {
                          return const Center(
                            child: LinearProgressIndicator(
                              backgroundColor: Colors.amber,
                            ),
                          );
                        } else {
                          return DropdownButtonFormField2<String>(
                            isExpanded: true,
                            hint: Text(
                              t.choosefromlist,
                              style: const TextStyle(fontFamily: 'Zahey'),
                            ),
                            items: snapshot.data!.docs
                                .map((DocumentSnapshot users) {
                              return DropdownMenuItem<String>(
                                  value: users.id,
                                  child: StatefulBuilder(
                                      builder: (context, menuSetState) {
                                    return InkWell(
                                        onTap: () {
                                          setState(() {
                                            fname =
                                                users.get('fname') as String;
                                            fname =
                                                users.get('lname') as String;
                                          });
                                        },
                                        child: Text(users.get('name')));
                                  }));
                            }).toList(),
                            value: userid,
                            onChanged: (value) {
                              setState(() {
                                userid = value as String;
                              });
                            },
                            buttonStyleData: const ButtonStyleData(
                              height: 40,
                              width: 200,
                            ),
                            dropdownStyleData: const DropdownStyleData(
                              maxHeight: 200,
                            ),
                            menuItemStyleData: const MenuItemStyleData(
                              height: 40,
                            ),
                            dropdownSearchData: DropdownSearchData(
                              searchController: usersEditingController,
                              searchInnerWidgetHeight: 50,
                              searchInnerWidget: Container(
                                height: 50,
                                padding: const EdgeInsets.only(
                                  top: 8,
                                  bottom: 4,
                                  right: 8,
                                  left: 8,
                                ),
                                child: TextFormField(
                                  expands: true,
                                  maxLines: null,
                                  controller: usersEditingController,
                                  decoration: InputDecoration(
                                    isDense: true,
                                    contentPadding: const EdgeInsets.symmetric(
                                      horizontal: 10,
                                      vertical: 8,
                                    ),
                                    hintText: 'بحث...',
                                    border: OutlineInputBorder(
                                      borderRadius: BorderRadius.circular(8),
                                    ),
                                  ),
                                ),
                              ),
                              searchMatchFn: (users, searchValue) {
                                return (users.value
                                    .toString()
                                    .contains(searchValue));
                              },
                            ),
                            //This to clear the search value when you close the menu
                            onMenuStateChange: (isOpen) {
                              if (!isOpen) {
                                usersEditingController.clear();
                              }
                            },
                          );
                        }
                      });
                }
              }),
        ),
      ],
    );
  }

//Check if user have  account
  Row haveAccChk(AppLocalizations t) {
    return Row(
      children: [
        Text(
          t.contHvAcc,
          style: kSubtitleStyle,
        ),
        Checkbox(
          value: isHvAccChecked,
          onChanged: (bool? value) {
            setState(() {
              isHvAccChecked = value!;
            });
          },
        )
      ],
    );
  }

//Full Name
  Row nameFields(AppLocalizations t) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: <Widget>[
        // First Name
        Expanded(
          flex: 1,
          child: firstName(t),
        ),
        const SizedBox(width: 15),
        //Last Name
        Expanded(
          flex: 1,
          child: lastName(t),
        ),
      ],
    );
  }

//Last Name
  TextFormField lastName(AppLocalizations t) {
    return TextFormField(
      controller: usersEditingController,
      textInputAction: TextInputAction.next,
      keyboardType: TextInputType.name,
      autofillHints: const [AutofillHints.familyName],
      key: const ValueKey('lname'),
      decoration: InputDecoration(
        label: Text(
          t.lname,
        ),
        labelStyle: const TextStyle(fontFamily: 'Zahey'),
        hintText: t.lname,
      ),
      validator: (value) {
        if (value!.isEmpty) {
          return t.required;
        } else {
          return null;
        }
      },
      onSaved: (value) {
        setState(() {
          lname = value!;
        });
      },
    );
  }

//First Name
  TextFormField firstName(AppLocalizations t) {
    return TextFormField(
      textInputAction: TextInputAction.next,
      keyboardType: TextInputType.name,
      autofillHints: const [AutofillHints.givenName],
      key: const ValueKey('fname'),
      decoration: InputDecoration(
        label: Text(
          t.fname,
        ),
        labelStyle: const TextStyle(fontFamily: 'Zahey'),
        hintText: t.fname,
      ),
      validator: (value) {
        if (value!.isEmpty) {
          return t.required;
        } else {
          return null;
        }
      },
      onSaved: (value) {
        setState(() {
          fname = value!;
        });
      },
    );
  }

// Multiselect contact types from firestore
  Row contactsType(AppLocalizations t) {
    return Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <
        Widget>[
      Expanded(
        flex: 1,
        child: StreamBuilder<QuerySnapshot>(
            stream:
                _fireStore.collection('conttyp').orderBy('title').snapshots(),
            builder: (context, snapshot) {
              if (!snapshot.hasData) {
                return const Center(
                  child: CircularProgressIndicator(
                    backgroundColor: Colors.amber,
                  ),
                );
              } else {
                return DropdownButtonFormField2<String>(
                  isExpanded: true,
                  hint: Text(
                    t.category,
                    style: const TextStyle(fontFamily: 'Zahey'),
                  ),

                  items: snapshot.data!.docs.map((DocumentSnapshot ctype) {
                    return DropdownMenuItem<String>(
                      value: ctype.id,
                      enabled: false,
                      child: StatefulBuilder(
                        builder: (context, menuSetState) {
                          final isSelected = selectedTypes.contains(ctype.id);
                          return InkWell(
                            onTap: () {
                              isSelected
                                  ? selectedTypes.remove(ctype.id)
                                  : selectedTypes.add(ctype.id);
                              print(selectedTypes);
                              menuSetState(() {});
                              setState(() {});
                            },
                            child: Container(
                              height: double.infinity,
                              padding:
                                  const EdgeInsets.symmetric(horizontal: 16.0),
                              child: isSelected
                                  ? Row(
                                      children: [
                                        const Icon(Icons.check_box_outlined,
                                            color: Colors.amber),
                                        const SizedBox(width: 10),
                                        Text(
                                          ctype['title'],
                                          softWrap: true,
                                          style: const TextStyle(
                                              fontWeight: FontWeight.bold,
                                              color: Colors.amber),
                                        ),
                                      ],
                                    )
                                  : Row(
                                      children: [
                                        const Icon(
                                            Icons.check_box_outline_blank),
                                        const SizedBox(width: 10),
                                        Text(
                                          ctype['title'],
                                          softWrap: true,
                                        ),
                                      ],
                                    ),
                            ),
                          );
                        },
                      ),
                    );
                  }).toList(),
                  value: selectedTypes.isEmpty ? null : selectedTypes.last,
                  onChanged: (value) {},
                  selectedItemBuilder: (context) {
                    return selectedTypes.map(
                      (item) {
                        return Container(
                          padding: const EdgeInsets.symmetric(horizontal: 16.0),
                          child: Text(
                            selectedTypes.join(', '),
                            style: const TextStyle(
                              overflow: TextOverflow.ellipsis,
                            ),
                            maxLines: 1,
                          ),
                        );
                      },
                    ).toList();
                  },
                  buttonStyleData: const ButtonStyleData(
                    height: 40,
                    width: 200,
                  ),
                  dropdownStyleData: const DropdownStyleData(
                    maxHeight: 200,
                  ),
                  menuItemStyleData: const MenuItemStyleData(
                    height: 40,
                  ),
                  dropdownSearchData: DropdownSearchData(
                    searchController: typeEditingController,
                    searchInnerWidgetHeight: 50,
                    searchInnerWidget: Container(
                      height: 50,
                      padding: const EdgeInsets.only(
                        top: 8,
                        bottom: 4,
                        right: 8,
                        left: 8,
                      ),
                      child: TextFormField(
                        expands: true,
                        maxLines: null,
                        controller: typeEditingController,
                        decoration: InputDecoration(
                          isDense: true,
                          contentPadding: const EdgeInsets.symmetric(
                            horizontal: 10,
                            vertical: 8,
                          ),
                          hintText: 'بحث...',
                          border: OutlineInputBorder(
                            borderRadius: BorderRadius.circular(8),
                          ),
                        ),
                      ),
                    ),
                    searchMatchFn: (item, searchValue) {
                      return (item.value!.contains(searchValue));
                    },
                  ),
                  //This to clear the search value when you close the menu
                  onMenuStateChange: (isOpen) {
                    if (!isOpen) {
                      usersEditingController.clear();
                    }
                  },
                );
              }
            }),
      ),
    ]);
  }

//Mobile Number
  TextFormField mobileNumber(AppLocalizations t) {
    return TextFormField(
      textInputAction: TextInputAction.next,
      keyboardType: TextInputType.phone,
      autofillHints: const [AutofillHints.telephoneNumber],
      key: const ValueKey('mobile1'),
      decoration: InputDecoration(
        label: Text(
          t.mobileD,
        ),
        labelStyle: const TextStyle(fontFamily: 'Zahey'),
        hintText: t.mobileD,
      ),
      validator: (value) {
        String pattern = r'(^(?:[+0])?[0-9]{10,12}$)';
        RegExp regExp = RegExp(pattern);
        if (value!.isEmpty) {
          return t.required;
        } else if (!regExp.hasMatch(value)) {
          return t.mobilex;
        } else {
          return null;
        }
      },
      onSaved: (value) {
        setState(() {
          mobile1 = value!;
        });
      },
    );
  }
}

@AhmedLSayed9
Copy link
Owner

The code you posted doesn't have your ContactType custom class.

Also, no need for screenshots + unrelated widgets code

@Gendows
Copy link
Author

Gendows commented Apr 1, 2023

sorry, copied the wrong file

Class File

import 'dart:convert';

class ConTyp {
  final String id;
  final String title;

  ConTyp({
    required this.title,
    required this.id,
  });

  Map<String, dynamic> toMap() {
    final result = <String, dynamic>{};

    result.addAll({'title': title});
    result.addAll({'id': id});

    return result;
  }

  factory ConTyp.fromMap(Map<String, dynamic> map) {
    return ConTyp(
      title: map['title'] ?? '',
      id: map['id'] ?? '',
    );
  }

  String toJson() => json.encode(toMap());

  factory ConTyp.fromJson(String source) => ConTyp.fromMap(json.decode(source));

  @override
  bool operator ==(dynamic other) {
    return identical(this, other) ||
        (other.runtimeType == runtimeType &&
            other is ConTyp &&
            (identical(other.id, id) || other.id == id) &&
            (identical(other.title, title) || other.title == title));
  }

  @override
  int get hashCode => Object.hash(runtimeType, id, title);

  @override
  String toString() {
    return "ContactType (id: $id, title: $title)";
  }
}

Widget file

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:projects/functions/contact_types.dart';

class TestScreen extends StatefulWidget {
  const TestScreen({super.key});

  @override
  State<TestScreen> createState() => _TestScreenState();
}

class _TestScreenState extends State<TestScreen> {
  List<ConTyp> selectedTypes = [];
  final TextEditingController typeSearhController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(
                stream: FirebaseFirestore.instance
                    .collection('conttyp')
                    .snapshots(),
                builder: (context, snapshot) {
                  if (!snapshot.hasData) {
                    return const Center(
                      child: LinearProgressIndicator(
                        backgroundColor: Colors.amber,
                      ),
                    );
                  } else {
                    return Expanded(
                      flex: 1,
                      child: Center(
                        child: DropdownButtonFormField2<ConTyp>(
                          isExpanded: true,
                          hint: Align(
                            alignment: AlignmentDirectional.center,
                            child: Text(
                              'Select Items',
                              style: TextStyle(
                                fontSize: 14,
                                color: Theme.of(context).hintColor,
                              ),
                            ),
                          ),
                          items:
                              snapshot.data!.docs.map((DocumentSnapshot typ) {
                            final ConTyp item =
                                ConTyp(id: typ.id, title: typ.get('title'));
                            return DropdownMenuItem<ConTyp>(
                              value: item,
                              //disable default onTap to avoid closing menu when selecting an item
                              enabled: false,
                              child: StatefulBuilder(
                                builder: (context, menuSetState) {
                                  final isSelected =
                                      selectedTypes.contains(item.title);
                                  //.contains((e) => e['title'] == item.title);
                                  return InkWell(
                                    onTap: () {
                                      isSelected
                                          ? selectedTypes.remove(item)
                                          : selectedTypes.add(item);
                                      print(selectedTypes);
                                      //This rebuilds the StatefulWidget to update the button's text
                                      setState(() {});
                                      //This rebuilds the dropdownMenu Widget to update the check mark
                                      menuSetState(() {});
                                    },
                                    child: Container(
                                      height: double.infinity,
                                      padding: const EdgeInsets.symmetric(
                                          horizontal: 16.0),
                                      child: Row(
                                        children: [
                                          isSelected
                                              ? const Icon(
                                                  Icons.check_box_outlined)
                                              : const Icon(Icons
                                                  .check_box_outline_blank),
                                          const SizedBox(width: 16),
                                          Text(
                                            item.title,
                                            style: const TextStyle(
                                              fontSize: 14,
                                            ),
                                          ),
                                        ],
                                      ),
                                    ),
                                  );
                                },
                              ),
                            );
                          }).toList(),
                          //Use last selected item as the current value so if we've limited menu height, it scroll to last item.
                          value:
                              selectedTypes.isEmpty ? null : selectedTypes.last,
                          onChanged: (value) {},
                          selectedItemBuilder: (context) {
                            return snapshot.data!.docs.map(
                              (item) {
                                return Container(
                                  alignment: AlignmentDirectional.center,
                                  padding: const EdgeInsets.symmetric(
                                      horizontal: 16.0),
                                  child: Text(
                                    selectedTypes
                                        .map((i) => i.title)
                                        .join(', '),
                                    style: const TextStyle(
                                      fontSize: 14,
                                      overflow: TextOverflow.ellipsis,
                                    ),
                                    maxLines: 1,
                                  ),
                                );
                              },
                            ).toList();
                          },
                          buttonStyleData: const ButtonStyleData(
                            height: 40,
                            width: 140,
                          ),
                          menuItemStyleData: const MenuItemStyleData(
                            height: 40,
                            padding: EdgeInsets.zero,
                          ),
                          dropdownSearchData: DropdownSearchData(
                            searchController: typeSearhController,
                            searchInnerWidgetHeight: 50,
                            searchInnerWidget: Container(
                              height: 50,
                              padding: const EdgeInsets.only(
                                top: 8,
                                bottom: 4,
                                right: 8,
                                left: 8,
                              ),
                              child: TextFormField(
                                expands: true,
                                maxLines: null,
                                controller: typeSearhController,
                                decoration: InputDecoration(
                                  isDense: true,
                                  contentPadding: const EdgeInsets.symmetric(
                                    horizontal: 10,
                                    vertical: 8,
                                  ),
                                  hintText: 'بحث...',
                                  border: OutlineInputBorder(
                                    borderRadius: BorderRadius.circular(8),
                                  ),
                                ),
                              ),
                            ),
                            searchMatchFn: (item, searchValue) {
                              return (item.value!.title.contains(searchValue));
                            },
                          ),
                          //This to clear the search value when you close the menu
                          onMenuStateChange: (isOpen) {
                            if (!isOpen) {
                              typeSearhController.clear();
                            }
                          },
                        ),
                      ),
                    );
                  }
                }),
          ]),
    );
  }
}

print(selectedTypes) list as i tap on items (see duplications of client item)

I/flutter (25969): [ContactType (id: client, title: عميل)]
I/flutter (25969): [ContactType (id: client, title: عميل), ContactType (id: contractor, title: مقاول)]
I/flutter (25969): [ContactType (id: client, title: عميل), ContactType (id: contractor, title: مقاول), ContactType (id: client, title: عميل)]

@Gendows
Copy link
Author

Gendows commented Apr 1, 2023

fixed by changing
selectedTypes.contains(item.title);
to
selectedTypes.contains(item);

@AhmedLSayed9
Copy link
Owner

Alright! :)

@Gendows
Copy link
Author

Gendows commented Apr 1, 2023

thank you for your help, you made my day 💯 🥇

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
guidance Question that needs advice or information.
Projects
None yet
Development

No branches or pull requests

2 participants