[builder] Apply null-safety migration

Change-Id: Ibb83865687702f6a5712759a8c73f898ec27cf35
Reviewed-on: https://dart-review.googlesource.com/c/dart_ci/+/224204
Reviewed-by: Alexander Thomas <athom@google.com>
diff --git a/builder/bin/update_results_database.dart b/builder/bin/update_results_database.dart
index 41a14f0..946bf96 100644
--- a/builder/bin/update_results_database.dart
+++ b/builder/bin/update_results_database.dart
@@ -1,5 +1,3 @@
-// @dart = 2.9
-
 import 'dart:convert';
 import 'dart:io';
 
@@ -13,11 +11,11 @@
 import 'package:googleapis_auth/auth_io.dart';
 import 'package:http/http.dart' as http;
 
-BuildInfo buildInfo;
+late BuildInfo buildInfo;
 
 Future<List<Map<String, dynamic>>> readChangedResults(File resultsFile) async {
   final lines = (await resultsFile.readAsLines())
-      .map((line) => jsonDecode(line) /*!*/ as Map<String, dynamic>);
+      .map((line) => jsonDecode(line)! as Map<String, dynamic>);
   if (lines.isEmpty) {
     print('Empty input results.json file');
     exit(1);
@@ -43,12 +41,12 @@
 Future<void> processResults(options, client, firestore) async {
   final inputFile = fileOption(options, 'results');
   final results = await readChangedResults(inputFile);
-  final String buildbucketID = options['buildbucket_id'];
-  final String baseRevision = options['base_revision'];
+  final String? buildbucketID = options['buildbucket_id'];
+  final String? baseRevision = options['base_revision'];
   final commitCache = CommitsCache(firestore, client);
   if (buildInfo is TryBuildInfo) {
-    await Tryjob(buildInfo, buildbucketID, baseRevision, commitCache, firestore,
-            client)
+    await Tryjob(buildInfo as TryBuildInfo, buildbucketID!, baseRevision!,
+            commitCache, firestore, client)
         .process(results);
   } else {
     await Build(buildInfo, commitCache, firestore).process(results);
diff --git a/builder/lib/src/builder.dart b/builder/lib/src/builder.dart
index 91223d2..9aeb495 100644
--- a/builder/lib/src/builder.dart
+++ b/builder/lib/src/builder.dart
@@ -2,6 +2,7 @@
 // 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:collection/collection.dart' show IterableExtension;
 import 'package:pool/pool.dart';
 
 import 'commits_cache.dart';
@@ -15,20 +16,20 @@
 /// [FirestoreService] object.
 /// Tryjob builds are represented by the class [Tryjob] instead.
 class Build {
-  final FirestoreService /*!*/ firestore;
-  final CommitsCache /*!*/ commitsCache;
-  final BuildInfo /*!*/ info;
+  final FirestoreService firestore;
+  final CommitsCache commitsCache;
+  final BuildInfo info;
   final TestNameLock testNameLock = TestNameLock();
-  /*late final*/ int /*!*/ startIndex;
-  /*late*/ int /*!*/ endIndex;
-  /*late*/ Commit /*!*/ endCommit;
-  List<Commit /*!*/ > commits;
+  late final int startIndex;
+  late int endIndex;
+  late Commit endCommit;
+  late List<Commit> commits;
   Map<String, int> tryApprovals = {};
   List<RevertedChanges> allRevertedChanges = [];
 
   bool success = true; // Changed to false if any unapproved failure is seen.
   int countChanges = 0;
-  int commitsFetched;
+  int? commitsFetched;
   List<String> approvalMessages = [];
   int countApprovalsCopied = 0;
 
@@ -36,7 +37,7 @@
 
   void log(String string) => firestore.log(string);
 
-  Future<void> process(List<Map<String, dynamic> /*!*/ > changes) async {
+  Future<void> process(List<Map<String, dynamic>> changes) async {
     log('store build commits info');
     await storeBuildCommitsInfo();
     log('update build info');
@@ -47,9 +48,8 @@
       // TODO(karlklose): add a flag to overwrite builder results.
       return;
     }
-    final configurations = changes
-        .map((change) => change['configuration'] as String /*!*/)
-        .toSet();
+    final configurations =
+        changes.map((change) => change['configuration'] as String).toSet();
     await update(configurations);
     log('storing ${changes.length} change(s)');
     await Pool(30).forEach(changes, guardedStoreChange).drain();
@@ -73,7 +73,7 @@
     log(report.join('\n'));
   }
 
-  Future<void> update(Iterable<String /*!*/ > configurations) async {
+  Future<void> update(Iterable<String> configurations) async {
     await storeConfigurationsInfo(configurations);
   }
 
@@ -92,8 +92,9 @@
     if (info.previousCommitHash == null) {
       startIndex = endIndex;
     } else {
-      final startCommit = await commitsCache.getCommit(info.previousCommitHash);
-      startIndex = startCommit /*!*/ .index + 1;
+      final startCommit =
+          await commitsCache.getCommit(info.previousCommitHash!);
+      startIndex = startCommit!.index + 1;
       if (startIndex > endIndex) {
         throw ArgumentError('Results received with empty blamelist\n'
             'previous commit: ${info.previousCommitHash}\n'
@@ -111,7 +112,7 @@
       endCommit
     ];
     for (final commit in commits) {
-      final index = commit /*!*/ .index;
+      final index = commit!.index;
       final review = commit.review;
       final reverted = commit.revertOf;
       if (review != null) {
@@ -141,16 +142,15 @@
     await fetchReviewsAndReverts();
     transformChange(change);
     final failure = isFailure(change);
-    bool /*!*/ approved;
+    bool approved;
     var result = await firestore.findResult(change, startIndex, endIndex);
     var activeResults = await firestore.findActiveResults(
         change['name'], change['configuration']);
     if (result == null) {
       final approvingIndex = tryApprovals[testResult(change)] ??
           allRevertedChanges
-              .firstWhere(
-                  (revertedChange) => revertedChange.approveRevert(change),
-                  orElse: () => null)
+              .firstWhereOrNull(
+                  (revertedChange) => revertedChange.approveRevert(change))
               ?.revertIndex;
       approved = approvingIndex != null;
       final newResult = constructResult(change, startIndex, endIndex,
@@ -191,7 +191,7 @@
 
 Map<String, dynamic> constructResult(
     Map<String, dynamic> change, int startIndex, int endIndex,
-    {bool /*!*/ approved, int landedReviewIndex, bool failure}) {
+    {required bool approved, int? landedReviewIndex, required bool failure}) {
   return {
     fName: change[fName],
     fResult: change[fResult],
@@ -200,9 +200,9 @@
     fBlamelistStartIndex: startIndex,
     fBlamelistEndIndex: endIndex,
     if (startIndex != endIndex && approved) fPinnedIndex: landedReviewIndex,
-    fConfigurations: <String /*!*/ >[change['configuration']],
+    fConfigurations: <String>[change['configuration']],
     fApproved: approved,
     if (failure) fActive: true,
-    if (failure) fActiveConfigurations: <String /*!*/ >[change['configuration']]
+    if (failure) fActiveConfigurations: <String>[change['configuration']]
   };
 }
diff --git a/builder/lib/src/commits_cache.dart b/builder/lib/src/commits_cache.dart
index 7fe9043..530d0cc 100644
--- a/builder/lib/src/commits_cache.dart
+++ b/builder/lib/src/commits_cache.dart
@@ -16,16 +16,16 @@
 /// The class fetches commits from Firestore if they are present,
 /// and fetches them from gitiles if not, and saves them to Firestore.
 class CommitsCache {
-  FirestoreService /*!*/ firestore;
-  final http.Client /*!*/ httpClient;
-  Map<String /*!*/, Commit> byHash = {};
+  FirestoreService firestore;
+  final http.Client httpClient;
+  Map<String, Commit> byHash = {};
   Map<int, Commit> byIndex = {};
-  int startIndex;
-  int endIndex;
+  int? startIndex;
+  int? endIndex;
 
   CommitsCache(this.firestore, this.httpClient);
 
-  Future<Commit /*!*/ > getCommit(String /*!*/ hash) async {
+  Future<Commit> getCommit(String hash) async {
     var commit = byHash[hash] ?? await _fetchByHash(hash);
     if (commit == null) {
       await _getNewCommits();
@@ -37,7 +37,7 @@
     return commit;
   }
 
-  Future<Commit /*!*/ > getCommitByIndex(int /*!*/ index) async {
+  Future<Commit> getCommitByIndex(int index) async {
     var commit = byIndex[index] ?? await _fetchByIndex(index);
     if (commit == null) {
       await _getNewCommits();
@@ -67,7 +67,7 @@
     if (startIndex == null || startIndex == index + 1) {
       startIndex = index;
       endIndex ??= index;
-    } else if (endIndex + 1 == index) {
+    } else if (endIndex! + 1 == index) {
       endIndex = index;
     } else {
       return;
@@ -76,25 +76,25 @@
     byIndex[index] = commit;
   }
 
-  Future<Commit> _fetchByHash(String /*!*/ hash) async {
+  Future<Commit?> _fetchByHash(String hash) async {
     final commit = await firestore.getCommit(hash);
     if (commit == null) return null;
     final index = commit.index;
     if (startIndex == null) {
       _cacheCommit(commit);
-    } else if (index < startIndex) {
-      for (var fetchIndex = startIndex - 1; fetchIndex > index; --fetchIndex) {
+    } else if (index < startIndex!) {
+      for (var fetchIndex = startIndex! - 1; fetchIndex > index; --fetchIndex) {
         // Other invocations may be fetching simultaneously.
-        if (fetchIndex < startIndex) {
+        if (fetchIndex < startIndex!) {
           final infillCommit = await firestore.getCommitByIndex(fetchIndex);
           _cacheCommit(infillCommit);
         }
       }
       _cacheCommit(commit);
-    } else if (index > endIndex) {
-      for (var fetchIndex = endIndex + 1; fetchIndex < index; ++fetchIndex) {
+    } else if (index > endIndex!) {
+      for (var fetchIndex = endIndex! + 1; fetchIndex < index; ++fetchIndex) {
         // Other invocations may be fetching simultaneously.
-        if (fetchIndex > endIndex) {
+        if (fetchIndex > endIndex!) {
           final infillCommit = await firestore.getCommitByIndex(fetchIndex);
           _cacheCommit(infillCommit);
         }
@@ -104,7 +104,7 @@
     return commit;
   }
 
-  Future<Commit> _fetchByIndex(int /*!*/ index) => firestore
+  Future<Commit?> _fetchByIndex(int index) => firestore
       .getCommitByIndex(index)
       .then((commit) => _fetchByHash(commit.hash));
 
@@ -173,7 +173,7 @@
   /// This function is idempotent and may be called multiple times
   /// concurrently.
   Future<void> landReview(Map<String, dynamic> commit, int index) async {
-    final review = _review(commit);
+    final review = _review(commit)!;
     // Optimization to avoid duplicate work: if another instance has linked
     // the review to its landed commit, do nothing.
     if (await firestore.reviewIsLanded(review)) return;
@@ -220,20 +220,20 @@
     '^Reviewed-on: https://dart-review.googlesource.com/c/sdk/\\+/(\\d+)\$',
     multiLine: true);
 
-int _review(Map<String, dynamic> commit) {
+int? _review(Map<String, dynamic> commit) {
   final match = reviewRegExp.firstMatch(commit['message']);
-  if (match != null) return int.parse(match.group(1));
+  if (match != null) return int.parse(match.group(1)!);
   return null;
 }
 
 final revertRegExp =
     RegExp('^This reverts commit ([\\da-f]+)\\.\$', multiLine: true);
 
-String _revert(Map<String, dynamic> commit) =>
+String? _revert(Map<String, dynamic> commit) =>
     revertRegExp.firstMatch(commit['message'])?.group(1);
 
 final relandRegExp =
     RegExp('^This is a reland of ([\\da-f]+)\\.?\$', multiLine: true);
 
-String _reland(Map<String, dynamic> commit) =>
+String? _reland(Map<String, dynamic> commit) =>
     relandRegExp.firstMatch(commit['message'])?.group(1);
diff --git a/builder/lib/src/firestore.dart b/builder/lib/src/firestore.dart
index 13d6824..0c69cd9 100644
--- a/builder/lib/src/firestore.dart
+++ b/builder/lib/src/firestore.dart
@@ -7,6 +7,7 @@
 import 'dart:math' show max, min;
 
 import 'package:builder/src/result.dart';
+import 'package:collection/collection.dart' show IterableExtension;
 import 'package:googleapis/firestore/v1.dart';
 import 'package:http/http.dart' as http;
 
@@ -20,18 +21,18 @@
   Commit(this.hash, this.document);
   Commit.fromJson(this.hash, Map<String, dynamic> data)
       : document = SafeDocument(Document(fields: taggedMap(data), name: ''));
-  int /*!*/ get index => document.getInt('index');
-  String get revertOf => document.getString(fRevertOf);
-  bool /*!*/ get isRevert => document.fields.containsKey(fRevertOf);
-  int get review => document.getInt(fReview);
+  int get index => document.getInt('index')!;
+  String? get revertOf => document.getString(fRevertOf);
+  bool get isRevert => document.fields.containsKey(fRevertOf);
+  int? get review => document.getInt(fReview);
 
-  Map<String, Object> toJson() => untagMap(document.fields);
+  Map<String, Object?> toJson() => untagMap(document.fields);
 }
 
 class FirestoreService {
-  final String /*!*/ project;
-  final FirestoreApi /*!*/ firestore;
-  final http.Client /*!*/ client;
+  final String project;
+  final FirestoreApi firestore;
+  final http.Client client;
   int documentsFetched = 0;
   int documentsWritten = 0;
 
@@ -50,11 +51,11 @@
   }
 
   Future<List<SafeDocument>> query(
-      {String /*!*/ from,
-      Filter where,
-      Order orderBy,
-      int limit,
-      String parent}) async {
+      {required String from,
+      Filter? where,
+      Order? orderBy,
+      int? limit,
+      String? parent}) async {
     final query = StructuredQuery();
     if (from != null) {
       query.from = inCollection(from);
@@ -71,7 +72,7 @@
     return runQuery(query, parent: parent);
   }
 
-  Future<Document /*!*/ > getDocument(String path) async {
+  Future<Document> getDocument(String path) async {
     try {
       final document = await firestore.projects.databases.documents.get(path);
       documentsFetched++;
@@ -82,7 +83,7 @@
     }
   }
 
-  Future<Document /*?*/ > getDocumentOrNull(
+  Future<Document?> getDocumentOrNull(
     String path,
   ) async {
     try {
@@ -107,7 +108,7 @@
   String get documents => '$database/documents';
 
   Future<List<SafeDocument>> runQuery(StructuredQuery query,
-      {String parent}) async {
+      {String? parent}) async {
     final request = RunQueryRequest()..structuredQuery = query;
     final parentPath = parent == null ? documents : '$documents/$parent';
     final queryResponse = await firestore.projects.databases.documents
@@ -116,7 +117,7 @@
     documentsFetched += queryResponse.length;
     return [
       for (final responseElement in queryResponse)
-        SafeDocument(responseElement.document /*!*/)
+        SafeDocument(responseElement.document!)
     ];
   }
 
@@ -128,7 +129,7 @@
 
   Future<bool> isStaging() => Future.value(project == 'dart-ci-staging');
 
-  Future<bool> hasPatchset(String /*!*/ review, String /*!*/ patchset) {
+  Future<bool> hasPatchset(String review, String patchset) {
     return documentExists('$documents/reviews/$review/patchsets/$patchset');
   }
 
@@ -137,7 +138,7 @@
     return Commit(document.name.split('/').last, document);
   }
 
-  Future<Commit> getCommit(String hash) async {
+  Future<Commit?> getCommit(String hash) async {
     final document = await getDocumentOrNull('$documents/commits/$hash');
     return document != null ? Commit(hash, SafeDocument(document)) : null;
   }
@@ -154,7 +155,7 @@
     return _commit(lastCommit.first);
   }
 
-  Future<void> addCommit(String /*!*/ id, Map<String, dynamic> data) async {
+  Future<void> addCommit(String id, Map<String, dynamic> data) async {
     try {
       final document = Document()..fields = taggedMap(data);
       await firestore.projects.databases.documents
@@ -183,7 +184,7 @@
     } else {
       final originalBuilder = SafeDocument(record).getString('builder');
       if (originalBuilder != builder) {
-        record.fields['builder'].stringValue = builder;
+        record.fields!['builder']!.stringValue = builder;
         await updateFields(record, ['builder']);
         log('Configuration document changed: $configuration -> $builder '
             '(was $originalBuilder)');
@@ -242,12 +243,12 @@
     documentsWritten++;
   }
 
-  Future<String> findResult(
+  Future<String?> findResult(
       Map<String, dynamic> change, int startIndex, int endIndex) async {
-    final name = change['name'] as String /*!*/;
-    final result = change['result'] as String /*!*/;
-    final previousResult = change['previous_result'] as String /*!*/;
-    final expected = change['expected'] as String /*!*/;
+    final name = change['name'] as String;
+    final result = change['result'] as String;
+    final previousResult = change['previous_result'] as String;
+    final expected = change['expected'] as String;
     final snapshot = await query(
         from: 'results',
         orderBy: orderBy('blamelist_end_index', false),
@@ -260,17 +261,15 @@
         limit: 5);
 
     bool blamelistIncludesChange(SafeDocument document) {
-      final before = endIndex < document.getInt('blamelist_start_index');
-      final after = startIndex > document.getInt('blamelist_end_index');
+      final before = endIndex < document.getInt('blamelist_start_index')!;
+      final after = startIndex > document.getInt('blamelist_end_index')!;
       return !before && !after;
     }
 
-    return snapshot
-        .firstWhere(blamelistIncludesChange, orElse: () => null)
-        ?.name;
+    return snapshot.firstWhereOrNull(blamelistIncludesChange)?.name;
   }
 
-  Future<Document /*!*/ > storeResult(Map<String, dynamic> result) async {
+  Future<Document> storeResult(Map<String, dynamic> result) async {
     final document = Document()..fields = taggedMap(result);
     final createdDocument = await firestore.projects.databases.documents
         .createDocument(document, documents, 'results');
@@ -279,18 +278,18 @@
     return createdDocument;
   }
 
-  Future<bool /*!*/ > updateResult(
-      String result, String configuration, int startIndex, int endIndex,
-      {/*required*/ bool failure}) async {
-    bool approved;
+  Future<bool> updateResult(
+      String result, String? configuration, int startIndex, int endIndex,
+      {required bool failure}) async {
+    late bool approved;
     await retryCommit(() async {
       final document = await getDocument(result);
-      final data = SafeDocument(document /*!*/);
+      final data = SafeDocument(document!);
       // Allow missing 'approved' field during transition period.
       approved = data.getBool('approved') ?? false;
       // Add the new configuration and narrow the blamelist.
-      final newStart = max(startIndex, data.getInt('blamelist_start_index'));
-      final newEnd = min(endIndex, data.getInt('blamelist_end_index'));
+      final newStart = max(startIndex, data.getInt('blamelist_start_index')!);
+      final newEnd = min(endIndex, data.getInt('blamelist_end_index')!);
       // TODO(karlklose): check for pinned, and remove the pin if the new range
       // doesn't include it?
       final updates = [
@@ -298,10 +297,10 @@
         'blamelist_end_index',
         if (failure) 'active'
       ];
-      document.fields['blamelist_start_index'] = taggedValue(newStart);
-      document.fields['blamelist_end_index'] = taggedValue(newEnd);
+      document.fields!['blamelist_start_index'] = taggedValue(newStart);
+      document.fields!['blamelist_end_index'] = taggedValue(newEnd);
       if (failure) {
-        document.fields['active'] = taggedValue(true);
+        document.fields!['active'] = taggedValue(true);
       }
       final addConfiguration = ArrayValue()
         ..values = [taggedValue(configuration)];
@@ -343,7 +342,7 @@
 
   /// Returns all results which are either pinned to or have a range that is
   /// this single index. // TODO: rename this function
-  Future<List<Map<String, Value> /*!*/ >> findRevertedChanges(int index) async {
+  Future<List<Map<String, Value>>> findRevertedChanges(int index) async {
     final pinnedResults =
         await query(from: 'results', where: fieldEquals('pinned_index', index));
     final results = pinnedResults.map((response) => response.fields).toList();
@@ -360,11 +359,11 @@
 
   Future<bool> storeTryChange(
       Map<String, dynamic> change, int review, int patchset) async {
-    final name = change['name'] as String /*!*/;
-    final result = change['result'] as String /*!*/;
-    final expected = change['expected'] as String /*!*/;
-    final previousResult = change['previous_result'] as String /*!*/;
-    final configuration = change['configuration'] as String /*!*/;
+    final name = change['name'] as String;
+    final result = change['result'] as String;
+    final expected = change['expected'] as String;
+    final previousResult = change['previous_result'] as String;
+    final configuration = change['configuration'] as String;
 
     // Find an existing result record for this test on this patchset.
     final responses = await query(
@@ -409,7 +408,7 @@
           'expected': expected,
           'review': review,
           'patchset': patchset,
-          'configurations': <String /*!*/ >[configuration],
+          'configurations': <String>[configuration],
           'approved': approved
         });
       await firestore.projects.databases.documents
@@ -442,7 +441,7 @@
   }
 
   Future<void> approveResult(Document document) async {
-    document.fields['approved'] = taggedValue(true);
+    document.fields!['approved'] = taggedValue(true);
     await _executeWrite([
       Write()
         ..update = document
@@ -453,13 +452,13 @@
   /// Removes [configuration] from the active configurations and marks the
   /// active result inactive when we remove the last active config.
   Future<void> removeActiveConfiguration(
-      SafeDocument activeResult, String configuration) async {
-    final configurations = activeResult.getList('active_configurations');
+      SafeDocument activeResult, String? configuration) async {
+    final configurations = activeResult.getList('active_configurations')!;
     assert(configurations.contains(configuration));
     await removeArrayEntry(
         activeResult, 'active_configurations', taggedValue(configuration));
     final document = await getDocument(activeResult.name);
-    activeResult = SafeDocument(document /*!*/);
+    activeResult = SafeDocument(document!);
     if (activeResult.getList('active_configurations')?.isEmpty == true) {
       activeResult.fields.remove('active_configurations');
       activeResult.fields.remove('active');
@@ -485,8 +484,8 @@
     ]);
   }
 
-  Future<List<SafeDocument /*!*/ >> findActiveResults(
-      String /*!*/ name, String /*!*/ configuration) async {
+  Future<List<SafeDocument>> findActiveResults(
+      String name, String configuration) async {
     final results = await query(
         from: 'results',
         where: compositeFilter([
@@ -514,7 +513,7 @@
         .createDocument(document, documents, 'reviews', documentId: review);
   }
 
-  Future<void> deleteDocument(String /*!*/ name) async {
+  Future<void> deleteDocument(String name) async {
     return _executeWrite([Write()..delete = name]);
   }
 
@@ -532,8 +531,7 @@
     return firestore.projects.databases.documents.batchWrite(request, database);
   }
 
-  Future<void> updateFields(
-      Document /*!*/ document, List<String> fields) async {
+  Future<void> updateFields(Document document, List<String> fields) async {
     await _executeWrite([
       Write()
         ..update = document
@@ -541,13 +539,8 @@
     ]);
   }
 
-  Future<void> storePatchset(
-      String /*!*/ review,
-      int /*!*/ patchset,
-      String /*!*/ kind,
-      String /*!*/ description,
-      int /*!*/ patchsetGroup,
-      int /*!*/ number) async {
+  Future<void> storePatchset(String review, int patchset, String kind,
+      String description, int patchsetGroup, int number) async {
     final document = Document()
       ..name = '$documents/reviews/$review/patchsets/$patchset'
       ..fields = taggedMap({
@@ -558,28 +551,28 @@
       });
     await _executeWrite([Write()..update = document]);
     log('Stored patchset: $documents/reviews/$review/patchsets/$patchset\n'
-        '${untagMap(document.fields)}');
+        '${untagMap(document.fields!)}');
     documentsWritten++;
   }
 
   /// Returns true if a review record in the database has a landed_index field,
   /// or if there is no record for the review in the database.  Reviews with no
   /// test failures have no record, and don't need to be linked when landing.
-  Future<bool> reviewIsLanded(int /*!*/ review) async {
+  Future<bool> reviewIsLanded(int review) async {
     final document = await getDocumentOrNull('$documents/reviews/$review');
     if (document == null) {
       return true;
     }
-    return document.fields.containsKey('landed_index');
+    return document.fields!.containsKey('landed_index');
   }
 
-  Future<void> linkReviewToCommit(int /*!*/ review, int index) async {
+  Future<void> linkReviewToCommit(int review, int index) async {
     final document = await getDocument('$documents/reviews/$review');
-    document.fields['landed_index'] = taggedValue(index);
+    document.fields!['landed_index'] = taggedValue(index);
     await updateFields(document, ['landed_index']);
   }
 
-  Future<void> linkCommentsToCommit(int /*!*/ review, int index) async {
+  Future<void> linkCommentsToCommit(int review, int index) async {
     final comments =
         await query(from: 'comments', where: fieldEquals('review', review));
     if (comments.isEmpty) return;
@@ -595,7 +588,7 @@
     await _executeWrite(writes);
   }
 
-  Future<List<SafeDocument /*!*/ >> tryApprovals(int review) async {
+  Future<List<SafeDocument>> tryApprovals(int review) async {
     final patchsets = await query(
         from: 'patchsets',
         parent: 'reviews/$review',
@@ -614,7 +607,7 @@
         ]));
   }
 
-  Future<List<SafeDocument /*!*/ >> tryResults(
+  Future<List<SafeDocument>> tryResults(
       int review, String configuration) async {
     final patchsets = await query(
         from: 'patchsets',
@@ -641,13 +634,12 @@
       String builder, int index, bool success) async {
     final path = '$documents/builds/$builder:$index';
     final document = await getDocument(path);
-    await _completeBuilderRecord(document /*!*/, success);
+    await _completeBuilderRecord(document!, success);
   }
 
-  Future<void> _completeBuilderRecord(
-      Document /*!*/ document, bool success) async {
+  Future<void> _completeBuilderRecord(Document document, bool success) async {
     await retryCommit(() async {
-      document.fields['success'] = taggedValue(success);
+      document.fields!['success'] = taggedValue(success);
       final write = Write()
         ..update = document
         ..updateMask = (DocumentMask()
diff --git a/builder/lib/src/firestore_helpers.dart b/builder/lib/src/firestore_helpers.dart
index aa4a92b..ea52654 100644
--- a/builder/lib/src/firestore_helpers.dart
+++ b/builder/lib/src/firestore_helpers.dart
@@ -5,15 +5,15 @@
 import 'package:googleapis/firestore/v1.dart';
 
 class SafeDocument {
-  final String /*!*/ name;
-  final Map<String, Value> /*!*/ fields;
+  final String name;
+  final Map<String, Value> fields;
 
   SafeDocument(Document document)
-      : name = document.name /*!*/,
-        fields = document.fields /*!*/;
+      : name = document.name!,
+        fields = document.fields!;
 
   Document toDocument() => Document(name: name, fields: fields);
-  int getInt(String name) {
+  int? getInt(String name) {
     final value = fields[name]?.integerValue;
     if (value == null) {
       return null;
@@ -21,15 +21,15 @@
     return int.parse(value);
   }
 
-  String getString(String name) {
+  String? getString(String name) {
     return fields[name]?.stringValue;
   }
 
-  bool getBool(String name) {
+  bool? getBool(String name) {
     return fields[name]?.booleanValue;
   }
 
-  List<dynamic> getList(String name) {
+  List<dynamic>? getList(String name) {
     final arrayValue = fields[name]?.arrayValue;
     if (arrayValue == null) return null;
     return arrayValue.values?.map(getValue)?.toList() ?? [];
@@ -37,7 +37,7 @@
 
   bool isNull(String name) {
     return !fields.containsKey(name) ||
-        fields['name'].nullValue == 'NULL_VALUE';
+        fields['name']!.nullValue == 'NULL_VALUE';
   }
 }
 
@@ -67,15 +67,15 @@
 
 dynamic getValue(Value value) {
   if (value.integerValue != null) {
-    return int.parse(value.integerValue);
+    return int.parse(value.integerValue!);
   } else if (value.stringValue != null) {
     return value.stringValue;
   } else if (value.booleanValue != null) {
     return value.booleanValue;
   } else if (value.arrayValue != null) {
-    return value.arrayValue.values.map(getValue).toList();
+    return value.arrayValue!.values!.map(getValue).toList();
   } else if (value.timestampValue != null) {
-    return DateTime.parse(value.timestampValue);
+    return DateTime.parse(value.timestampValue!);
   } else if (value.nullValue != null) {
     return null;
   }
diff --git a/builder/lib/src/gerrit_change.dart b/builder/lib/src/gerrit_change.dart
index 0cd189e..8025584 100644
--- a/builder/lib/src/gerrit_change.dart
+++ b/builder/lib/src/gerrit_change.dart
@@ -21,7 +21,7 @@
 
   final http.Client httpClient;
   final FirestoreService firestore;
-  final String /*!*/ review;
+  final String review;
   final String patchset;
 
   GerritInfo(int review, int patchset, this.firestore, this.httpClient)
@@ -53,7 +53,7 @@
       ..sort((a, b) => (a['_number'] as int).compareTo(b['_number']));
     int patchsetGroupFirst = 1;
     for (Map<String, dynamic> revision in revisions) {
-      int number = revision['_number'] /*!*/;
+      int number = revision['_number']!;
       if (!trivialKinds.contains(revision['kind'])) {
         patchsetGroupFirst = number;
       }
@@ -62,7 +62,7 @@
     }
   }
 
-  static String revert(Map<String, dynamic> reviewInfo) {
+  static String? revert(Map<String, dynamic> reviewInfo) {
     final current = reviewInfo['current_revision'];
     final commit = reviewInfo['revisions'][current]['commit'];
     final regExp =
diff --git a/builder/lib/src/result.dart b/builder/lib/src/result.dart
index 20a95a2..d3eecf5 100644
--- a/builder/lib/src/result.dart
+++ b/builder/lib/src/result.dart
@@ -10,21 +10,21 @@
 import 'package:googleapis/firestore/v1.dart' show Value;
 
 class ResultRecord {
-  final Map<String, Value> /*!*/ fields;
+  final Map<String, Value> fields;
 
   ResultRecord(this.fields);
 
-  bool /*!*/ get approved => fields['approved'].booleanValue;
+  bool get approved => fields['approved']!.booleanValue!;
 
   @override
   String toString() => jsonEncode(fields);
 
   int get blamelistEndIndex {
-    return int.parse(fields['blamelist_end_index'].integerValue);
+    return int.parse(fields['blamelist_end_index']!.integerValue!);
   }
 
-  bool containsActiveConfiguration(String /*!*/ configuration) {
-    for (final value in fields['active_configurations'].arrayValue.values) {
+  bool containsActiveConfiguration(String configuration) {
+    for (final value in fields['active_configurations']!.arrayValue!.values!) {
       if (value.stringValue != null && value.stringValue == configuration) {
         return true;
       }
@@ -50,7 +50,7 @@
 const fConfigurations = 'configurations';
 const fActiveConfigurations = 'active_configurations';
 
-bool isChangedResult(Map<String, dynamic> /*!*/ change) =>
+bool isChangedResult(Map<String, dynamic> change) =>
     change[fChanged] && (!change[fFlaky] || !change[fPreviousFlaky]);
 
 /// Whether the change will be marked as an active failure.
@@ -70,7 +70,7 @@
   }
 }
 
-String fromStringOrValue(dynamic value) {
+String? fromStringOrValue(dynamic value) {
   return value is Value ? value.stringValue : value;
 }
 
@@ -96,10 +96,10 @@
 class BuildInfo {
   static final commitRefRegExp = RegExp(r'refs/changes/(\d*)/(\d*)');
 
-  final String /*!*/ builderName;
+  final String builderName;
   final int buildNumber;
-  final String /*!*/ commitRef;
-  final String previousCommitHash;
+  final String commitRef;
+  final String? previousCommitHash;
 
   BuildInfo(Map<String, dynamic> result)
       : builderName = result['builder_name'],
@@ -113,7 +113,7 @@
     if (match == null) {
       return BuildInfo(result);
     } else {
-      return TryBuildInfo(result, int.parse(match[1]), int.parse(match[2]));
+      return TryBuildInfo(result, int.parse(match[1]!), int.parse(match[2]!));
     }
   }
 }
@@ -126,11 +126,11 @@
 }
 
 class TestNameLock {
-  final locks = <String /*!*/, Future<void>>{};
+  final locks = <String, Future<void>>{};
 
   Future<void> guardedCall(Future<void> Function(Map<String, dynamic> change) f,
-      Map<String, dynamic> /*!*/ change) async {
-    final name = change[fName] /*!*/;
+      Map<String, dynamic> change) async {
+    final name = change[fName]!;
     while (locks.containsKey(name)) {
       await locks[name];
     }
diff --git a/builder/lib/src/reverted_changes.dart b/builder/lib/src/reverted_changes.dart
index e16304b..eb80ec2 100644
--- a/builder/lib/src/reverted_changes.dart
+++ b/builder/lib/src/reverted_changes.dart
@@ -17,14 +17,14 @@
   final index = revertedCommit.index;
   final changes = await firestore.findRevertedChanges(index);
   return RevertedChanges(index, revertIndex, changes,
-      groupBy(changes, (change) => getValue(change[fName])));
+      groupBy(changes, (change) => getValue(change[fName]!)));
 }
 
 class RevertedChanges {
   final int index;
   final int revertIndex;
-  final List<Map<String, Value> /*!*/ > changes;
-  final Map<String /*!*/, List<Map<String, Value> /*!*/ >> changesForTest;
+  final List<Map<String, Value>> changes;
+  final Map<String, List<Map<String, Value>>> changesForTest;
 
   RevertedChanges(
       this.index, this.revertIndex, this.changes, this.changesForTest);
@@ -34,6 +34,6 @@
     return isFailure(revert) &&
         reverted != null &&
         reverted.any(
-            (change) => revert[fResult] == getValue(change[fPreviousResult]));
+            (change) => revert[fResult] == getValue(change[fPreviousResult]!));
   }
 }
diff --git a/builder/lib/src/tryjob.dart b/builder/lib/src/tryjob.dart
index 31e371b..f88c014 100644
--- a/builder/lib/src/tryjob.dart
+++ b/builder/lib/src/tryjob.dart
@@ -57,17 +57,17 @@
 }
 
 class Tryjob {
-  final http.Client /*!*/ httpClient;
-  final FirestoreService /*!*/ firestore;
-  final CommitsCache /*!*/ commits;
+  final http.Client httpClient;
+  final FirestoreService firestore;
+  final CommitsCache commits;
   final counter = ChangeCounter();
-  TryBuildInfo /*!*/ info;
+  TryBuildInfo info;
   final TestNameLock testNameLock = TestNameLock();
-  String /*!*/ baseRevision;
+  String baseRevision;
   bool success = true;
-  List<SafeDocument /*!*/ > landedResults;
-  Map<String /*!*/, SafeDocument /*!*/ > lastLandedResultByName = {};
-  final String /*!*/ buildbucketID;
+  late List<SafeDocument> landedResults;
+  Map<String, SafeDocument> lastLandedResultByName = {};
+  final String buildbucketID;
 
   Tryjob(this.info, this.buildbucketID, this.baseRevision, this.commits,
       this.firestore, this.httpClient);
@@ -79,29 +79,28 @@
         .update();
   }
 
-  bool isNotLandedResult(Map<String, dynamic> /*!*/ change) {
+  bool isNotLandedResult(Map<String, dynamic> change) {
     return !lastLandedResultByName.containsKey(change[fName]) ||
         change[fResult] !=
-            lastLandedResultByName[change[fName]].getString(fResult);
+            lastLandedResultByName[change[fName]]!.getString(fResult);
   }
 
-  Future<void> process(List<Map<String, dynamic> /*!*/ > results) async {
+  Future<void> process(List<Map<String, dynamic>> results) async {
     await update();
     log('storing ${results.length} change(s)');
-    final resultsByConfiguration =
-        groupBy<Map<String, dynamic> /*!*/, String /*!*/ >(
-            results, (result) => result['configuration']);
+    final resultsByConfiguration = groupBy<Map<String, dynamic>, String>(
+        results, (result) => result['configuration']);
 
     for (final configuration in resultsByConfiguration.keys) {
       if (baseRevision != null && info.previousCommitHash != null) {
         landedResults = await fetchLandedResults(configuration);
         // Map will contain the last result with each name.
         lastLandedResultByName = {
-          for (final result in landedResults) result.getString(fName): result
+          for (final result in landedResults) result.getString(fName)!: result
         };
       }
       final changes =
-          resultsByConfiguration[configuration].where(isNotLandedResult);
+          resultsByConfiguration[configuration]!.where(isNotLandedResult);
       await Pool(30).forEach(changes, guardedStoreChange).drain();
     }
 
@@ -121,7 +120,7 @@
     log(report.join('\n'));
   }
 
-  Future<void> guardedStoreChange(Map<String, dynamic> /*!*/ change) =>
+  Future<void> guardedStoreChange(Map<String, dynamic> change) =>
       testNameLock.guardedCall(storeChange, change);
 
   Future<void> storeChange(Map<String, dynamic> change) async {
@@ -136,11 +135,10 @@
     }
   }
 
-  Future<List<SafeDocument /*!*/ >> fetchLandedResults(
-      String configuration) async {
-    final resultsBase = await commits.getCommit(info.previousCommitHash);
+  Future<List<SafeDocument>> fetchLandedResults(String configuration) async {
+    final resultsBase = await commits.getCommit(info.previousCommitHash!);
     final rebaseBase = await commits.getCommit(baseRevision);
-    if (resultsBase /*!*/ .index > rebaseBase /*!*/ .index) {
+    if (resultsBase!.index > rebaseBase!.index) {
       print('Try build is rebased on $baseRevision, which is before '
           'the commit ${info.previousCommitHash} with CI comparison results');
       return [];
diff --git a/builder/pubspec.yaml b/builder/pubspec.yaml
index ade43a4..6b3244f 100644
--- a/builder/pubspec.yaml
+++ b/builder/pubspec.yaml
@@ -3,7 +3,7 @@
 version: 0.1.0
 
 environment:
-  sdk: '^2.10.0'
+  sdk: '>=2.12.0 <3.0.0'
 
 dependencies:
   args: ^2.3.0
@@ -12,6 +12,7 @@
   http: ^0.13.0
   pool: ^1.5.0
   retry: 3.1.0
+  collection: ^1.15.0-nullsafety.4
 
 dev_dependencies:
   lints: ^1.0.0
diff --git a/builder/test/approvals_test.dart b/builder/test/approvals_test.dart
index c85a444..ddc955e 100644
--- a/builder/test/approvals_test.dart
+++ b/builder/test/approvals_test.dart
@@ -22,41 +22,40 @@
 // To run against the staging database, use a service account.
 // with write access to dart_ci_staging datastore.
 
-/*late final*/ FirestoreService /*!*/ firestore;
-/*late final*/ http.Client /*!*/ client;
-/*late final*/ CommitsCache /*!*/ commitsCache;
+late final FirestoreService firestore;
+late final http.Client client;
+late final CommitsCache commitsCache;
 // The real commits and reviews we will test on, fetched from Firestore.
 // These globals are populated by loadTestCommits().
 const testCommitsStart = 80801;
 const reviewWithComments = '215021';
-/*late final*/ String /*!*/ index1; // Index of the final commit in the test range
-/*late final*/ String /*!*/ commit1; // Hash of that commit
-/*late final*/ String review; // CL number of that commit's Gerrit review
-/*late final*/ String /*!*/ lastPatchset; // Final patchset in that review
-/*late final*/ String lastPatchsetRef; // 'refs/changes/[review]/[patchset]'
-/*late final*/ String /*!*/
-    patchsetGroup; // First patchset in the final patchset group
-/*late final*/ String patchsetGroupRef;
-/*late final*/ String earlyPatchset; // Patchset not in the final patchset group
-/*late final*/ String earlyPatchsetRef;
+late final String index1; // Index of the final commit in the test range
+late final String commit1; // Hash of that commit
+late final String review; // CL number of that commit's Gerrit review
+late final String lastPatchset; // Final patchset in that review
+late final String lastPatchsetRef; // 'refs/changes/[review]/[patchset]'
+late final String patchsetGroup; // First patchset in the final patchset group
+late final String patchsetGroupRef;
+late final String earlyPatchset; // Patchset not in the final patchset group
+late final String earlyPatchsetRef;
 // Earlier commit with a review
-/*late final*/ String /*!*/ index2;
-/*late final*/ String /*!*/ commit2;
-/*late final*/ String review2;
-/*late final*/ String /*!*/ patchset2;
-/*late final*/ String patchset2Ref;
+late final String index2;
+late final String commit2;
+late final String review2;
+late final String patchset2;
+late final String patchset2Ref;
 // Commits before commit2
-/*late final*/ String index3;
-/*late final*/ String /*!*/ commit3;
-/*late final*/ String index4;
-/*late final*/ String /*!*/ commit4;
+late final String index3;
+late final String commit3;
+late final String index4;
+late final String commit4;
 
-final buildersToRemove = <String /*!*/ >{};
-final testsToRemove = <String /*!*/ >{};
+final buildersToRemove = <String>{};
+final testsToRemove = <String>{};
 
 void registerChangeForDeletion(Map<String, dynamic> change) {
-  buildersToRemove.add(change['builder_name'] as String /*!*/);
-  testsToRemove.add(change['name'] as String /*!*/);
+  buildersToRemove.add(change['builder_name'] as String);
+  testsToRemove.add(change['name'] as String);
 }
 
 Future<void> removeBuildersAndResults() async {
@@ -96,10 +95,10 @@
       where: fieldLessThanOrEqual('landed_index', startIndex),
       limit: 2);
   final firstReview = reviews.first;
-  index1 = firstReview.fields['landed_index'].integerValue;
+  index1 = firstReview.fields['landed_index']!.integerValue!;
   review = firstReview.name.split('/').last;
   final secondReview = reviews.last;
-  index2 = secondReview.fields['landed_index'].integerValue;
+  index2 = secondReview.fields['landed_index']!.integerValue!;
   review2 = secondReview.name.split('/').last;
   index3 = (int.parse(index2) - 1).toString();
   index4 = (int.parse(index2) - 2).toString();
@@ -110,9 +109,9 @@
     orderBy: orderBy('number', true),
   );
   final patchsetFields = patchsets.last.fields;
-  lastPatchset = patchsetFields['number'].integerValue;
+  lastPatchset = patchsetFields['number']!.integerValue!;
   lastPatchsetRef = 'refs/changes/$review/$lastPatchset';
-  patchsetGroup = patchsetFields['patchset_group'].integerValue;
+  patchsetGroup = patchsetFields['patchset_group']!.integerValue!;
   patchsetGroupRef = 'refs/changes/$review/$patchsetGroup';
   earlyPatchset = '1';
   earlyPatchsetRef = 'refs/changes/$review/$earlyPatchset';
@@ -121,7 +120,7 @@
     parent: 'reviews/$review2',
     orderBy: orderBy('number', true),
   );
-  patchset2 = patchsets2.last.fields['number'].integerValue;
+  patchset2 = patchsets2.last.fields['number']!.integerValue!;
   patchset2Ref = 'refs/changes/$review/$patchset2';
 
   // Get commit hashes for the landed reviews, and for a commit before them
@@ -136,21 +135,21 @@
           .split('/')
           .last
   };
-  commit1 = commits[index1];
-  commit2 = commits[index2];
-  commit3 = commits[index3];
-  commit4 = commits[index4];
+  commit1 = commits[index1]!;
+  commit2 = commits[index2]!;
+  commit3 = commits[index3]!;
+  commit4 = commits[index4]!;
 }
 
 Tryjob makeTryjob(String name, Map<String, dynamic> firstChange,
-        {String baseCommit}) =>
-    Tryjob(BuildInfo.fromResult(firstChange), 'bbID_$name',
+        {String? baseCommit}) =>
+    Tryjob(BuildInfo.fromResult(firstChange) as TryBuildInfo, 'bbID_$name',
         baseCommit ?? commit4, commitsCache, firestore, client);
 
 const newFailure = 'Pass/RuntimeError/Pass';
 Map<String, dynamic> makeTryChange(
     String name, String result, String patchsetRef,
-    {String testName}) {
+    {String? testName}) {
   final results = result.split('/');
   final previous = results[0];
   final current = results[1];
@@ -183,7 +182,7 @@
 
 Map<String, dynamic> makeChange(
     String name, String result, String commit, String previousCommit,
-    {String testName}) {
+    {String? testName}) {
   final change = {
     ...makeTryChange(name, result, '', testName: testName),
     'commit_hash': commit,
@@ -280,12 +279,12 @@
         from: 'comments',
         where: fieldEquals('review', int.parse(reviewWithComments)));
     final landedIndex =
-        commentsQuery.first.fields[fBlamelistStartIndex].integerValue;
+        commentsQuery.first.fields[fBlamelistStartIndex]!.integerValue!;
     for (final item in commentsQuery) {
       final fields = item.fields;
-      expect(fields[fBlamelistStartIndex].integerValue, landedIndex);
-      expect(fields[fBlamelistEndIndex].integerValue, landedIndex);
-      expect(fields[fReview].integerValue, reviewWithComments);
+      expect(fields[fBlamelistStartIndex]!.integerValue, landedIndex);
+      expect(fields[fBlamelistEndIndex]!.integerValue, landedIndex);
+      expect(fields[fReview]!.integerValue, reviewWithComments);
       fields.remove(fBlamelistStartIndex);
       fields.remove(fBlamelistEndIndex);
       await firestore.updateFields(
@@ -293,8 +292,8 @@
     }
     var reviewDocument = await firestore
         .getDocument('${firestore.documents}/reviews/$reviewWithComments');
-    expect(reviewDocument.fields['landed_index'].integerValue, landedIndex);
-    reviewDocument.fields.remove('landed_index');
+    expect(reviewDocument.fields!['landed_index']!.integerValue, landedIndex);
+    reviewDocument.fields!.remove('landed_index');
     await firestore.updateFields(reviewDocument, ['landed_index']);
 
     await firestore.linkReviewToCommit(
@@ -306,17 +305,18 @@
         where: fieldEquals('review', int.parse(reviewWithComments)));
     for (final item in commentsQuery) {
       final fields = item.fields;
-      expect(fields[fBlamelistStartIndex].integerValue, landedIndex);
-      expect(fields[fBlamelistEndIndex].integerValue, landedIndex);
-      expect(fields[fReview].integerValue, reviewWithComments);
+      expect(fields[fBlamelistStartIndex]!.integerValue, landedIndex);
+      expect(fields[fBlamelistEndIndex]!.integerValue, landedIndex);
+      expect(fields[fReview]!.integerValue, reviewWithComments);
     }
     reviewDocument = await firestore
         .getDocument('${firestore.documents}/reviews/$reviewWithComments');
-    expect(reviewDocument.fields['landed_index'].integerValue, landedIndex);
+    expect(reviewDocument.fields!['landed_index']!.integerValue, landedIndex);
   });
 }
 
-Future<void> checkTryBuild(String name, {bool success, bool truncated}) async {
+Future<void> checkTryBuild(String name,
+    {bool? success, bool? truncated}) async {
   final buildbucketId = 'bbID_$name';
   final buildDocuments = await firestore.query(
       from: 'try_builds', where: fieldEquals('buildbucket_id', buildbucketId));
@@ -330,10 +330,10 @@
   }
 }
 
-Future<void> checkBuild(String builder, String index, {bool success}) async {
+Future<void> checkBuild(String? builder, String index, {bool? success}) async {
   final document = await firestore
       .getDocument('${firestore.documents}/builds/$builder:$index');
-  expect(document.fields['success'].booleanValue, success);
+  expect(document.fields!['success']!.booleanValue, success);
 }
 
 Future<void> checkResult(Map<String, dynamic> change, String startIndex,
@@ -342,8 +342,8 @@
   final resultName = await firestore.findResult(
       change, int.parse(startIndex), int.parse(endIndex));
   expect(resultName, isNotNull);
-  final resultDocument = await firestore.getDocument(resultName /*!*/);
-  final data = untagMap(resultDocument.fields);
+  final resultDocument = await firestore.getDocument(resultName!);
+  final data = untagMap(resultDocument.fields!);
   expect(data[fName], change[fName]);
   expect(data[fBlamelistStartIndex], int.parse(startIndex));
   expect(data[fBlamelistEndIndex], int.parse(endIndex));
diff --git a/builder/test/fakes.dart b/builder/test/fakes.dart
index 8502ea7..72a0dc3 100644
--- a/builder/test/fakes.dart
+++ b/builder/test/fakes.dart
@@ -17,8 +17,8 @@
 class BuilderTest {
   final client = HttpClientMock();
   final firestore = FirestoreServiceFake();
-  CommitsCache commitsCache;
-  Build builder;
+  late CommitsCache commitsCache;
+  late Build builder;
   Map<String, dynamic> firstChange;
 
   BuilderTest(this.firstChange) {
@@ -51,16 +51,16 @@
   Future<bool> isStaging() async => false;
 
   @override
-  Future<Commit> getCommit(String hash) async {
+  Future<Commit?> getCommit(String hash) async {
     final commit = commits[hash];
     if (commit == null) {
       return null;
     }
-    return Commit.fromJson(hash, commits[hash]);
+    return Commit.fromJson(hash, commits[hash]!);
   }
 
   @override
-  Future<Commit> getCommitByIndex(int index) {
+  Future<Commit> getCommitByIndex(int? index) {
     for (final entry in commits.entries) {
       if (entry.value[fIndex] == index) {
         return Future.value(Commit.fromJson(entry.key, entry.value));
@@ -71,7 +71,7 @@
 
   @override
   Future<Commit> getLastCommit() => getCommitByIndex(
-      commits.values.map<int>((commit) => commit[fIndex]).reduce(max));
+      commits.values.map<int?>((commit) => commit[fIndex]).reduce(max));
 
   @override
   Future<void> addCommit(String id, Map<String, dynamic> data) async {
@@ -81,8 +81,8 @@
   @override
   Future<String> findResult(
       Map<String, dynamic> change, int startIndex, int endIndex) {
-    String resultId;
-    int resultEndIndex;
+    String? resultId;
+    int? resultEndIndex;
     for (final entry in results.entries) {
       final result = entry.value;
       if (result[fName] == change[fName] &&
@@ -103,20 +103,20 @@
 
   @override
   Future<List<SafeDocument>> findActiveResults(
-      String name, String configuration) async {
+      String? name, String? configuration) async {
     return [
       for (final id in results.keys)
-        if (results[id][fName] == name &&
-            results[id][fActiveConfigurations] != null &&
-            results[id][fActiveConfigurations].contains(configuration))
+        if (results[id]![fName] == name &&
+            results[id]![fActiveConfigurations] != null &&
+            results[id]![fActiveConfigurations].contains(configuration))
           SafeDocument(Document()
-            ..fields = taggedMap(Map.from(results[id]))
+            ..fields = taggedMap(Map.from(results[id]!))
             ..name = id)
     ];
   }
 
   @override
-  Future<Document> storeResult(Map<String, dynamic> result) async {
+  Future<Document?> storeResult(Map<String, dynamic> result) async {
     final id = 'resultDocumentID$addedResultIdCounter';
     addedResultIdCounter++;
     results[id] = result;
@@ -125,16 +125,16 @@
 
   @override
   Future<bool> updateResult(
-      String resultId, String configuration, int startIndex, int endIndex,
-      {bool failure}) {
-    final result = Map<String, dynamic>.from(results[resultId]);
+      String resultId, String? configuration, int startIndex, int endIndex,
+      {required bool failure}) {
+    final result = Map<String, dynamic>.from(results[resultId]!);
 
     result[fBlamelistStartIndex] =
         max<int>(startIndex, result[fBlamelistStartIndex]);
 
     result[fBlamelistEndIndex] = min<int>(endIndex, result[fBlamelistEndIndex]);
     if (!result[fConfigurations].contains(configuration)) {
-      result[fConfigurations] = List<String>.from(result[fConfigurations])
+      result[fConfigurations] = List<String?>.from(result[fConfigurations])
         ..add(configuration)
         ..sort();
     }
@@ -142,7 +142,7 @@
       result[fActive] = true;
       if (!result[fActiveConfigurations].contains(configuration)) {
         result[fActiveConfigurations] =
-            List<String>.from(result[fActiveConfigurations])
+            List<String?>.from(result[fActiveConfigurations])
               ..add(configuration)
               ..sort();
       }
@@ -153,8 +153,8 @@
 
   @override
   Future<void> removeActiveConfiguration(
-      SafeDocument activeResult, String configuration) async {
-    final result = Map<String, dynamic>.from(results[activeResult.name]);
+      SafeDocument activeResult, String? configuration) async {
+    final result = Map<String, dynamic>.from(results[activeResult.name]!);
     result[fActiveConfigurations] = List.from(result[fActiveConfigurations])
       ..remove(configuration);
     if (result[fActiveConfigurations].isEmpty) {
diff --git a/builder/test/firestore_test.dart b/builder/test/firestore_test.dart
index 57f0516..3c9add7 100644
--- a/builder/test/firestore_test.dart
+++ b/builder/test/firestore_test.dart
@@ -93,10 +93,10 @@
       expect(activeResult.fields, contains('active'));
       await firestore.removeActiveConfiguration(
           activeResult, 'configuration 2');
-      final document = await firestore.getDocument(createdResultDocument.name);
+      final document = await firestore.getDocument(createdResultDocument.name!);
       expect(document.fields, isNot(contains('active')));
       expect(document.fields, isNot(contains('active_configurations')));
-      await firestore.deleteDocument(createdResultDocument.name);
+      await firestore.deleteDocument(createdResultDocument.name!);
     });
 
     test('approved try result fetching', () async {
diff --git a/builder/test/results_test.dart b/builder/test/results_test.dart
index a3e4b97..98379e3 100644
--- a/builder/test/results_test.dart
+++ b/builder/test/results_test.dart
@@ -27,7 +27,7 @@
     final builderTest = BuilderTest(landedCommitChange);
     builderTest.firestore.commits
         .removeWhere((key, value) => value[fIndex] > existingCommitIndex);
-    when(builderTest.client.get(any))
+    when(builderTest.client.get(any!))
         .thenAnswer((_) => Future(() => ResponseFake(gitilesLog)));
     await builderTest.storeBuildCommitsInfo();
     await builderTest.builder.fetchReviewsAndReverts();
@@ -35,9 +35,9 @@
     expect(builderTest.builder.startIndex, existingCommitIndex + 1);
     expect(builderTest.builder.tryApprovals,
         {testResult(review44445Result): 54, testResult(review77779Result): 53});
-    expect((await builderTest.firestore.getCommit(commit53Hash)).toJson(),
+    expect((await builderTest.firestore.getCommit(commit53Hash))!.toJson(),
         commit53);
-    expect((await builderTest.firestore.getCommit(landedCommitHash)).toJson(),
+    expect((await builderTest.firestore.getCommit(landedCommitHash))!.toJson(),
         landedCommit);
   });
 
@@ -56,8 +56,11 @@
           ..['configuration'] = 'another configuration';
     await builderTest.storeChange(changeAnotherConfiguration);
     expect(builderTest.builder.success, true);
-    expect(builderTest.firestore.results['activeResultID'],
-        Map.from(activeResult)..remove(fActiveConfigurations)..remove(fActive));
+    expect(
+        builderTest.firestore.results['activeResultID'],
+        Map.from(activeResult)
+          ..remove(fActiveConfigurations)
+          ..remove(fActive));
     expect(builderTest.builder.countApprovalsCopied, 1);
     expect(builderTest.builder.countChanges, 2);
     expect(
diff --git a/builder/test/revert_test.dart b/builder/test/revert_test.dart
index 91e6fdc..968edf5 100644
--- a/builder/test/revert_test.dart
+++ b/builder/test/revert_test.dart
@@ -15,13 +15,13 @@
   test('fetch commit that is a revert', () async {
     final builderTest = BuilderTest(revertUnchangedChange);
     builderTest.firestore.commits[revertedCommitHash] = revertedCommit;
-    when(builderTest.client.get(any))
+    when(builderTest.client.get(any!))
         .thenAnswer((_) => Future(() => ResponseFake(revertGitilesLog)));
     await builderTest.storeBuildCommitsInfo();
     expect(builderTest.builder.endIndex, revertCommit['index']);
     expect(builderTest.builder.startIndex, landedCommit['index'] + 1);
     expect(
-        (await builderTest.builder.firestore.getCommit(revertCommitHash))
+        (await builderTest.builder.firestore.getCommit(revertCommitHash))!
             .toJson(),
         revertCommit);
   });
@@ -29,33 +29,33 @@
   test('fetch commit that is a reland (as a reland)', () async {
     final builderTest = BuilderTest(relandUnchangedChange);
     builderTest.firestore.commits[revertedCommitHash] = revertedCommit;
-    when(builderTest.client.get(any)).thenAnswer(
+    when(builderTest.client.get(any!)).thenAnswer(
         (_) => Future(() => ResponseFake(revertAndRelandGitilesLog)));
     await builderTest.storeBuildCommitsInfo();
     expect(builderTest.builder.endIndex, relandCommit['index']);
     expect(builderTest.builder.startIndex, revertCommit['index'] + 1);
     expect(
-        (await builderTest.builder.firestore.getCommit(revertCommitHash))
+        (await builderTest.builder.firestore.getCommit(revertCommitHash))!
             .toJson(),
         revertCommit);
     expect(
-        (await builderTest.builder.firestore.getCommit(commit56Hash)).toJson(),
+        (await builderTest.builder.firestore.getCommit(commit56Hash))!.toJson(),
         commit56);
     expect(
-        (await builderTest.builder.firestore.getCommit(relandCommitHash))
+        (await builderTest.builder.firestore.getCommit(relandCommitHash))!
             .toJson(),
         relandCommit);
   });
 
   test('fetch commit that is a reland (as a revert)', () async {
     final builderTest = RevertBuilderTest(relandUnchangedChange);
-    when(builderTest.client.get(any))
+    when(builderTest.client.get(any!))
         .thenAnswer((_) => Future(() => ResponseFake(relandGitilesLog)));
     await builderTest.storeBuildCommitsInfo();
     expect(builderTest.builder.endIndex, relandCommit['index']);
     expect(builderTest.builder.startIndex, revertCommit['index'] + 1);
     expect(
-        (await builderTest.builder.firestore.getCommit(relandCommitHash))
+        (await builderTest.builder.firestore.getCommit(relandCommitHash))!
             .toJson(),
         relandCommit);
   });
@@ -257,7 +257,7 @@
 };
 
 // Git logs
-String escape(s) => s.replaceAll('"', '\\"');
+String? escape(s) => s.replaceAll('"', '\\"');
 String revertGitilesLog = gitilesLog([revertCommitJson]);
 String relandGitilesLog = gitilesLog([relandCommitJson(relandAsRevert)]);
 String revertAndRelandGitilesLog = gitilesLog(
diff --git a/builder/test/tryjob_test.dart b/builder/test/tryjob_test.dart
index 08718ff..69c3e87 100644
--- a/builder/test/tryjob_test.dart
+++ b/builder/test/tryjob_test.dart
@@ -21,15 +21,15 @@
 // To run against the staging database, use a service account.
 // with write access to dart_ci_staging datastore.
 
-FirestoreService firestore;
-http.Client client;
-CommitsCache commitsCache;
+late FirestoreService firestore;
+late http.Client client;
+late CommitsCache commitsCache;
 // The real commits and reviews we will test on, fetched from Firestore
 const testCommitsStart = 80836;
-Map<String, String> data;
+late Map<String, String?> data;
 
-final buildersToRemove = <String>{};
-final testsToRemove = <String>{};
+final buildersToRemove = <String?>{};
+final testsToRemove = <String?>{};
 
 void registerChangeForDeletion(Map<String, dynamic> change) {
   buildersToRemove.add(change['builder_name']);
@@ -53,7 +53,7 @@
   }
 }
 
-Future<Map<String, String>> loadTestCommits(int startIndex) async {
+Future<Map<String, String?>> loadTestCommits(int startIndex) async {
   // Get review data for the last two landed CLs before or at startIndex.
   final reviews = await firestore.query(
       from: 'reviews',
@@ -61,10 +61,10 @@
       where: fieldLessThanOrEqual('landed_index', startIndex),
       limit: 2);
   final firstReview = reviews.first;
-  final String index = firstReview.fields['landed_index'].integerValue;
+  final String? index = firstReview.fields['landed_index']!.integerValue;
   final String review = firstReview.name.split('/').last;
   final secondReview = reviews.last;
-  final String landedIndex = secondReview.fields['landed_index'].integerValue;
+  final String landedIndex = secondReview.fields['landed_index']!.integerValue!;
   final String landedReview = secondReview.name.split('/').last;
   // expect(int.parse(index), greaterThan(int.parse(landedIndex)));
   final String baseIndex = (int.parse(landedIndex) - 1).toString();
@@ -74,21 +74,21 @@
     parent: 'reviews/$review',
     orderBy: orderBy('number', true),
   );
-  final patchset = patchsets.last.fields['number'].integerValue;
+  final patchset = patchsets.last.fields['number']!.integerValue;
   final previousPatchset = '1';
   final landedPatchsets = await firestore.query(
     from: 'patchsets',
     parent: 'reviews/$landedReview',
     orderBy: orderBy('number', true),
   );
-  final landedPatchset = landedPatchsets.last.fields['number'].integerValue;
+  final landedPatchset = landedPatchsets.last.fields['number']!.integerValue;
 
   // Get commit hashes for the landed reviews, and for a commit before them
   var commits = {
     for (final index in [index, landedIndex, baseIndex])
       index: (await firestore.query(
               from: 'commits',
-              where: fieldEquals('index', int.parse(index)),
+              where: fieldEquals('index', int.parse(index!)),
               limit: 1))
           .first
           .name
@@ -113,16 +113,16 @@
 }
 
 Tryjob makeTryjob(String name, Map<String, dynamic> firstChange) => Tryjob(
-    BuildInfo.fromResult(firstChange),
+    BuildInfo.fromResult(firstChange) as TryBuildInfo,
     'bbID_$name',
-    data['landedCommit'],
+    data['landedCommit']!,
     commitsCache,
     firestore,
     client);
 
 Tryjob makeLandedTryjob(String name, Map<String, dynamic> firstChange) =>
-    Tryjob(BuildInfo.fromResult(firstChange), 'bbID_$name', data['baseCommit'],
-        commitsCache, firestore, client);
+    Tryjob(BuildInfo.fromResult(firstChange) as TryBuildInfo, 'bbID_$name',
+        data['baseCommit']!, commitsCache, firestore, client);
 
 Map<String, dynamic> makeChange(String name, String result,
     {bool flaky = false}) {
@@ -160,14 +160,15 @@
   return makeChange(name, result)..['commit_hash'] = data['landedPatchsetRef'];
 }
 
-Future<void> checkTryBuild(String name, {bool success, bool truncated}) async {
+Future<void> checkTryBuild(String name,
+    {bool? success, bool? truncated}) async {
   final buildbucketId = 'bbID_$name';
   final buildDocuments = await firestore.query(
       from: 'try_builds', where: fieldEquals('buildbucket_id', buildbucketId));
   expect(buildDocuments.length, 1);
-  expect(buildDocuments.single.fields['success'].booleanValue, success);
+  expect(buildDocuments.single.fields['success']!.booleanValue, success);
   if (truncated != null) {
-    expect(buildDocuments.single.fields['truncated'].booleanValue, truncated);
+    expect(buildDocuments.single.fields['truncated']!.booleanValue, truncated);
   } else {
     expect(buildDocuments.single.fields.containsKey('truncated'), isFalse);
   }
@@ -210,7 +211,7 @@
     final result = await firestore.query(
         from: 'try_results', where: fieldEquals('name', 'failure_test'));
     expect(result.length, 1);
-    expect(result.single.getList('configurations').length, 2);
+    expect(result.single.getList('configurations')!.length, 2);
   });
 
   test('landedFailure', () async {
@@ -284,21 +285,21 @@
   test('patchsets', () async {
     final document = await firestore.getDocument(
         '${firestore.documents}/reviews/${data['review']}/patchsets/${data['patchset']}');
-    final fields = untagMap(document.fields);
+    final fields = untagMap(document.fields!);
     expect(fields['number'].toString(), data['patchset']);
     await firestore.storePatchset(
-        data['review'],
+        data['review']!,
         fields['number'],
         fields['kind'],
         fields['description'],
         fields['patchset_group'],
         fields['number']);
-    final document1 = await firestore.getDocument(document.name);
-    expect(untagMap(document1.fields), equals(fields));
+    final document1 = await firestore.getDocument(document.name!);
+    expect(untagMap(document1.fields!), equals(fields));
     fields['number'] += 1;
     fields['description'] = 'test description';
     await firestore.storePatchset(
-        data['review'],
+        data['review']!,
         fields['number'],
         fields['kind'],
         fields['description'],
@@ -307,7 +308,7 @@
     final name =
         '${firestore.documents}/reviews/${data['review']}/patchsets/${fields['number']}';
     final document2 = await firestore.getDocument(name);
-    final fields2 = untagMap(document2.fields);
+    final fields2 = untagMap(document2.fields!);
     expect(fields2, equals(fields));
     await firestore.deleteDocument(name);
   });