Strong Mode Dart: Fixing Common Problems

If you’re having problems converting your code to strong mode, this page can help. Be sure to also check out Strong Mode Dart for an overview of what “sound Dart” means, and how strong mode contributes to making Dart a sound language.

For a complete list of sources about strong mode and sound Dart, see other resources in Strong Mode Dart.

Troubleshooting

Am I really using strong mode?

If you’re not seeing strong mode errors or warnings, make sure that you’re using strong mode. A good test is to add the following code to a file:

bool b = [0][0];

If you’re using strong mode, you’ll see the following issue from the analyzer:

error • A value of type 'int' can't be assigned to a variable of type 'bool' • invalid_assignment

I’m not using strong mode and I think I should be

Strong mode is enforced by the Dart analyzer. How you troubleshoot strong mode depends on whether you are running dartanalyzer from the command line, or via one of the JetBrains IDEs.

Command line analyzer

If you are running dartanalyzer from the command line and you don’t see expected strong mode errors, try the following:

  • If your project contains an analysis options file, make sure you’ve specified strong mode: true correctly. For more information, see Specifying strong mode.
  • Run the analyzer with the --strong option:

    dartanalyzer --strong <file-or-directory>
    

For information on how to set up an analysis options file, see Customize Static Analysis.

JetBrains IDEs

Make sure that you have an analysis options file with strong mode turned on. This file needs to be placed in a content root, or in a parent directory of your content root. The steps for viewing a project’s content root varies a bit for WebStorm and IntelliJ.

Note that a large project may have multiple content roots. The following instructions describe how to see a list of content roots in WebStorm or IntelliJ.

WebStorm
In the Preferences panel (WebStorm > Preferences), click Directories from the list on the left. The +Add Content Root button in the column on the far right appears above the content roots, shown in bold.
IntelliJ
In the Project Structure panel (File > Project Structure), Modules is selected from the list on the left by default. The +Add Content Root button in the column on the far right appears above the content roots, shown in bold.

For more information on where to put your analysis options file, see the analysis options file, part of Customize Static Analysis.

Static errors and warnings

This section shows how to fix some of the errors and warnings you might see from the analyzer or an IDE with strong mode enabled.

Static analysis can’t catch all errors. For help fixing strong-mode errors that appear only at runtime, see Runtime errors.

Undefined member

error • The <member> '...' isn't defined for the class '...' • undefined_<member>

These errors usually appear in code where a variable is statically known to be some supertype but the code assumes a subtype.

Example

In the following code, the analyzer complains that context2D is undefined:

var canvas = querySelector('canvas');
canvas.context2D.lineTo(x, y);
error • The getter 'context2D' isn't defined for the class 'Element' • undefined_getter

Fix: Replace the definition of the member with an explicit type declaration or a downcast

The querySelector() method statically returns an Element, but the code assumes it returns the subtype CanvasElement where context2D is defined. The canvas field is declared as var which, in classic Dart, types it as dynamic and silences all errors. Strong mode Dart infers canvas to be an Element.

You can fix this error with an explicit type declaration:

CanvasElement canvas = querySelector('canvas');
canvas.context2D.lineTo(x, y);

The code above passes static analysis when implicit casts are permitted.

You can also use an explicit downcast:

var canvas = querySelector('canvas') as CanvasElement;
canvas.context2D.lineTo(x, y);

Otherwise, use dynamic in situations where you cannot use a single type:

dynamic canvasOrImg = querySelector('canvas, img');
var width = canvasOrImg.width;

Invalid method override

error • Invalid override. The type of '...'  isn't a subtype of '...'  • strong_mode_invalid_method_override

These errors typically occur when a subclass tightens up a method’s parameter types by specifying a subclass of the original class.

Example

In the following example, the parameters to the add() method are of type int, a subtype of num, which is the parameter type used in the parent class.

abstract class NumberAdder {
  num add(num a, num b);
}

class MyAdder extends NumberAdder {
  int add(int a, int b) => a + b;
}
error • Invalid override. The type of 'MyAdder.add' ('(int, int) → int') isn't a subtype of 'NumberAdder.add' ('(num, num) → num') • strong_mode_invalid_method_override

Consider the following scenario where floating point values are passed to an MyAdder:

NumberAdder adder = new MyAdder();
adder.add(1.2, 3.4);

If the override were allowed, the code would raise an error at runtime.

Fix: Widen the method’s parameter types

The subclass’s method should accept every object that the superclass’s method takes.

Fix the example by widening the types in the subclass:

abstract class NumberAdder {
  num add(num a, num b);
}

class MyAdder extends NumberAdder {
  num add(num a, num b) => a + b;
}

For more information, see Use proper input parameter types when overriding methods.


Missing type arguments

error • Invalid override. The type of '...'  isn't a subtype of '...'  • strong_mode_invalid_method_override

Example

In the following example, Subclass extends Superclass<T> but doesn’t specify a type argument. The analyzer infers Subclass<dynamic>, which results in an invalid override error on method(int).

class Superclass<T> {
  void method(T t) { ... }
}

class Subclass extends Superclass {
  void method(int i) { ... }
}
error • Invalid override. The type of 'Subclass.method' ('(int) → void') isn't a subtype of 'Superclass<dynamic>.method' ('(dynamic) → void') • strong_mode_invalid_method_override

Fix: Specify type arguments for the generic subclass

When a generic subclass neglects to specify a type argument, the analyzer infers the dynamic type. This is likely to cause errors.

You can fix the example by specifying the type on the subclass:

class Superclass<T> {
  void method(T t) { ... }
}

class Subclass extends Superclass<int> {
  void method(int i) { ... }
}

Unexpected collection element type

error • A value of type '...' can't be assigned to a variable of type '...' • invalid_assignment

