[shared_preferences] Convert macOS to Pigeon (#6914)

* Add Pigeon and update Dart, based on iOS implementation

* Update Swift implementation

* Update Swift tests

* Adjust Dart test and add Pigeon TODOs

* Version bump

* Format

* Update to Pigeon 5.0 for Swift warning fix
diff --git a/packages/shared_preferences/shared_preferences_macos/CHANGELOG.md b/packages/shared_preferences/shared_preferences_macos/CHANGELOG.md
index fc8a78a..f0a8526 100644
--- a/packages/shared_preferences/shared_preferences_macos/CHANGELOG.md
+++ b/packages/shared_preferences/shared_preferences_macos/CHANGELOG.md
@@ -1,5 +1,6 @@
-## NEXT
+## 2.0.5
 
+* Converts platform channel to Pigeon.
 * Updates code for `no_leading_underscores_for_local_identifiers` lint.
 * Updates minimum Flutter version to 2.10.
 
diff --git a/packages/shared_preferences/shared_preferences_macos/example/macos/RunnerTests/RunnerTests.swift b/packages/shared_preferences/shared_preferences_macos/example/macos/RunnerTests/RunnerTests.swift
index 7da66cb..9a1b2f0 100644
--- a/packages/shared_preferences/shared_preferences_macos/example/macos/RunnerTests/RunnerTests.swift
+++ b/packages/shared_preferences/shared_preferences_macos/example/macos/RunnerTests/RunnerTests.swift
@@ -4,85 +4,55 @@
 
 import FlutterMacOS
 import XCTest
-import shared_preferences_macos
+@testable import shared_preferences_macos
 
 class RunnerTests: XCTestCase {
-  func testHandlesCommitNoOp() throws {
-    let plugin = SharedPreferencesPlugin()
-    let call = FlutterMethodCall(methodName: "commit", arguments: nil)
-    var called = false
-    plugin.handle(
-      call,
-      result: { (result: Any?) -> Void in
-        called = true
-        XCTAssert(result as? Bool == true)
-      })
-    XCTAssert(called)
-  }
-
   func testSetAndGet() throws {
     let plugin = SharedPreferencesPlugin()
-    let setCall = FlutterMethodCall(
-      methodName: "setInt",
-      arguments: [
-        "key": "flutter.foo",
-        "value": 42,
-      ])
-    plugin.handle(
-      setCall,
-      result: { (result: Any?) -> Void in
-        XCTAssert(result as? Bool == true)
-      })
 
-    var value: Int?
-    plugin.handle(
-      FlutterMethodCall(methodName: "getAll", arguments: nil),
-      result: { (result: Any?) -> Void in
-        if let prefs = result as? [String: Any] {
-          value = prefs["flutter.foo"] as? Int
-        }
-      })
-    XCTAssertEqual(value, 42)
+    plugin.setBool(key: "flutter.aBool", value: true)
+    plugin.setDouble(key: "flutter.aDouble", value: 3.14)
+    plugin.setValue(key: "flutter.anInt", value: 42)
+    plugin.setValue(key: "flutter.aString", value: "hello world")
+    plugin.setValue(key: "flutter.aStringList", value: ["hello", "world"])
+
+    let storedValues = plugin.getAll()
+    XCTAssertEqual(storedValues["flutter.aBool"] as? Bool, true)
+    XCTAssertEqual(storedValues["flutter.aDouble"] as! Double, 3.14, accuracy: 0.0001)
+    XCTAssertEqual(storedValues["flutter.anInt"] as? Int, 42)
+    XCTAssertEqual(storedValues["flutter.aString"] as? String, "hello world")
+    XCTAssertEqual(storedValues["flutter.aStringList"] as? Array<String>, ["hello", "world"])
+  }
+
+  func testRemove() throws {
+    let plugin = SharedPreferencesPlugin()
+    let testKey = "flutter.foo"
+    plugin.setValue(key: testKey, value: 42)
+
+    // Make sure there is something to remove, so the test can't pass due to a set failure.
+    let preRemovalValues = plugin.getAll()
+    XCTAssertEqual(preRemovalValues[testKey] as? Int, 42)
+
+    // Then verify that removing it works.
+    plugin.remove(key: testKey)
+
+    let finalValues = plugin.getAll()
+    XCTAssertNil(finalValues[testKey] as Any?)
   }
 
   func testClear() throws {
     let plugin = SharedPreferencesPlugin()
-    let setCall = FlutterMethodCall(
-      methodName: "setInt",
-      arguments: [
-        "key": "flutter.foo",
-        "value": 42,
-      ])
-    plugin.handle(setCall, result: { (result: Any?) -> Void in })
+    let testKey = "flutter.foo"
+    plugin.setValue(key: testKey, value: 42)
 
     // Make sure there is something to clear, so the test can't pass due to a set failure.
-    let getCall = FlutterMethodCall(methodName: "getAll", arguments: nil)
-    var value: Int?
-    plugin.handle(
-      getCall,
-      result: { (result: Any?) -> Void in
-        if let prefs = result as? [String: Any] {
-          value = prefs["flutter.foo"] as? Int
-        }
-      })
-    XCTAssertEqual(value, 42)
+    let preRemovalValues = plugin.getAll()
+    XCTAssertEqual(preRemovalValues[testKey] as? Int, 42)
 
-    // Clear the value.
-    plugin.handle(
-      FlutterMethodCall(methodName: "clear", arguments: nil),
-      result: { (result: Any?) -> Void in
-        XCTAssert(result as? Bool == true)
-      })
+    // Then verify that clearing works.
+    plugin.clear()
 
-    // Get the value again, which should clear |value|.
-    plugin.handle(
-      getCall,
-      result: { (result: Any?) -> Void in
-        if let prefs = result as? [String: Any] {
-          value = prefs["flutter.foo"] as? Int
-          XCTAssert(prefs.isEmpty)
-        }
-      })
-    XCTAssertEqual(value, nil)
+    let finalValues = plugin.getAll()
+    XCTAssertNil(finalValues[testKey] as Any?)
   }
 }
diff --git a/packages/shared_preferences/shared_preferences_macos/lib/messages.g.dart b/packages/shared_preferences/shared_preferences_macos/lib/messages.g.dart
new file mode 100644
index 0000000..f7c6c21
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_macos/lib/messages.g.dart
@@ -0,0 +1,157 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+// Autogenerated from Pigeon (v5.0.0), do not edit directly.
+// See also: https://pub.dev/packages/pigeon
+// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import
+import 'dart:async';
+import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
+
+import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
+import 'package:flutter/services.dart';
+
+class UserDefaultsApi {
+  /// Constructor for [UserDefaultsApi].  The [binaryMessenger] named argument is
+  /// available for dependency injection.  If it is left null, the default
+  /// BinaryMessenger will be used which routes to the host platform.
+  UserDefaultsApi({BinaryMessenger? binaryMessenger})
+      : _binaryMessenger = binaryMessenger;
+  final BinaryMessenger? _binaryMessenger;
+
+  static const MessageCodec<Object?> codec = StandardMessageCodec();
+
+  Future<void> remove(String arg_key) async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.UserDefaultsApi.remove', codec,
+        binaryMessenger: _binaryMessenger);
+    final List<Object?>? replyList =
+        await channel.send(<Object?>[arg_key]) as List<Object?>?;
+    if (replyList == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+      );
+    } else if (replyList.length > 1) {
+      throw PlatformException(
+        code: replyList[0]! as String,
+        message: replyList[1] as String?,
+        details: replyList[2],
+      );
+    } else {
+      return;
+    }
+  }
+
+  Future<void> setBool(String arg_key, bool arg_value) async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.UserDefaultsApi.setBool', codec,
+        binaryMessenger: _binaryMessenger);
+    final List<Object?>? replyList =
+        await channel.send(<Object?>[arg_key, arg_value]) as List<Object?>?;
+    if (replyList == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+      );
+    } else if (replyList.length > 1) {
+      throw PlatformException(
+        code: replyList[0]! as String,
+        message: replyList[1] as String?,
+        details: replyList[2],
+      );
+    } else {
+      return;
+    }
+  }
+
+  Future<void> setDouble(String arg_key, double arg_value) async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.UserDefaultsApi.setDouble', codec,
+        binaryMessenger: _binaryMessenger);
+    final List<Object?>? replyList =
+        await channel.send(<Object?>[arg_key, arg_value]) as List<Object?>?;
+    if (replyList == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+      );
+    } else if (replyList.length > 1) {
+      throw PlatformException(
+        code: replyList[0]! as String,
+        message: replyList[1] as String?,
+        details: replyList[2],
+      );
+    } else {
+      return;
+    }
+  }
+
+  Future<void> setValue(String arg_key, Object arg_value) async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.UserDefaultsApi.setValue', codec,
+        binaryMessenger: _binaryMessenger);
+    final List<Object?>? replyList =
+        await channel.send(<Object?>[arg_key, arg_value]) as List<Object?>?;
+    if (replyList == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+      );
+    } else if (replyList.length > 1) {
+      throw PlatformException(
+        code: replyList[0]! as String,
+        message: replyList[1] as String?,
+        details: replyList[2],
+      );
+    } else {
+      return;
+    }
+  }
+
+  Future<Map<String?, Object?>> getAll() async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.UserDefaultsApi.getAll', codec,
+        binaryMessenger: _binaryMessenger);
+    final List<Object?>? replyList = await channel.send(null) as List<Object?>?;
+    if (replyList == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+      );
+    } else if (replyList.length > 1) {
+      throw PlatformException(
+        code: replyList[0]! as String,
+        message: replyList[1] as String?,
+        details: replyList[2],
+      );
+    } else if (replyList[0] == null) {
+      throw PlatformException(
+        code: 'null-error',
+        message: 'Host platform returned null value for non-null return value.',
+      );
+    } else {
+      return (replyList[0] as Map<Object?, Object?>?)!.cast<String?, Object?>();
+    }
+  }
+
+  Future<void> clear() async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.UserDefaultsApi.clear', codec,
+        binaryMessenger: _binaryMessenger);
+    final List<Object?>? replyList = await channel.send(null) as List<Object?>?;
+    if (replyList == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+      );
+    } else if (replyList.length > 1) {
+      throw PlatformException(
+        code: replyList[0]! as String,
+        message: replyList[1] as String?,
+        details: replyList[2],
+      );
+    } else {
+      return;
+    }
+  }
+}
diff --git a/packages/shared_preferences/shared_preferences_macos/lib/shared_preferences_macos.dart b/packages/shared_preferences/shared_preferences_macos/lib/shared_preferences_macos.dart
index a97fe13..25e8c5d 100644
--- a/packages/shared_preferences/shared_preferences_macos/lib/shared_preferences_macos.dart
+++ b/packages/shared_preferences/shared_preferences_macos/lib/shared_preferences_macos.dart
@@ -2,52 +2,66 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:async';
-
 import 'package:flutter/services.dart';
 import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart';
