Migrate dev/bots to null safety (#86522)

diff --git a/dev/bots/analyze.dart b/dev/bots/analyze.dart
index dc98d75..e24c1fd5 100644
--- a/dev/bots/analyze.dart
+++ b/dev/bots/analyze.dart
@@ -167,17 +167,17 @@
     }
     for (int lineNumber in linesWithDeprecations) {
       try {
-        final Match match1 = _deprecationPattern1.firstMatch(lines[lineNumber]);
+        final Match? match1 = _deprecationPattern1.firstMatch(lines[lineNumber]);
         if (match1 == null)
           throw 'Deprecation notice does not match required pattern.';
-        final String indent = match1[1];
+        final String indent = match1[1]!;
         lineNumber += 1;
         if (lineNumber >= lines.length)
           throw 'Incomplete deprecation notice.';
-        Match match3;
-        String message;
+        Match? match3;
+        String? message;
         do {
-          final Match match2 = _deprecationPattern2.firstMatch(lines[lineNumber]);
+          final Match? match2 = _deprecationPattern2.firstMatch(lines[lineNumber]);
           if (match2 == null) {
             String possibleReason = '';
             if (lines[lineNumber].trimLeft().startsWith('"')) {
@@ -188,7 +188,7 @@
           if (!lines[lineNumber].startsWith("$indent  '"))
             throw 'Unexpected deprecation notice indent.';
           if (message == null) {
-            final String firstChar = String.fromCharCode(match2[1].runes.first);
+            final String firstChar = String.fromCharCode(match2[1]!.runes.first);
             if (firstChar.toUpperCase() != firstChar)
               throw 'Deprecation notice should be a grammatically correct sentence and start with a capital letter; see style guide: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo';
           }
@@ -198,14 +198,14 @@
             throw 'Incomplete deprecation notice.';
           match3 = _deprecationPattern3.firstMatch(lines[lineNumber]);
         } while (match3 == null);
-        final int v1 = int.parse(match3[1]);
-        final int v2 = int.parse(match3[2]);
+        final int v1 = int.parse(match3[1]!);
+        final int v2 = int.parse(match3[2]!);
         final bool hasV4 = match3[4] != null;
         if (v1 > 1 || (v1 == 1 && v2 >= 20)) {
           if (!hasV4)
             throw 'Deprecation notice does not accurately indicate a dev branch version number; please see https://flutter.dev/docs/development/tools/sdk/releases to find the latest dev build version number.';
         }
-        if (!message.endsWith('.') && !message.endsWith('!') && !message.endsWith('?'))
+        if (!message!.endsWith('.') && !message.endsWith('!') && !message.endsWith('?'))
           throw 'Deprecation notice should be a grammatically correct sentence and end with a period.';
         if (!lines[lineNumber].startsWith("$indent  '"))
           throw 'Unexpected deprecation notice indent.';
@@ -238,7 +238,7 @@
 }
 
 Future<void> verifyNoMissingLicense(String workingDirectory, { bool checkMinimums = true }) async {
-  final int overrideMinimumMatches = checkMinimums ? null : 0;
+  final int? overrideMinimumMatches = checkMinimums ? null : 0;
   await _verifyNoMissingLicenseForExtension(workingDirectory, 'dart', overrideMinimumMatches ?? 2000, _generateLicense('// '));
   await _verifyNoMissingLicenseForExtension(workingDirectory, 'java', overrideMinimumMatches ?? 39, _generateLicense('// '));
   await _verifyNoMissingLicenseForExtension(workingDirectory, 'h', overrideMinimumMatches ?? 30, _generateLicense('// '));
@@ -291,7 +291,7 @@
   final List<File> dartFiles = await _allFiles(path.join(workingDirectory, 'packages'), 'dart', minimumMatches: 1500).toList();
   for (final File file in dartFiles) {
     for (final String line in file.readAsLinesSync()) {
-      final Match match = _testImportPattern.firstMatch(line);
+      final Match? match = _testImportPattern.firstMatch(line);
       if (match != null && !_exemptTestImports.contains(match.group(2)))
         errors.add(file.path);
     }
@@ -333,11 +333,11 @@
   for (final String directory in directories) {
     dependencyMap[directory] = await _findFlutterDependencies(path.join(srcPath, directory), errors, checkForMeta: directory != 'foundation');
   }
-  assert(dependencyMap['material'].contains('widgets') &&
-         dependencyMap['widgets'].contains('rendering') &&
-         dependencyMap['rendering'].contains('painting')); // to make sure we're convinced _findFlutterDependencies is finding some
+  assert(dependencyMap['material']!.contains('widgets') &&
+         dependencyMap['widgets']!.contains('rendering') &&
+         dependencyMap['rendering']!.contains('painting')); // to make sure we're convinced _findFlutterDependencies is finding some
   for (final String package in dependencyMap.keys) {
-    if (dependencyMap[package].contains(package)) {
+    if (dependencyMap[package]!.contains(package)) {
       errors.add(
         'One of the files in the $yellow$package$reset package imports that package recursively.'
       );
@@ -345,7 +345,7 @@
   }
 
   for (final String key in dependencyMap.keys) {
-    for (final String dependency in dependencyMap[key]) {
+    for (final String dependency in dependencyMap[key]!) {
       if (dependencyMap[dependency] != null)
         continue;
       // Sanity check before performing _deepSearch, to ensure there's no rogue
@@ -360,7 +360,7 @@
   }
 
   for (final String package in dependencyMap.keys) {
-    final List<String> loop = _deepSearch<String>(dependencyMap, package);
+    final List<String>? loop = _deepSearch<String>(dependencyMap, package);
     if (loop != null) {
       errors.add('${yellow}Dependency loop:$reset ${loop.join(' depends on ')}');
     }
@@ -973,7 +973,7 @@
   const Hash256(0x63D2ABD0041C3E3B, 0x4B52AD8D382353B5, 0x3C51C6785E76CE56, 0xED9DACAD2D2E31C4),
 };
 
-Future<void> verifyNoBinaries(String workingDirectory, { Set<Hash256> legacyBinaries }) async {
+Future<void> verifyNoBinaries(String workingDirectory, { Set<Hash256>? legacyBinaries }) async {
   // Please do not add anything to the _legacyBinaries set above.
   // We have a policy of not checking in binaries into this repository.
   // If you are adding/changing template images, use the flutter_template_images
@@ -1051,7 +1051,7 @@
       .toList();
 }
 
-Stream<File> _allFiles(String workingDirectory, String extension, { @required int minimumMatches }) async* {
+Stream<File> _allFiles(String workingDirectory, String? extension, { required int minimumMatches }) async* {
   final Set<String> gitFileNamesSet = <String>{};
   gitFileNamesSet.addAll((await _gitFiles(workingDirectory)).map((File f) => path.canonicalize(f.absolute.path)));
 
@@ -1101,8 +1101,8 @@
 
 class EvalResult {
   EvalResult({
-    this.stdout,
-    this.stderr,
+    required this.stdout,
+    required this.stderr,
     this.exitCode = 0,
   });
 
@@ -1113,18 +1113,13 @@
 
 // TODO(ianh): Refactor this to reuse the code in run_command.dart
 Future<EvalResult> _evalCommand(String executable, List<String> arguments, {
-  @required String workingDirectory,
-  Map<String, String> environment,
-  bool skip = false,
+  required String workingDirectory,
+  Map<String, String>? environment,
   bool allowNonZeroExit = false,
   bool runSilently = false,
 }) async {
   final String commandDescription = '${path.relative(executable, from: workingDirectory)} ${arguments.join(' ')}';
   final String relativeWorkingDir = path.relative(workingDirectory);
-  if (skip) {
-    printProgress('SKIPPING', relativeWorkingDir, commandDescription);
-    return null;
-  }
 
   if (!runSilently) {
     printProgress('RUNNING', relativeWorkingDir, commandDescription);
@@ -1168,8 +1163,8 @@
     '--consumer-only',
   ]);
   if (result.exitCode != 0) {
-    print(result.stdout);
-    print(result.stderr);
+    print(result.stdout as Object);
+    print(result.stderr as Object);
     exit(result.exitCode);
   }
   final Set<String> dependencySet = <String>{};
@@ -1217,7 +1212,7 @@
   }
 }
 
-Future<void> _runFlutterAnalyze(String workingDirectory, {
+Future<CommandResult> _runFlutterAnalyze(String workingDirectory, {
   List<String> options = const <String>[],
 }) async {
   return runCommand(
@@ -1235,9 +1230,9 @@
     .map<Set<String>>((File file) {
       final Set<String> result = <String>{};
       for (final String line in file.readAsLinesSync()) {
-        Match match = _importPattern.firstMatch(line);
+        Match? match = _importPattern.firstMatch(line);
         if (match != null)
-          result.add(match.group(2));
+          result.add(match.group(2)!);
         if (checkForMeta) {
           match = _importMetaPattern.firstMatch(line);
           if (match != null) {
@@ -1250,23 +1245,23 @@
       }
       return result;
     })
-    .reduce((Set<String> value, Set<String> element) {
+    .reduce((Set<String>? value, Set<String> element) {
       value ??= <String>{};
       value.addAll(element);
       return value;
     });
 }
 
-List<T> _deepSearch<T>(Map<T, Set<T>> map, T start, [ Set<T> seen ]) {
+List<T>? _deepSearch<T>(Map<T, Set<T>> map, T start, [ Set<T>? seen ]) {
   if (map[start] == null)
     return null; // We catch these separately.
 
-  for (final T key in map[start]) {
+  for (final T key in map[start]!) {
     if (key == start)
       continue; // we catch these separately
     if (seen != null && seen.contains(key))
       return <T>[start, key];
-    final List<T> result = _deepSearch<T>(
+    final List<T>? result = _deepSearch<T>(
       map,
       key,
       <T>{
diff --git a/dev/bots/browser.dart b/dev/bots/browser.dart
index 0b0651e..e0303db 100644
--- a/dev/bots/browser.dart
+++ b/dev/bots/browser.dart
@@ -6,7 +6,6 @@
 import 'dart:io' as io;
 
 import 'package:flutter_devicelab/framework/browser.dart';
-import 'package:meta/meta.dart';
 import 'package:shelf/shelf.dart';
 import 'package:shelf/shelf_io.dart' as shelf_io;
 import 'package:shelf_static/shelf_static.dart';
@@ -21,13 +20,13 @@
 /// request to "/test-result" containing result data as plain text body of the
 /// request. This function has no opinion about what that string contains.
 Future<String> evalTestAppInChrome({
-  @required String appUrl,
-  @required String appDirectory,
+  required String appUrl,
+  required String appDirectory,
   int serverPort = 8080,
   int browserDebugPort = 8081,
 }) async {
-  io.HttpServer server;
-  Chrome chrome;
+  io.HttpServer? server;
+  Chrome? chrome;
   try {
     final Completer<String> resultCompleter = Completer<String>();
     server = await io.HttpServer.bind('localhost', serverPort);
@@ -63,13 +62,13 @@
   AppServer._(this._server, this.chrome, this.onChromeError);
 
   static Future<AppServer> start({
-    @required String appUrl,
-    @required String appDirectory,
-    @required String cacheControl,
+    required String appUrl,
+    required String appDirectory,
+    required String cacheControl,
     int serverPort = 8080,
     int browserDebugPort = 8081,
     bool headless = true,
-    List<Handler> additionalRequestHandlers,
+    List<Handler>? additionalRequestHandlers,
   }) async {
     io.HttpServer server;
     Chrome chrome;
@@ -106,7 +105,7 @@
   final Chrome chrome;
 
   Future<void> stop() async {
-    chrome?.stop();
-    await _server?.close();
+    chrome.stop();
+    await _server.close();
   }
 }
diff --git a/dev/bots/flutter_compact_formatter.dart b/dev/bots/flutter_compact_formatter.dart
index 0b5552c..9bb08fd 100644
--- a/dev/bots/flutter_compact_formatter.dart
+++ b/dev/bots/flutter_compact_formatter.dart
@@ -5,8 +5,6 @@
 import 'dart:convert';
 import 'dart:io';
 
-import 'package:meta/meta.dart';
-
 final Stopwatch _stopwatch = Stopwatch();
 
 /// A wrapper around package:test's JSON reporter.
@@ -75,7 +73,7 @@
   ///
   /// Callers are responsible for splitting multiple lines before calling this
   /// method.
-  TestResult processRawOutput(String raw) {
+  TestResult? processRawOutput(String raw) {
     assert(raw != null);
     // We might be getting messages from Flutter Tool about updating/building.
     if (!raw.startsWith('{')) {
@@ -83,7 +81,7 @@
       return null;
     }
     final Map<String, dynamic> decoded = json.decode(raw) as Map<String, dynamic>;
-    final TestResult originalResult = _tests[decoded['testID']];
+    final TestResult? originalResult = _tests[decoded['testID']];
     switch (decoded['type'] as String) {
       case 'done':
         stdout.write(_clearLine);
@@ -104,9 +102,9 @@
         _tests[testData['id'] as int] = TestResult(
           id: testData['id'] as int,
           name: testData['name'] as String,
-          line: testData['root_line'] as int ?? testData['line'] as int,
-          column: testData['root_column'] as int ?? testData['column'] as int,
-          path: testData['root_url'] as String ?? testData['url'] as String,
+          line: testData['root_line'] as int? ?? testData['line'] as int,
+          column: testData['root_column'] as int? ?? testData['column'] as int,
+          path: testData['root_url'] as String? ?? testData['url'] as String,
           startTime: decoded['time'] as int,
         );
         break;
@@ -173,9 +171,9 @@
         case TestStatus.failed:
           failed.addAll(<String>[
             '$_bold${_red}Failed ${result.name} (${result.pathLineColumn}):',
-            result.errorMessage,
+            result.errorMessage!,
             _noColor + _red,
-            result.stackTrace,
+            result.stackTrace!,
           ]);
           failed.addAll(result.messages);
           failed.add(_noColor);
@@ -209,12 +207,12 @@
 /// The detailed status of a test run.
 class TestResult {
   TestResult({
-    @required this.id,
-    @required this.name,
-    @required this.line,
-    @required this.column,
-    @required this.path,
-    @required this.startTime,
+    required this.id,
+    required this.name,
+    required this.line,
+    required this.column,
+    required this.path,
+    required this.startTime,
     this.status = TestStatus.started,
   })  : assert(id != null),
         assert(name != null),
@@ -254,13 +252,13 @@
 
   /// The error message from the test, from an `expect`, an [Exception] or
   /// [Error].
-  String errorMessage;
+  String? errorMessage;
 
   /// The stacktrace from a test failure.
-  String stackTrace;
+  String? stackTrace;
 
   /// The time, in milliseconds relative to suite startup, that the test ended.
-  int endTime;
+  int? endTime;
 
   /// The total time, in milliseconds, that the test took.
   int get totalTime => (endTime ?? _stopwatch.elapsedMilliseconds) - startTime;
diff --git a/dev/bots/prepare_package.dart b/dev/bots/prepare_package.dart
index 4c88c79..e3f3e5c 100644
--- a/dev/bots/prepare_package.dart
+++ b/dev/bots/prepare_package.dart
@@ -11,7 +11,6 @@
 import 'package:crypto/crypto.dart';
 import 'package:crypto/src/digest_sink.dart';
 import 'package:http/http.dart' as http;
-import 'package:meta/meta.dart' show required;
 import 'package:path/path.dart' as path;
 import 'package:platform/platform.dart' show Platform, LocalPlatform;
 import 'package:process/process.dart';
@@ -32,7 +31,7 @@
   PreparePackageException(this.message, [this.result]);
 
   final String message;
-  final ProcessResult result;
+  final ProcessResult? result;
   int get exitCode => result?.exitCode ?? -1;
 
   @override
@@ -41,7 +40,7 @@
     if (message != null) {
       output += ': $message';
     }
-    final String stderr = result?.stderr as String ?? '';
+    final String stderr = result?.stderr as String? ?? '';
     if (stderr.isNotEmpty) {
       output += ':\n$stderr';
     }
@@ -60,7 +59,6 @@
     case Branch.stable:
       return 'stable';
   }
-  return null;
 }
 
 Branch fromBranchName(String name) {
@@ -81,7 +79,7 @@
 /// properly without dropping any.
 class ProcessRunner {
   ProcessRunner({
-    ProcessManager processManager,
+    ProcessManager? processManager,
     this.subprocessOutput = true,
     this.defaultWorkingDirectory,
     this.platform = const LocalPlatform(),
@@ -102,10 +100,10 @@
 
   /// Sets the default directory used when `workingDirectory` is not specified
   /// to [runProcess].
-  final Directory defaultWorkingDirectory;
+  final Directory? defaultWorkingDirectory;
 
   /// The environment to run processes with.
-  Map<String, String> environment;
+  late Map<String, String> environment;
 
   /// Run the command and arguments in `commandLine` as a sub-process from
   /// `workingDirectory` if set, or the [defaultWorkingDirectory] if not. Uses
@@ -115,7 +113,7 @@
   /// command completes with a non-zero exit code.
   Future<String> runProcess(
     List<String> commandLine, {
-    Directory workingDirectory,
+    Directory? workingDirectory,
     bool failOk = false,
   }) async {
     workingDirectory ??= defaultWorkingDirectory ?? Directory.current;
@@ -125,7 +123,7 @@
     final List<int> output = <int>[];
     final Completer<void> stdoutComplete = Completer<void>();
     final Completer<void> stderrComplete = Completer<void>();
-    Process process;
+    late Process process;
     Future<int> allComplete() async {
       await stderrComplete.future;
       await stdoutComplete.future;
@@ -197,10 +195,10 @@
     this.revision,
     this.branch, {
     this.strict = true,
-    ProcessManager processManager,
+    ProcessManager? processManager,
     bool subprocessOutput = true,
     this.platform = const LocalPlatform(),
-    HttpReader httpReader,
+    HttpReader? httpReader,
   })  : assert(revision.length == 40),
         flutterRoot = Directory(path.join(tempDir.path, 'flutter')),
         httpReader = httpReader ?? http.readBytes,
@@ -252,9 +250,9 @@
   /// [http.readBytes].
   final HttpReader httpReader;
 
-  File _outputFile;
-  String _version;
-  String _flutter;
+  late File _outputFile;
+  late String _version;
+  late String _flutter;
 
   /// Get the name of the channel as a string.
   String get branchName => getBranchName(branch);
@@ -446,14 +444,14 @@
     }
   }
 
-  Future<String> _runFlutter(List<String> args, {Directory workingDirectory}) {
+  Future<String> _runFlutter(List<String> args, {Directory? workingDirectory}) {
     return _processRunner.runProcess(
       <String>[_flutter, ...args],
       workingDirectory: workingDirectory ?? flutterRoot,
     );
   }
 
-  Future<String> _runGit(List<String> args, {Directory workingDirectory}) {
+  Future<String> _runGit(List<String> args, {Directory? workingDirectory}) {
     return _processRunner.runProcess(
       <String>['git', ...args],
       workingDirectory: workingDirectory ?? flutterRoot,
@@ -462,7 +460,7 @@
 
   /// Unpacks the given zip file into the currentDirectory (if set), or the
   /// same directory as the archive.
-  Future<String> _unzipArchive(File archive, {Directory workingDirectory}) {
+  Future<String> _unzipArchive(File archive, {Directory? workingDirectory}) {
     workingDirectory ??= Directory(path.dirname(archive.absolute.path));
     List<String> commandLine;
     if (platform.isWindows) {
@@ -532,7 +530,7 @@
     this.version,
     this.outputFile,
     this.dryRun, {
-    ProcessManager processManager,
+    ProcessManager? processManager,
     bool subprocessOutput = true,
     this.platform = const LocalPlatform(),
   })  : assert(revision.length == 40),
@@ -662,7 +660,7 @@
 
   Future<String> _runGsUtil(
     List<String> args, {
-    Directory workingDirectory,
+    Directory? workingDirectory,
     bool failOk = false,
   }) async {
     if (dryRun) {
@@ -671,7 +669,7 @@
     }
     if (platform.isWindows) {
       return _processRunner.runProcess(
-        <String>['python', path.join(platform.environment['DEPOT_TOOLS'], 'gsutil.py'), '--', ...args],
+        <String>['python', path.join(platform.environment['DEPOT_TOOLS']!, 'gsutil.py'), '--', ...args],
         workingDirectory: workingDirectory,
         failOk: failOk,
       );
@@ -699,14 +697,14 @@
   }
 
   Future<String> _cloudCopy({
-    @required String src,
-    @required String dest,
-    int cacheSeconds,
+    required String src,
+    required String dest,
+    int? cacheSeconds,
   }) async {
     // We often don't have permission to overwrite, but
     // we have permission to remove, so that's what we do.
     await _runGsUtil(<String>['rm', dest], failOk: true);
-    String mimeType;
+    String? mimeType;
     if (dest.endsWith('.tar.xz')) {
       mimeType = 'application/x-gtar';
     }
@@ -841,7 +839,7 @@
   final Branch branch = fromBranchName(parsedArguments['branch'] as String);
   final ArchiveCreator creator = ArchiveCreator(tempDir, outputDir, revision, branch, strict: parsedArguments['publish'] as bool);
   int exitCode = 0;
-  String message;
+  late String message;
   try {
     final String version = await creator.initializeRepo();
     final File outputFile = await creator.createArchive();
diff --git a/dev/bots/pubspec.yaml b/dev/bots/pubspec.yaml
index 9bbd7fd..2d5579e 100644
--- a/dev/bots/pubspec.yaml
+++ b/dev/bots/pubspec.yaml
@@ -2,7 +2,7 @@
 description: Scripts which run on bots.
 
 environment:
-  sdk: ">=2.2.2 <3.0.0"
+  sdk: ">=2.12.0 <3.0.0"
 
 dependencies:
   args: 2.1.1
diff --git a/dev/bots/run_command.dart b/dev/bots/run_command.dart
index bee2a69..13a97f2 100644
--- a/dev/bots/run_command.dart
+++ b/dev/bots/run_command.dart
@@ -19,12 +19,12 @@
 /// code fails the test immediately by exiting the test process with exit code
 /// 1.
 Stream<String> runAndGetStdout(String executable, List<String> arguments, {
-  String workingDirectory,
-  Map<String, String> environment,
+  String? workingDirectory,
+  Map<String, String>? environment,
   bool expectNonZeroExit = false,
 }) async* {
   final StreamController<String> output = StreamController<String>();
-  final Future<CommandResult> command = runCommand(
+  final Future<CommandResult?> command = runCommand(
     executable,
     arguments,
     workingDirectory: workingDirectory,
@@ -52,8 +52,8 @@
   final io.Process process;
 
   final Stopwatch _time;
-  final Future<List<List<int>>> _savedStdout;
-  final Future<List<List<int>>> _savedStderr;
+  final Future<List<List<int>>>? _savedStdout;
+  final Future<List<List<int>>>? _savedStderr;
 
   /// Evaluates when the [process] exits.
   ///
@@ -63,8 +63,8 @@
     _time.stop();
 
     // Saved output is null when OutputMode.print is used.
-    final String flattenedStdout = _savedStdout != null ? _flattenToString(await _savedStdout) : null;
-    final String flattenedStderr = _savedStderr != null ? _flattenToString(await _savedStderr) : null;
+    final String? flattenedStdout = _savedStdout != null ? _flattenToString((await _savedStdout)!) : null;
+    final String? flattenedStderr = _savedStderr != null ? _flattenToString((await _savedStderr)!) : null;
     return CommandResult._(exitCode, _time.elapsed, flattenedStdout, flattenedStderr);
   }
 }
@@ -80,10 +80,10 @@
   final Duration elapsedTime;
 
   /// Standard output decoded as a string using UTF8 decoder.
-  final String flattenedStdout;
+  final String? flattenedStdout;
 
   /// Standard error output decoded as a string using UTF8 decoder.
-  final String flattenedStderr;
+  final String? flattenedStderr;
 }
 
 /// Starts the `executable` and returns a command object representing the
@@ -97,11 +97,11 @@
 /// `outputMode` controls where the standard output from the command process
 /// goes. See [OutputMode].
 Future<Command> startCommand(String executable, List<String> arguments, {
-  String workingDirectory,
-  Map<String, String> environment,
+  String? workingDirectory,
+  Map<String, String>? environment,
   OutputMode outputMode = OutputMode.print,
-  bool Function(String) removeLine,
-  void Function(String, io.Process) outputListener,
+  bool Function(String)? removeLine,
+  void Function(String, io.Process)? outputListener,
 }) async {
   final String commandDescription = '${path.relative(executable, from: workingDirectory)} ${arguments.join(' ')}';
   final String relativeWorkingDir = path.relative(workingDirectory ?? io.Directory.current.path);
@@ -113,7 +113,7 @@
     environment: environment,
   );
 
-  Future<List<List<int>>> savedStdout, savedStderr;
+  Future<List<List<int>>>? savedStdout, savedStderr;
   final Stream<List<int>> stdoutSource = process.stdout
     .transform<String>(const Utf8Decoder())
     .transform(const LineSplitter())
@@ -150,27 +150,22 @@
 /// an indefinitely running process, for example, by waiting until the process
 /// emits certain output.
 ///
-/// Returns the result of the finished process, or null if `skip` is true.
+/// Returns the result of the finished process.
 ///
 /// `outputMode` controls where the standard output from the command process
 /// goes. See [OutputMode].
 Future<CommandResult> runCommand(String executable, List<String> arguments, {
-  String workingDirectory,
-  Map<String, String> environment,
+  String? workingDirectory,
+  Map<String, String>? environment,
   bool expectNonZeroExit = false,
-  int expectedExitCode,
-  String failureMessage,
+  int? expectedExitCode,
+  String? failureMessage,
   OutputMode outputMode = OutputMode.print,
-  bool skip = false,
-  bool Function(String) removeLine,
-  void Function(String, io.Process) outputListener,
+  bool Function(String)? removeLine,
+  void Function(String, io.Process)? outputListener,
 }) async {
   final String commandDescription = '${path.relative(executable, from: workingDirectory)} ${arguments.join(' ')}';
   final String relativeWorkingDir = path.relative(workingDirectory ?? io.Directory.current.path);
-  if (skip) {
-    printProgress('SKIPPING', relativeWorkingDir, commandDescription);
-    return null;
-  }
 
   final Command command = await startCommand(executable, arguments,
     workingDirectory: workingDirectory,
diff --git a/dev/bots/service_worker_test.dart b/dev/bots/service_worker_test.dart
index c5d7295..3e64ef7 100644
--- a/dev/bots/service_worker_test.dart
+++ b/dev/bots/service_worker_test.dart
@@ -5,7 +5,6 @@
 import 'dart:async';
 import 'dart:io';
 
-import 'package:meta/meta.dart';
 import 'package:path/path.dart' as path;
 import 'package:shelf/shelf.dart';
 
@@ -37,7 +36,7 @@
   );
 }
 
-Future<void> _rebuildApp({ @required int version }) async {
+Future<void> _rebuildApp({ required int version }) async {
   await _setAppVersion(version);
   await runCommand(
     _flutter,
@@ -56,7 +55,7 @@
 
 /// A drop-in replacement for `package:test` expect that can run outside the
 /// test zone.
-void expect(Object actual, Object expected) {
+void expect(Object? actual, Object? expected) {
   final Matcher matcher = wrapMatcher(expected);
   final Map<Object, Object> matchState = <Object, Object>{};
   if (matcher.matches(actual, matchState)) {
@@ -68,7 +67,7 @@
 }
 
 Future<void> runWebServiceWorkerTest({
-  @required bool headless,
+  required bool headless,
 }) async {
   await _rebuildApp(version: 1);
 
@@ -78,25 +77,25 @@
     requestedPathCounts.clear();
   }
 
-  AppServer server;
+  AppServer? server;
   Future<void> waitForAppToLoad(Map<String, int> waitForCounts) async {
     print('Waiting for app to load $waitForCounts');
-    await Future.any(<Future<void>>[
+    await Future.any(<Future<Object?>>[
       () async {
         while (!waitForCounts.entries.every((MapEntry<String, int> entry) => (requestedPathCounts[entry.key] ?? 0) >= entry.value)) {
           await Future<void>.delayed(const Duration(milliseconds: 100));
         }
       }(),
-      server.onChromeError.then((String error) {
+      server!.onChromeError.then((String error) {
         throw Exception('Chrome error: $error');
       }),
     ]);
   }
 
-  String reportedVersion;
+  String? reportedVersion;
 
   Future<void> startAppServer({
-    @required String cacheControl,
+    required String cacheControl,
   }) async {
     final int serverPort = await findAvailablePort();
     final int browserDebugPort = await findAvailablePort();
@@ -113,7 +112,7 @@
         (Request request) {
           final String requestedPath = request.url.path;
           requestedPathCounts.putIfAbsent(requestedPath, () => 0);
-          requestedPathCounts[requestedPath] += 1;
+          requestedPathCounts[requestedPath] = requestedPathCounts[requestedPath]! + 1;
           if (requestedPath == 'CLOSE') {
             reportedVersion = request.url.queryParameters['version'];
             return Response.ok('OK');
@@ -158,7 +157,7 @@
     reportedVersion = null;
 
     print('With cache: test page reload');
-    await server.chrome.reloadPage();
+    await server!.chrome.reloadPage();
     await waitForAppToLoad(<String, int>{
       'CLOSE': 1,
       'flutter_service_worker.js': 1,
@@ -175,7 +174,7 @@
     await _rebuildApp(version: 2);
 
     // Since we're caching, we need to ignore cache when reloading the page.
-    await server.chrome.reloadPage(ignoreCache: true);
+    await server!.chrome.reloadPage(ignoreCache: true);
     await waitForAppToLoad(<String, int>{
       'CLOSE': 1,
       'flutter_service_worker.js': 2,
@@ -195,7 +194,7 @@
 
     expect(reportedVersion, '2');
     reportedVersion = null;
-    await server.stop();
+    await server!.stop();
 
 
     //////////////////////////////////////////////////////
@@ -231,7 +230,7 @@
     reportedVersion = null;
 
     print('No cache: test page reload');
-    await server.chrome.reloadPage();
+    await server!.chrome.reloadPage();
     await waitForAppToLoad(<String, int>{
       'CLOSE': 1,
       'flutter_service_worker.js': 1,
@@ -254,7 +253,7 @@
     // refresh when running Chrome manually as normal. At the time of writing
     // this test I wasn't able to figure out what's wrong with the way we run
     // Chrome from tests.
-    await server.chrome.reloadPage(ignoreCache: true);
+    await server!.chrome.reloadPage(ignoreCache: true);
     await waitForAppToLoad(<String, int>{
       'CLOSE': 1,
       'flutter_service_worker.js': 1,
diff --git a/dev/bots/test.dart b/dev/bots/test.dart
index 346c77a..8d6213f 100644
--- a/dev/bots/test.dart
+++ b/dev/bots/test.dart
@@ -9,7 +9,6 @@
 
 import 'package:file/file.dart' as fs;
 import 'package:file/local.dart';
-import 'package:meta/meta.dart';
 import 'package:path/path.dart' as path;
 
 import 'browser.dart';
@@ -26,7 +25,7 @@
 ///
 /// If the output does not match expectations, the function shall return an
 /// appropriate error message.
-typedef OutputChecker = String Function(CommandResult);
+typedef OutputChecker = String? Function(CommandResult);
 
 final String exe = Platform.isWindows ? '.exe' : '';
 final String bat = Platform.isWindows ? '.bat' : '';
@@ -73,7 +72,7 @@
 ///
 /// The last shard also runs the Web plugin tests.
 int get webShardCount => Platform.environment.containsKey('WEB_SHARD_COUNT')
-  ? int.parse(Platform.environment['WEB_SHARD_COUNT'])
+  ? int.parse(Platform.environment['WEB_SHARD_COUNT']!)
   : 8;
 /// Tests that we don't run on Web for compilation reasons.
 //
@@ -148,7 +147,7 @@
   }
   final String expectedVersion = File(engineVersionFile).readAsStringSync().trim();
   final CommandResult result = await runCommand(flutterTester, <String>['--help'], outputMode: OutputMode.capture);
-  final String actualVersion = result.flattenedStderr.split('\n').firstWhere((final String line) {
+  final String actualVersion = result.flattenedStderr!.split('\n').firstWhere((final String line) {
     return line.startsWith('Flutter Engine Version:');
   });
   if (!actualVersion.contains(expectedVersion)) {
@@ -199,7 +198,7 @@
                 path.join('test_smoke_test', 'pending_timer_fail_test.dart'),
             expectFailure: true,
             printOutput: false, outputChecker: (CommandResult result) {
-          return result.flattenedStdout.contains('failingPendingTimerTest')
+          return result.flattenedStdout!.contains('failingPendingTimerTest')
               ? null
               : 'Failed to find the stack trace for the pending Timer.';
         }),
@@ -250,7 +249,7 @@
   // Smoke tests are special and run first for all test shards.
   // Run all smoke tests for other shards.
   // Only shard smoke tests when explicitly specified.
-  final String shardName = Platform.environment[kShardKey];
+  final String? shardName = Platform.environment[kShardKey];
   if (shardName == kSmokeTestShardName) {
     testsToRun = _selectIndexOfTotalSubshard<ShardRunner>(tests);
   } else {
@@ -261,7 +260,7 @@
   }
 
   // Verify that we correctly generated the version file.
-  final String versionError = await verifyVersion(File(path.join(flutterRoot, 'version')));
+  final String? versionError = await verifyVersion(File(path.join(flutterRoot, 'version')));
   if (versionError != null)
     exitWithError(<String>[versionError]);
 }
@@ -479,7 +478,7 @@
 }
 
 Future<void> _flutterBuildApk(String relativePathToApplication, {
-  @required bool release,
+  required bool release,
   bool verifyCaching = false,
   List<String> additionalArgs = const <String>[],
 }) async {
@@ -492,7 +491,7 @@
 }
 
 Future<void> _flutterBuildIpa(String relativePathToApplication, {
-  @required bool release,
+  required bool release,
   List<String> additionalArgs = const <String>[],
   bool verifyCaching = false,
 }) async {
@@ -506,7 +505,7 @@
 }
 
 Future<void> _flutterBuildLinux(String relativePathToApplication, {
-  @required bool release,
+  required bool release,
   bool verifyCaching = false,
   List<String> additionalArgs = const <String>[],
 }) async {
@@ -521,7 +520,7 @@
 }
 
 Future<void> _flutterBuildMacOS(String relativePathToApplication, {
-  @required bool release,
+  required bool release,
   bool verifyCaching = false,
   List<String> additionalArgs = const <String>[],
 }) async {
@@ -536,7 +535,7 @@
 }
 
 Future<void> _flutterBuildWin32(String relativePathToApplication, {
-  @required bool release,
+  required bool release,
   bool verifyCaching = false,
   List<String> additionalArgs = const <String>[],
 }) async {
@@ -554,7 +553,7 @@
   String relativePathToApplication,
   String platformLabel,
   String platformBuildName, {
-  @required bool release,
+  required bool release,
   bool verifyCaching = false,
   List<String> additionalArgs = const <String>[],
 }) async {
@@ -598,11 +597,11 @@
 }
 
 bool _allTargetsCached(File performanceFile) {
-  final Map<String, Object> data = json.decode(performanceFile.readAsStringSync())
-    as Map<String, Object>;
-  final List<Map<String, Object>> targets = (data['targets'] as List<Object>)
-    .cast<Map<String, Object>>();
-  return targets.every((Map<String, Object> element) => element['skipped'] == true);
+  final Map<String, Object?> data = json.decode(performanceFile.readAsStringSync())
+    as Map<String, Object?>;
+  final List<Map<String, Object?>> targets = (data['targets']! as List<Object?>)
+    .cast<Map<String, Object?>>();
+  return targets.every((Map<String, Object?> element) => element['skipped'] == true);
 }
 
 Future<void> _flutterBuildDart2js(String relativePathToApplication, String target, { bool expectNonZeroExit = false }) async {
@@ -760,7 +759,7 @@
       expectFailure: true,
       printOutput: false,
       outputChecker: (CommandResult result) {
-        final Iterable<Match> matches = httpClientWarning.allMatches(result.flattenedStdout);
+        final Iterable<Match> matches = httpClientWarning.allMatches(result.flattenedStdout!);
         if (matches == null || matches.isEmpty || matches.length > 1) {
           return 'Failed to print warning about HttpClientUsage, or printed it too many times.\n'
                  'stdout:\n${result.flattenedStdout}';
@@ -959,8 +958,8 @@
 /// Runs one of the `dev/integration_tests/web_e2e_tests` tests.
 Future<void> _runWebE2eTest(
   String name, {
-  @required String buildMode,
-  @required String renderer,
+  required String buildMode,
+  required String renderer,
 }) async {
   await _runFlutterDriverWebTest(
     target: path.join('test_driver', '$name.dart'),
@@ -971,10 +970,10 @@
 }
 
 Future<void> _runFlutterDriverWebTest({
-  @required String target,
-  @required String buildMode,
-  @required String renderer,
-  @required String testAppDirectory,
+  required String target,
+  required String buildMode,
+  required String renderer,
+  required String testAppDirectory,
   bool expectFailure = false,
   bool silenceBrowserOutput = false,
 }) async {
@@ -987,7 +986,7 @@
   await runCommand(
     flutter,
     <String>[
-      ...?flutterTestArgs,
+      ...flutterTestArgs,
       'drive',
       '--target=$target',
       '--browser-name=chrome',
@@ -1083,7 +1082,7 @@
 /// plugins version file, when null [flutterPluginsVersionFile] is used.
 Future<String> getFlutterPluginsVersion({
   fs.FileSystem fileSystem = const LocalFileSystem(),
-  String pluginsVersionFile,
+  String? pluginsVersionFile,
 }) async {
   final File versionFile = fileSystem.file(pluginsVersionFile ?? flutterPluginsVersionFile);
   final String versionFileContents = await versionFile.readAsString();
@@ -1163,7 +1162,7 @@
 //
 // If an existing chromedriver is already available on port 4444, the existing
 // process is reused and this variable remains null.
-Command _chromeDriver;
+Command? _chromeDriver;
 
 Future<bool> _isChromeDriverRunning() async {
   try {
@@ -1208,7 +1207,7 @@
     return;
   }
   print('Stopping chromedriver');
-  _chromeDriver.process.kill();
+  _chromeDriver!.process.kill();
 }
 
 /// Exercises the old gallery in a browser for a long period of time, looking
@@ -1231,7 +1230,7 @@
   await runCommand(
     flutter,
     <String>[
-      ...?flutterTestArgs,
+      ...flutterTestArgs,
       'drive',
       if (canvasKit)
         '--dart-define=FLUTTER_WEB_USE_SKIA=true',
@@ -1315,7 +1314,7 @@
   await runCommand(
     flutter,
     <String>[
-      ...?flutterTestArgs,
+      ...flutterTestArgs,
       'build',
       'web',
       '--release',
@@ -1394,8 +1393,8 @@
   if (success) {
     print('${green}Web stack trace integration test passed.$reset');
   } else {
-    print(result.flattenedStdout);
-    print(result.flattenedStderr);
+    print(result.flattenedStdout!);
+    print(result.flattenedStderr!);
     print('${red}Web stack trace integration test failed.$reset');
     exit(1);
   }
@@ -1413,7 +1412,7 @@
       // TODO(ferhatb): Run web tests with both rendering backends.
       '--web-renderer=html', // use html backend for web tests.
       '--sound-null-safety', // web tests do not autodetect yet.
-      ...?flutterTestArgs,
+      ...flutterTestArgs,
       ...tests,
     ],
     workingDirectory: workingDirectory,
@@ -1429,17 +1428,17 @@
 // dependent targets are only built on some engines).
 // See https://github.com/flutter/flutter/issues/72368
 Future<void> _pubRunTest(String workingDirectory, {
-  List<String> testPaths,
+  List<String>? testPaths,
   bool enableFlutterToolAsserts = true,
   bool useBuildRunner = false,
-  String coverage,
+  String? coverage,
   bool forceSingleCore = false,
-  Duration perTestTimeout,
+  Duration? perTestTimeout,
   bool includeLocalEngineEnv = false,
   bool ensurePrecompiledTool = true,
 }) async {
-  int cpus;
-  final String cpuVariable = Platform.environment['CPU']; // CPU is set in cirrus.yml
+  int? cpus;
+  final String? cpuVariable = Platform.environment['CPU']; // CPU is set in cirrus.yml
   if (cpuVariable != null) {
     cpus = int.tryParse(cpuVariable, radix: 10);
     if (cpus == null) {
@@ -1525,13 +1524,12 @@
 }
 
 Future<void> _runFlutterTest(String workingDirectory, {
-  String script,
+  String? script,
   bool expectFailure = false,
   bool printOutput = true,
-  OutputChecker outputChecker,
+  OutputChecker? outputChecker,
   List<String> options = const <String>[],
-  bool skip = false,
-  Map<String, String> environment,
+  Map<String, String>? environment,
   List<String> tests = const <String>[],
 }) async {
   assert(!printOutput || outputChecker == null, 'Output either can be printed or checked but not both');
@@ -1539,7 +1537,7 @@
   final List<String> args = <String>[
     'test',
     ...options,
-    ...?flutterTestArgs,
+    ...flutterTestArgs,
   ];
 
   final bool shouldProcessOutput = useFlutterTestFormatter && !expectFailure && !options.contains('--coverage');
@@ -1554,8 +1552,6 @@
       print('Script: $green$script$reset');
       if (!printOutput)
         print('This is one of the tests that does not normally print output.');
-      if (skip)
-        print('This is one of the tests that is normally skipped in this configuration.');
       exit(1);
     }
     args.add(script);
@@ -1574,12 +1570,11 @@
       workingDirectory: workingDirectory,
       expectNonZeroExit: expectFailure,
       outputMode: outputMode,
-      skip: skip,
       environment: environment,
     );
 
     if (outputChecker != null) {
-      final String message = outputChecker(result);
+      final String? message = outputChecker(result);
       if (message != null)
         exitWithError(<String>[message]);
     }
@@ -1612,7 +1607,7 @@
 }
 
 Map<String, String> _initGradleEnvironment() {
-  final String androidSdkRoot = (Platform.environment['ANDROID_HOME']?.isEmpty ?? true)
+  final String? androidSdkRoot = (Platform.environment['ANDROID_HOME']?.isEmpty ?? true)
       ? Platform.environment['ANDROID_SDK_ROOT']
       : Platform.environment['ANDROID_HOME'];
   if (androidSdkRoot == null || androidSdkRoot.isEmpty) {
@@ -1620,7 +1615,7 @@
     exit(1);
   }
   return <String, String>{
-    'ANDROID_HOME': androidSdkRoot,
+    'ANDROID_HOME': androidSdkRoot!,
     'ANDROID_SDK_ROOT': androidSdkRoot,
   };
 }
@@ -1654,7 +1649,7 @@
   formatter.finish();
 }
 
-CiProviders get ciProvider {
+CiProviders? get ciProvider {
   if (Platform.environment['CIRRUS_CI'] == 'true') {
     return CiProviders.cirrus;
   }
@@ -1668,11 +1663,12 @@
 String get branchName {
   switch(ciProvider) {
     case CiProviders.cirrus:
-      return Platform.environment['CIRRUS_BRANCH'];
+      return Platform.environment['CIRRUS_BRANCH']!;
     case CiProviders.luci:
-      return Platform.environment['LUCI_BRANCH'];
+      return Platform.environment['LUCI_BRANCH']!;
+    case null:
+      return '';
   }
-  return '';
 }
 
 /// Checks the given file's contents to determine if they match the allowed
@@ -1680,7 +1676,7 @@
 ///
 /// Returns null if the contents are good. Returns a string if they are bad.
 /// The string is an error message.
-Future<String> verifyVersion(File file) async {
+Future<String?> verifyVersion(File file) async {
   final RegExp pattern = RegExp(
     r'^(\d+)\.(\d+)\.(\d+)((-\d+\.\d+)?\.pre(\.\d+)?)?$');
   final String version = await file.readAsString();
@@ -1705,7 +1701,7 @@
 /// 3_3
 List<T> _selectIndexOfTotalSubshard<T>(List<T> tests, {String subshardKey = kSubshardKey}) {
   // Example: "1_3" means the first (one-indexed) shard of three total shards.
-  final String subshardName = Platform.environment[subshardKey];
+  final String? subshardName = Platform.environment[subshardKey];
   if (subshardName == null) {
     print('$kSubshardKey environment variable is missing, skipping sharding');
     return tests;
@@ -1713,14 +1709,14 @@
   print('$bold$subshardKey=$subshardName$reset');
 
   final RegExp pattern = RegExp(r'^(\d+)_(\d+)$');
-  final Match match = pattern.firstMatch(subshardName);
+  final Match? match = pattern.firstMatch(subshardName);
   if (match == null || match.groupCount != 2) {
     print('${red}Invalid subshard name "$subshardName". Expected format "[int]_[int]" ex. "1_3"');
     exit(1);
   }
   // One-indexed.
-  final int index = int.parse(match.group(1));
-  final int total = int.parse(match.group(2));
+  final int index = int.parse(match!.group(1)!);
+  final int total = int.parse(match.group(2)!);
   if (index > total) {
     print('${red}Invalid subshard name "$subshardName". Index number must be greater or equal to total.');
     exit(1);
@@ -1760,16 +1756,16 @@
 const String CIRRUS_TASK_NAME = 'CIRRUS_TASK_NAME';
 
 Future<void> _runFromList(Map<String, ShardRunner> items, String key, String name, int positionInTaskName) async {
-  String item = Platform.environment[key];
+  String? item = Platform.environment[key];
   if (item == null && Platform.environment.containsKey(CIRRUS_TASK_NAME)) {
-    final List<String> parts = Platform.environment[CIRRUS_TASK_NAME].split('-');
+    final List<String> parts = Platform.environment[CIRRUS_TASK_NAME]!.split('-');
     assert(positionInTaskName < parts.length);
     item = parts[positionInTaskName];
   }
   if (item == null) {
     for (final String currentItem in items.keys) {
       print('$bold$key=$currentItem$reset');
-      await items[currentItem]();
+      await items[currentItem]!();
       print('');
     }
   } else {
@@ -1779,6 +1775,6 @@
       exit(1);
     }
     print('$bold$key=$item$reset');
-    await items[item]();
+    await items[item]!();
   }
 }
diff --git a/dev/bots/test/prepare_package_test.dart b/dev/bots/test/prepare_package_test.dart
index 509d0f9..1db1282 100644
--- a/dev/bots/test/prepare_package_test.dart
+++ b/dev/bots/test/prepare_package_test.dart
@@ -74,16 +74,16 @@
       });
     });
     group('ArchiveCreator for $platformName', () {
-      ArchiveCreator creator;
-      Directory tempDir;
+      late ArchiveCreator creator;
+      late Directory tempDir;
       Directory flutterDir;
       Directory cacheDir;
-      FakeProcessManager processManager;
+      late FakeProcessManager processManager;
       final List<List<String>> args = <List<String>>[];
       final List<Map<Symbol, dynamic>> namedArgs = <Map<Symbol, dynamic>>[];
-      String flutter;
+      late String flutter;
 
-      Future<Uint8List> fakeHttpReader(Uri url, {Map<String, String> headers}) {
+      Future<Uint8List> fakeHttpReader(Uri url, {Map<String, String>? headers}) {
         return Future<Uint8List>.value(Uint8List(0));
       }
 
@@ -118,7 +118,7 @@
         final String archiveName = path.join(tempDir.absolute.path,
             'flutter_${platformName}_v1.2.3-dev${platform.isLinux ? '.tar.xz' : '.zip'}');
 
-        processManager.addCommands(convertResults(<String, List<ProcessResult>>{
+        processManager.addCommands(convertResults(<String, List<ProcessResult>?>{
           'git clone -b dev https://chromium.googlesource.com/external/github.com/flutter/flutter': null,
           'git reset --hard $testRef': null,
           'git remote set-url origin https://github.com/flutter/flutter.git': null,
@@ -147,7 +147,7 @@
         final String createBase = path.join(tempDir.absolute.path, 'create_');
         final String archiveName = path.join(tempDir.absolute.path,
             'flutter_${platformName}_v1.2.3-dev${platform.isLinux ? '.tar.xz' : '.zip'}');
-        final Map<String, List<ProcessResult>> calls = <String, List<ProcessResult>>{
+        final Map<String, List<ProcessResult>?> calls = <String, List<ProcessResult>?>{
           'git clone -b dev https://chromium.googlesource.com/external/github.com/flutter/flutter': null,
           'git reset --hard $testRef': null,
           'git remote set-url origin https://github.com/flutter/flutter.git': null,
@@ -197,7 +197,7 @@
         final String createBase = path.join(tempDir.absolute.path, 'create_');
         final String archiveName = path.join(tempDir.absolute.path,
             'flutter_${platformName}_v1.2.3-dev${platform.isLinux ? '.tar.xz' : '.zip'}');
-        final Map<String, List<ProcessResult>> calls = <String, List<ProcessResult>>{
+        final Map<String, List<ProcessResult>?> calls = <String, List<ProcessResult>?>{
           'git clone -b dev https://chromium.googlesource.com/external/github.com/flutter/flutter': null,
           'git reset --hard $testRef': null,
           'git remote set-url origin https://github.com/flutter/flutter.git': null,
@@ -239,7 +239,7 @@
             'flutter_${platformName}_v1.2.3-dev${platform.isLinux ? '.tar.xz' : '.zip'}');
         final ProcessResult codesignFailure = ProcessResult(1, 1, '', 'code object is not signed at all');
         final String binPath = path.join(tempDir.path, 'flutter', 'bin', 'cache', 'dart-sdk', 'bin', 'dart');
-        final Map<String, List<ProcessResult>> calls = <String, List<ProcessResult>>{
+        final Map<String, List<ProcessResult>?> calls = <String, List<ProcessResult>?>{
           'git clone -b dev https://chromium.googlesource.com/external/github.com/flutter/flutter': null,
           'git reset --hard $testRef': null,
           'git remote set-url origin https://github.com/flutter/flutter.git': null,
@@ -286,8 +286,8 @@
     });
 
     group('ArchivePublisher for $platformName', () {
-      FakeProcessManager processManager;
-      Directory tempDir;
+      late FakeProcessManager processManager;
+      late Directory tempDir;
       final String gsutilCall = platform.isWindows
           ? 'python ${path.join("D:", "depot_tools", "gsutil.py")}'
           : 'gsutil.py';
@@ -346,7 +346,7 @@
 ''';
         File(jsonPath).writeAsStringSync(releasesJson);
         File(archivePath).writeAsStringSync('archive contents');
-        final Map<String, List<ProcessResult>> calls = <String, List<ProcessResult>>{
+        final Map<String, List<ProcessResult>?> calls = <String, List<ProcessResult>?>{
           // This process fails because the file does NOT already exist
           '$gsutilCall -- stat $gsArchivePath': <ProcessResult>[ProcessResult(0, 1, '', '')],
           '$gsutilCall -- rm $gsArchivePath': null,
@@ -442,7 +442,7 @@
 ''';
         File(jsonPath).writeAsStringSync(releasesJson);
         File(archivePath).writeAsStringSync('archive contents');
-        final Map<String, List<ProcessResult>> calls = <String, List<ProcessResult>>{
+        final Map<String, List<ProcessResult>?> calls = <String, List<ProcessResult>?>{
           // This process fails because the file does NOT already exist
           '$gsutilCall -- stat $gsArchivePath': <ProcessResult>[ProcessResult(0, 1, '', '')],
           '$gsutilCall -- rm $gsArchivePath': null,
@@ -552,7 +552,7 @@
 ''';
         File(jsonPath).writeAsStringSync(releasesJson);
         File(archivePath).writeAsStringSync('archive contents');
-        final Map<String, List<ProcessResult>> calls = <String, List<ProcessResult>>{
+        final Map<String, List<ProcessResult>?> calls = <String, List<ProcessResult>?>{
           '$gsutilCall -- rm $gsArchivePath': null,
           '$gsutilCall -- -h Content-Type:$archiveMime cp $archivePath $gsArchivePath': null,
           '$gsutilCall -- cp $gsJsonPath $jsonPath': null,
@@ -567,10 +567,10 @@
   }
 }
 
-List<FakeCommand> convertResults(Map<String, List<ProcessResult>> results) {
+List<FakeCommand> convertResults(Map<String, List<ProcessResult>?> results) {
   final List<FakeCommand> commands = <FakeCommand>[];
   for (final String key in results.keys) {
-    final List<ProcessResult> candidates = results[key];
+    final List<ProcessResult>? candidates = results[key];
     final List<String> args = key.split(' ');
     if (candidates == null) {
       commands.add(FakeCommand(
@@ -581,8 +581,8 @@
         commands.add(FakeCommand(
           command: args,
           exitCode: result.exitCode,
-          stderr: result.stderr?.toString(),
-          stdout: result.stdout?.toString(),
+          stderr: result.stderr.toString(),
+          stdout: result.stdout.toString(),
         ));
       }
     }
diff --git a/dev/bots/test/sdk_directory_has_space_test.dart b/dev/bots/test/sdk_directory_has_space_test.dart
index 8a4704f..f5578f3 100644
--- a/dev/bots/test/sdk_directory_has_space_test.dart
+++ b/dev/bots/test/sdk_directory_has_space_test.dart
@@ -12,10 +12,10 @@
   test('We are in a directory with a space in it', () async {
     // The Flutter SDK should be in a directory with a space in it, to make sure
     // our tools support that.
-    final String expectedName = Platform.environment['FLUTTER_SDK_PATH_WITH_SPACE'];
+    final String? expectedName = Platform.environment['FLUTTER_SDK_PATH_WITH_SPACE'];
     expect(expectedName, 'flutter sdk');
     expect(expectedName, contains(' '));
     final List<String> parts = path.split(Directory.current.absolute.path);
-    expect(parts.reversed.take(3), <String>['bots', 'dev', expectedName]);
+    expect(parts.reversed.take(3), <String?>['bots', 'dev', expectedName]);
   }, skip: true); // https://github.com/flutter/flutter/issues/62919
 }
diff --git a/dev/bots/test/test_test.dart b/dev/bots/test/test_test.dart
index 83565ca..d1030f9 100644
--- a/dev/bots/test/test_test.dart
+++ b/dev/bots/test/test_test.dart
@@ -23,7 +23,7 @@
 
 void main() {
   group('verifyVersion()', () {
-    MemoryFileSystem fileSystem;
+    late MemoryFileSystem fileSystem;
 
     setUp(() {
       fileSystem = MemoryFileSystem.test();
@@ -97,7 +97,7 @@
     const ProcessManager processManager = LocalProcessManager();
 
     Future<ProcessResult> runScript(
-        [Map<String, String> environment, List<String> otherArgs = const <String>[]]) async {
+        [Map<String, String>? environment, List<String> otherArgs = const <String>[]]) async {
       final String dart = path.absolute(
           path.join('..', '..', 'bin', 'cache', 'dart-sdk', 'bin', 'dart'));
       final ProcessResult scriptProcess = processManager.runSync(<String>[
diff --git a/dev/bots/unpublish_package.dart b/dev/bots/unpublish_package.dart
index dfe9d6e..3e8e2bb 100644
--- a/dev/bots/unpublish_package.dart
+++ b/dev/bots/unpublish_package.dart
@@ -32,7 +32,7 @@
   UnpublishException(this.message, [this.result]);
 
   final String message;
-  final ProcessResult result;
+  final ProcessResult? result;
   int get exitCode => result?.exitCode ?? -1;
 
   @override
@@ -41,7 +41,7 @@
     if (message != null) {
       output += ': $message';
     }
-    final String stderr = result?.stderr as String ?? '';
+    final String stderr = result?.stderr as String? ?? '';
     if (stderr.isNotEmpty) {
       output += ':\n$stderr';
     }
@@ -60,10 +60,9 @@
     case Channel.stable:
       return 'stable';
   }
-  return null;
 }
 
-Channel fromChannelName(String name) {
+Channel fromChannelName(String? name) {
   switch (name) {
     case 'beta':
       return Channel.beta;
@@ -87,7 +86,6 @@
     case PublishedPlatform.windows:
       return 'windows';
   }
-  return null;
 }
 
 PublishedPlatform fromPublishedPlatform(String name) {
@@ -135,10 +133,10 @@
 
   /// Sets the default directory used when `workingDirectory` is not specified
   /// to [runProcess].
-  final Directory defaultWorkingDirectory;
+  final Directory? defaultWorkingDirectory;
 
   /// The environment to run processes with.
-  Map<String, String> environment;
+  late Map<String, String> environment;
 
   /// Run the command and arguments in `commandLine` as a sub-process from
   /// `workingDirectory` if set, or the [defaultWorkingDirectory] if not. Uses
@@ -148,7 +146,7 @@
   /// command completes with a non-zero exit code.
   Future<String> runProcess(
     List<String> commandLine, {
-    Directory workingDirectory,
+    Directory? workingDirectory,
     bool failOk = false,
   }) async {
     workingDirectory ??= defaultWorkingDirectory ?? Directory.current;
@@ -158,7 +156,7 @@
     final List<int> output = <int>[];
     final Completer<void> stdoutComplete = Completer<void>();
     final Completer<void> stderrComplete = Completer<void>();
-    Process process;
+    late Process process;
     Future<int> allComplete() async {
       await stderrComplete.future;
       await stdoutComplete.future;
@@ -221,12 +219,12 @@
     this.channels,
     this.platform, {
     this.confirmed = false,
-    ProcessManager processManager,
+    ProcessManager? processManager,
     bool subprocessOutput = true,
   })  : assert(revisionsBeingRemoved.length == 40),
         metadataGsPath = '$gsReleaseFolder/${getMetadataFilename(platform)}',
         _processRunner = ProcessRunner(
-          processManager: processManager,
+          processManager: processManager ?? const LocalProcessManager(),
           subprocessOutput: subprocessOutput,
         );
 
@@ -249,8 +247,8 @@
     final Map<Channel, Map<String, String>> paths = await _getArchivePaths(releases);
     releases.removeWhere((Map<String, String> value) => revisionsBeingRemoved.contains(value['hash']) && channels.contains(fromChannelName(value['channel'])));
     releases.sort((Map<String, String> a, Map<String, String> b) {
-      final DateTime aDate = DateTime.parse(a['release_date']);
-      final DateTime bDate = DateTime.parse(b['release_date']);
+      final DateTime aDate = DateTime.parse(a['release_date']!);
+      final DateTime bDate = DateTime.parse(b['release_date']!);
       return bDate.compareTo(aDate);
     });
     jsonData['releases'] = releases;
@@ -277,12 +275,12 @@
     final Set<String> hashes = <String>{};
     final Map<Channel, Map<String, String>> paths = <Channel, Map<String, String>>{};
     for (final Map<String, String> revision in releases) {
-      final String hash = revision['hash'];
+      final String hash = revision['hash']!;
       final Channel channel = fromChannelName(revision['channel']);
       hashes.add(hash);
       if (revisionsBeingRemoved.contains(hash) && channels.contains(channel)) {
         paths[channel] ??= <String, String>{};
-        paths[channel][hash] = revision['archive'];
+        paths[channel]![hash] = revision['archive']!;
       }
     }
     final Set<String> missingRevisions = revisionsBeingRemoved.difference(hashes.intersection(revisionsBeingRemoved));
@@ -330,7 +328,7 @@
 
   Future<String> _runGsUtil(
     List<String> args, {
-    Directory workingDirectory,
+    Directory? workingDirectory,
     bool failOk = false,
     bool confirm = false,
   }) async {
@@ -351,7 +349,7 @@
     final List<String> files = <String>[];
     print('${confirmed ? 'Removing' : 'Would remove'} the following release archives:');
     for (final Channel channel in paths.keys) {
-      final Map<String, String> hashes = paths[channel];
+      final Map<String, String> hashes = paths[channel]!;
       for (final String hash in hashes.keys) {
         final String file = '$gsReleaseFolder/${hashes[hash]}';
         files.add(file);
@@ -367,7 +365,7 @@
     // We often don't have permission to overwrite, but
     // we have permission to remove, so that's what we do first.
     await _runGsUtil(<String>['rm', dest], failOk: true, confirm: confirmed);
-    String mimeType;
+    String? mimeType;
     if (dest.endsWith('.tar.xz')) {
       mimeType = 'application/x-gtar';
     }
@@ -497,8 +495,8 @@
   final List<String> platformOptions = platformArg.isNotEmpty ? platformArg : allowedPlatformNames;
   final List<PublishedPlatform> platforms = platformOptions.map<PublishedPlatform>((String value) => fromPublishedPlatform(value)).toList();
   int exitCode = 0;
-  String message;
-  String stack;
+  late String message;
+  late String stack;
   try {
     for (final PublishedPlatform platform in platforms) {
       final ArchiveUnpublisher publisher = ArchiveUnpublisher(