Flutter Firestore Collection Dependent Drop Down Menus

Issue

I am trying to implement cascaded drop down menus using flutter with firestore as backend. Here is my code so far and it works by loading all the categories and subcategories independently.

class AddProductScreen extends StatefulWidget {
  final ProductData? data;
  AddProductScreen({this.data});
  @override
  AddProductScreenState createState() => AddProductScreenState();
}

class AddProductScreenState extends State<AddProductScreen> {

  AsyncMemoizer categoryMemoizer = AsyncMemoizer<List<CategoryData>>();
  AsyncMemoizer subCategoryMemoizer = AsyncMemoizer<List<SubCategoryData>>();

  CategoryData? selectedCategory;
  SubCategoryData? selectedSubCategory;
  // CategoryData selectedSubCategory;

  List<CategoryData> categories = [];
  List<SubCategoryData> subCategories = [];

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

  Future<void> init() async {
    categories = await categoryService.categoriesFuture();
    subCategories = await subCategoryService.categoriesFuture();
    setState(() {});
  }

  @override
  void setState(fn) {
    if (mounted) super.setState(fn);
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      backgroundColor: white,
      appBar: AppBar(
        backgroundColor: white,
        elevation: 0.0,
        title: Text('Title'),
        actions: [
          isUpdate
              ? IconButton(
                  icon: Icon(Icons.delete_forever, color: black),
                  onPressed: () {
                    _showMyDialog();
                  },
                ).paddingOnly(right: 8)
              : SizedBox(),
        ],
      ),
      body: SingleChildScrollView(
        child: Form(
          key: formKey,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              if (categories.isNotEmpty)
                Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text('Select Category', style: boldTextStyle(size: 18)),
                    8.height,
                    Container(
                      width: context.width() * 0.45,
                      decoration: BoxDecoration(
                          borderRadius: radius(), color: Colors.grey.shade200),
                      padding:
                          EdgeInsets.symmetric(horizontal: 16, vertical: 4),
                      child: DropdownButton(
                        underline: Offstage(),
                        items: categories.map((e) {
                          return DropdownMenuItem(
                              child: Text(e.name.validate()), value: e);
                        }).toList(),
                        isExpanded: true,
                        value: selectedCategory,
                        onChanged: (dynamic c) {
                          selectedCategory = c;
                          setState(() {
                          });
                        },
                      ),
                    ),
                  ],
                ),
              if (subCategories.isNotEmpty)
                Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text('Select Sub Category', style: boldTextStyle(size: 18)),
                    8.height,
                    Container(
                      width: context.width() * 0.45,
                      decoration: BoxDecoration(
                          borderRadius: radius(), color: Colors.grey.shade200),
                      padding:
                          EdgeInsets.symmetric(horizontal: 16, vertical: 4),
                      child: DropdownButton(
                        underline: Offstage(),
                        items: subCategories.map((e) {
                          return DropdownMenuItem(
                              child: Text(e.name.validate()), value: e);
                        }).toList(),
                        isExpanded: true,
                        value: selectedSubCategory,
                        onChanged: (dynamic c) {
                          selectedSubCategory = c;
                          setState(() {});
                        },
                      ),
                    ),
                  ],
                ),
            ],
          ).paddingAll(16),
        ),
      ),
    ).cornerRadiusWithClipRRect(16);
  }
}

and here are the firestore calls

  Future<List<SubCategoryData>> categoriesFuture() async {
    return await ref!.get().then((x) => x.docs
        .map((y) => SubCategoryData.fromJson(y.data() as Map<String, dynamic>))
        .toList());
  }

  Future<List<SubCategoryData>> categoriesFutureById(String? doc) async {
    DocumentReference categoryRef = db.doc('categories/' + doc.toString());
    return await ref!
        .where(SubCategoryKeys.categoryRef, isEqualTo: categoryRef)
        .get()
        .then((x) => x.docs
            .map((y) =>
                SubCategoryData.fromJson(y.data() as Map<String, dynamic>))
            .toList());
  }

What should I do when the onchanged method of the first down is called?

Solution

I finally resolved it by modifying the onChanged method of first drop down as follow.

onChanged: (dynamic c) {
  selectedCategory = c;
  if (selectedCategory!.id != null) {
    loadSubcategories(selectedCategory!.id);
  }
  setState(() {});
},

Future<void> loadSubcategories(String? docId) async {
    DocumentReference categoryRef = db.doc('categories/' + docId.toString());

    subCategoryService.categoriesFutureById(categoryRef).then((value) {
      // isLoading = false;
      log(value);
      subCategories.clear();
      subCategories.addAll(value);
      selectedSubCategory = subCategories.first;

      setState(() {});
    }).catchError((e) {
      //isLoading = false;
      setState(() {});
      toast(e.toString());
    });
}

Answered By – rnative

Answer Checked By – Jay B. (FlutterFixes Admin)

Leave a Reply

Your email address will not be published. Required fields are marked *