capacitor-plugins
capacitor-plugins copied to clipboard
feat: [Storage Plugin] Support for iOS suites
Feature Request
Describe the Feature Request
While developing extensions in iOS, you may find a need to share application or user data between the host app and the extension.
To do so, we need to pass a "suite name" at the initialization of the userDefaults
init?(suiteName suitename: String?)
Platform Support Requested
- [ ] Android
- [x] iOS
- [ ] Electron
- [ ] Web
Describe Preferred Solution
Possibility to initialize the store with a suite name.
Describe Alternatives
An old cordova plugin is doing the job : https://github.com/apla/me.apla.cordova.app-preferences
Related Code
Additional Context
May concerns Android as well.
Issues tagged with feature request are closed but tracked for reactions to gauge interest.
Or if you are really interested and have the knowledge to implement this, you can send a PR.
I just started looking into created an iOS widget to show some data on the home screen from within my ioinic capacitor app. I'm going to need a way of defining a suiteName so that when I store some data in the app, the widget extension can read the data and display it. Is there any plan to work on this now that iOS 14 beta is out?
Let's go fellas! iOS 14 is here. No time to delay.
Pretty please implement this! :)
Support for this is needed for many app extension scenarios, for example share extension, today extension and many of the other extensions.
We've ran into this when trying to create a share extension, and it seems like others have too (just one example from your forums): https://forum.ionicframework.com/t/capacitor-plugin-for-share-extension-from-ios-android/187970
@sandstrom @ThomasKientz There is a workaround for this.
@capacitor/storage is using UserDefaults.standard for storing data which is not able to share with app extension. What you need to do is migrating UserDefaults to App Groups. You can find the example code here
This migration will be run when iOS app is loaded in AppDelegate.swift. I'd recommend put it in
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
You don't needs to migrate all the keys/values though, just pick the important keys to share with your App Extension.
I just posted about this yesterday in the forums before finding this post today. Does the Capacitor Storage plugin still only support reading from UserDefaults.standard? I noticed the Storage plugin has a "group" property in the options object, but it seems that doesn't refer to an App Group...
@taylorhlabs it does still only support reading from UserDefaults.standard
Make sure to upvote the issue so the ionic team can track it
@hieuhoangtrung Thanks for your suggestion. In your scenario how did you access (on the Ionic/js/capacitor side) the data stored in the group's UserDefaults? The Storage plugin does not seem to allow for this, not sure how else it might be done?
so, I'm a poor iOS Developer (an Ionic hacker who learned Swift last night), but I implemented some changes to the Storage plugin that would allow for the passing of a 'suite' keyword, and then the Storage plugin would respect/retrieve from the NSUserDefaults of that suite.
I needed this because I want to share data between by Share Extension (heh heh) and my main app, as others here mentioned.
If someone competent wanted to review my changes beforehand (@jcesarmobile ??), I'd be willing to do a PR, but I'm not confident enough in my knowledge to just go for it. (Also I have not tried Android although I think the extra parameter, if passed, would just be ignored).
Changes were these: (complete altered files attached).
In definitions.d.ts, add the suite parameter
export interface ConfigureOptions {
/**
* Set the storage group.
*
* Storage groups are used to organize key/value pairs.
*
* Using the value 'NativeStorage' provides backwards-compatibility with
* [`cordova-plugin-nativestorage`](https://www.npmjs.com/package/cordova-plugin-nativestorage).
* WARNING: The `clear()` method can delete unintended values when using the
* 'NativeStorage' group.
*
* @default CapacitorStorage
* @since 1.0.0
*/
group?: string;
suite?: string;
}
in the Storage.swift file, add the suite parameter, hang on to it during initialization,
public struct StorageConfiguration {
public enum Group {
case named(String), cordovaNativeStorage
}
let group: Group
// add optional suite string for use with NSUserDefaults
let suite: String;
public init(for suite: String = "", for group: Group = .named("CapacitorStorage")) {
self.group = group
self.suite = suite
}
}
then alter the creation of the defaults variable to include the suite as necessary:
public class Storage {
private let configuration: StorageConfiguration
private var defaults: UserDefaults {
if(configuration.suite.isEmpty) {
return UserDefaults.standard
} else {
// if the suite configuration parameter is passed, use that suite for the UserDefaults DB.
return UserDefaults(suiteName: configuration.suite) ?? UserDefaults.standard
}
}
in StoragePlugin.swift, grab the suite parameter if it is passed and give it to the constructor
public class StoragePlugin: CAPPlugin {
private var storage = Storage(with: StorageConfiguration())
@objc func configure(_ call: CAPPluginCall) {
let group = call.getString("group")
let suite = call.getString("suite")
let configuration: StorageConfiguration
if let suite = suite, group == nil {
// if only suite is passed, just use that.
configuration = StorageConfiguration(for: suite);
} else {
if let group = group, let suite = suite {
if group == "NativeStorage" {
configuration = StorageConfiguration(for: suite, for: .cordovaNativeStorage)
} else {
configuration = StorageConfiguration(for: suite, for: .named(group))
}
} else {
configuration = StorageConfiguration()
}
}
storage = Storage(with: configuration)
call.resolve()
}
In my share extension, to set the parameter:
...
UserDefaults(suiteName: <your-app-group-id>)?.set(value, forKey: <your desired storage key>)
...
then, in my (Ionic) app, I do this, and it works:
const setOptions = async () => {
await Storage.configure({ suite: '<your-app-group-id>' });
};
setOptions();
const checkValue = async () => {
const { value } = await Storage.get({ key: '<your desired storage key>' });
console.log('storage:', value);
};
checkValue();
The only gotcha I have run across is that when you set the value, you must prepend 'CapacitorStorage.' to the key name because the Storage plugin will prepend that prefix before asking for it. E.g. if you want to store a key called 'foo', you must actually store 'CapacitorStorage.foo' .
Also I don't know that the suite name always has to be a shared App Group ID, but in particular if you want to share data between a Share Extension and an App, I believe that is required.
@jcesarmobile ping