Search code examples
flutterdartgenericsextension-methodsnull-safety

How to make a generic functions in List<T> to get a Non-Optional type in Dart - Null Safety


We are migrating our Flutter app into null safety and I've encountered a bunch of missing helpers in order to get rid easily of an optional into a non-optional of the same type.

To get you in the context, I would like to have a

List<String?> optionalList = ['hi', 'hola', null];

and similar to compactMap() in Swift, I would like to have a function that can convert any map into a map that excludes the null items and only return a list of non-optional items ex:

final List<String> newNonOptionalList = optionalList.compactMap((item) => item.toUpperCase());
// newNonOptionalList = ['HI', 'HOLA']

I'm trying to make a helper for these kinds of cases, including many others I found valuable for Dart.

Does anyone have an idea of why it is not working, this is a little of what I've tried so far:

extension ListNullSafetyExtension<T> on List<T?>? {

Iterable<R> compactMap<R>(R Function(T element) convert) sync* {
  for (var element in this.removeNulls()) {
    if (element != null) yield convert(element);
  }
}

  List<T> removeNulls() {
    if (this == null) return [];
    this!.removeWhere((value) => (value == null));
    return List.from(this!).whereType<T>().toList();
  }
}


Even though this does remove the nulls and executes the map, it always returns the same optional type in the list, meaning it doesn't unwrap it and removeNulls() returns a List<dynamic> instead of a List<the_type_I_used>

I would really appreciate your help in understanding why these 2 functions never change their type since I can only find very basic generics posts of the topic.


Solution

  • You can easily collapse a List<T?> to a List<T> with null elements filtered out by using Iterable.whereType<T>():

    List<String?> optionalList = ['hi', 'hola', null];
    List<String> newNonOptionalList = optionalList.whereType<String>().toList();
    

    If you really want your own extension method (even though it doesn't add much):

    extension IterableNullSafetyExtension<T> on Iterable<T?>? {
      Iterable<T> removeNulls() => this?.whereType<T>() ?? [];
    }
    

    Additionally, one thing wrong with your attempted implementation is that ListNullSafetyExtension<T> is already generic and parameterized on T. When you later try to add an extension method:

    List<T> removeNulls<T>() {
    

    That extra <T> at the end makes removeNulls generic again, using a T that's independent of the T from ListNullSafetyExtension. Don't do that.

    Also see: How to convert a List<String?> to List in null safe Dart? Stack Overflow