Home > Back-end >  Bad State: no element when using firstWhere function
Bad State: no element when using firstWhere function

Time:11-07

I’m having this error above when I try to compare the parameter I get from the route with the value of a list.

MealDetailScreen widget

import 'package:flutter/material.dart';

import '../dummy_data.dart';

class MealDetailScreen extends StatelessWidget {
  static const detailScreenRouteName = '/meal-detail';

  @override
  Widget build(BuildContext context) {
    final mealId = ModalRoute.of(context)!.settings.arguments;
    final selectedMeal = DUMMY_MEALS.firstWhere((meal) => meal.id == mealId);
    return Scaffold(
      appBar: AppBar(title: Text('$mealId')),
      body: Container(
        child: Column(
          children: [
            Container(
              height: 300,
              width: double.infinity,
              child: Image.network(
                selectedMeal.imageUrl,
                fit: BoxFit.cover,
              ),
            )
          ],
        ),
      ),
    );
  }
}

If i try to add the optional argument 'orElse' to the firstWhere function i still get an error: this time it is The return type 'Null' isn't a 'Meal', as required by the closure's context.

This is the list im using to compare the id.

const DUMMY_MEALS = [
  Meal(
    isVegetarian: false,
    isLactoseFree: false,
    isVegan: false,
    id: 'm1',
    categories: [
      'c1',
      'c2',
    ],
    title: 'Spaghetti with Tomato Sauce',
    affordability: Affordability.Affordable,
    complexity: Complexity.Simple,
    imageUrl: '...',
    duration: 20,
    ingredients: ['4 Tomatoes', '...'],
    steps: ['Cut the tomatoes and the onion into small pieces.', '...'],
    isGlutenFree: false,
  ),
];

And this is how i pass the id as parameter

void selectMeal(BuildContext context) {
    Navigator.of(context)
        .pushNamed(MealDetailScreen.detailScreenRouteName, arguments: {id});
  }

Meal model

import 'package:flutter/foundation.dart';

enum Complexity { Simple, Challenging, Hard }

enum Affordability { Affordable, Pricey, Luxurious }

class Meal {
  final String id;
  final List<String> categories;
  final String title;
  final String imageUrl;
  final List<String> ingredients;
  final List<String> steps;
  final int duration;
  final Complexity complexity;
  final Affordability affordability;
  final bool isGlutenFree;
  final bool isLactoseFree;
  final bool isVegan;
  final bool isVegetarian;

  const Meal(
      {required this.id,
      required this.categories,
      required this.title,
      required this.imageUrl,
      required this.ingredients,
      required this.steps,
      required this.duration,
      required this.complexity,
      required this.affordability,
      required this.isGlutenFree,
      required this.isLactoseFree,
      required this.isVegan,
      required this.isVegetarian});
}

CodePudding user response:

While passing arguments: {id} you are passing _HashSet<String>. But for single value id string will be enough, else use map.

In this case passing argument will be

  Navigator.of(context)
        .pushNamed(MealDetailScreen.detailScreenRouteName, arguments: id);

While iterating the DUMMY_MEALS list it is possible that we will get an id that is not included on DUMMY_MEALS list. In this case you can create and pass emptyMeal on orElse state or just use try catch to handle exception.


    Meal? selectedMeal;

    try {
      final selectedMeal = DUMMY_MEALS.firstWhere((meal) => meal.id == mealId);
    } catch (e) {
      print(e.toString());
    }

While the selectedMeal is nullable, we can check if it contains meal or not.

 return Scaffold(
      appBar: AppBar(title: Text('$mealId')),
      body: selectedMeal == null
          ? Text("Cound not find data")
          : Container(
              child: Column(
                children: [
                  Text(selectedMeal.title),
                ],
              ),
            ),
    );

CodePudding user response:

The answer has been given. But I want mention that if you're using pushNamed method, it recommended that you manage passing parameters with onGenerateRoute . So you don't have nullcheck arguments or context needed.

ModalRoute.of(context)?.settings?.arguments? 
MaterialApp(
  onGenerateRoute: (settings) {
    if (settings.name == PassArgumentsScreen.routeName) {
      final args = settings.arguments as ScreenArguments;
      return MaterialPageRoute(
        builder: (context) {
          return PassArgumentsScreen(
            title: args.title,
            message: args.message,
          );
        },
      );
    }
    assert(false, 'Need to implement ${settings.name}');
    return null;
  },
)

References: Pass arguments to a named route

  • Related