skip to Main Content

I’m trying to build a shopping list based on meals ingredients that are passed in Route Settings on which user can select/deselect each items separately. Here is the code:

import 'package:cookup/src/consts/colors.dart';
import 'package:flutter/material.dart';
import '/dummy_data.dart';

class ShoppingListScreen extends StatefulWidget {
  static const routeName = '/shoppig-list';

  final Function toggleFavorite;
  final Function isFavorite;

  ShoppingListScreen(this.toggleFavorite, this.isFavorite);

  @override
  State<ShoppingListScreen> createState() => _ShoppingListScreenState();
}

class _ShoppingListScreenState extends State<ShoppingListScreen> {
  bool isSelected = false;

  @override
  Widget build(BuildContext context) {
    final mealId = ModalRoute.of(context)?.settings.arguments as String;
    final selectedMeal = DUMMY_MEALS.firstWhere((meal) => meal.id == mealId);

    return Scaffold(
      appBar: AppBar(
        iconTheme: IconThemeData(color: kFontColorBlack),
        backgroundColor: kBackgroundColor,
        title: Text(
          '${selectedMeal.title}',
          maxLines: 2,
          overflow: TextOverflow.ellipsis,
          textAlign: TextAlign.center,
          style: TextStyle(color: kFontColorBlack),
        ),
        actions: [
          IconButton(
            icon: Icon(
              widget.isFavorite(mealId)
                  ? Icons.favorite
                  : Icons.favorite_border,
            ),
            onPressed: () => widget.toggleFavorite(mealId),
          ),
        ],
      ),
      body: SingleChildScrollView(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Container(
              height: 250,
              width: double.infinity,
              child: Image.network(
                selectedMeal.imageUrl,
                fit: BoxFit.cover,
              ),
            ),
            Container(
              alignment: Alignment.topLeft,
              margin: EdgeInsets.symmetric(vertical: 10),
              child: const Padding(
                padding: EdgeInsets.all(16.0),
                child: Text(
                  "Ingredients",
                  style: TextStyle(
                    fontWeight: FontWeight.bold,
                    fontSize: 22.0,
                  ),
                ),
              ),
            ),
            ListView.builder(
              itemCount: selectedMeal.ingredients.length,
              shrinkWrap: true,
              physics: const NeverScrollableScrollPhysics(),
              itemBuilder: (context, index) {
                return CheckboxListTile(
                  title: Text(selectedMeal.ingredients[index]),
                  value: isSelected,
                  controlAffinity: ListTileControlAffinity.leading,
                  onChanged: (
                    value,
                  ) {
                    setState(() {
                      isSelected = value!;
                    });
                  },
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

and the result is visible here as I got stuck having all items being selcted/deselected on click:
enter image description here
Which is not the expeted behaviour as I need only one item being seletced on click. I was trying variious things but end up having either the above result, having some error regaridng incorrect list Range or I got error that dependOnInheritedWidgetOfExactType<_ModalScopeStatus>() or dependOnInheritedElement() was called before _ShoppingListScreenState.initState() completed. What I am doing wrong? Please help! 🙂

2

Answers


  1. Chosen as BEST ANSWER

    Thanks! I have updated the model from the list to a map with a boolean value and used below builder code - it works as expected now:

                ListView.builder(
              itemCount: selectedMeal.ingredients.length,
              shrinkWrap: true,
              physics: const NeverScrollableScrollPhysics(),
              itemBuilder: (context, index) {
                final ingredient =
                    selectedMeal.ingredients.keys.toList()[index];
                return CheckboxListTile(
                  title: Text(ingredient),
                  key: Key(ingredient),
                  value: selectedIngredients[ingredient] ?? false,
                  controlAffinity: ListTileControlAffinity.leading,
                  onChanged: (
                    value,
                  ) {
                    setState(() {
                      selectedIngredients[ingredient] = value!;
                    });
                  },
                );
              },
            ),
    

  2. The reason why you are getting this error is you are passing the same value
    isSelected in all your items in the ListView.builder.

    You have to add a boolean field in your model class which will store the value for each and every item in your list.

    Consider following example for model class.

    class DummyMeals {
     final String name;
     final String id;
     bool isSelected;
    
     DummyMeals({
      required this.name,
      required this.id,
      this.isSelected = false,
     });
    }
    
    ListView.builder(
              itemCount: itemList.length,
              shrinkWrap: true,
              physics: const NeverScrollableScrollPhysics(),
              itemBuilder: (context, index) {
                return CheckboxListTile(
                  key: Key('$index'),
                  title: Text(itemList[index].name),
                  value: itemList[index].isSelected,
                  controlAffinity: ListTileControlAffinity.leading,
                  onChanged: (val) {
                    setState(() {
                      itemList[index].isSelected = !itemList[index].isSelected;
                    });
                  },
                );
              },
            ),
    

    Hope this helps !

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search