19

I have a dart list:

List<String?> vals;

I want to remove any null values and convert it to a List<String>. I've tried:

List<String> removeNulls(List<String?> list) {
  return list.where((c) => c != null).toList() as List<String>;
}

At run time I'm getting the following error:

List<String?>' is not a subtype of type 'List<String>?'

What is the correct way to resolve this?

jamesdlin
  • 81,374
  • 13
  • 159
  • 204
Brett Sutton
  • 3,900
  • 2
  • 28
  • 53
  • 2
    The package `collection`, provides an extension to do that: [whereNotNull()](https://pub.dev/documentation/collection/latest/collection/IterableNullableExtension/whereNotNull.html) – Mattia Apr 01 '21 at 08:26

2 Answers2

35
  • Ideally you'd start with a List<String> in the first place. If you're building your list like:

    String? s = maybeNullString();
    var list = <String?>[
      'foo',
      'bar',
      someCondition ? 'baz' : null,
      s,
    ];
    

    then you instead can use collection-if to avoid inserting null elements:

    String? s = maybeNullString();
    var list = <String?>[
      'foo',
      'bar',
      if (someCondition) 'baz',
      if (s != null) s,
    ];
    
  • Dart 3 adds a nonNulls extension to filter out nulls:

    var list = <String?>['foo', 'bar', null, 'baz', null];
    var withoutNulls = list.nonNulls.toList();
    
  • Prior to Dart 3, an easy way to filter out null values from an Iterable<T?> and get an Iterable<T> result is to use .whereType<T>(). For example:

    var list = <String?>['foo', 'bar', null, 'baz', null];
    var withoutNulls = list.whereType<String>().toList();
    
  • Another approach is to use collection-for with collection-if:

    var list = <String?>['foo', 'bar', null, 'baz', null];
    var withoutNulls = <String>[
      for (var s in list)
        if (s != null) s
    ];
    
  • Finally, if you already know that your List doesn't contain any null elements but just need to cast the elements to a non-nullable type, other options are to use List.from:

    var list = <String?>['foo', 'bar', 'baz'];
    var withoutNulls = List<String>.from(list);
    

    or if you don't want to create a new List, Iterable.cast:

    var list = <String?>['foo', 'bar', 'baz'];
    var withoutNulls = list.cast<String>();
    
jamesdlin
  • 81,374
  • 13
  • 159
  • 204
  • So there is no way to cast the string without recreating it. My code removed the nulls, it was the cast that was the core issue. – Brett Sutton Apr 01 '21 at 02:53
  • @BrettSutton I had forgotten about `Iterable.cast`/`List.cast`, which wouldn't need to create a new `List`. I've updated my answer. – jamesdlin Apr 01 '21 at 06:42
1
  • Without creating a new List

    void main() {
      List<String?> list = ['a', null, 'b', 'c', null];
      list.removeWhere((e) => e == null); // <-- This is all you need.
      print(list); // [a, b, c]
    }
    
  • Creating a new List

    First create a method, filter for example:

    List<String> filter(List<String?> input) {
      input.removeWhere((e) => e == null);
      return List<String>.from(input);
    }
    

    You can now use it:

    void main() {
      List<String?> list = ['a', null, 'b', 'c', null];
      List<String> filteredList = filter(list); // New list
      print(filteredList); // [a, b, c]
    }
    

To use retainWhere, replace the predicate in removeWhere with (e) => e != null

CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440