Null safe and unsafe in a single codebase (#689)

diff --git a/benchmark/bench.dart b/benchmark/bench.dart
index 06af555..3a7d6c6 100644
--- a/benchmark/bench.dart
+++ b/benchmark/bench.dart
@@ -15,12 +15,12 @@
 import 'package:dart_services/src/sdk.dart';
 import 'package:logging/logging.dart';
 
+const nullSafe = false;
+
 void main(List<String> args) async {
   final json = args.contains('--json');
-
   final harness = BenchmarkHarness(asJson: json);
-
-  final compiler = Compiler(Sdk());
+  final compiler = Compiler(Sdk(), nullSafe);
 
   Logger.root.level = Level.WARNING;
   Logger.root.onRecord.listen((LogRecord record) {
@@ -59,7 +59,7 @@
     String name,
     this.source,
   ) : super('analyzer.$name') {
-    analysisServer = DartAnalysisServerWrapper();
+    analysisServer = DartAnalysisServerWrapper(nullSafe);
   }
 
   @override
@@ -107,7 +107,7 @@
   final AnalysisServerWrapper analysisServer;
 
   AnalysisServerBenchmark(String name, this.source)
-      : analysisServer = DartAnalysisServerWrapper(),
+      : analysisServer = DartAnalysisServerWrapper(nullSafe),
         super('completion.$name');
 
   @override
diff --git a/cloud_run_null_safety.Dockerfile b/cloud_run_null_safety.Dockerfile
new file mode 100644
index 0000000..c4205d0
--- /dev/null
+++ b/cloud_run_null_safety.Dockerfile
@@ -0,0 +1,38 @@
+FROM google/dart:2.12.2
+
+# We install unzip and remove the apt-index again to keep the
+# docker image diff small.
+RUN apt-get update && \
+  apt-get install -y unzip && \
+  rm -rf /var/lib/apt/lists/*
+
+WORKDIR /app
+RUN groupadd --system dart && \
+  useradd --no-log-init --system --home /home/dart --create-home -g dart dart
+RUN chown dart:dart /app
+
+# Switch to a new, non-root user to use the flutter tool.
+# The Flutter tool won't perform its actions when run as root.
+USER dart
+
+COPY --chown=dart:dart tool/dart_cloud_run.sh /dart_runtime/
+RUN chmod a+x /dart_runtime/dart_cloud_run.sh
+COPY --chown=dart:dart pubspec.* /app/
+RUN pub get
+COPY --chown=dart:dart . /app
+RUN pub get --offline
+
+ENV PATH="/home/dart/.pub-cache/bin:${PATH}"
+
+# Set the Flutter SDK up for web compilation.
+RUN dart pub run grinder setup-flutter-sdk
+
+# Build the dill file
+RUN dart pub run grinder build-storage-artifacts validate-storage-artifacts
+
+# Clear out any arguments the base images might have set and ensure we start
+# the Dart app using custom script enabling debug modes.
+CMD []
+
+ENTRYPOINT ["/dart_runtime/dart_cloud_run.sh", "--port", "${PORT}", \
+  "--redis-url", "redis://10.0.0.4:6379", "--null-safety"]
diff --git a/lib/services_cloud_run.dart b/lib/services_cloud_run.dart
index 060c018..edac169 100644
--- a/lib/services_cloud_run.dart
+++ b/lib/services_cloud_run.dart
@@ -25,7 +25,8 @@
 Future<void> main(List<String> args) async {
   final parser = ArgParser()
     ..addOption('port', abbr: 'p')
-    ..addOption('redis-url');
+    ..addOption('redis-url')
+    ..addFlag('null-safety');
   final results = parser.parse(args);
 
   // Cloud Run supplies the port to bind to in the environment.
@@ -39,6 +40,7 @@
   }
 
   final redisServerUri = results['redis-url'] as String;
+  final nullSafety = results['null-safety'] as bool;
 
   Logger.root.level = Level.FINER;
   Logger.root.onRecord.listen((LogRecord record) {
@@ -55,16 +57,18 @@
     port: $port
     sdkPath: ${Sdk.sdkPath}
     redisServerUri: $redisServerUri
+    nullSafety: $nullSafety
     Cloud Run Environment variables:
     $cloudRunEnvVars''');
 
-  final server = await EndpointsServer.serve(port, redisServerUri);
+  final server = await EndpointsServer.serve(port, redisServerUri, nullSafety);
   _logger.info('Listening on port ${server.port}');
 }
 
 class EndpointsServer {
-  static Future<EndpointsServer> serve(int port, String redisServerUri) async {
-    final endpointsServer = EndpointsServer._(port, redisServerUri);
+  static Future<EndpointsServer> serve(
+      int port, String redisServerUri, bool nullSafety) async {
+    final endpointsServer = EndpointsServer._(port, redisServerUri, nullSafety);
 
     await endpointsServer.init();
     endpointsServer.server = await shelf.serve(
@@ -86,7 +90,7 @@
   CommonServerImpl _commonServerImpl;
   FlutterWebManager flutterWebManager;
 
-  EndpointsServer._(this.port, this.redisServerUri) {
+  EndpointsServer._(this.port, this.redisServerUri, bool nullSafety) {
     _commonServerImpl = CommonServerImpl(
       _ServerContainer(),
       redisServerUri == null
@@ -97,6 +101,7 @@
               // https://cloud.google.com/run/docs/reference/container-contract#env-vars
               Platform.environment['K_REVISION'],
             ),
+      nullSafety,
     );
     commonServerApi = CommonServerApi(_commonServerImpl);
 
diff --git a/lib/services_dev.dart b/lib/services_dev.dart
index 317aa9e..e78b9bb 100644
--- a/lib/services_dev.dart
+++ b/lib/services_dev.dart
@@ -26,8 +26,7 @@
   parser
     ..addOption('port', abbr: 'p', defaultsTo: '8080')
     ..addOption('server-url', defaultsTo: 'http://localhost')
-    ..addOption('proxy-target',
-        help: 'URL base to proxy compilation requests to');
+    ..addFlag('null-safety');
 
   final result = parser.parse(args);
   final port = int.tryParse(result['port'] as String);
@@ -43,15 +42,15 @@
     if (record.stackTrace != null) print(record.stackTrace);
   });
 
-  EndpointsServer.serve(port, result['proxy-target'] as String)
+  EndpointsServer.serve(port, result['null-safety'] as bool)
       .then((EndpointsServer server) {
     _logger.info('Listening on port ${server.port}');
   });
 }
 
 class EndpointsServer {
-  static Future<EndpointsServer> serve(int port, String proxyTarget) {
-    final endpointsServer = EndpointsServer._(port, proxyTarget);
+  static Future<EndpointsServer> serve(int port, bool nullSafety) {
+    final endpointsServer = EndpointsServer._(port, nullSafety);
 
     return shelf
         .serve(endpointsServer.handler, InternetAddress.anyIPv4, port)
@@ -63,7 +62,6 @@
 
   final int port;
   HttpServer server;
-  final String proxyTarget;
 
   Pipeline pipeline;
   Handler handler;
@@ -71,13 +69,12 @@
   CommonServerApi commonServerApi;
   FlutterWebManager flutterWebManager;
 
-  EndpointsServer._(this.port, this.proxyTarget) {
-    final commonServerImpl = (proxyTarget != null && proxyTarget.isNotEmpty)
-        ? CommonServerImplProxy(proxyTarget)
-        : CommonServerImpl(
-            _ServerContainer(),
-            _Cache(),
-          );
+  EndpointsServer._(this.port, bool nullSafety) {
+    final commonServerImpl = CommonServerImpl(
+      _ServerContainer(),
+      _Cache(),
+      nullSafety,
+    );
     commonServerApi = CommonServerApi(commonServerImpl);
     commonServerImpl.init();
 
diff --git a/lib/src/analysis_server.dart b/lib/src/analysis_server.dart
index 7b23cd7..9443dcb 100644
--- a/lib/src/analysis_server.dart
+++ b/lib/src/analysis_server.dart
@@ -35,17 +35,21 @@
 const Duration _ANALYSIS_SERVER_TIMEOUT = Duration(seconds: 35);
 
 class DartAnalysisServerWrapper extends AnalysisServerWrapper {
-  DartAnalysisServerWrapper() : super(Sdk.sdkPath);
+  DartAnalysisServerWrapper(this._nullSafety) : super(Sdk.sdkPath);
+  final bool _nullSafety;
 
   @override
-  String get _sourceDirPath => FlutterWebManager.dartTemplateProject.path;
+  String get _sourceDirPath =>
+      FlutterWebManager.dartTemplateProject(_nullSafety).path;
 }
 
 class FlutterAnalysisServerWrapper extends AnalysisServerWrapper {
-  FlutterAnalysisServerWrapper() : super(Sdk.sdkPath);
+  FlutterAnalysisServerWrapper(this._nullSafety) : super(Sdk.sdkPath);
+  final bool _nullSafety;
 
   @override
-  String get _sourceDirPath => FlutterWebManager.flutterTemplateProject.path;
+  String get _sourceDirPath =>
+      FlutterWebManager.flutterTemplateProject(_nullSafety).path;
 }
 
 abstract class AnalysisServerWrapper {
diff --git a/lib/src/analysis_servers.dart b/lib/src/analysis_servers.dart
index 0c8d063..79ed436 100644
--- a/lib/src/analysis_servers.dart
+++ b/lib/src/analysis_servers.dart
@@ -20,7 +20,8 @@
 final Logger _logger = Logger('analysis_servers');
 
 class AnalysisServersWrapper {
-  AnalysisServersWrapper();
+  AnalysisServersWrapper(this._nullSafety);
+  final bool _nullSafety;
 
   FlutterWebManager _flutterWebManager;
   DartAnalysisServerWrapper _dartAnalysisServer;
@@ -44,9 +45,9 @@
 
   Future<void> warmup() async {
     _logger.info('Beginning AnalysisServersWrapper init().');
-    _dartAnalysisServer = DartAnalysisServerWrapper();
+    _dartAnalysisServer = DartAnalysisServerWrapper(_nullSafety);
     _flutterWebManager = FlutterWebManager();
-    _flutterAnalysisServer = FlutterAnalysisServerWrapper();
+    _flutterAnalysisServer = FlutterAnalysisServerWrapper(_nullSafety);
 
     await _dartAnalysisServer.init();
     _logger.info('Dart analysis server initialized.');
diff --git a/lib/src/common.dart b/lib/src/common.dart
index 6efcb02..07880bd 100644
--- a/lib/src/common.dart
+++ b/lib/src/common.dart
@@ -42,6 +42,15 @@
 }
 """;
 
+const sampleCodeWebNullSafe = """
+import 'dart:html';
+
+void main() {
+  print("hello");
+  querySelector('#foo')?.text = 'bar';
+}
+""";
+
 const sampleCodeFlutter = '''
 import 'package:flutter/material.dart';
 
@@ -132,6 +141,74 @@
 }
 ''';
 
+const sampleCodeFlutterCounterNullSafe = r'''
+import 'package:flutter/material.dart';
+
+void main() => runApp(MyApp());
+
+class MyApp extends StatelessWidget {
+  @override
+  Widget build(BuildContext context) {
+    return MaterialApp(
+      title: 'Flutter Demo',
+      debugShowCheckedModeBanner: false,
+      theme: ThemeData(
+        primarySwatch: Colors.blue,
+      ),
+      home: MyHomePage(title: 'Flutter Demo Home Page'),
+    );
+  }
+}
+
+class MyHomePage extends StatefulWidget {
+  final String title;
+  MyHomePage({Key? key, required this.title}) : super(key: key);
+
+
+
+  @override
+  _MyHomePageState createState() => _MyHomePageState();
+}
+
+class _MyHomePageState extends State<MyHomePage> {
+  int _counter = 0;
+
+  void _incrementCounter() {
+    setState(() {
+      _counter++;
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: Text(widget.title),
+      ),
+      body: Center(
+        child: Column(
+          mainAxisAlignment: MainAxisAlignment.center,
+          children: <Widget>[
+            Text(
+              'You have pushed the button this many times:',
+            ),
+            Text(
+              '$_counter',
+              style: Theme.of(context).textTheme.headline4,
+            ),
+          ],
+        ),
+      ),
+      floatingActionButton: FloatingActionButton(
+        onPressed: _incrementCounter,
+        tooltip: 'Increment',
+        child: Icon(Icons.add),
+      ),
+    );
+  }
+}
+''';
+
 // From https://gist.github.com/johnpryan/b3ccb26497ac84895540185935ed5825
 const sampleCodeFlutterSunflower = '''
 import 'package:flutter/material.dart';
@@ -272,6 +349,145 @@
 }
 ''';
 
+const sampleCodeFlutterSunflowerNullSafe = r'''
+import 'dart:math' as math;
+import 'package:flutter/material.dart';
+
+final Color primaryColor = Colors.orange;
+final TargetPlatform platform = TargetPlatform.android;
+
+void main() {
+  runApp(Sunflower());
+}
+
+class SunflowerPainter extends CustomPainter {
+  static const seedRadius = 2.0;
+  static const scaleFactor = 4;
+  static const tau = math.pi * 2;
+
+  static final phi = (math.sqrt(5) + 1) / 2;
+
+  final int seeds;
+
+  SunflowerPainter(this.seeds);
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    final center = size.width / 2;
+
+    for (var i = 0; i < seeds; i++) {
+      final theta = i * tau / phi;
+      final r = math.sqrt(i) * scaleFactor;
+      final x = center + r * math.cos(theta);
+      final y = center - r * math.sin(theta);
+      final offset = Offset(x, y);
+      if (!size.contains(offset)) {
+        continue;
+      }
+      drawSeed(canvas, x, y);
+    }
+  }
+
+  @override
+  bool shouldRepaint(SunflowerPainter oldDelegate) {
+    return oldDelegate.seeds != this.seeds;
+  }
+
+  // Draw a small circle representing a seed centered at (x,y).
+  void drawSeed(Canvas canvas, double x, double y) {
+    final paint = Paint()
+      ..strokeWidth = 2
+      ..style = PaintingStyle.fill
+      ..color = primaryColor;
+    canvas.drawCircle(Offset(x, y), seedRadius, paint);
+  }
+}
+
+class Sunflower extends StatefulWidget {
+  @override
+  State<StatefulWidget> createState() {
+    return _SunflowerState();
+  }
+}
+
+class _SunflowerState extends State<Sunflower> {
+  double seeds = 100.0;
+
+  int get seedCount => seeds.floor();
+
+  @override
+  Widget build(BuildContext context) {
+    return MaterialApp(
+      debugShowCheckedModeBanner: false,
+      theme: ThemeData().copyWith(
+        platform: platform,
+        brightness: Brightness.dark,
+        sliderTheme: SliderThemeData.fromPrimaryColors(
+          primaryColor: primaryColor,
+          primaryColorLight: primaryColor,
+          primaryColorDark: primaryColor,
+          valueIndicatorTextStyle: DefaultTextStyle.fallback().style,
+        ),
+      ),
+      home: Scaffold(
+        appBar: AppBar(title: Text("Sunflower")),
+        drawer: Drawer(
+            child: ListView(
+          children: [
+            DrawerHeader(
+              child: Center(
+                child: Container(
+                  child: Text(
+                    "Sunflower 🌻",
+                    style: TextStyle(fontSize: 32),
+                  ),
+                ),
+              ),
+            ),
+          ],
+        )),
+        body: Container(
+          constraints: BoxConstraints.expand(),
+          decoration:
+              BoxDecoration(border: Border.all(color: Colors.transparent)),
+          child: Column(
+            crossAxisAlignment: CrossAxisAlignment.center,
+            mainAxisAlignment: MainAxisAlignment.start,
+            children: [
+              Container(
+                decoration: BoxDecoration(
+                    border: Border.all(color: Colors.transparent)),
+                child: SizedBox(
+                  width: 400,
+                  height: 400,
+                  child: CustomPaint(
+                    painter: SunflowerPainter(seedCount),
+                  ),
+                ),
+              ),
+              Text("Showing $seedCount seeds"),
+              ConstrainedBox(
+                constraints: BoxConstraints.tightFor(width: 300),
+                child: Slider.adaptive(
+                  min: 20,
+                  max: 2000,
+                  value: seeds,
+                  onChanged: (newValue) {
+                    setState(() {
+                      seeds = newValue;
+                    });
+                  },
+                ),
+              ),
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+}
+''';
+
 // From https://gist.github.com/RedBrogdon/ecb28c29c646b7f38139b1e7f44129b7
 const sampleCodeFlutterDraggableCard = '''
 import 'package:flutter/material.dart';
@@ -387,6 +603,120 @@
 }
 ''';
 
+const sampleCodeFlutterDraggableCardNullSafe = '''
+import 'package:flutter/material.dart';
+import 'package:flutter/physics.dart';
+
+main() {
+  runApp(
+    MaterialApp(
+      debugShowCheckedModeBanner: false,
+      home: PhysicsCardDragDemo(),
+    ),
+  );
+}
+
+class PhysicsCardDragDemo extends StatelessWidget {
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: Text('A draggable card!'),
+      ),
+      body: DraggableCard(
+        child: FlutterLogo(
+          size: 128,
+        ),
+      ),
+    );
+  }
+}
+
+class DraggableCard extends StatefulWidget {
+  final Widget child;
+  DraggableCard({required this.child});
+
+  @override
+  _DraggableCardState createState() => _DraggableCardState();
+}
+
+class _DraggableCardState extends State<DraggableCard>
+    with SingleTickerProviderStateMixin {
+  AnimationController? _controller;
+  Alignment _dragAlignment = Alignment.center;
+  Animation<Alignment>? _animation;
+
+  void _runAnimation(Offset pixelsPerSecond, Size size) {
+    _animation = _controller!.drive(
+      AlignmentTween(
+        begin: _dragAlignment,
+        end: Alignment.center,
+      ),
+    );
+
+    final unitsPerSecondX = pixelsPerSecond.dx / size.width;
+    final unitsPerSecondY = pixelsPerSecond.dy / size.height;
+    final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY);
+    final unitVelocity = unitsPerSecond.distance;
+
+    const spring = SpringDescription(
+      mass: 30,
+      stiffness: 1,
+      damping: 1,
+    );
+
+    final simulation = SpringSimulation(spring, 0, 1, -unitVelocity);
+
+    _controller!.animateWith(simulation);
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    _controller = AnimationController(vsync: this);
+
+    _controller!.addListener(() {
+      setState(() {
+        _dragAlignment = _animation!.value;
+      });
+    });
+  }
+
+  @override
+  void dispose() {
+    _controller!.dispose();
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final size = MediaQuery.of(context).size;
+    return GestureDetector(
+      onPanDown: (details) {
+        _controller!.stop();
+      },
+      onPanUpdate: (details) {
+        setState(() {
+          _dragAlignment += Alignment(
+            details.delta.dx / (size.width / 2),
+            details.delta.dy / (size.height / 2),
+          );
+        });
+      },
+      onPanEnd: (details) {
+        _runAnimation(details.velocity.pixelsPerSecond, size);
+      },
+      child: Align(
+        alignment: _dragAlignment,
+        child: Card(
+          child: widget.child,
+        ),
+      ),
+    );
+  }
+}
+''';
+
 // From https://gist.github.com/RedBrogdon/40308e0a5f47acba46ba62f4d8be2bf4
 const sampleCodeFlutterImplicitAnimations = '''
 import 'package:flutter/material.dart';
@@ -493,6 +823,111 @@
 }
 ''';
 
+const sampleCodeFlutterImplicitAnimationsNullSafe = '''
+import 'dart:math';
+import 'package:flutter/material.dart';
+
+
+class DiscData {
+  static final _rng = Random();
+
+  final double size;
+  final Color color;
+  final Alignment alignment;
+
+  DiscData()
+      : size = _rng.nextDouble() * 40 + 10,
+        color = Color.fromARGB(
+          _rng.nextInt(200),
+          _rng.nextInt(255),
+          _rng.nextInt(255),
+          _rng.nextInt(255),
+        ),
+        alignment = Alignment(
+          _rng.nextDouble() * 2 - 1,
+          _rng.nextDouble() * 2 - 1,
+        );
+}
+
+void main() async {
+  runApp(
+    MaterialApp(
+      debugShowCheckedModeBanner: false,
+      home: Scaffold(
+        body: Container(
+          color: Color(0xFF15202D),
+          child: SizedBox.expand(
+            child: VariousDiscs(50),
+          ),
+        ),
+      ),
+    ),
+  );
+}
+
+class VariousDiscs extends StatefulWidget {
+  final int numberOfDiscs;
+
+  VariousDiscs(this.numberOfDiscs);
+
+  @override
+  _VariousDiscsState createState() => _VariousDiscsState();
+}
+
+class _VariousDiscsState extends State<VariousDiscs> {
+  final _discs = <DiscData>[];
+
+  @override
+  void initState() {
+    super.initState();
+    _makeDiscs();
+  }
+
+  void _makeDiscs() {
+    _discs.clear();
+    for (int i = 0; i < widget.numberOfDiscs; i++) {
+      _discs.add(DiscData());
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return GestureDetector(
+      onTap: () => setState(() {
+        _makeDiscs();
+      }),
+      child: Stack(
+        children: [
+          Center(
+            child: Text(
+              'Click a disc!',
+              style: TextStyle(color: Colors.white, fontSize: 50),
+            ),
+          ),
+          for (final disc in _discs)
+            Positioned.fill(
+              child: AnimatedAlign(
+                duration: Duration(milliseconds: 500),
+                curve: Curves.easeInOut,
+                alignment: disc.alignment,
+                child: AnimatedContainer(
+                  duration: Duration(milliseconds: 500),
+                  decoration: BoxDecoration(
+                    color: disc.color,
+                    shape: BoxShape.circle,
+                  ),
+                  height: disc.size,
+                  width: disc.size,
+                ),
+              ),
+            ),
+        ],
+      ),
+    );
+  }
+}
+''';
+
 const sampleCodeMultiFoo = """
 import 'bar.dart';
 
@@ -518,6 +953,17 @@
 }
 """;
 
+const sampleCodeAsyncNullSafe = """
+import 'dart:html';
+
+void main() async {
+  print("hello");
+  querySelector('#foo')?.text = 'bar';
+  var foo = await HttpRequest.getString('http://www.google.com');
+  print(foo);
+}
+""";
+
 const sampleCodeError = '''
 void main() {
   print("hello")
diff --git a/lib/src/common_server_impl.dart b/lib/src/common_server_impl.dart
index c826d2e..214d5a7 100644
--- a/lib/src/common_server_impl.dart
+++ b/lib/src/common_server_impl.dart
@@ -37,6 +37,7 @@
 class CommonServerImplProxy implements CommonServerImpl {
   const CommonServerImplProxy(this._proxyTarget);
   final String _proxyTarget;
+  final bool _nullSafety = false;
 
   Future<R> _postToProxy<R extends $pb.GeneratedMessage>(
       String url, $pb.GeneratedMessage request, R responseProto) async {
@@ -163,6 +164,7 @@
 class CommonServerImpl {
   final ServerContainer _container;
   final ServerCache _cache;
+  final bool _nullSafety;
 
   Compiler _compiler;
   AnalysisServersWrapper _analysisServers;
@@ -175,6 +177,7 @@
   CommonServerImpl(
     this._container,
     this._cache,
+    this._nullSafety,
   ) {
     hierarchicalLoggingEnabled = true;
     log.level = Level.ALL;
@@ -182,8 +185,8 @@
 
   Future<void> init() async {
     log.info('Beginning CommonServer init().');
-    _analysisServers = AnalysisServersWrapper();
-    _compiler = Compiler(Sdk());
+    _analysisServers = AnalysisServersWrapper(_nullSafety);
+    _compiler = Compiler(Sdk(), _nullSafety);
 
     await _compiler.warmup();
     await _analysisServers.warmup();
diff --git a/lib/src/compiler.dart b/lib/src/compiler.dart
index f7bc73d..44bdb84 100644
--- a/lib/src/compiler.dart
+++ b/lib/src/compiler.dart
@@ -26,8 +26,9 @@
   final FlutterWebManager _flutterWebManager;
   final String _dartdevcPath;
   final BazelWorkerDriver _ddcDriver;
+  final bool _nullSafety;
 
-  Compiler(this._sdk)
+  Compiler(this._sdk, this._nullSafety)
       : _dartdevcPath = path.join(Sdk.sdkPath, 'bin', 'dartdevc'),
         _ddcDriver = BazelWorkerDriver(
             () => Process.start(
@@ -63,7 +64,8 @@
     _logger.info('Temp directory created: ${temp.path}');
 
     try {
-      await copyPath(FlutterWebManager.dartTemplateProject.path, temp.path);
+      await copyPath(
+          FlutterWebManager.dartTemplateProject(_nullSafety).path, temp.path);
       await Directory(path.join(temp.path, 'lib')).create(recursive: true);
 
       final arguments = <String>[
@@ -71,6 +73,10 @@
         '--terse',
         if (!returnSourceMap) '--no-source-maps',
         '--packages=${path.join('.dart_tool', 'package_config.json')}',
+        if (_nullSafety) ...[
+          '--sound-null-safety',
+          '--enable-experiment=non-nullable',
+        ],
         ...['-o', '$kMainDart.js'],
         path.join('lib', kMainDart),
       ];
@@ -131,9 +137,11 @@
       final usingFlutter = _flutterWebManager.usesFlutterWeb(imports);
       if (usingFlutter) {
         await copyPath(
-            FlutterWebManager.flutterTemplateProject.path, temp.path);
+            FlutterWebManager.flutterTemplateProject(_nullSafety).path,
+            temp.path);
       } else {
-        await copyPath(FlutterWebManager.dartTemplateProject.path, temp.path);
+        await copyPath(
+            FlutterWebManager.dartTemplateProject(_nullSafety).path, temp.path);
       }
 
       await Directory(path.join(temp.path, 'lib')).create(recursive: true);
@@ -150,12 +158,19 @@
         '--modules=amd',
         if (usingFlutter) ...[
           '-s',
-          _flutterWebManager.summaryFilePath,
+          _flutterWebManager.summaryFilePath(_nullSafety),
           '-s',
-          '${Sdk.flutterBinPath}/cache/flutter_web_sdk/flutter_web_sdk/kernel/flutter_ddc_sdk.dill'
+          '${Sdk.flutterBinPath}/cache/flutter_web_sdk/flutter_web_sdk/kernel/' +
+              (_nullSafety
+                  ? 'flutter_ddc_sdk_sound.dill'
+                  : 'flutter_ddc_sdk.dill'),
         ],
         ...['-o', path.join(temp.path, '$kMainDart.js')],
         ...['--module-name', 'dartpad_main'],
+        if (_nullSafety) ...[
+          '--sound-null-safety',
+          '--enable-experiment=non-nullable',
+        ],
         bootstrapPath,
         '--packages=${path.join(temp.path, '.dart_tool', 'package_config.json')}',
       ];
@@ -184,7 +199,8 @@
         final results = DDCCompilationResults(
           compiledJS: processedJs,
           modulesBaseUrl: 'https://storage.googleapis.com/'
-              'compilation_artifacts/${_sdk.versionFull}/',
+              '${_nullSafety ? 'nnbd_artifacts' : 'compilation_artifacts'}'
+              '/${_sdk.versionFull}/',
         );
         return results;
       }
diff --git a/lib/src/flutter_web.dart b/lib/src/flutter_web.dart
index 0f0bf2c..99048ae 100644
--- a/lib/src/flutter_web.dart
+++ b/lib/src/flutter_web.dart
@@ -8,16 +8,29 @@
 
 /// Support for handling Flutter web snippets.
 class FlutterWebManager {
-  static final Directory flutterTemplateProject = Directory(path.join(
-      Directory.current.path, 'project_templates', 'flutter_project'));
+  static Directory flutterTemplateProject(bool nullSafety) =>
+      Directory(path.join(
+        Directory.current.path,
+        'project_templates',
+        nullSafety ? 'null-safe' : 'null-unsafe',
+        'flutter_project',
+      ));
 
-  static final Directory dartTemplateProject = Directory(
-      path.join(Directory.current.path, 'project_templates', 'dart_project'));
+  static Directory dartTemplateProject(bool nullSafety) => Directory(path.join(
+        Directory.current.path,
+        'project_templates',
+        nullSafety ? 'null-safe' : 'null-unsafe',
+        'dart_project',
+      ));
 
   FlutterWebManager();
 
-  String get summaryFilePath {
-    return path.join('artifacts', 'flutter_web.dill');
+  String summaryFilePath(bool nullSafety) {
+    return path.join(
+      'artifacts',
+      nullSafety ? 'null-safe' : 'null-unsafe',
+      'flutter_web.dill',
+    );
   }
 
   static const Set<String> _flutterWebImportPrefixes = {
diff --git a/test/analysis_server_test.dart b/test/analysis_server_test.dart
index a2021cb..fbd0b0c 100644
--- a/test/analysis_server_test.dart
+++ b/test/analysis_server_test.dart
@@ -61,166 +61,173 @@
 void defineTests() {
   AnalysisServerWrapper analysisServer;
 
-  group('Platform SDK analysis_server', () {
-    setUp(() async {
-      analysisServer = DartAnalysisServerWrapper();
-      await analysisServer.init();
-    });
+  for (final nullSafety in [false, true]) {
+    group('Null ${nullSafety ? 'Safe' : 'Unsafe'} Platform SDK analysis_server',
+        () {
+      setUp(() async {
+        analysisServer = DartAnalysisServerWrapper(nullSafety);
+        await analysisServer.init();
+      });
 
-    tearDown(() => analysisServer.shutdown());
+      tearDown(() => analysisServer.shutdown());
 
-    test('simple_completion', () async {
-      // Just after i.
-      final results = await analysisServer.complete(completionCode, 32);
-      expect(results.replacementLength, 0);
-      expect(results.replacementOffset, 32);
-      expectCompletionsContains(results, 'abs');
-      expect(completionsContains(results, 'codeUnitAt'), false);
-    });
+      test('simple_completion', () async {
+        // Just after i.
+        final results = await analysisServer.complete(completionCode, 32);
+        expect(results.replacementLength, 0);
+        expect(results.replacementOffset, 32);
+        expectCompletionsContains(results, 'abs');
+        expect(completionsContains(results, 'codeUnitAt'), false);
+      });
 
-    test('repro #126 - completions polluted on second request', () async {
-      // https://github.com/dart-lang/dart-services/issues/126
-      return analysisServer.complete(completionFilterCode, 17).then((results) {
+      test('repro #126 - completions polluted on second request', () async {
+        // https://github.com/dart-lang/dart-services/issues/126
         return analysisServer
             .complete(completionFilterCode, 17)
             .then((results) {
-          expect(results.replacementLength, 2);
-          expect(results.replacementOffset, 16);
-          expect(completionsContains(results, 'print'), true);
-          expect(completionsContains(results, 'pow'), false);
+          return analysisServer
+              .complete(completionFilterCode, 17)
+              .then((results) {
+            expect(results.replacementLength, 2);
+            expect(results.replacementOffset, 16);
+            expect(completionsContains(results, 'print'), true);
+            expect(completionsContains(results, 'pow'), false);
+          });
         });
       });
+
+      test('import_test', () async {
+        // We're testing here that we don't have any path imports - we don't want
+        // to enable browsing the file system.
+        final testCode = "import '/'; main() { int a = 0; a. }";
+        final results = await analysisServer.complete(testCode, 9);
+        final completions = results.completions;
+
+        if (completions.isNotEmpty) {
+          expect(completions.every((completion) {
+            return completion.completion['completion'].startsWith('dart:');
+          }), true);
+        }
+      });
+
+      test('import_dart_core_test', () async {
+        // Ensure we can import dart: imports.
+        final testCode = "import 'dart:c'; main() { int a = 0; a. }";
+
+        final results = await analysisServer.complete(testCode, 14);
+        final completions = results.completions;
+
+        expect(
+          completions.every((completion) =>
+              completion.completion['completion'].startsWith('dart:')),
+          true,
+        );
+        expect(
+          completions.any((completion) =>
+              completion.completion['completion'].startsWith('dart:')),
+          true,
+        );
+      });
+
+      test('import_and_other_test', () async {
+        final testCode = "import '/'; main() { int a = 0; a. }";
+        final results = await analysisServer.complete(testCode, 34);
+
+        expect(completionsContains(results, 'abs'), true);
+      });
+
+      test('simple_quickFix', () async {
+        final results = await analysisServer.getFixes(quickFixesCode, 25);
+
+        expect(results.fixes.length, 2);
+
+        // Fixes are not guaranteed to arrive in a particular order.
+        results.fixes.sort((a, b) => a.offset.compareTo(b.offset));
+
+        expect(results.fixes[0].offset, 20);
+        expect(results.fixes[0].length, 1); // We need an insertion.
+
+        expect(results.fixes[1].offset, 24);
+        expect(results.fixes[1].length, 1); // We need an insertion.
+
+        expect(results.fixes[1].fixes.length, 1);
+
+        final candidateFix = results.fixes[1].fixes[0];
+
+        expect(candidateFix.message.contains(';'), true);
+        expect(candidateFix.edits[0].length, 0);
+        expect(candidateFix.edits[0].offset, 25);
+        expect(candidateFix.edits[0].replacement, ';');
+      });
+
+      test('simple_format', () async {
+        final results = await analysisServer.format(badFormatCode, 0);
+        expect(results.newString, formattedCode);
+      });
+
+      test('format good code', () async {
+        final results =
+            await analysisServer.format(formattedCode.replaceAll('\n', ' '), 0);
+        expect(results.newString, formattedCode);
+      });
+
+      test('format with issues', () async {
+        final results = await analysisServer.format(formatWithIssues, 0);
+        expect(results.newString, formatWithIssues);
+      });
+
+      test('analyze', () async {
+        final results = await analysisServer.analyze(sampleCode);
+        expect(results.issues, isEmpty);
+      });
+
+      test('analyze with errors', () async {
+        final results = await analysisServer.analyze(sampleCodeError);
+        expect(results.issues, hasLength(1));
+      });
+
+      test('analyze strong', () async {
+        final results = await analysisServer.analyze(sampleStrongError);
+        expect(results.issues, hasLength(1));
+        final issue = results.issues.first;
+        expect(issue.kind, 'error');
+      });
+
+      test('filter completions', () async {
+        // just after A
+        final idx = 61;
+        expect(completionLargeNamespaces.substring(idx - 1, idx), 'A');
+        final results =
+            await analysisServer.complete(completionLargeNamespaces, 61);
+        expect(completionsContains(results, 'A'), true);
+        expect(completionsContains(results, 'AB'), true);
+        expect(completionsContains(results, 'ABC'), true);
+        expect(completionsContains(results, 'a'), true);
+        expect(completionsContains(results, 'ZZ'), false);
+      });
     });
 
-    test('import_test', () async {
-      // We're testing here that we don't have any path imports - we don't want
-      // to enable browsing the file system.
-      final testCode = "import '/'; main() { int a = 0; a. }";
-      final results = await analysisServer.complete(testCode, 9);
-      final completions = results.completions;
+    group(
+        'Null ${nullSafety ? 'Safe' : 'Unsafe'} Flutter cached SDK analysis_server',
+        () {
+      setUp(() async {
+        analysisServer = FlutterAnalysisServerWrapper(nullSafety);
+        await analysisServer.init();
+      });
 
-      if (completions.isNotEmpty) {
-        expect(completions.every((completion) {
-          return completion.completion['completion'].startsWith('dart:');
-        }), true);
-      }
+      tearDown(() => analysisServer.shutdown());
+
+      test('analyze working Dart code', () async {
+        final results = await analysisServer.analyze(sampleCode);
+        expect(results.issues, isEmpty);
+      });
+
+      test('analyze working Flutter code', () async {
+        final results = await analysisServer.analyze(sampleCode);
+        expect(results.issues, isEmpty);
+      });
     });
-
-    test('import_dart_core_test', () async {
-      // Ensure we can import dart: imports.
-      final testCode = "import 'dart:c'; main() { int a = 0; a. }";
-
-      final results = await analysisServer.complete(testCode, 14);
-      final completions = results.completions;
-
-      expect(
-        completions.every((completion) =>
-            completion.completion['completion'].startsWith('dart:')),
-        true,
-      );
-      expect(
-        completions.any((completion) =>
-            completion.completion['completion'].startsWith('dart:')),
-        true,
-      );
-    });
-
-    test('import_and_other_test', () async {
-      final testCode = "import '/'; main() { int a = 0; a. }";
-      final results = await analysisServer.complete(testCode, 34);
-
-      expect(completionsContains(results, 'abs'), true);
-    });
-
-    test('simple_quickFix', () async {
-      final results = await analysisServer.getFixes(quickFixesCode, 25);
-
-      expect(results.fixes.length, 2);
-
-      // Fixes are not guaranteed to arrive in a particular order.
-      results.fixes.sort((a, b) => a.offset.compareTo(b.offset));
-
-      expect(results.fixes[0].offset, 20);
-      expect(results.fixes[0].length, 1); // We need an insertion.
-
-      expect(results.fixes[1].offset, 24);
-      expect(results.fixes[1].length, 1); // We need an insertion.
-
-      expect(results.fixes[1].fixes.length, 1);
-
-      final candidateFix = results.fixes[1].fixes[0];
-
-      expect(candidateFix.message.contains(';'), true);
-      expect(candidateFix.edits[0].length, 0);
-      expect(candidateFix.edits[0].offset, 25);
-      expect(candidateFix.edits[0].replacement, ';');
-    });
-
-    test('simple_format', () async {
-      final results = await analysisServer.format(badFormatCode, 0);
-      expect(results.newString, formattedCode);
-    });
-
-    test('format good code', () async {
-      final results =
-          await analysisServer.format(formattedCode.replaceAll('\n', ' '), 0);
-      expect(results.newString, formattedCode);
-    });
-
-    test('format with issues', () async {
-      final results = await analysisServer.format(formatWithIssues, 0);
-      expect(results.newString, formatWithIssues);
-    });
-
-    test('analyze', () async {
-      final results = await analysisServer.analyze(sampleCode);
-      expect(results.issues, isEmpty);
-    });
-
-    test('analyze with errors', () async {
-      final results = await analysisServer.analyze(sampleCodeError);
-      expect(results.issues, hasLength(1));
-    });
-
-    test('analyze strong', () async {
-      final results = await analysisServer.analyze(sampleStrongError);
-      expect(results.issues, hasLength(1));
-      final issue = results.issues.first;
-      expect(issue.kind, 'error');
-    });
-
-    test('filter completions', () async {
-      // just after A
-      final idx = 61;
-      expect(completionLargeNamespaces.substring(idx - 1, idx), 'A');
-      final results =
-          await analysisServer.complete(completionLargeNamespaces, 61);
-      expect(completionsContains(results, 'A'), true);
-      expect(completionsContains(results, 'AB'), true);
-      expect(completionsContains(results, 'ABC'), true);
-      expect(completionsContains(results, 'a'), true);
-      expect(completionsContains(results, 'ZZ'), false);
-    });
-  });
-
-  group('Flutter cached SDK analysis_server', () {
-    setUp(() async {
-      analysisServer = FlutterAnalysisServerWrapper();
-      await analysisServer.init();
-    });
-
-    tearDown(() => analysisServer.shutdown());
-
-    test('analyze working Dart code', () async {
-      final results = await analysisServer.analyze(sampleCode);
-      expect(results.issues, isEmpty);
-    });
-
-    test('analyze working Flutter code', () async {
-      final results = await analysisServer.analyze(sampleCode);
-      expect(results.issues, isEmpty);
-    });
-  });
+  }
 }
 
 bool completionsContains(proto.CompleteResponse response, String expected) =>
diff --git a/test/common_server_api_protobuf_test.dart b/test/common_server_api_protobuf_test.dart
index 52c86e1..ed0b8c4 100644
--- a/test/common_server_api_protobuf_test.dart
+++ b/test/common_server_api_protobuf_test.dart
@@ -90,7 +90,7 @@
     setUpAll(() async {
       container = MockContainer();
       cache = MockCache();
-      commonServerImpl = CommonServerImpl(container, cache);
+      commonServerImpl = CommonServerImpl(container, cache, false);
       commonServerApi = CommonServerApi(commonServerImpl);
       await commonServerImpl.init();
 
diff --git a/test/common_server_api_test.dart b/test/common_server_api_test.dart
index 50a935e..1071556 100644
--- a/test/common_server_api_test.dart
+++ b/test/common_server_api_test.dart
@@ -275,7 +275,7 @@
     setUpAll(() async {
       container = MockContainer();
       cache = MockCache();
-      commonServerImpl = CommonServerImpl(container, cache);
+      commonServerImpl = CommonServerImpl(container, cache, false);
       commonServerApi = CommonServerApi(commonServerImpl);
       await commonServerImpl.init();
 
diff --git a/test/compiler_test.dart b/test/compiler_test.dart
index c89fcad..2cc737e 100644
--- a/test/compiler_test.dart
+++ b/test/compiler_test.dart
@@ -14,130 +14,143 @@
 void defineTests() {
   Compiler compiler;
 
-  group('compiler', () {
-    setUpAll(() async {
-      compiler = Compiler(Sdk());
-      await compiler.warmup();
-    });
+  for (final nullSafety in [false, true]) {
+    group('Null ${nullSafety ? 'Safe' : 'Unsafe'} Compiler', () {
+      setUpAll(() async {
+        compiler = Compiler(Sdk(), nullSafety);
+        await compiler.warmup();
+      });
 
-    tearDownAll(() async {
-      await compiler.dispose();
-    });
+      tearDownAll(() async {
+        await compiler.dispose();
+      });
 
-    test('simple', () async {
-      final result = await compiler.compile(sampleCode);
-      print(result.problems);
+      test('simple', () async {
+        final result = await compiler.compile(sampleCode);
 
-      expect(result.success, true);
-      expect(result.compiledJS, isNotEmpty);
-      expect(result.sourceMap, isNull);
-    });
+        expect(result.success, true);
+        expect(result.compiledJS, isNotEmpty);
+        expect(result.sourceMap, isNull);
+      });
 
-    Future<void> Function() _generateCompilerDDCTest(String sample) =>
-        () async {
-          final result = await compiler.compileDDC(sample);
-          print(result.problems);
+      Future<void> Function() _generateCompilerDDCTest(String sample) =>
+          () async {
+            final result = await compiler.compileDDC(sample);
 
-          expect(result.success, true);
-          expect(result.compiledJS, isNotEmpty);
-          expect(result.modulesBaseUrl, isNotEmpty);
+            expect(result.success, true);
+            expect(result.compiledJS, isNotEmpty);
+            expect(result.modulesBaseUrl, isNotEmpty);
 
-          expect(result.compiledJS, contains("define('dartpad_main', ["));
-        };
+            expect(result.compiledJS, contains("define('dartpad_main', ["));
+          };
 
-    test(
-      'compileDDC simple',
-      _generateCompilerDDCTest(sampleCode),
-    );
+      test(
+        'compileDDC simple',
+        _generateCompilerDDCTest(sampleCode),
+      );
 
-    test(
-      'compileDDC with web',
-      _generateCompilerDDCTest(sampleCodeWeb),
-    );
+      test(
+        'compileDDC with web',
+        _generateCompilerDDCTest(
+            nullSafety ? sampleCodeWebNullSafe : sampleCodeWeb),
+      );
 
-    test(
-      'compileDDC with Flutter',
-      _generateCompilerDDCTest(sampleCodeFlutter),
-    );
+      test(
+        'compileDDC with Flutter',
+        _generateCompilerDDCTest(sampleCodeFlutter),
+      );
 
-    test(
-      'compileDDC with Flutter Counter',
-      _generateCompilerDDCTest(sampleCodeFlutterCounter),
-    );
+      test(
+        'compileDDC with Flutter Counter',
+        _generateCompilerDDCTest(nullSafety
+            ? sampleCodeFlutterCounterNullSafe
+            : sampleCodeFlutterCounter),
+      );
 
-    test(
-      'compileDDC with Flutter Sunflower',
-      _generateCompilerDDCTest(sampleCodeFlutterSunflower),
-    );
+      test(
+        'compileDDC with Flutter Sunflower',
+        _generateCompilerDDCTest(nullSafety
+            ? sampleCodeFlutterSunflowerNullSafe
+            : sampleCodeFlutterSunflower),
+      );
 
-    test(
-      'compileDDC with Flutter Draggable Card',
-      _generateCompilerDDCTest(sampleCodeFlutterDraggableCard),
-    );
+      test(
+        'compileDDC with Flutter Draggable Card',
+        _generateCompilerDDCTest(nullSafety
+            ? sampleCodeFlutterDraggableCardNullSafe
+            : sampleCodeFlutterDraggableCard),
+      );
 
-    test(
-      'compileDDC with Flutter Implicit Animations',
-      _generateCompilerDDCTest(sampleCodeFlutterImplicitAnimations),
-    );
+      test(
+        'compileDDC with Flutter Implicit Animations',
+        _generateCompilerDDCTest(nullSafety
+            ? sampleCodeFlutterImplicitAnimationsNullSafe
+            : sampleCodeFlutterImplicitAnimations),
+      );
 
-    test(
-      'compileDDC with async',
-      _generateCompilerDDCTest(sampleCodeAsync),
-    );
+      test(
+        'compileDDC with async',
+        _generateCompilerDDCTest(
+            nullSafety ? sampleCodeAsyncNullSafe : sampleCodeAsync),
+      );
 
-    test('compileDDC with single error', () async {
-      final result = await compiler.compileDDC(sampleCodeError);
-      expect(result.success, false);
-      expect(result.problems.length, 1);
-      expect(result.problems[0].toString(),
-          contains('Error: Expected \';\' after this.'));
-    });
+      test('compileDDC with single error', () async {
+        final result = await compiler.compileDDC(sampleCodeError);
+        expect(result.success, false);
+        expect(result.problems.length, 1);
+        expect(result.problems[0].toString(),
+            contains('Error: Expected \';\' after this.'));
+      });
 
-    test('compileDDC with multiple errors', () async {
-      final result = await compiler.compileDDC(sampleCodeErrors);
-      expect(result.success, false);
-      expect(result.problems.length, 1);
-      expect(result.problems[0].toString(),
-          contains('Error: Method not found: \'print1\'.'));
-      expect(result.problems[0].toString(),
-          contains('Error: Method not found: \'print2\'.'));
-      expect(result.problems[0].toString(),
-          contains('Error: Method not found: \'print3\'.'));
-    });
+      test('compileDDC with multiple errors', () async {
+        final result = await compiler.compileDDC(sampleCodeErrors);
+        expect(result.success, false);
+        expect(result.problems.length, 1);
+        expect(result.problems[0].toString(),
+            contains('Error: Method not found: \'print1\'.'));
+        expect(result.problems[0].toString(),
+            contains('Error: Method not found: \'print2\'.'));
+        expect(result.problems[0].toString(),
+            contains('Error: Method not found: \'print3\'.'));
+      });
 
-    test('sourcemap', () async {
-      final result = await compiler.compile(sampleCode, returnSourceMap: true);
-      expect(result.success, true);
-      expect(result.compiledJS, isNotEmpty);
-      expect(result.sourceMap, isNotNull);
-      expect(result.sourceMap, isNotEmpty);
-    });
+      test('sourcemap', () async {
+        final result =
+            await compiler.compile(sampleCode, returnSourceMap: true);
+        expect(result.success, true);
+        expect(result.compiledJS, isNotEmpty);
+        expect(result.sourceMap, isNotNull);
+        expect(result.sourceMap, isNotEmpty);
+      });
 
-    test('version', () async {
-      final result = await compiler.compile(sampleCode, returnSourceMap: true);
-      expect(result.sourceMap, isNotNull);
-      expect(result.sourceMap, isNotEmpty);
-    });
+      test('version', () async {
+        final result =
+            await compiler.compile(sampleCode, returnSourceMap: true);
+        expect(result.sourceMap, isNotNull);
+        expect(result.sourceMap, isNotEmpty);
+      });
 
-    test('simple web', () async {
-      final result = await compiler.compile(sampleCodeWeb);
-      expect(result.success, true);
-    });
+      test('simple web', () async {
+        final result = await compiler
+            .compile(nullSafety ? sampleCodeWebNullSafe : sampleCodeWeb);
+        expect(result.success, true);
+      });
 
-    test('web async', () async {
-      final result = await compiler.compile(sampleCodeAsync);
-      expect(result.success, true);
-    });
+      test('web async', () async {
+        final result = await compiler
+            .compile(nullSafety ? sampleCodeAsyncNullSafe : sampleCodeAsync);
+        expect(result.success, true);
+      });
 
-    test('errors', () async {
-      final result = await compiler.compile(sampleCodeError);
-      expect(result.success, false);
-      expect(result.problems.length, 1);
-      expect(result.problems[0].toString(), contains('Error: Expected'));
-    });
+      test('errors', () async {
+        final result = await compiler.compile(sampleCodeError);
+        expect(result.success, false);
+        expect(result.problems.length, 1);
+        expect(result.problems[0].toString(), contains('Error: Expected'));
+      });
 
-    test('good import', () async {
-      const code = '''
+      test('good import', () async {
+        const code = '''
 import 'dart:html';
 
 void main() {
@@ -146,47 +159,48 @@
 }
 
 ''';
-      final result = await compiler.compile(code);
-      expect(result.problems.length, 0);
-    });
+        final result = await compiler.compile(code);
+        expect(result.problems.length, 0);
+      });
 
-    test('bad import - local', () async {
-      const code = '''
+      test('bad import - local', () async {
+        const code = '''
 import 'foo.dart';
 void main() { missingMethod ('foo'); }
 ''';
-      final result = await compiler.compile(code);
-      expect(result.problems.first.message,
-          equals('unsupported import: foo.dart'));
-    });
+        final result = await compiler.compile(code);
+        expect(result.problems.first.message,
+            equals('unsupported import: foo.dart'));
+      });
 
-    test('bad import - http', () async {
-      const code = '''
+      test('bad import - http', () async {
+        const code = '''
 import 'http://example.com';
 void main() { missingMethod ('foo'); }
 ''';
-      final result = await compiler.compile(code);
-      expect(result.problems.first.message,
-          equals('unsupported import: http://example.com'));
-    });
+        final result = await compiler.compile(code);
+        expect(result.problems.first.message,
+            equals('unsupported import: http://example.com'));
+      });
 
-    test('disallow compiler warnings', () async {
-      final result = await compiler.compile(sampleCodeErrors);
-      expect(result.success, false);
-    });
+      test('disallow compiler warnings', () async {
+        final result = await compiler.compile(sampleCodeErrors);
+        expect(result.success, false);
+      });
 
-    test('transitive errors', () async {
-      const code = '''
+      test('transitive errors', () async {
+        const code = '''
 import 'dart:foo';
 void main() { print ('foo'); }
 ''';
-      final result = await compiler.compile(code);
-      expect(result.problems.length, 1);
-    });
+        final result = await compiler.compile(code);
+        expect(result.problems.length, 1);
+      });
 
-    test('errors for dart 2', () async {
-      final result = await compiler.compile(sampleDart2Error);
-      expect(result.problems.length, 1);
+      test('errors for dart 2', () async {
+        final result = await compiler.compile(sampleDart2Error);
+        expect(result.problems.length, 1);
+      });
     });
-  });
+  }
 }
diff --git a/test/flutter_analysis_server_test.dart b/test/flutter_analysis_server_test.dart
index c0de77b..8c85674 100644
--- a/test/flutter_analysis_server_test.dart
+++ b/test/flutter_analysis_server_test.dart
@@ -13,271 +13,104 @@
 import 'package:dart_services/src/server_cache.dart';
 import 'package:test/test.dart';
 
-const counterApp = r'''
-// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import 'package:flutter/material.dart';
-
-void main() => runApp(MyApp());
-
-class MyApp extends StatelessWidget {
-  @override
-  Widget build(BuildContext context) {
-    return MaterialApp(
-      title: 'Flutter Demo',
-      debugShowCheckedModeBanner: false,
-      theme: ThemeData(
-        primarySwatch: Colors.blue,
-      ),
-      home: MyHomePage(title: 'Flutter Demo Home Page'),
-    );
-  }
-}
-
-class MyHomePage extends StatefulWidget {
-  MyHomePage({Key key, this.title}) : super(key: key);
-
-  final String title;
-
-  @override
-  _MyHomePageState createState() => _MyHomePageState();
-}
-
-class _MyHomePageState extends State<MyHomePage> {
-  int _counter = 0;
-
-  void _incrementCounter() {
-    setState(() {
-      _counter++;
-    });
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    return Scaffold(
-      appBar: AppBar(
-        title: Text(widget.title),
-      ),
-      body: Center(
-        child: Column(
-          mainAxisAlignment: MainAxisAlignment.center,
-          children: <Widget>[
-            Text(
-              'You have pushed the button this many times:',
-            ),
-            Text(
-              '$_counter',
-              style: Theme.of(context).textTheme.headline4,
-            ),
-          ],
-        ),
-      ),
-      floatingActionButton: FloatingActionButton(
-        onPressed: _incrementCounter,
-        tooltip: 'Increment',
-        child: Icon(Icons.add),
-      ),
-    );
-  }
-}
-''';
-
-const draggableAndPhysicsApp = '''
-import 'package:flutter/material.dart';
-import 'package:flutter/physics.dart';
-
-main() {
-  runApp(
-    MaterialApp(
-      debugShowCheckedModeBanner: false,
-      home: PhysicsCardDragDemo(),
-    ),
-  );
-}
-
-class PhysicsCardDragDemo extends StatelessWidget {
-  @override
-  Widget build(BuildContext context) {
-    return Scaffold(
-      appBar: AppBar(
-        title: Text('A draggable card!'),
-      ),
-      body: DraggableCard(
-        child: FlutterLogo(
-          size: 128,
-        ),
-      ),
-    );
-  }
-}
-
-class DraggableCard extends StatefulWidget {
-  final Widget child;
-  DraggableCard({this.child});
-
-  @override
-  _DraggableCardState createState() => _DraggableCardState();
-}
-
-class _DraggableCardState extends State<DraggableCard>
-    with SingleTickerProviderStateMixin {
-  AnimationController _controller;
-  Alignment _dragAlignment = Alignment.center;
-  Animation<Alignment> _animation;
-
-  void _runAnimation(Offset pixelsPerSecond, Size size) {
-    _animation = _controller.drive(
-      AlignmentTween(
-        begin: _dragAlignment,
-        end: Alignment.center,
-      ),
-    );
-
-    final unitsPerSecondX = pixelsPerSecond.dx / size.width;
-    final unitsPerSecondY = pixelsPerSecond.dy / size.height;
-    final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY);
-    final unitVelocity = unitsPerSecond.distance;
-
-    const spring = SpringDescription(
-      mass: 30,
-      stiffness: 1,
-      damping: 1,
-    );
-
-    final simulation = SpringSimulation(spring, 0, 1, -unitVelocity);
-
-    _controller.animateWith(simulation);
-  }
-
-  @override
-  void initState() {
-    super.initState();
-    _controller = AnimationController(vsync: this);
-
-    _controller.addListener(() {
-      setState(() {
-        _dragAlignment = _animation.value;
-      });
-    });
-  }
-
-  @override
-  void dispose() {
-    _controller.dispose();
-    super.dispose();
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    final size = MediaQuery.of(context).size;
-    return GestureDetector(
-      onPanDown: (details) {
-        _controller.stop();
-      },
-      onPanUpdate: (details) {
-        setState(() {
-          _dragAlignment += Alignment(
-            details.delta.dx / (size.width / 2),
-            details.delta.dy / (size.height / 2),
-          );
-        });
-      },
-      onPanEnd: (details) {
-        _runAnimation(details.velocity.pixelsPerSecond, size);
-      },
-      child: Align(
-        alignment: _dragAlignment,
-        child: Card(
-          child: widget.child,
-        ),
-      ),
-    );
-  }
-}
-''';
-
 void main() => defineTests();
 
 void defineTests() {
-  group('Flutter SDK analysis_server', () {
-    AnalysisServerWrapper analysisServer;
+  for (final nullSafety in [false, true]) {
+    group('Null ${nullSafety ? 'Safe' : 'Unsafe'} Flutter SDK analysis_server',
+        () {
+      AnalysisServerWrapper analysisServer;
 
-    setUp(() async {
-      analysisServer = FlutterAnalysisServerWrapper();
-      await analysisServer.init();
-      await analysisServer.warmup();
+      setUp(() async {
+        analysisServer = FlutterAnalysisServerWrapper(nullSafety);
+        await analysisServer.init();
+        await analysisServer.warmup();
+      });
+
+      tearDown(() async {
+        await analysisServer.shutdown();
+      });
+
+      test('analyze counter app', () async {
+        final results = await analysisServer.analyze(nullSafety
+            ? sampleCodeFlutterCounterNullSafe
+            : sampleCodeFlutterCounter);
+        expect(results.issues, isEmpty);
+      });
+
+      test('analyze Draggable Physics sample', () async {
+        final results = await analysisServer.analyze(nullSafety
+            ? sampleCodeFlutterDraggableCardNullSafe
+            : sampleCodeFlutterDraggableCard);
+        expect(results.issues, isEmpty);
+      });
     });
 
-    tearDown(() async {
-      await analysisServer.shutdown();
+    group(
+        'Null ${nullSafety ? 'Safe' : 'Unsafe'} Flutter SDK analysis_server with analysis servers',
+        () {
+      AnalysisServersWrapper analysisServersWrapper;
+
+      setUp(() async {
+        analysisServersWrapper = AnalysisServersWrapper(nullSafety);
+        await analysisServersWrapper.warmup();
+      });
+
+      tearDown(() async {
+        await analysisServersWrapper.shutdown();
+      });
+
+      test('analyze counter app', () async {
+        final results = await analysisServersWrapper.analyze(nullSafety
+            ? sampleCodeFlutterCounterNullSafe
+            : sampleCodeFlutterCounter);
+        expect(results.issues, isEmpty);
+      });
+
+      test('analyze Draggable Physics sample', () async {
+        final results = await analysisServersWrapper.analyze(nullSafety
+            ? sampleCodeFlutterDraggableCardNullSafe
+            : sampleCodeFlutterDraggableCard);
+        expect(results.issues, isEmpty);
+      });
     });
 
-    test('analyze counter app', () async {
-      final results = await analysisServer.analyze(counterApp);
-      expect(results.issues, isEmpty);
+    group(
+        'Null ${nullSafety ? 'Safe' : 'Unsafe'} CommonServerImpl flutter analyze',
+        () {
+      CommonServerImpl commonServerImpl;
+
+      _MockContainer container;
+      _MockCache cache;
+
+      setUp(() async {
+        container = _MockContainer();
+        cache = _MockCache();
+        commonServerImpl = CommonServerImpl(container, cache, nullSafety);
+        await commonServerImpl.init();
+      });
+
+      tearDown(() async {
+        await commonServerImpl.shutdown();
+      });
+
+      test('counter app', () async {
+        final results = await commonServerImpl.analyze(SourceRequest()
+          ..source = nullSafety
+              ? sampleCodeFlutterCounterNullSafe
+              : sampleCodeFlutterCounter);
+        expect(results.issues, isEmpty);
+      });
+
+      test('Draggable Physics sample', () async {
+        final results = await commonServerImpl.analyze(SourceRequest()
+          ..source = nullSafety
+              ? sampleCodeFlutterDraggableCardNullSafe
+              : sampleCodeFlutterDraggableCard);
+        expect(results.issues, isEmpty);
+      });
     });
-
-    test('analyze Draggable Physics sample', () async {
-      final results = await analysisServer.analyze(draggableAndPhysicsApp);
-      expect(results.issues, isEmpty);
-    });
-  });
-
-  group('Flutter SDK analysis_server with analysis servers', () {
-    AnalysisServersWrapper analysisServersWrapper;
-
-    setUp(() async {
-      analysisServersWrapper = AnalysisServersWrapper();
-      await analysisServersWrapper.warmup();
-    });
-
-    tearDown(() async {
-      await analysisServersWrapper.shutdown();
-    });
-
-    test('analyze counter app', () async {
-      final results = await analysisServersWrapper.analyze(counterApp);
-      expect(results.issues, isEmpty);
-    });
-
-    test('analyze Draggable Physics sample', () async {
-      final results =
-          await analysisServersWrapper.analyze(draggableAndPhysicsApp);
-      expect(results.issues, isEmpty);
-    });
-  });
-
-  group('CommonServerImpl flutter analyze', () {
-    CommonServerImpl commonServerImpl;
-
-    _MockContainer container;
-    _MockCache cache;
-
-    setUp(() async {
-      container = _MockContainer();
-      cache = _MockCache();
-      commonServerImpl = CommonServerImpl(container, cache);
-      await commonServerImpl.init();
-    });
-
-    tearDown(() async {
-      await commonServerImpl.shutdown();
-    });
-
-    test('counter app', () async {
-      final results =
-          await commonServerImpl.analyze(SourceRequest()..source = counterApp);
-      expect(results.issues, isEmpty);
-    });
-
-    test('Draggable Physics sample', () async {
-      final results = await commonServerImpl
-          .analyze(SourceRequest()..source = draggableAndPhysicsApp);
-      expect(results.issues, isEmpty);
-    });
-  });
+  }
 }
 
 class _MockContainer implements ServerContainer {
diff --git a/test/flutter_web_test.dart b/test/flutter_web_test.dart
index c8058fa..45f080c 100644
--- a/test/flutter_web_test.dart
+++ b/test/flutter_web_test.dart
@@ -12,70 +12,77 @@
 void main() => defineTests();
 
 void defineTests() {
-  group('FlutterWebManager', () {
-    FlutterWebManager flutterWebManager;
+  for (final nullSafety in [false, true]) {
+    group('Null ${nullSafety ? 'Safe' : 'Unsafe'} FlutterWebManager', () {
+      FlutterWebManager flutterWebManager;
 
-    setUp(() async {
-      flutterWebManager = FlutterWebManager();
+      setUp(() async {
+        flutterWebManager = FlutterWebManager();
+      });
+
+      test('inited', () async {
+        expect(
+            await FlutterWebManager.flutterTemplateProject(nullSafety).exists(),
+            isTrue);
+        final file = File(path.join(
+            FlutterWebManager.flutterTemplateProject(nullSafety).path,
+            '.dart_tool',
+            'package_config.json'));
+        expect(await file.exists(), isTrue);
+      });
+
+      test('usesFlutterWeb', () {
+        expect(flutterWebManager.usesFlutterWeb({''}), isFalse);
+        expect(flutterWebManager.usesFlutterWeb({'dart:html'}), isFalse);
+        expect(flutterWebManager.usesFlutterWeb({'dart:ui'}), isTrue);
+        expect(flutterWebManager.usesFlutterWeb({'package:flutter'}), isTrue);
+        expect(flutterWebManager.usesFlutterWeb({'package:flutter/'}), isTrue);
+      });
+
+      test('getUnsupportedImport', () {
+        expect(flutterWebManager.getUnsupportedImport({'dart:html'}), isNull);
+        expect(flutterWebManager.getUnsupportedImport({'dart:ui'}), isNull);
+        expect(flutterWebManager.getUnsupportedImport({'package:flutter/'}),
+            isNull);
+        expect(flutterWebManager.getUnsupportedImport({'package:path'}),
+            equals('package:path'));
+        expect(flutterWebManager.getUnsupportedImport({'foo.dart'}),
+            equals('foo.dart'));
+        // dart:io is an unsupported package
+        expect(flutterWebManager.getUnsupportedImport({'dart:io'}),
+            equals('dart:io'));
+      });
     });
 
-    test('inited', () async {
-      expect(await FlutterWebManager.flutterTemplateProject.exists(), isTrue);
-      final file = File(path.join(FlutterWebManager.flutterTemplateProject.path,
-          '.dart_tool', 'package_config.json'));
-      expect(await file.exists(), isTrue);
+    group('Null ${nullSafety ? 'Safe' : 'Unsafe'} FlutterWebManager inited',
+        () {
+      FlutterWebManager flutterWebManager;
+
+      setUpAll(() async {
+        flutterWebManager = FlutterWebManager();
+      });
+
+      test('packagesFilePath', () async {
+        final packageConfig = File(path.join(
+            FlutterWebManager.flutterTemplateProject(nullSafety).path,
+            '.dart_tool',
+            'package_config.json'));
+        expect(await packageConfig.exists(), true);
+        final contents = jsonDecode(await packageConfig.readAsString());
+        expect(contents['packages'], isNotEmpty);
+        expect(
+            (contents['packages'] as List)
+                .where((element) => element['name'] == 'flutter'),
+            isNotEmpty);
+      });
+
+      test('summaryFilePath', () {
+        final summaryFilePath = flutterWebManager.summaryFilePath(nullSafety);
+        expect(summaryFilePath, isNotEmpty);
+
+        final file = File(summaryFilePath);
+        expect(file.existsSync(), isTrue);
+      });
     });
-
-    test('usesFlutterWeb', () {
-      expect(flutterWebManager.usesFlutterWeb({''}), isFalse);
-      expect(flutterWebManager.usesFlutterWeb({'dart:html'}), isFalse);
-      expect(flutterWebManager.usesFlutterWeb({'dart:ui'}), isTrue);
-      expect(flutterWebManager.usesFlutterWeb({'package:flutter'}), isTrue);
-      expect(flutterWebManager.usesFlutterWeb({'package:flutter/'}), isTrue);
-    });
-
-    test('getUnsupportedImport', () {
-      expect(flutterWebManager.getUnsupportedImport({'dart:html'}), isNull);
-      expect(flutterWebManager.getUnsupportedImport({'dart:ui'}), isNull);
-      expect(
-          flutterWebManager.getUnsupportedImport({'package:flutter/'}), isNull);
-      expect(flutterWebManager.getUnsupportedImport({'package:path'}),
-          equals('package:path'));
-      expect(flutterWebManager.getUnsupportedImport({'foo.dart'}),
-          equals('foo.dart'));
-      // dart:io is an unsupported package
-      expect(flutterWebManager.getUnsupportedImport({'dart:io'}),
-          equals('dart:io'));
-    });
-  });
-
-  group('FlutterWebManager inited', () {
-    FlutterWebManager flutterWebManager;
-
-    setUpAll(() async {
-      flutterWebManager = FlutterWebManager();
-    });
-
-    test('packagesFilePath', () async {
-      final packageConfig = File(path.join(
-          FlutterWebManager.flutterTemplateProject.path,
-          '.dart_tool',
-          'package_config.json'));
-      expect(await packageConfig.exists(), true);
-      final contents = jsonDecode(await packageConfig.readAsString());
-      expect(contents['packages'], isNotEmpty);
-      expect(
-          (contents['packages'] as List)
-              .where((element) => element['name'] == 'flutter'),
-          isNotEmpty);
-    });
-
-    test('summaryFilePath', () {
-      final summaryFilePath = flutterWebManager.summaryFilePath;
-      expect(summaryFilePath, isNotEmpty);
-
-      final file = File(summaryFilePath);
-      expect(file.existsSync(), isTrue);
-    });
-  });
+  }
 }
diff --git a/tool/fuzz_driver.dart b/tool/fuzz_driver.dart
index ba87400..4f0a7fa 100644
--- a/tool/fuzz_driver.dart
+++ b/tool/fuzz_driver.dart
@@ -123,17 +123,17 @@
 
   container = MockContainer();
   cache = MockCache();
-  commonServerImpl = CommonServerImpl(container, cache);
+  commonServerImpl = CommonServerImpl(container, cache, false);
   await commonServerImpl.init();
 
-  analysisServer = analysis_server.DartAnalysisServerWrapper();
+  analysisServer = analysis_server.DartAnalysisServerWrapper(false);
   await analysisServer.init();
 
   print('Warming up analysis server');
   await analysisServer.warmup();
 
   print('Warming up compiler');
-  compiler = comp.Compiler(Sdk());
+  compiler = comp.Compiler(Sdk(), false);
   await compiler.warmup();
   print('SetupTools done');
 }
diff --git a/tool/grind.dart b/tool/grind.dart
index d662c31..4162143 100644
--- a/tool/grind.dart
+++ b/tool/grind.dart
@@ -45,6 +45,13 @@
 
 @Task()
 @Depends(buildStorageArtifacts)
+Future<void> serveNullSafety() async {
+  await runWithLogging(Platform.executable,
+      arguments: ['bin/server_dev.dart', '--port', '8084', '--null-safety']);
+}
+
+@Task()
+@Depends(buildStorageArtifacts)
 Future<void> serveWithProxyTarget() async {
   await runWithLogging(Platform.executable, arguments: [
     'bin/server_dev.dart',
@@ -85,10 +92,14 @@
 void validateStorageArtifacts() async {
   final version = Sdk().versionFull;
 
-  const urlBase = 'https://storage.googleapis.com/compilation_artifacts/';
+  const nullUnsafeUrlBase =
+      'https://storage.googleapis.com/compilation_artifacts/';
+  const nullSafeUrlBase = 'https://storage.googleapis.com/nnbd_artifacts/';
 
-  for (final artifact in compilationArtifacts) {
-    await _validateExists('$urlBase$version/$artifact');
+  for (final urlBase in [nullUnsafeUrlBase, nullSafeUrlBase]) {
+    for (final artifact in compilationArtifacts) {
+      await _validateExists('$urlBase$version/$artifact');
+    }
   }
 }
 
@@ -114,27 +125,29 @@
     await templatesPath.delete(recursive: true);
   }
 
-  final dartProjectPath =
-      Directory(path.join(templatesPath.path, 'dart_project'));
-  final dartProjectDir = await dartProjectPath.create(recursive: true);
-  joinFile(dartProjectDir, ['pubspec.yaml'])
-      .writeAsStringSync(createPubspec(includeFlutterWeb: false));
-  await _runDartPubGet(dartProjectDir);
-  joinFile(dartProjectDir, ['analysis_options.yaml'])
-      .writeAsStringSync(createDartAnalysisOptions());
+  for (final nullSafety in [true, false]) {
+    final dartProjectPath = Directory(path.join(templatesPath.path,
+        nullSafety ? 'null-safe' : 'null-unsafe', 'dart_project'));
+    final dartProjectDir = await dartProjectPath.create(recursive: true);
+    joinFile(dartProjectDir, ['pubspec.yaml']).writeAsStringSync(
+        createPubspec(includeFlutterWeb: false, nullSafety: nullSafety));
+    await _runDartPubGet(dartProjectDir);
+    joinFile(dartProjectDir, ['analysis_options.yaml'])
+        .writeAsStringSync(createDartAnalysisOptions());
 
-  final flutterProjectPath =
-      Directory(path.join(templatesPath.path, 'flutter_project'));
-  final flutterProjectDir = await flutterProjectPath.create(recursive: true);
-  joinFile(flutterProjectDir, ['pubspec.yaml'])
-      .writeAsStringSync(createPubspec(includeFlutterWeb: true));
-  await _runFlutterPubGet(flutterProjectDir);
-  // TODO(gspencergoog): Convert this to use the flutter recommended lints as
-  // soon as those are finalized (the current proposal is to leave the
-  // analysis_options_user.yaml file as-is and replace it with a package, to
-  // avoid massive breakage).
-  joinFile(flutterProjectDir, ['analysis_options.yaml']).writeAsStringSync(
-      'include: package:flutter/analysis_options_user.yaml\n');
+    final flutterProjectPath = Directory(path.join(templatesPath.path,
+        nullSafety ? 'null-safe' : 'null-unsafe', 'flutter_project'));
+    final flutterProjectDir = await flutterProjectPath.create(recursive: true);
+    joinFile(flutterProjectDir, ['pubspec.yaml']).writeAsStringSync(
+        createPubspec(includeFlutterWeb: true, nullSafety: nullSafety));
+    await _runFlutterPubGet(flutterProjectDir);
+    // TODO(gspencergoog): Convert this to use the flutter recommended lints as
+    // soon as those are finalized (the current proposal is to leave the
+    // analysis_options_user.yaml file as-is and replace it with a package, to
+    // avoid massive breakage).
+    joinFile(flutterProjectDir, ['analysis_options.yaml']).writeAsStringSync(
+        'include: package:flutter/analysis_options_user.yaml\n');
+  }
 }
 
 Future<void> _runDartPubGet(Directory dir) async {
@@ -160,18 +173,28 @@
 @Task('build the sdk compilation artifacts for upload to google storage')
 @Depends(sdkInit, buildProjectTemplates)
 void buildStorageArtifacts() async {
-  // build and copy dart_sdk.js, flutter_web.js, and flutter_web.dill
-  final temp = Directory.systemTemp.createTempSync('flutter_web_sample');
+  delete(getDir('artifacts'));
+  final instructions = <String>[];
 
-  try {
-    await _buildStorageArtifacts(temp);
-  } finally {
-    temp.deleteSync(recursive: true);
+  for (final nullSafe in [false, true]) {
+    // build and copy dart_sdk.js, flutter_web.js, and flutter_web.dill
+    final temp = Directory.systemTemp.createTempSync('flutter_web_sample');
+
+    try {
+      instructions.add(await _buildStorageArtifacts(temp, nullSafe));
+    } finally {
+      temp.deleteSync(recursive: true);
+    }
+  }
+  log('\nFrom the dart-services project root dir, run:');
+  for (final instruction in instructions) {
+    log(instruction);
   }
 }
 
-Future<void> _buildStorageArtifacts(Directory dir) async {
-  final pubspec = createPubspec(includeFlutterWeb: true);
+Future<String> _buildStorageArtifacts(Directory dir, bool nullSafety) async {
+  final pubspec =
+      createPubspec(includeFlutterWeb: true, nullSafety: nullSafety);
   joinFile(dir, ['pubspec.yaml']).writeAsStringSync(pubspec);
 
   // run flutter pub get
@@ -222,12 +245,23 @@
   //     --modules=amd package:flutter/animation.dart ...
   final compilerPath = path.join(
       Sdk.flutterSdkPath, 'bin', 'cache', 'dart-sdk', 'bin', 'dartdevc');
-  final dillPath = path.join(Sdk.flutterSdkPath, 'bin', 'cache',
-      'flutter_web_sdk', 'flutter_web_sdk', 'kernel', 'flutter_ddc_sdk.dill');
+  final dillPath = path.join(
+    Sdk.flutterSdkPath,
+    'bin',
+    'cache',
+    'flutter_web_sdk',
+    'flutter_web_sdk',
+    'kernel',
+    nullSafety ? 'flutter_ddc_sdk_sound.dill' : 'flutter_ddc_sdk.dill',
+  );
 
   final args = <String>[
     '-s',
     dillPath,
+    if (nullSafety) ...[
+      '--sound-null-safety',
+      '--enable-experiment=non-nullable'
+    ],
     '--modules=amd',
     '-o',
     'flutter_web.js',
@@ -241,12 +275,15 @@
   );
 
   // Copy both to the project directory.
-  final artifactsDir = getDir('artifacts');
-  delete(artifactsDir);
-  artifactsDir.createSync();
+  final artifactsDir =
+      getDir(path.join('artifacts', nullSafety ? 'null-safe' : 'null-unsafe'));
+  artifactsDir.createSync(recursive: true);
 
-  final sdkJsPath = path.join(Sdk.flutterSdkPath,
-      'bin/cache/flutter_web_sdk/flutter_web_sdk/kernel/amd-canvaskit-html/dart_sdk.js');
+  final sdkJsPath = path.join(
+      Sdk.flutterSdkPath,
+      nullSafety
+          ? 'bin/cache/flutter_web_sdk/flutter_web_sdk/kernel/amd-canvaskit-html-sound/dart_sdk.js'
+          : 'bin/cache/flutter_web_sdk/flutter_web_sdk/kernel/amd-canvaskit-html/dart_sdk.js');
 
   copy(getFile(sdkJsPath), artifactsDir);
   copy(joinFile(dir, ['flutter_web.js']), artifactsDir);
@@ -254,9 +291,8 @@
 
   // Emit some good google storage upload instructions.
   final version = Sdk().versionFull;
-  log('\nFrom the dart-services project root dir, run:');
-  log('  gsutil -h "Cache-Control:public, max-age=86400" cp -z js '
-      'artifacts/*.js gs://compilation_artifacts/$version/');
+  return ('  gsutil -h "Cache-Control:public, max-age=86400" cp -z js ${artifactsDir.path}/*.js'
+      ' gs://${nullSafety ? 'nnbd_artifacts' : 'compilation_artifacts'}/$version/');
 }
 
 @Task('Reinitialize the Flutter submodule.')
@@ -371,12 +407,15 @@
 
 const String _samplePackageName = 'dartpad_sample';
 
-String createPubspec({@required bool includeFlutterWeb}) {
+String createPubspec({
+  @required bool includeFlutterWeb,
+  @required bool nullSafety,
+}) {
   // Mark the samples as not null safe.
   var content = '''
 name: $_samplePackageName
 environment:
-  sdk: '>=2.10.0 <3.0.0'
+  sdk: '>=${nullSafety ? '2.12.0' : '2.10.0'} <3.0.0'
 ''';
 
   if (includeFlutterWeb) {