43

Hi everyone this is my whole method :

Future<void> init() async {
    FirebaseAuth.instance.userChanges().listen((user) {
      if (user != null) {
        _loginState = ApplicationLoginState.loggedIn;
        _guestBookSubscription = FirebaseFirestore.instance
            .collection('guestbook')
            .orderBy('timestamp', descending: true)
            .limit(3)
            .snapshots()
            .listen((snapshot) {
          _guestBookMessages = [];
          snapshot.docs.forEach((document) {
            _guestBookMessages.add(
              GuestBookMessage(
                name: document.data()['name'] as String,
                message: document.data()['text'] as String,
              ),
            );
          });
          notifyListeners();
        });
      } else {
        _loginState = ApplicationLoginState.loggedOut;
        _guestBookMessages = [];
        _guestBookSubscription?.cancel();
      }
      notifyListeners();
    });
  }

the part that dart complains about is this one :

snapshot.docs.forEach((document) {
            _guestBookMessages.add(
              GuestBookMessage(
                name: document.data()['name'] as String,
                message: document.data()['text'] as String,
              ),
            );
          });

how can I change this method without ruining the whole functionality ? Im just looking for a way that makes dart happy . I appreciate your help in advance.

5 Answers 5

54

AVOID using forEach with a function literal.

AVOID:

snapshot.docs.forEach((document) {
  ...
});

GOOD:

for (final document in snapshot.docs) {
  // Rest of your code
}
Sign up to request clarification or add additional context in comments.

6 Comments

@BugsHappen probably because it will make the code look way more complex. It is recommended to either do like Sifat proposed above, or I think even better is to create another function that you will supply to forEach method. It is described here: link Quote: "If you want to invoke some already existing function on each element, forEach() is fine." So define a new function and use it in forEach.
Could you explain why either is good/bad?
Agreed, the "good/bad" terminology is unhelpful. I think that in most languages it is more performant to use a loop rather than call an anonymous function repeatedly. This is because functions come with the overhead of allocating and cleaning up a stack, whereas a loop gets optimized into more "flat" code by the compiler. I assume that is the case here as well, and why Dart issues a warning.
It is ridiculous tbh. They should improve their compiler/interpreter instead of giving such recommendations.
The latter is less readable in my opinion
|
7

Using like data.forEach(function); example:

void main() async {
  List<int> list = [1, 2, 3, 4, 5];

  //recommended
  list.forEach(showSquareNumbers);

  //not recommended
  list.forEach((int number) => showSquareNumbers(number));
  list.forEach((int number) {
    showSquareNumbers(number);
  });
}

void showSquareNumbers(int data) {
  print("$data * $data = ${data * data}");
}

This is my opinion.

I think the forEach seems more complicated than the for loop and the forEach can't use continue and break (return is available but not a thing happens when using return).

void test(List data) {
  data.forEach((element) {
    print(element);
    if(element) return;
  });

  for (var element in data) {
    print(element);
    if(element) continue;
    if(element) break;
    if(element) return;
  }
}

I think we should use the for instead of the forEach loop when your forEach loop seems like this code below because the for loop have many options more than forEach as I said.

  data.forEach((element) {
    print(element)
  });
  //or
  data.forEach((element) => print(element));

I think the forEach loop is used for short code (easy to understand) and when you want to do something you don't care about the result like this code (using with Function(dynamic)).

void test(List data) {
  void showInTerminal(e) {
    print("data is $e");
  }
  data.forEach(showInTerminal);
  
  Function(dynamic) function = showInTerminal;
  data.forEach(function);
}

Make sure the data type and function(type) are the same.

//error
void test(List<Map> data) {
  void showInTerminal(String e) {
    print("data is $e");
  }
  data.forEach(showInTerminal);
}

//success
void test(List<Map> data) {
  void showInTerminal(Map e) {
    print("data is $e");
  }
  data.forEach(showInTerminal);
}

I code like this. I think it's easy to read.

void test() {
  dataList.forEach(removeData);
  fileList.forEach(removeFile);
}

Comments

4

There is a debate below the accepted answer, about readability of the recommended lint rules at stake here. This is the rule in question causing the problem : https://dart.dev/tools/linter-rules/avoid_function_literals_in_foreach_calls

You can disable it by modifying your analysis_options.yaml file :

linter:
  rules:
    avoid_function_literals_in_foreach_calls: false

Comments

0

It is because of how closures behave.

I took the default example in dartpad and slightly modified.

Try this example in dartpad.

void main() {
  for (var i = 0; i < 10; i++) {
    print('hello ${i + 1}');
  }
  print('');
  
  List<int> numbers = [0,1,2,3,4,5,6,7,8,9];
  
  numbers.forEach((i) => print('hello ${i + 1}')); 
  
}

The result here will be the same as you are not returning from either loop.

Result -

hello 1
hello 2
hello 3
hello 4
hello 5
hello 6
hello 7
hello 8
hello 9
hello 10

hello 1
hello 2
hello 3
hello 4
hello 5
hello 6
hello 7
hello 8
hello 9
hello 10

But as soon as you return from the loop, see the behaviour change.

void main() {

  List<int> numbers = [0,1,2,3,4,5,6,7,8,9];  
  
  for (var i = 0; i < 10; i++) {
     var j = 5;
    print('hello ${i + 1}');
     if(i == j) {
        print('for loop inside if');
       return; ///// ---> 1 
     }
  }
  print('');  
  
  
  numbers.forEach((i) { 
    var j = 5;
    print('hello ${i + 1}');
     if(i == j) {
       print('foreach inside if');
       return;  ///// ---> 2
     }
  });  
  
  
}

Result -

hello 1
hello 2
hello 3
hello 4
hello 5
hello 6
for loop inside if

As soon as you return from the for loop, you return from the main method. When you return from the forEach you will only break out of that if block and it will continue to run forEach.

Now see how it works when I switch the order.

void main() {

  List<int> numbers = [0,1,2,3,4,5,6,7,8,9];  
  
  numbers.forEach((i) { 
    var j = 5;
    print('hello ${i + 1}');
     if(i == j) {
       print('foreach inside if');
       return;  ///// ---> 2
     }
  });  
  
  print('');  
  
  for (var i = 0; i < 10; i++) {
     var j = 5;
    print('hello ${i + 1}');
     if(i == j) {
        print('for loop inside if');
       return; ///// ---> 1 
     }
  }  
}

The result is -

hello 1
hello 2
hello 3
hello 4
hello 5
hello 6
foreach inside if
hello 7
hello 8
hello 9
hello 10

hello 1
hello 2
hello 3
hello 4
hello 5
hello 6
for loop inside if

Contrary to what Dart suggests, depending on what you are trying to achieve, you may want to use one or the other.

Comments

-6

Use await for:

await for (final document in snapshot.docs) {
  // Your code...
}

3 Comments

thanks for your attention . i tried your solution but here the method 'listen' isn't avaliable to use
@delaram Sorry I mistakenly wrote .docs in the code. I've updated it though.
Why use await for instead of foreach or for loop?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.