Convert to null safety
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3cfab09..f9fec32 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 # Change Log for `process_runner`
 
+## 4.0.0-nullsafety
+
+* Convert to non-nullable by default, enable null-safety experiment for Dart.
+
 ## 3.1.1
 
 * Reverted part of the migrated null safety changes, as defaulting to the
diff --git a/analysis_options.yaml b/analysis_options.yaml
index db2cc78..452eeb3 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -16,6 +16,8 @@
 # include: package:pedantic/analysis_options.yaml
 
 analyzer:
+  enable-experiment:
+    - non-nullable
   strong-mode:
     implicit-casts: false
     implicit-dynamic: false
diff --git a/ci/analyze.sh b/ci/analyze.sh
index 283098a..bdb49f6 100755
--- a/ci/analyze.sh
+++ b/ci/analyze.sh
@@ -17,7 +17,7 @@
 function analyze() {
   # Make sure we pass the analyzer
   echo "Checking dartanalyzer..."
-  fails_analyzer="$(find lib test ci -name "*.dart" | xargs dartanalyzer --options analysis_options.yaml)"
+  fails_analyzer="$(find lib test ci -name "*.dart" | xargs dartanalyzer --enable-experiment=non-nullable --options analysis_options.yaml)"
   if [[ "$fails_analyzer" == *"[error]"* ]]; then
     echo "FAILED"
     echo "$fails_analyzer"
diff --git a/ci/test.sh b/ci/test.sh
index 6939bc8..35d25f3 100755
--- a/ci/test.sh
+++ b/ci/test.sh
@@ -17,4 +17,4 @@
 cd "$REPO_DIR"
 
 # Run the tests.
-pub run test
+pub run --enable-experiment=non-nullable test
diff --git a/example/main.dart b/example/main.dart
index 53f53bc..f2e3e52 100644
--- a/example/main.dart
+++ b/example/main.dart
@@ -9,6 +9,8 @@
 // of single-threaded CPU-intensive commands by a multple of the number of
 // processor cores you have (modulo being disk/network bound, of course).
 
+// @dart = 2.10
+
 import 'dart:io';
 
 import 'package:process_runner/process_runner.dart';
@@ -89,7 +91,7 @@
 ''';
 }
 
-String findOption(String option, List<String> args) {
+String? findOption(String option, List<String> args) {
   for (int i = 0; i < args.length - 1; ++i) {
     if (args[i] == option) {
       return args[i + 1];
@@ -117,7 +119,7 @@
   final bool printReport = args.contains('--report');
   // If the numWorkers is set to null, then the ProcessPool will automatically
   // select the number of processes based on how many CPU cores the machine has.
-  final int numWorkers = int.tryParse(findOption('workers', args) ?? '');
+  final int? numWorkers = int.tryParse(findOption('workers', args) ?? '');
   final Directory workingDirectory = Directory(findOption('workingDirectory', args) ?? '.');
   final List<String> cmds = findAllOptions('cmd', args).toList();
 
@@ -126,7 +128,7 @@
   List<String> fileCommands = <String>[];
   // Read from stdin if the --file option is set to '-'.
   if (commandFile == '-') {
-    String line = stdin.readLineSync();
+    String? line = stdin.readLineSync();
     while (line != null) {
       fileCommands.add(line);
       line = stdin.readLineSync();
@@ -165,6 +167,6 @@
     if (printReport) {
       print('\nFinished job ${done.name}');
     }
-    stdout.write(done.result.stdout);
+    stdout.write(done.result!.stdout);
   }
 }
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
index fcb215a..9b96455 100644
--- a/example/pubspec.yaml
+++ b/example/pubspec.yaml
@@ -3,14 +3,13 @@
 # found in the LICENSE file.
 
 name: process_runner_example
-version: 1.0.0
+version: 2.0.0-nullsafety
 description: A an example for process_runner.
 homepage: https://github.com/google/process_runner/example
 
 dependencies:
-  args: ^1.6.0
   process_runner:
     path: ..
 
 environment:
-  sdk: '>=2.3.0 <3.0.0'
+  sdk: '>=2.10.0-4.0.dev <2.10.0'
\ No newline at end of file
diff --git a/lib/src/process_pool.dart b/lib/src/process_pool.dart
index c8e0ca2..55c98b8 100644
--- a/lib/src/process_pool.dart
+++ b/lib/src/process_pool.dart
@@ -17,7 +17,7 @@
 class WorkerJob {
   WorkerJob(
     this.command, {
-    String name,
+    String? name,
     this.workingDirectory,
     this.printOutput = false,
     this.stdin,
@@ -37,7 +37,7 @@
   final List<String> command;
 
   /// The working directory that the command should be executed in.
-  final Directory workingDirectory;
+  final Directory? workingDirectory;
 
   /// If set, the stream to read the stdin for this process from.
   ///
@@ -45,14 +45,14 @@
   /// the process.
   ///
   /// If both [stdin] and [stdinRaw] are set, only [stdinRaw] will be used.
-  final Stream<String> stdin;
+  final Stream<String>? stdin;
 
   /// If set, the stream to read the raw stdin for this process from.
   ///
   /// It will be used directly, and not encoded (as [stdin] would be).
   ///
   /// If both [stdin] and [stdinRaw] are set, only [stdinRaw] will be used.
-  final Stream<List<int>> stdinRaw;
+  final Stream<List<int>>? stdinRaw;
 
   /// Whether or not this command should print it's stdout when it runs.
   final bool printOutput;
@@ -71,7 +71,7 @@
   ///
   /// If no process runner is supplied to the pool, then the decoder will be the
   /// same as the [ProcessPool.encoding] that was set on the pool.
-  ProcessRunnerResult result;
+  ProcessRunnerResult? result;
 
   /// Once the job is complete, if it had an exception while running, this
   /// member contains the exception.
@@ -96,8 +96,8 @@
 /// (presumably single-threaded) processes are finished.
 class ProcessPool {
   ProcessPool({
-    int numWorkers,
-    ProcessRunner processRunner,
+    int? numWorkers,
+    ProcessRunner? processRunner,
     this.printReport = defaultPrintReport,
     this.encoding = const SystemEncoding(),
   })  : processRunner = processRunner ?? ProcessRunner(decoder: encoding),
@@ -109,7 +109,7 @@
   ///
   /// Defaults to [defaultProgressReport], which prints the progress report to
   /// stdout.
-  final ProcessPoolProgressReporter printReport;
+  final ProcessPoolProgressReporter? printReport;
 
   /// The decoder to use for decoding the stdout, stderr, and output of a
   /// process, and encoding the stdin from the job.
diff --git a/lib/src/process_runner.dart b/lib/src/process_runner.dart
index 081dd52..7ace328 100644
--- a/lib/src/process_runner.dart
+++ b/lib/src/process_runner.dart
@@ -19,7 +19,7 @@
   ProcessRunnerException(this.message, {this.result});
 
   final String message;
-  final ProcessRunnerResult result;
+  final ProcessRunnerResult? result;
 
   int get exitCode => result?.exitCode ?? -1;
 
@@ -80,19 +80,19 @@
   /// [decoder].
   String get stdout {
     _stdout ??= decoder.decode(stdoutRaw);
-    return _stdout;
+    return _stdout!;
   }
 
-  String _stdout;
+  String? _stdout;
 
   /// Returns a lazily-decoded version of the data in [stderrRaw], decoded using
   /// [decoder].
   String get stderr {
     _stderr ??= decoder.decode(stderrRaw);
-    return _stderr;
+    return _stderr!;
   }
 
-  String _stderr;
+  String? _stderr;
 
   /// Returns a lazily-decoded version of the data in [outputRaw], decoded using
   /// [decoder].
@@ -100,10 +100,10 @@
   /// Information appears in the order supplied by the process.
   String get output {
     _output ??= decoder.decode(outputRaw);
-    return _output;
+    return _output!;
   }
 
-  String _output;
+  String? _output;
 }
 
 /// A helper class for classes that want to run a process, optionally have the
@@ -111,9 +111,9 @@
 /// the stdout, stderr, and interleaved output properly without dropping any.
 class ProcessRunner {
   ProcessRunner({
-    Directory defaultWorkingDirectory,
+    Directory? defaultWorkingDirectory,
     this.processManager = const LocalProcessManager(),
-    Map<String, String> environment,
+    Map<String, String>? environment,
     this.includeParentEnvironment = true,
     this.printOutputDefault = false,
     this.decoder = const SystemEncoding(),
@@ -176,10 +176,10 @@
   /// The `printOutput` argument defaults to the value of [printOutputDefault].
   Future<ProcessRunnerResult> runProcess(
     List<String> commandLine, {
-    Directory workingDirectory,
-    bool printOutput,
+    Directory? workingDirectory,
+    bool? printOutput,
     bool failOk = false,
-    Stream<List<int>> stdin,
+    Stream<List<int>>? stdin,
   }) async {
     workingDirectory ??= defaultWorkingDirectory;
     printOutput ??= printOutputDefault;
@@ -193,15 +193,15 @@
     final Completer<void> stderrComplete = Completer<void>();
     final Completer<void> stdinComplete = Completer<void>();
 
-    Process process;
+    late Process process;
     Future<int> allComplete() async {
       if (stdin != null) {
         await stdinComplete.future;
-        await process?.stdin?.close();
+        await process.stdin.close();
       }
       await stderrComplete.future;
       await stdoutComplete.future;
-      return process?.exitCode ?? Future<int>.value(0);
+      return process.exitCode;
     }
 
     try {
@@ -213,14 +213,14 @@
       );
       if (stdin != null) {
         stdin.listen((List<int> data) {
-          process?.stdin?.add(data);
+          process.stdin.add(data);
         }, onDone: () async => stdinComplete.complete());
       }
       process.stdout.listen(
         (List<int> event) {
           stdoutOutput.addAll(event);
           combinedOutput.addAll(event);
-          if (printOutput) {
+          if (printOutput!) {
             stdout.add(event);
           }
         },
@@ -230,7 +230,7 @@
         (List<int> event) {
           stderrOutput.addAll(event);
           combinedOutput.addAll(event);
-          if (printOutput) {
+          if (printOutput!) {
             stderr.add(event);
           }
         },
diff --git a/pubspec.yaml b/pubspec.yaml
index 46e0061..8c52f7a 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -3,21 +3,20 @@
 # found in the LICENSE file.
 
 name: process_runner
-version: 3.1.1
+version: 4.0.0-nullsafety.2
 description: A process invocation astraction for Dart that manages a multiprocess queue.
 homepage: https://github.com/google/process_runner
 
 dependencies:
-  async: ^2.4.2
-  file: ^5.0.0
-  meta: ^1.1.2
-  path: ^1.5.1
-  platform: ^2.2.0
-  process: ^3.0.13
+  file: ^6.0.0-nullsafety.1
+  meta: ^1.3.0-nullsafety.2
+  path: ^1.8.0-nullsafety
+  platform: ^3.0.0-nullsafety.1
+  process: ^4.0.0-nullsafety.1
+  async: ^2.5.0-nullsafety
 
 dev_dependencies:
-  mockito: ^4.1.1
-  test: ^1.0.0
+  test: ^1.16.0-nullsafety.1
 
 environment:
-  sdk: '>=2.3.0 <3.0.0'
+  sdk: '>=2.10.0-4.0.dev <2.10.0'
diff --git a/test/src/fake_process_manager.dart b/test/src/fake_process_manager.dart
index ac36a80..8daffef 100644
--- a/test/src/fake_process_manager.dart
+++ b/test/src/fake_process_manager.dart
@@ -66,8 +66,8 @@
 
   ProcessResult _popResult(FakeInvocationRecord command) {
     expect(fakeResults, isNotEmpty);
-    List<ProcessResult> foundResult;
-    FakeInvocationRecord foundCommand;
+    late List<ProcessResult> foundResult;
+    late FakeInvocationRecord foundCommand;
     for (final FakeInvocationRecord fakeCommand in fakeResults.keys) {
       if (fakeCommand.invocation.length != command.invocation.length) {
         continue;
@@ -80,7 +80,7 @@
         }
       }
       if (listsIdentical) {
-        foundResult = fakeResults[fakeCommand];
+        foundResult = fakeResults[fakeCommand]!;
         foundCommand = fakeCommand;
         break;
       }
@@ -112,7 +112,7 @@
   }
 
   @override
-  bool canRun(dynamic executable, {String workingDirectory}) {
+  bool canRun(dynamic executable, {String? workingDirectory}) {
     return true;
   }
 
@@ -122,15 +122,14 @@
   }
 
   @override
-  Future<ProcessResult> run(
-    List<dynamic> command, {
-    String workingDirectory,
-    Map<String, String> environment,
-    bool includeParentEnvironment = true,
-    bool runInShell = false,
-    Encoding stdoutEncoding = systemEncoding,
-    Encoding stderrEncoding = systemEncoding,
-  }) {
+  Future<ProcessResult> run(List<dynamic> command,
+      {String? workingDirectory,
+      Map<String, String>? environment,
+      bool includeParentEnvironment = true,
+      bool runInShell = false,
+      Encoding stdoutEncoding = systemEncoding,
+      Encoding stderrEncoding = systemEncoding,
+    }) {
     if (commandsThrow) {
       throw const ProcessException('failed_executable', <String>[]);
     }
@@ -138,15 +137,13 @@
   }
 
   @override
-  ProcessResult runSync(
-    List<dynamic> command, {
-    String workingDirectory,
-    Map<String, String> environment,
-    bool includeParentEnvironment = true,
-    bool runInShell = false,
-    Encoding stdoutEncoding = systemEncoding,
-    Encoding stderrEncoding = systemEncoding,
-  }) {
+  ProcessResult runSync(List<dynamic> command,
+      {String? workingDirectory,
+      Map<String, String>? environment,
+      bool includeParentEnvironment = true,
+      bool runInShell = false,
+      Encoding stdoutEncoding = systemEncoding,
+      Encoding stderrEncoding = systemEncoding}) {
     if (commandsThrow) {
       throw const ProcessException('failed_executable', <String>[]);
     }
@@ -154,14 +151,12 @@
   }
 
   @override
-  Future<Process> start(
-    List<dynamic> command, {
-    String workingDirectory,
-    Map<String, String> environment,
-    bool includeParentEnvironment = true,
-    bool runInShell = false,
-    ProcessStartMode mode = ProcessStartMode.normal,
-  }) {
+  Future<Process> start(List<dynamic> command,
+      {String? workingDirectory,
+      Map<String, String>? environment,
+      bool includeParentEnvironment = true,
+      bool runInShell = false,
+      ProcessStartMode mode = ProcessStartMode.normal}) {
     if (commandsThrow) {
       throw const ProcessException('failed_executable', <String>[]);
     }
diff --git a/test/src/process_pool_test.dart b/test/src/process_pool_test.dart
index c73b3a8..9cbbdb9 100644
--- a/test/src/process_pool_test.dart
+++ b/test/src/process_pool_test.dart
@@ -66,10 +66,10 @@
         WorkerJob(<String>['command', 'arg1', 'arg2'], name: 'job 1'),
       ];
       final List<WorkerJob> completed = await processPool.runToCompletion(jobs);
-      expect(completed.first.result.exitCode, equals(-1));
-      expect(completed.first.result.stdout, equals('output1'));
-      expect(completed.first.result.stderr, equals('stderr1'));
-      expect(completed.first.result.output, equals('output1stderr1'));
+      expect(completed.first.result?.exitCode, equals(-1));
+      expect(completed.first.result?.stdout, equals('output1'));
+      expect(completed.first.result?.stderr, equals('stderr1'));
+      expect(completed.first.result?.output, equals('output1stderr1'));
     });
     test('Commands the throw exceptions report results', () async {
       fakeProcessManager = FakeProcessManager((String value) {}, commandsThrow: true);