-
Notifications
You must be signed in to change notification settings - Fork 9.7k
[shared_preferences] upgraded ios to using pigeon #4732
Changes from all commits
ec4440a
949b3e6
39aad3f
e9ddd40
502a358
b48d6f5
235edd2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,7 @@ | ||
| ## 2.1.0 | ||
|
|
||
| * Upgrades to using Pigeon. | ||
|
|
||
| ## 2.0.10 | ||
|
|
||
| * Switches to an in-package method channel implementation. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,68 +3,7 @@ | |
| // found in the LICENSE file. | ||
|
|
||
| #import "FLTSharedPreferencesPlugin.h" | ||
|
|
||
| static NSString *const CHANNEL_NAME = @"plugins.flutter.io/shared_preferences_ios"; | ||
|
|
||
| @implementation FLTSharedPreferencesPlugin | ||
|
|
||
| + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar { | ||
| FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:CHANNEL_NAME | ||
| binaryMessenger:registrar.messenger]; | ||
| [channel setMethodCallHandler:^(FlutterMethodCall *call, FlutterResult result) { | ||
| NSString *method = [call method]; | ||
| NSDictionary *arguments = [call arguments]; | ||
|
|
||
| if ([method isEqualToString:@"getAll"]) { | ||
| result(getAllPrefs()); | ||
| } else if ([method isEqualToString:@"setBool"]) { | ||
| NSString *key = arguments[@"key"]; | ||
| NSNumber *value = arguments[@"value"]; | ||
| [[NSUserDefaults standardUserDefaults] setBool:value.boolValue forKey:key]; | ||
| result(@YES); | ||
| } else if ([method isEqualToString:@"setInt"]) { | ||
| NSString *key = arguments[@"key"]; | ||
| NSNumber *value = arguments[@"value"]; | ||
| // int type in Dart can come to native side in a variety of forms | ||
| // It is best to store it as is and send it back when needed. | ||
| // Platform channel will handle the conversion. | ||
| [[NSUserDefaults standardUserDefaults] setValue:value forKey:key]; | ||
| result(@YES); | ||
| } else if ([method isEqualToString:@"setDouble"]) { | ||
| NSString *key = arguments[@"key"]; | ||
| NSNumber *value = arguments[@"value"]; | ||
| [[NSUserDefaults standardUserDefaults] setDouble:value.doubleValue forKey:key]; | ||
| result(@YES); | ||
| } else if ([method isEqualToString:@"setString"]) { | ||
| NSString *key = arguments[@"key"]; | ||
| NSString *value = arguments[@"value"]; | ||
| [[NSUserDefaults standardUserDefaults] setValue:value forKey:key]; | ||
| result(@YES); | ||
| } else if ([method isEqualToString:@"setStringList"]) { | ||
| NSString *key = arguments[@"key"]; | ||
| NSArray *value = arguments[@"value"]; | ||
| [[NSUserDefaults standardUserDefaults] setValue:value forKey:key]; | ||
| result(@YES); | ||
| } else if ([method isEqualToString:@"commit"]) { | ||
| // synchronize is deprecated. | ||
| // "this method is unnecessary and shouldn't be used." | ||
| result(@YES); | ||
| } else if ([method isEqualToString:@"remove"]) { | ||
| [[NSUserDefaults standardUserDefaults] removeObjectForKey:arguments[@"key"]]; | ||
| result(@YES); | ||
| } else if ([method isEqualToString:@"clear"]) { | ||
| NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; | ||
| for (NSString *key in getAllPrefs()) { | ||
| [defaults removeObjectForKey:key]; | ||
| } | ||
| result(@YES); | ||
| } else { | ||
| result(FlutterMethodNotImplemented); | ||
| } | ||
| }]; | ||
| } | ||
|
|
||
| #pragma mark - Private | ||
| #import "messages.g.h" | ||
|
|
||
| static NSMutableDictionary *getAllPrefs() { | ||
| NSString *appDomain = [[NSBundle mainBundle] bundleIdentifier]; | ||
|
|
@@ -80,4 +19,50 @@ + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar { | |
| return filteredPrefs; | ||
| } | ||
|
|
||
| @interface FLTSharedPreferencesPlugin () <UserDefaultsApi> | ||
| @end | ||
|
|
||
| @implementation FLTSharedPreferencesPlugin | ||
|
|
||
| + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar { | ||
| FLTSharedPreferencesPlugin *plugin = [[FLTSharedPreferencesPlugin alloc] init]; | ||
| UserDefaultsApiSetup(registrar.messenger, plugin); | ||
| } | ||
|
|
||
| // Must not return nil unless "error" is set. | ||
| - (nullable NSDictionary<NSString *, id> *)getAllWithError: | ||
| (FlutterError *_Nullable __autoreleasing *_Nonnull)error { | ||
| return getAllPrefs(); | ||
| } | ||
|
|
||
| - (void)clearWithError:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { | ||
| NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; | ||
| for (NSString *key in getAllPrefs()) { | ||
| [defaults removeObjectForKey:key]; | ||
| } | ||
| } | ||
|
|
||
| - (void)removeKey:(nonnull NSString *)key | ||
| error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { | ||
| [[NSUserDefaults standardUserDefaults] removeObjectForKey:key]; | ||
| } | ||
|
|
||
| - (void)setBoolKey:(nonnull NSString *)key | ||
| value:(nonnull NSNumber *)value | ||
| error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { | ||
| [[NSUserDefaults standardUserDefaults] setBool:value.boolValue forKey:key]; | ||
| } | ||
|
|
||
| - (void)setDoubleKey:(nonnull NSString *)key | ||
| value:(nonnull NSNumber *)value | ||
| error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { | ||
| [[NSUserDefaults standardUserDefaults] setDouble:value.doubleValue forKey:key]; | ||
| } | ||
|
|
||
| - (void)setValueKey:(nonnull NSString *)key | ||
| value:(nonnull NSString *)value | ||
| error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { | ||
| [[NSUserDefaults standardUserDefaults] setValue:value forKey:key]; | ||
| } | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pigeon note: I think there's a potential red flag here that we've created over two hundred lines of generated code to make an 80-line implementation file 10 lines longer. Strong typing is definitely good, but the tradeoff here is getting to be somewhat questionable. I think it's worth thinking about ways to make pigeon feel lighter weight for simple plugins.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think lines of code is the correct way to measure the change because of objc formatting and the need to distinguish between compile time verified lines and runtime verified lines. Here's instead how the changes should be measured:
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another thing to note is that xcode generated most of this code so it didn't feel heavy weight when writing it. Once you implement a protocol xcode offers to fill it in. |
||
| @end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| // 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 (v1.0.16), do not edit directly. | ||
| // See also: https://pub.dev/packages/pigeon | ||
| #import <Foundation/Foundation.h> | ||
| @protocol FlutterBinaryMessenger; | ||
| @protocol FlutterMessageCodec; | ||
| @class FlutterError; | ||
| @class FlutterStandardTypedData; | ||
|
|
||
| NS_ASSUME_NONNULL_BEGIN | ||
|
|
||
| /// The codec used by UserDefaultsApi. | ||
| NSObject<FlutterMessageCodec> *UserDefaultsApiGetCodec(void); | ||
|
|
||
| @protocol UserDefaultsApi | ||
| - (void)removeKey:(NSString *)key error:(FlutterError *_Nullable *_Nonnull)error; | ||
| - (void)setBoolKey:(NSString *)key | ||
| value:(NSNumber *)value | ||
| error:(FlutterError *_Nullable *_Nonnull)error; | ||
| - (void)setDoubleKey:(NSString *)key | ||
| value:(NSNumber *)value | ||
| error:(FlutterError *_Nullable *_Nonnull)error; | ||
| - (void)setValueKey:(NSString *)key value:(id)value error:(FlutterError *_Nullable *_Nonnull)error; | ||
| - (nullable NSDictionary<NSString *, id> *)getAllWithError:(FlutterError *_Nullable *_Nonnull)error; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is Pigeon annotating this as nullable? It's non-nullable in the Dart specification; that seems like a significant Pigeon bug. (Or is this stale?)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not stale. It was probably done because of error handling. If there is an error you don't want people to artificially return some value.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yea, reading through the code, that's what it's for. If you return nil and set an error, it will throw a
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That means that it's currently impossible to distinguish in the context of the native code between a nullable and a non-nullable return value, which is definitely not great. We can't do anything compiler-enforceable I guess, which is unfortunate, but it seems like we should at least have pigeon put a comment on methods like this that says: so that there's clear information within the context of the generated native code what the required behavior is.
It looks like that's only on the Dart side. Shouldn't we be validating in ObjC code that either
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yea, those are improvements we can do when we implement nullable return types. I'll add a note to that issue: flutter/flutter#98452 (comment)
It doesn't necessarily have to happen on the objc side. The end result will look the same to a user, an exception on the Dart side. I think generating a
Yea, it's a good idea. I don't know how helpful it will be in practice because it won't show up where people are actually implementing the logic.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Right, I wasn't thinking of the end user, but the plugin developer(s). Especially if multiple platforms share method channels (which is still overwhelmingly the common case in the ecosystem). If the exception is in the Dart code, it points someone investigating in the wrong direction; probably not a huge effect, but anything that makes it harder for people to root-cause bugs isn't ideal. |
||
| - (void)clearWithError:(FlutterError *_Nullable *_Nonnull)error; | ||
| @end | ||
|
|
||
| extern void UserDefaultsApiSetup(id<FlutterBinaryMessenger> binaryMessenger, | ||
| NSObject<UserDefaultsApi> *_Nullable api); | ||
|
|
||
| NS_ASSUME_NONNULL_END | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,178 @@ | ||
| // 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 (v1.0.16), do not edit directly. | ||
| // See also: https://pub.dev/packages/pigeon | ||
| #import "messages.g.h" | ||
| #import <Flutter/Flutter.h> | ||
|
|
||
| #if !__has_feature(objc_arc) | ||
| #error File requires ARC to be enabled. | ||
| #endif | ||
|
|
||
| static NSDictionary<NSString *, id> *wrapResult(id result, FlutterError *error) { | ||
| NSDictionary *errorDict = (NSDictionary *)[NSNull null]; | ||
| if (error) { | ||
| errorDict = @{ | ||
| @"code" : (error.code ? error.code : [NSNull null]), | ||
| @"message" : (error.message ? error.message : [NSNull null]), | ||
| @"details" : (error.details ? error.details : [NSNull null]), | ||
| }; | ||
| } | ||
| return @{ | ||
| @"result" : (result ? result : [NSNull null]), | ||
| @"error" : errorDict, | ||
| }; | ||
| } | ||
|
|
||
| @interface UserDefaultsApiCodecReader : FlutterStandardReader | ||
| @end | ||
| @implementation UserDefaultsApiCodecReader | ||
| @end | ||
|
|
||
| @interface UserDefaultsApiCodecWriter : FlutterStandardWriter | ||
| @end | ||
| @implementation UserDefaultsApiCodecWriter | ||
| @end | ||
|
|
||
| @interface UserDefaultsApiCodecReaderWriter : FlutterStandardReaderWriter | ||
| @end | ||
| @implementation UserDefaultsApiCodecReaderWriter | ||
| - (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { | ||
| return [[UserDefaultsApiCodecWriter alloc] initWithData:data]; | ||
| } | ||
| - (FlutterStandardReader *)readerWithData:(NSData *)data { | ||
| return [[UserDefaultsApiCodecReader alloc] initWithData:data]; | ||
| } | ||
| @end | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pigeon note: It would be nice to optimize the generator to not create these at all when there are no custom types.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Filed issue: flutter/flutter#97845 |
||
|
|
||
| NSObject<FlutterMessageCodec> *UserDefaultsApiGetCodec() { | ||
| static dispatch_once_t s_pred = 0; | ||
| static FlutterStandardMessageCodec *s_sharedObject = nil; | ||
| dispatch_once(&s_pred, ^{ | ||
| UserDefaultsApiCodecReaderWriter *readerWriter = | ||
| [[UserDefaultsApiCodecReaderWriter alloc] init]; | ||
| s_sharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; | ||
| }); | ||
| return s_sharedObject; | ||
| } | ||
|
|
||
| void UserDefaultsApiSetup(id<FlutterBinaryMessenger> binaryMessenger, | ||
| NSObject<UserDefaultsApi> *api) { | ||
| { | ||
| FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel | ||
| messageChannelWithName:@"dev.flutter.pigeon.UserDefaultsApi.remove" | ||
| binaryMessenger:binaryMessenger | ||
| codec:UserDefaultsApiGetCodec()]; | ||
| if (api) { | ||
| NSCAssert([api respondsToSelector:@selector(removeKey:error:)], | ||
| @"UserDefaultsApi api (%@) doesn't respond to @selector(removeKey:error:)", api); | ||
| [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { | ||
| NSArray *args = message; | ||
| NSString *arg_key = args[0]; | ||
| FlutterError *error; | ||
| [api removeKey:arg_key error:&error]; | ||
| callback(wrapResult(nil, error)); | ||
| }]; | ||
| } else { | ||
| [channel setMessageHandler:nil]; | ||
| } | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pigeon note: Couldn't we reduce the amount of generated boilerplate code by making this whole block a helper function, and only generate the block and name each time? (And the assertion I guess). static void SomeHelper(
id<FlutterBinaryMessenger> binaryMessenger,
NSObject<SharedPreferencesApi *api,
NSString *channelName,
<insert correct block syntax here> methodHandler) {
FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel
messageChannelWithName:channelName
binaryMessenger:binaryMessenger
codec:SharedPreferencesApiGetCodec()];
if (api) {
[channel setMessageHandler:messageHandler];
} else {
[channel setMessageHandler:nil];
}
}Then each code block here becomes just: NSCAssert(!api || [api respondsToSelector:@selector(removeKey:error:)],
@"SharedPreferencesApi api (%@) doesn't respond to @selector(removeKey:error:)",
api);
SomeHelper(binaryMessenger, api, @"dev.flutter.pigeon.SharedPreferencesApi.remove",
^(id _Nullable message, FlutterReply callback) {
NSArray *args = message;
NSString *arg_key = args[0];
FlutterError *error;
NSNumber *output = [api removeKey:arg_key error:&error];
callback(wrapResult(output, error));
});It wouldn't even need the scoping block.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yea, that could be something we look into. It isn't boilerplate since it is generated and can be treated as a black box by the user. Boilerplate to me means code a user has to manage that has semantic meaning that is small compared to its verbosity. |
||
| { | ||
| FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel | ||
| messageChannelWithName:@"dev.flutter.pigeon.UserDefaultsApi.setBool" | ||
| binaryMessenger:binaryMessenger | ||
| codec:UserDefaultsApiGetCodec()]; | ||
| if (api) { | ||
| NSCAssert([api respondsToSelector:@selector(setBoolKey:value:error:)], | ||
| @"UserDefaultsApi api (%@) doesn't respond to @selector(setBoolKey:value:error:)", | ||
| api); | ||
| [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { | ||
| NSArray *args = message; | ||
| NSString *arg_key = args[0]; | ||
| NSNumber *arg_value = args[1]; | ||
| FlutterError *error; | ||
| [api setBoolKey:arg_key value:arg_value error:&error]; | ||
| callback(wrapResult(nil, error)); | ||
| }]; | ||
| } else { | ||
| [channel setMessageHandler:nil]; | ||
| } | ||
| } | ||
| { | ||
| FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel | ||
| messageChannelWithName:@"dev.flutter.pigeon.UserDefaultsApi.setDouble" | ||
| binaryMessenger:binaryMessenger | ||
| codec:UserDefaultsApiGetCodec()]; | ||
| if (api) { | ||
| NSCAssert([api respondsToSelector:@selector(setDoubleKey:value:error:)], | ||
| @"UserDefaultsApi api (%@) doesn't respond to @selector(setDoubleKey:value:error:)", | ||
| api); | ||
| [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { | ||
| NSArray *args = message; | ||
| NSString *arg_key = args[0]; | ||
| NSNumber *arg_value = args[1]; | ||
| FlutterError *error; | ||
| [api setDoubleKey:arg_key value:arg_value error:&error]; | ||
| callback(wrapResult(nil, error)); | ||
| }]; | ||
| } else { | ||
| [channel setMessageHandler:nil]; | ||
| } | ||
| } | ||
| { | ||
| FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel | ||
| messageChannelWithName:@"dev.flutter.pigeon.UserDefaultsApi.setValue" | ||
| binaryMessenger:binaryMessenger | ||
| codec:UserDefaultsApiGetCodec()]; | ||
| if (api) { | ||
| NSCAssert([api respondsToSelector:@selector(setValueKey:value:error:)], | ||
| @"UserDefaultsApi api (%@) doesn't respond to @selector(setValueKey:value:error:)", | ||
| api); | ||
| [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { | ||
| NSArray *args = message; | ||
| NSString *arg_key = args[0]; | ||
| id arg_value = args[1]; | ||
| FlutterError *error; | ||
| [api setValueKey:arg_key value:arg_value error:&error]; | ||
| callback(wrapResult(nil, error)); | ||
| }]; | ||
| } else { | ||
| [channel setMessageHandler:nil]; | ||
| } | ||
| } | ||
| { | ||
| FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel | ||
| messageChannelWithName:@"dev.flutter.pigeon.UserDefaultsApi.getAll" | ||
| binaryMessenger:binaryMessenger | ||
| codec:UserDefaultsApiGetCodec()]; | ||
| if (api) { | ||
| NSCAssert([api respondsToSelector:@selector(getAllWithError:)], | ||
| @"UserDefaultsApi api (%@) doesn't respond to @selector(getAllWithError:)", api); | ||
| [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { | ||
| FlutterError *error; | ||
| NSDictionary<NSString *, id> *output = [api getAllWithError:&error]; | ||
| callback(wrapResult(output, error)); | ||
| }]; | ||
| } else { | ||
| [channel setMessageHandler:nil]; | ||
| } | ||
| } | ||
| { | ||
| FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel | ||
| messageChannelWithName:@"dev.flutter.pigeon.UserDefaultsApi.clear" | ||
| binaryMessenger:binaryMessenger | ||
| codec:UserDefaultsApiGetCodec()]; | ||
| if (api) { | ||
| NSCAssert([api respondsToSelector:@selector(clearWithError:)], | ||
| @"UserDefaultsApi api (%@) doesn't respond to @selector(clearWithError:)", api); | ||
| [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { | ||
| FlutterError *error; | ||
| [api clearWithError:&error]; | ||
| callback(wrapResult(nil, error)); | ||
| }]; | ||
| } else { | ||
| [channel setMessageHandler:nil]; | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add a:
here, so that we have an explicit flag at the implementation point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done