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?
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());
});
}