+import 'messages.g.dart';
 
-const MethodChannel _kChannel =
-    MethodChannel('plugins.flutter.io/shared_preferences_macos');
+typedef _Setter = Future<void> Function(String key, Object value);
 
-/// The macOS implementation of [SharedPreferencesStorePlatform].
-///
-/// This class implements the `package:shared_preferences` functionality for macOS.
+/// macOS implementation of shared_preferences.
 class SharedPreferencesMacOS extends SharedPreferencesStorePlatform {
-  /// Registers this class as the default instance of [SharedPreferencesStorePlatform].
+  final UserDefaultsApi _api = UserDefaultsApi();
+  late final Map<String, _Setter> _setters = <String, _Setter>{
+    'Bool': (String key, Object value) {
+      return _api.setBool(key, value as bool);
+    },
+    'Double': (String key, Object value) {
+      return _api.setDouble(key, value as double);
+    },
+    'Int': (String key, Object value) {
+      return _api.setValue(key, value as int);
+    },
+    'String': (String key, Object value) {
+      return _api.setValue(key, value as String);
+    },
+    'StringList': (String key, Object value) {
+      return _api.setValue(key, value as List<String?>);
+    },
+  };
+
+  /// Registers this class as the default instance of
+  /// [SharedPreferencesStorePlatform].
   static void registerWith() {
     SharedPreferencesStorePlatform.instance = SharedPreferencesMacOS();
   }
 
   @override
-  Future<bool> remove(String key) async {
-    return (await _kChannel.invokeMethod<bool>(
-      'remove',
-      <String, dynamic>{'key': key},
-    ))!;
-  }
-
-  @override
-  Future<bool> setValue(String valueType, String key, Object value) async {
-    return (await _kChannel.invokeMethod<bool>(
-      'set$valueType',
-      <String, dynamic>{'key': key, 'value': value},
-    ))!;
-  }
-
-  @override
   Future<bool> clear() async {
-    return (await _kChannel.invokeMethod<bool>('clear'))!;
+    await _api.clear();
+    return true;
   }
 
   @override
   Future<Map<String, Object>> getAll() async {
-    final Map<String, Object>? preferences =
-        await _kChannel.invokeMapMethod<String, Object>('getAll');
+    final Map<String?, Object?> result = await _api.getAll();
+    return result.cast<String, Object>();
+  }
 
-    if (preferences == null) {
-      return <String, Object>{};
+  @override
+  Future<bool> remove(String key) async {
+    await _api.remove(key);
+    return true;
+  }
+
+  @override
+  Future<bool> setValue(String valueType, String key, Object value) async {
+    final _Setter? setter = _setters[valueType];
+    if (setter == null) {
+      throw PlatformException(
+          code: 'InvalidOperation',
+          message: '"$valueType" is not a supported type.');
     }
-    return preferences;
+    await setter(key, value);
+    return true;
   }
 }
diff --git a/packages/shared_preferences/shared_preferences_macos/macos/Classes/SharedPreferencesPlugin.swift b/packages/shared_preferences/shared_preferences_macos/macos/Classes/SharedPreferencesPlugin.swift
index 91b4244..6e5e79d 100644
--- a/packages/shared_preferences/shared_preferences_macos/macos/Classes/SharedPreferencesPlugin.swift
+++ b/packages/shared_preferences/shared_preferences_macos/macos/Classes/SharedPreferencesPlugin.swift
@@ -5,44 +5,36 @@
 import FlutterMacOS
 import Foundation
 
-public class SharedPreferencesPlugin: NSObject, FlutterPlugin {
+public class SharedPreferencesPlugin: NSObject, FlutterPlugin, UserDefaultsApi {
   public static func register(with registrar: FlutterPluginRegistrar) {
-    let channel = FlutterMethodChannel(
-      name: "plugins.flutter.io/shared_preferences_macos",
-      binaryMessenger: registrar.messenger)
     let instance = SharedPreferencesPlugin()
-    registrar.addMethodCallDelegate(instance, channel: channel)
+    UserDefaultsApiSetup.setUp(binaryMessenger: registrar.messenger, api: instance)
   }
 
-  public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
-    switch call.method {
-    case "getAll":
-      result(getAllPrefs())
-    case "setBool",
-         "setInt",
-         "setDouble",
-         "setString",
-         "setStringList":
-      let arguments = call.arguments as! [String: Any]
-      let key = arguments["key"] as! String
-      UserDefaults.standard.set(arguments["value"], forKey: key)
-      result(true)
-    case "commit":
-      // UserDefaults does not need to be synchronized.
-      result(true)
-    case "remove":
-      let arguments = call.arguments as! [String: Any]
-      let key = arguments["key"] as! String
-      UserDefaults.standard.removeObject(forKey: key)
-      result(true)
-    case "clear":
-      let defaults = UserDefaults.standard
-      for (key, _) in getAllPrefs() {
-        defaults.removeObject(forKey: key)
-      }
-      result(true)
-    default:
-      result(FlutterMethodNotImplemented)
+  func getAll() -> [String? : Any?] {
+    return getAllPrefs();
+  }
+
+  func setBool(key: String, value: Bool) {
+    UserDefaults.standard.set(value, forKey: key)
+  }
+
+  func setDouble(key: String, value: Double) {
+    UserDefaults.standard.set(value, forKey: key)
+  }
+
+  func setValue(key: String, value: Any) {
+    UserDefaults.standard.set(value, forKey: key)
+  }
+
+  func remove(key: String) {
+    UserDefaults.standard.removeObject(forKey: key)
+  }
+
+  func clear() {
+    let defaults = UserDefaults.standard
+    for (key, _) in getAllPrefs() {
+      defaults.removeObject(forKey: key)
     }
   }
 }
diff --git a/packages/shared_preferences/shared_preferences_macos/macos/Classes/messages.g.swift b/packages/shared_preferences/shared_preferences_macos/macos/Classes/messages.g.swift
new file mode 100644
index 0000000..933217b
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_macos/macos/Classes/messages.g.swift
@@ -0,0 +1,111 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+// Autogenerated from Pigeon (v5.0.0), do not edit directly.
+// See also: https://pub.dev/packages/pigeon
+
+import Foundation
+#if os(iOS)
+import Flutter
+#elseif os(macOS)
+import FlutterMacOS
+#else
+#error("Unsupported platform.")
+#endif
+
+
+/// Generated class from Pigeon.
+/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
+protocol UserDefaultsApi {
+  func remove(key: String)
+  func setBool(key: String, value: Bool)
+  func setDouble(key: String, value: Double)
+  func setValue(key: String, value: Any)
+  func getAll() -> [String?: Any?]
+  func clear()
+}
+
+/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
+class UserDefaultsApiSetup {
+  /// The codec used by UserDefaultsApi.
+  /// Sets up an instance of `UserDefaultsApi` to handle messages through the `binaryMessenger`.
+  static func setUp(binaryMessenger: FlutterBinaryMessenger, api: UserDefaultsApi?) {
+    let removeChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.UserDefaultsApi.remove", binaryMessenger: binaryMessenger)
+    if let api = api {
+      removeChannel.setMessageHandler { message, reply in
+        let args = message as! [Any?]
+        let keyArg = args[0] as! String
+        api.remove(key: keyArg)
+        reply(wrapResult(nil))
+      }
+    } else {
+      removeChannel.setMessageHandler(nil)
+    }
+    let setBoolChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.UserDefaultsApi.setBool", binaryMessenger: binaryMessenger)
+    if let api = api {
+      setBoolChannel.setMessageHandler { message, reply in
+        let args = message as! [Any?]
+        let keyArg = args[0] as! String
+        let valueArg = args[1] as! Bool
+        api.setBool(key: keyArg, value: valueArg)
+        reply(wrapResult(nil))
+      }
+    } else {
+      setBoolChannel.setMessageHandler(nil)
+    }
+    let setDoubleChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.UserDefaultsApi.setDouble", binaryMessenger: binaryMessenger)
+    if let api = api {
+      setDoubleChannel.setMessageHandler { message, reply in
+        let args = message as! [Any?]
+        let keyArg = args[0] as! String
+        let valueArg = args[1] as! Double
+        api.setDouble(key: keyArg, value: valueArg)
+        reply(wrapResult(nil))
+      }
+    } else {
+      setDoubleChannel.setMessageHandler(nil)
+    }
+    let setValueChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.UserDefaultsApi.setValue", binaryMessenger: binaryMessenger)
+    if let api = api {
+      setValueChannel.setMessageHandler { message, reply in
+        let args = message as! [Any?]
+        let keyArg = args[0] as! String
+        let valueArg = args[1]!
+        api.setValue(key: keyArg, value: valueArg)
+        reply(wrapResult(nil))
+      }
+    } else {
+      setValueChannel.setMessageHandler(nil)
+    }
+    let getAllChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.UserDefaultsApi.getAll", binaryMessenger: binaryMessenger)
+    if let api = api {
+      getAllChannel.setMessageHandler { _, reply in
+        let result = api.getAll()
+        reply(wrapResult(result))
+      }
+    } else {
+      getAllChannel.setMessageHandler(nil)
+    }
+    let clearChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.UserDefaultsApi.clear", binaryMessenger: binaryMessenger)
+    if let api = api {
+      clearChannel.setMessageHandler { _, reply in
+        api.clear()
+        reply(wrapResult(nil))
+      }
+    } else {
+      clearChannel.setMessageHandler(nil)
+    }
+  }
+}
+
+private func wrapResult(_ result: Any?) -> [Any?] {
+  return [result]
+}
+
+private func wrapError(_ error: FlutterError) -> [Any?] {
+  return [
+    error.code,
+    error.message,
+    error.details
+  ]
+}
diff --git a/packages/shared_preferences/shared_preferences_macos/pigeons/copyright_header.txt b/packages/shared_preferences/shared_preferences_macos/pigeons/copyright_header.txt
new file mode 100644
index 0000000..fb682b1
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_macos/pigeons/copyright_header.txt
@@ -0,0 +1,3 @@
+Copyright 2013 The Flutter Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
\ No newline at end of file
diff --git a/packages/shared_preferences/shared_preferences_macos/pigeons/messages.dart b/packages/shared_preferences/shared_preferences_macos/pigeons/messages.dart
new file mode 100644
index 0000000..bda7185
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_macos/pigeons/messages.dart
@@ -0,0 +1,25 @@
+// Copyright 2013 The Flutter Authors. 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:pigeon/pigeon.dart';
+
+@ConfigurePigeon(PigeonOptions(
+  dartOut: 'lib/messages.g.dart',
+  dartTestOut: 'test/test_api.g.dart',
+  swiftOut: 'macos/Classes/messages.g.swift',
+  copyrightHeader: 'pigeons/copyright_header.txt',
+))
+@HostApi(dartHostTestHandler: 'TestUserDefaultsApi')
+abstract class UserDefaultsApi {
+  void remove(String key);
+  // TODO(stuartmorgan): Give these setters better Swift signatures (_,forKey:)
+  // once https://github.com/flutter/flutter/issues/105932 is fixed.
+  void setBool(String key, bool value);
+  void setDouble(String key, double value);
+  void setValue(String key, Object value);
+  // TODO(stuartmorgan): Make these non-nullable once
+  // https://github.com/flutter/flutter/issues/97848 is fixed.
+  Map<String?, Object?> getAll();
+  void clear();
+}
diff --git a/packages/shared_preferences/shared_preferences_macos/pubspec.yaml b/packages/shared_preferences/shared_preferences_macos/pubspec.yaml
index 77f5f11..2ddf998 100644
--- a/packages/shared_preferences/shared_preferences_macos/pubspec.yaml
+++ b/packages/shared_preferences/shared_preferences_macos/pubspec.yaml
@@ -2,7 +2,7 @@
 description: macOS implementation of the shared_preferences plugin.
 repository: https://github.com/flutter/plugins/tree/main/packages/shared_preferences/shared_preferences_macos
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22
-version: 2.0.4
+version: 2.0.5
 
 environment:
   sdk: ">=2.12.0 <3.0.0"
@@ -24,3 +24,4 @@
 dev_dependencies:
   flutter_test:
     sdk: flutter
+  pigeon: ^5.0.0
diff --git a/packages/shared_preferences/shared_preferences_macos/test/shared_preferences_macos_test.dart b/packages/shared_preferences/shared_preferences_macos/test/shared_preferences_macos_test.dart
index 8e71e40..f32ba4c 100644
--- a/packages/shared_preferences/shared_preferences_macos/test/shared_preferences_macos_test.dart
+++ b/packages/shared_preferences/shared_preferences_macos/test/shared_preferences_macos_test.dart
@@ -5,113 +5,111 @@
 import 'package:flutter/services.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:shared_preferences_macos/shared_preferences_macos.dart';
-import 'package:shared_preferences_platform_interface/method_channel_shared_preferences.dart';
 import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart';
 
+import 'test_api.g.dart';
+
+class _MockSharedPreferencesApi implements TestUserDefaultsApi {
+  final Map<String, Object> items = <String, Object>{};
+
+  @override
+  Map<String?, Object?> getAll() {
+    return items;
+  }
+
+  @override
+  void remove(String key) {
+    items.remove(key);
+  }
+
+  @override
+  void setBool(String key, bool value) {
+    items[key] = value;
+  }
+
+  @override
+  void setDouble(String key, double value) {
+    items[key] = value;
+  }
+
+  @override
+  void setValue(String key, Object value) {
+    items[key] = value;
+  }
+
+  @override
+  void clear() {
+    items.clear();
+  }
+}
+
 void main() {
   TestWidgetsFlutterBinding.ensureInitialized();
+  late _MockSharedPreferencesApi api;
 
-  group(MethodChannelSharedPreferencesStore, () {
-    const MethodChannel channel = MethodChannel(
-      'plugins.flutter.io/shared_preferences_macos',
-    );
+  setUp(() {
+    api = _MockSharedPreferencesApi();
+    TestUserDefaultsApi.setup(api);
+  });
 
-    const Map<String, Object> kTestValues = <String, Object>{
-      'flutter.String': 'hello world',
-      'flutter.Bool': true,
-      'flutter.Int': 42,
-      'flutter.Double': 3.14159,
-      'flutter.StringList': <String>['foo', 'bar'],
-    };
-    // Create a dummy in-memory implementation to back the mocked method channel
-    // API to simplify validation of the expected calls.
-    late InMemorySharedPreferencesStore testData;
+  test('registerWith', () {
+    SharedPreferencesMacOS.registerWith();
+    expect(
+        SharedPreferencesStorePlatform.instance, isA<SharedPreferencesMacOS>());
+  });
 
-    final List<MethodCall> log = <MethodCall>[];
-    late SharedPreferencesStorePlatform store;
+  test('remove', () async {
+    final SharedPreferencesMacOS plugin = SharedPreferencesMacOS();
+    api.items['flutter.hi'] = 'world';
+    expect(await plugin.remove('flutter.hi'), isTrue);
+    expect(api.items.containsKey('flutter.hi'), isFalse);
+  });
 
-    setUp(() async {
-      testData = InMemorySharedPreferencesStore.empty();
+  test('clear', () async {
+    final SharedPreferencesMacOS plugin = SharedPreferencesMacOS();
+    api.items['flutter.hi'] = 'world';
+    expect(await plugin.clear(), isTrue);
+    expect(api.items.containsKey('flutter.hi'), isFalse);
+  });
 
-      channel.setMockMethodCallHandler((MethodCall methodCall) async {
-        log.add(methodCall);
-        if (methodCall.method == 'getAll') {
-          return testData.getAll();
-        }
-        if (methodCall.method == 'remove') {
-          final String key = (methodCall.arguments['key'] as String?)!;
-          return testData.remove(key);
-        }
-        if (methodCall.method == 'clear') {
-          return testData.clear();
-        }
-        final RegExp setterRegExp = RegExp(r'set(.*)');
-        final Match? match = setterRegExp.matchAsPrefix(methodCall.method);
-        if (match?.groupCount == 1) {
-          final String valueType = match!.group(1)!;
-          final String key = (methodCall.arguments['key'] as String?)!;
-          final Object value = (methodCall.arguments['value'] as Object?)!;
-          return testData.setValue(valueType, key, value);
-        }
-        fail('Unexpected method call: ${methodCall.method}');
-      });
-      log.clear();
-    });
+  test('getAll', () async {
+    final SharedPreferencesMacOS plugin = SharedPreferencesMacOS();
+    api.items['flutter.aBool'] = true;
+    api.items['flutter.aDouble'] = 3.14;
+    api.items['flutter.anInt'] = 42;
+    api.items['flutter.aString'] = 'hello world';
+    api.items['flutter.aStringList'] = <String>['hello', 'world'];
+    final Map<String?, Object?> all = await plugin.getAll();
+    expect(all.length, 5);
+    expect(all['flutter.aBool'], api.items['flutter.aBool']);
+    expect(all['flutter.aDouble'],
+        closeTo(api.items['flutter.aDouble']! as num, 0.0001));
+    expect(all['flutter.anInt'], api.items['flutter.anInt']);
+    expect(all['flutter.aString'], api.items['flutter.aString']);
+    expect(all['flutter.aStringList'], api.items['flutter.aStringList']);
+  });
 
-    test('registers instance', () {
-      SharedPreferencesMacOS.registerWith();
-      expect(SharedPreferencesStorePlatform.instance,
-          isA<SharedPreferencesMacOS>());
-    });
+  test('setValue', () async {
+    final SharedPreferencesMacOS plugin = SharedPreferencesMacOS();
+    expect(await plugin.setValue('Bool', 'flutter.Bool', true), isTrue);
+    expect(api.items['flutter.Bool'], true);
+    expect(await plugin.setValue('Double', 'flutter.Double', 1.5), isTrue);
+    expect(api.items['flutter.Double'], 1.5);
+    expect(await plugin.setValue('Int', 'flutter.Int', 12), isTrue);
+    expect(api.items['flutter.Int'], 12);
+    expect(await plugin.setValue('String', 'flutter.String', 'hi'), isTrue);
+    expect(api.items['flutter.String'], 'hi');
+    expect(
+        await plugin
+            .setValue('StringList', 'flutter.StringList', <String>['hi']),
+        isTrue);
+    expect(api.items['flutter.StringList'], <String>['hi']);
+  });
 
-    test('getAll', () async {
-      store = SharedPreferencesMacOS();
-      testData = InMemorySharedPreferencesStore.withData(kTestValues);
-      expect(await store.getAll(), kTestValues);
-      expect(log.single.method, 'getAll');
-    });
-
-    test('remove', () async {
-      store = SharedPreferencesMacOS();
-      testData = InMemorySharedPreferencesStore.withData(kTestValues);
-      expect(await store.remove('flutter.String'), true);
-      expect(await store.remove('flutter.Bool'), true);
-      expect(await store.remove('flutter.Int'), true);
-      expect(await store.remove('flutter.Double'), true);
-      expect(await testData.getAll(), <String, dynamic>{
-        'flutter.StringList': <String>['foo', 'bar'],
-      });
-
-      expect(log, hasLength(4));
-      for (final MethodCall call in log) {
-        expect(call.method, 'remove');
-      }
-    });
-
-    test('setValue', () async {
-      store = SharedPreferencesMacOS();
-      expect(await testData.getAll(), isEmpty);
-      for (final String key in kTestValues.keys) {
-        final Object value = kTestValues[key]!;
-        expect(await store.setValue(key.split('.').last, key, value), true);
-      }
-      expect(await testData.getAll(), kTestValues);
-
-      expect(log, hasLength(5));
-      expect(log[0].method, 'setString');
-      expect(log[1].method, 'setBool');
-      expect(log[2].method, 'setInt');
-      expect(log[3].method, 'setDouble');
-      expect(log[4].method, 'setStringList');
-    });
-
-    test('clear', () async {
-      store = SharedPreferencesMacOS();
-      testData = InMemorySharedPreferencesStore.withData(kTestValues);
-      expect(await testData.getAll(), isNotEmpty);
-      expect(await store.clear(), true);
-      expect(await testData.getAll(), isEmpty);
-      expect(log.single.method, 'clear');
-    });
+  test('setValue with unsupported type', () {
+    final SharedPreferencesMacOS plugin = SharedPreferencesMacOS();
+    expect(() async {
+      await plugin.setValue('Map', 'flutter.key', <String, String>{});
+    }, throwsA(isA<PlatformException>()));
   });
 }
diff --git a/packages/shared_preferences/shared_preferences_macos/test/test_api.g.dart b/packages/shared_preferences/shared_preferences_macos/test/test_api.g.dart
new file mode 100644
index 0000000..baa6737
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_macos/test/test_api.g.dart
@@ -0,0 +1,147 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+// Autogenerated from Pigeon (v5.0.0), do not edit directly.
+// See also: https://pub.dev/packages/pigeon
+// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import
+// ignore_for_file: avoid_relative_lib_imports
+import 'dart:async';
+import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
+import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
+import 'package:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import 'package:shared_preferences_macos/messages.g.dart';
+
+abstract class TestUserDefaultsApi {
+  static const MessageCodec<Object?> codec = StandardMessageCodec();
+
+  void remove(String key);
+
+  void setBool(String key, bool value);
+
+  void setDouble(String key, double value);
+
+  void setValue(String key, Object value);
+
+  Map<String?, Object?> getAll();
+
+  void clear();
+
+  static void setup(TestUserDefaultsApi? api,
+      {BinaryMessenger? binaryMessenger}) {
+    {
+      final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+          'dev.flutter.pigeon.UserDefaultsApi.remove', codec,
+          binaryMessenger: binaryMessenger);
+      if (api == null) {
+        channel.setMockMessageHandler(null);
+      } else {
+        channel.setMockMessageHandler((Object? message) async {
+          assert(message != null,
+              'Argument for dev.flutter.pigeon.UserDefaultsApi.remove was null.');
+          final List<Object?> args = (message as List<Object?>?)!;
+          final String? arg_key = (args[0] as String?);
+          assert(arg_key != null,
+              'Argument for dev.flutter.pigeon.UserDefaultsApi.remove was null, expected non-null String.');
+          api.remove(arg_key!);
+          return <Object?>[];
+        });
+      }
+    }
+    {
+      final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+          'dev.flutter.pigeon.UserDefaultsApi.setBool', codec,
+          binaryMessenger: binaryMessenger);
+      if (api == null) {
+        channel.setMockMessageHandler(null);
+      } else {
+        channel.setMockMessageHandler((Object? message) async {
+          assert(message != null,
+              'Argument for dev.flutter.pigeon.UserDefaultsApi.setBool was null.');
+          final List<Object?> args = (message as List<Object?>?)!;
+          final String? arg_key = (args[0] as String?);
+          assert(arg_key != null,
+              'Argument for dev.flutter.pigeon.UserDefaultsApi.setBool was null, expected non-null String.');
+          final bool? arg_value = (args[1] as bool?);
+          assert(arg_value != null,
+              'Argument for dev.flutter.pigeon.UserDefaultsApi.setBool was null, expected non-null bool.');
+          api.setBool(arg_key!, arg_value!);
+          return <Object?>[];
+        });
+      }
+    }
+    {
+      final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+          'dev.flutter.pigeon.UserDefaultsApi.setDouble', codec,
+          binaryMessenger: binaryMessenger);
+      if (api == null) {
+        channel.setMockMessageHandler(null);
+      } else {
+        channel.setMockMessageHandler((Object? message) async {
+          assert(message != null,
+              'Argument for dev.flutter.pigeon.UserDefaultsApi.setDouble was null.');
+          final List<Object?> args = (message as List<Object?>?)!;
+          final String? arg_key = (args[0] as String?);
+          assert(arg_key != null,
+              'Argument for dev.flutter.pigeon.UserDefaultsApi.setDouble was null, expected non-null String.');
+          final double? arg_value = (args[1] as double?);
+          assert(arg_value != null,
+              'Argument for dev.flutter.pigeon.UserDefaultsApi.setDouble was null, expected non-null double.');
+          api.setDouble(arg_key!, arg_value!);
+          return <Object?>[];
+        });
+      }
+    }
+    {
+      final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+          'dev.flutter.pigeon.UserDefaultsApi.setValue', codec,
+          binaryMessenger: binaryMessenger);
+      if (api == null) {
+        channel.setMockMessageHandler(null);
+      } else {
+        channel.setMockMessageHandler((Object? message) async {
+          assert(message != null,
+              'Argument for dev.flutter.pigeon.UserDefaultsApi.setValue was null.');
+          final List<Object?> args = (message as List<Object?>?)!;
+          final String? arg_key = (args[0] as String?);
+          assert(arg_key != null,
+              'Argument for dev.flutter.pigeon.UserDefaultsApi.setValue was null, expected non-null String.');
+          final Object? arg_value = (args[1] as Object?);
+          assert(arg_value != null,
+              'Argument for dev.flutter.pigeon.UserDefaultsApi.setValue was null, expected non-null Object.');
+          api.setValue(arg_key!, arg_value!);
+          return <Object?>[];
+        });
+      }
+    }
+    {
+      final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+          'dev.flutter.pigeon.UserDefaultsApi.getAll', codec,
+          binaryMessenger: binaryMessenger);
+      if (api == null) {
+        channel.setMockMessageHandler(null);
+      } else {
+        channel.setMockMessageHandler((Object? message) async {
+          // ignore message
+          final Map<String?, Object?> output = api.getAll();
+          return <Object?>[output];
+        });
+      }
+    }
+    {
+      final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+          'dev.flutter.pigeon.UserDefaultsApi.clear', codec,
+          binaryMessenger: binaryMessenger);
+      if (api == null) {
+        channel.setMockMessageHandler(null);
+      } else {
+        channel.setMockMessageHandler((Object? message) async {
+          // ignore message
+          api.clear();
+          return <Object?>[];
+        });
+      }
+    }
+  }
+}