Dart Flutter WTF does this mean?

I am an experienced Java programmer, who is learning Dart/Flutter programming. I like to learn by writing small test programs and also by reading existing code from functioning applications to understand the best practices and to know the generally accepted ways of doing various things.

While going through such code, I came across many Dart statements, constructs, and code fragments, which I got me to say, "WTF is this?". Mostly, these are things that are very different from the Java world.  In that sense, these are mostly just notes to self. I hope to follow up on these and update this blog with my findings.

 

  1. Overlay.of(context)!.insert(overlayEntry!);: Need to understand this "not dot" (!.) business.

    The ! operator turns a nullable reference to non-nullable. It is basically a guarantee provided by the programmer to the compiler that the reference on which the method is being invoked is definitely NOT going to be null. If, for any reason it turns out to be null, the program will crash.

    Read more about null safety in Dart. Also here.

    Here is an example:

    void main() {
        String? x = getValue();  //String? implies that x may be assigned a null
        //print(x.length);  //This won't compile because x may be null
        print(x?.length); //This is ok, because if x is null then x?.length will be null, and null will be printed. 
        print(x!.length); //x! implies that I am guaranteeing that x will not be null so, Compiles fine. But throws TypeError at run time:
        //Uncaught TypeError: Cannot read properties of null (reading 'length')Error: TypeError: Cannot read properties of null (reading 'length')
    }
    
    String? getValue(){  //String? implies that this method may return a null
      return null; //"hello"; 
    }
    
    Read about other null related operators such as ?? and ? also.

  2. Navigator(
        key: navigatorKey,
        pages:  <Page<void>>[
            MaterialPage(key: ValueKey('home'), child: MyHomePage()),
            if (_myRoute ==  MyRoute.gallery)
            MaterialPage(key: ValueKey('gallery'), child: GalleryPage(_tab)),
            if (_myRoute ==  MyRoute.seeAll)
            MaterialPage(key: ValueKey('seeAll'), child: MyLinkPage()),
            if (_myRoute ==  MyRoute.more)
            MaterialPage(key: ValueKey('seeAll/more'), child: ContentDetail()),
        ],
        onPopPage: _handlePopPage,
    )    
        
    What in the world is being cooked here?

    1. In Dart, the "new" keyword is optional. So, a Navigator object is being created.
    2. Dart has "named parameters" for constructors and methods. Thus, three parameters named key, pages, and onPopPage are being passed to Navigator's constructor.
    3. pages: <Page<void>>[ ... ] : pages is a named parameter of type List<Page<dynamic>>. (How do I know this? By hovering over Navigator and looking at all the available constructors.). So, <Page<void>> is creating a List of Page<dynamic> objects. Check out short hand way of creating lists and maps in Dart.
    4. Dart allows inlining of if statements (and for and switch and probably others) while creating collections. Therefore, if (_myRoute == MyRoute.gallery) MaterialPage(key: ValueKey('home'), child: MyHomePage()), just means that the element MaterialPage(key: ValueKey('home'), child: MyHomePage()), will be added to the collect if _myRoute is equal to MyRoute.gallery.

      You can even create a collections using a for loop like this:

      <Page<void>[ for (var oldpage in someOtherPagesCollection) oldpage; ] ]


  3. What are curly braces doing here ?
        class HomePage extends Page {
              final Function(String) onColorTap;
              HomePage({@required this.onColorTap}) : super(key: ValueKey('HomePage'));
              
              ...
        } 
      


    This is Dart's way of defining named parameters. Named parameters are optional by default. To make them required, you put @required (in Dart <2.1) or just required. Here, onColorTap is a named parameter and it is required. Anyone instantiating HomePage must do: Homepage(onColorTap:whatever).

    Read about Dart's named and positional parameters.

  4. The ?? operator: You can't imagine what this operator does in Dart. Even after reading the following sample code from Effective Dart, I had a hard time making a mental map of this operator:
    // If you want null to be false:
    if (optionalThing?.isEnabled ?? false) {
      print('Have enabled thing.');
    }
    
    // If you want null to be true:
    if (optionalThing?.isEnabled ?? true) {
      print('Have enabled thing or nothing.');
    }    
        
    This code is very weird for Java programmers because I have never come across a Java method with boolean return type returning null. I was also thrown off by it because I was thinking of ?? as a boolean operator and trying to create a "truth table" for it. But this little bugger is not a boolean operator at all!

    The key to nail this operator is to read it as "if null". For example, "optionalThing?.isEnabled ?? false" should be read as "if optionalThing?.isEnabled is null, then false". The above code now start making some sense.

    A better example of its usage is this:
    String? getData(int len){ //String? means this method may return a null
      if(len == 0) return null;
      else return "hello";
    }
    
    main() {
      //String val1 = getData(0); //This won't compile because getData may return null but val1 is non-nullable
      String val = getData(0) ?? "Got null"; //Good, because if getData(0) is null, then "Got null" is assigned to val
      print(val); //prints Got null
    }
      
    The above code shows how you can make use of old nullable return values with new non-nullable variables. It is basically just a shortcut for the ternary operator in java: (something == null) ? "give this non null value" : something
  5. Asynchronous Programming: Java and Dart do things very differently in this respect. In Java, you have threads. A thread executes a sequence of statements and, if, within that sequence of statements, there is a statement that blocks the execution (such as I/O, network, etc.), then that thread just blocks until that operation returns a result. You use multiple threads in your Java program to keep executing different parts of your application.

    In Dart, you have just one thread but this thread never blocks. The way it works is that it keeps processing "events" sequentially from an "event queue". This thread is therefore called "the event loop". Every code fragment that is being executed is basically a part of an event. If a statement in a code fragment includes a call to a blocking operation, that blocking operation is treated as an event and that event is appended to the event queue to be executed later. The call itself returns an empty Future and the event loop proceeds to execute the next statement without blocking. For example:

    main() {
      var myFuture = Future(
          (){
            print("In Future"); 
            return 10;
          }
      );
      print("Exiting main");
    }    
        
    It produces the following output:
    Exiting main
    In Future    
    
    Observe that "Exiting main" is printed before "In Future" because while executing the above code, the event loop simply appends the execution of the Future to the event queue and then proceeds to execute the next statement. The event loop doesn't block. The event loop will execute the Future after it is done with processing the current event (i.e. executing the current flow of statements). Check out this article for more details.