This sometimes happens when you create a simple dynamic collection and the analyzer infers the type in a way you didn’t expect. When you later add values of a different type, the analyzer reports an issue.

Example

The following code initializes a map with several (String, int) pairs. The analyzer infers that map to be of type <String, int> but the code seems to assume either <String, dynamic> or <String, num>. When the code adds a (String, float) pair, the analyzer complains:

// Inferred as Map<String, int>
var map = {'a': 1, 'b': 2, 'c': 3};
map['d'] = 1.5; // a double is not an int
error • A value of type 'double' can't be assigned to a variable of type 'int' • invalid_assignment

Fix: Specify the type explicitly

The example can be fixed by explicitly defining the map’s type to be <String, num>.

var map = <String, num>{'a': 1, 'b': 2, 'c': 3};
map['d'] = 1.5;

Alternatively, if you want this map to accept any value, specify the type as <String, dynamic>.


Constructor initialization list super() call

error • super call must be last in an initializer list (see https://goo.gl/EY6hDP): '...' • strong_mode_invalid_super_invocation

This error occurs when the super() call is not last in a constructor’s initialization list.

Example

HoneyBadger(Eats food, String name)
    : super(food),
      _name = name { ... }
error • super call must be last in an initializer list (see https://goo.gl/EY6hDP): 'super(food)' • strong_mode_invalid_super_invocation

Fix: Put the super() call last

The compiler can generate simpler code if it relies on the super() call appearing last.

Fix this error by moving the super() call:

HoneyBadger(Eats food, String name)
    : _name = name,
      super(food) { ... }

For more information, see DO place the super() call last in a constructor initialization list in Effective Dart.


A function of type … can’t be assigned

warning • A function of type '...' can't be assigned to a location of type '...' • strong_mode_uses_dynamic_as_bottom

In Dart 1.x dynamic was both a top type (supertype of all types) and a bottom type (subtype of all types) depending on the context. This meant it was legal to assign, for example, a function with a parameter of type String to a place that expected a function type with a parameter of dynamic.

However, in Dart 2 passing something other than dynamic (or another top type, such as Object, or a specific bottom type, such as Null) results in a compile-time warning.

Example

typedef bool Filter(dynamic any);
Filter filter = (String x) => x.contains('Hello');
warning • A function of type '(String) → bool' can't be assigned to a location of type '(dynamic) → bool' • strong_mode_uses_dynamic_as_bottom

Fix: Add generic type parameters or cast from dynamic explicitly

When possible, avoid this warning by adding type parameters:

typedef bool Filter<T>(T any);
Filter<String> filter = (String x) => x.contains('Hello');

Otherwise use casting:

typedef bool Filter(dynamic any);
Filter filter = (x) => (x as String).contains('Hello');

Runtime errors

Unlike Dart 1.x, Dart 2 enforces a sound type system. Roughly, this means you can’t get into a situation where the value stored in a variable is different than the variable’s static type. Like most modern statically typed languages, Dart accomplishes this with a combination of static (compile-time) and dynamic (runtime) checking.

For example, the following type error is detected at compile-time (when the implicit casts option is disabled):

List<int> numbers = [1, 2, 3];
List<String> string = numbers;

Since neither List<int> nor List<String> is a subtype of the other, Dart rules this out statically. You can see other examples of these static analysis errors in Unexpected collection element type.

The strong-mode errors discussed in the remainder of this section are reported when you use a strong mode runtime.

Invalid casts

To ensure type safety, Dart needs to insert runtime checks in some cases. Consider:

assumeStrings(List<Object> objects) {
  List<String> strings = objects; // Runtime downcast check
  String string = strings[0]; // Expect a String value
}

The assignment to strings is downcasting the List<Object> to List<String> implicitly (as if you wrote as List<String>), so if the value you pass in objects at runtime is a List<String>, then the cast succeeds.

Otherwise, the cast will fail at runtime:

assumeStrings(<int>[1, 2, 3]);
Exception: type 'List<int>' is not a subtype of type 'List<String>'

Fix: Tighten or correct types

Sometimes, lack of a type, especially with empty collections, means that a <dynamic> collection is created, instead of the typed one you intended. Adding an explicit type argument can help:

var list = <String>[];
list.add('a string');
list.add('another');
assumeStrings(list);

You can also more precisely type the local variable, and let inference help:

List<String> list = [];
list.add('a string');
list.add('another');
assumeStrings(list);

In cases where you are working with a collection that you don’t create, such as from JSON or an external data source, you can use the cast() method provided by List and other collection classes.

Here’s an example of the preferred solution: tightening the object’s type.

Map<String, dynamic> json = getFromExternalSource();
var names = json['names'] as List;
assumeStrings(names.cast<String>());

If you can’t tighten the type or use cast, you can copy the object in a different way. For example:

Map<String, dynamic> json = getFromExternalSource();
var names = json['names'] as List;
// Use `as` and `toList` until 2.0.0-dev.22.0, when `cast` is available:
assumeStrings(names.map((name) => name as String).toList());

Appendix

The covariant keyword

Some (rarely used) coding patterns rely on tightening a type by overriding a parameter’s type with a subtype, which is illegal in strong mode Dart. In this case, you can use the covariant keyword to tell the analyzer that you are doing this intentionally. This removes the static error and instead checks for an invalid parameter type at runtime.

The following shows how you might use covariant:

class Animal {
  void chase(Animal x) { ... }
}

class Mouse extends Animal { ... }

class Cat extends Animal {
  void chase(covariant Mouse x) { ... }
}

Although this example shows using covariant in the subtype, the covariant keyword can be placed in either the superclass or the subclass method. Usually the superclass method is the best place to put it. The covariant keyword applies to a single parameter and is also supported on setters and fields.