capacitor-plugins icon indicating copy to clipboard operation
capacitor-plugins copied to clipboard

Native Access on Preferences (iOS)

Open Excel1 opened this issue 1 year ago • 5 comments

Bug Report

Im using the Preferences Plugin to store the current used locale on the device.

// language.ts
await Preferences.set({
      key: 'locale',
      value: updatedLocale.value,
    });

Now I want to access the locale in the Notification Service Extension (iOS) to modify my Notification based on the language. On Android I access the Preferences by using shared Preferences with the Prefix CapacitorStorage and everything works fine. On iOS I tried but I can't retrieve the key/value:

//NotificationServiceExtension.swift
          let i = UserDefaults.standard.string(forKey: "CapacitorStorage.locale")
          let u = UserDefaults.standard.string(forKey: "locale")
          let sharedDefaults = UserDefaults(suiteName: "CapacitorStorage")
          if let locale = sharedDefaults?.string(forKey: "locale") {
              print("Locale in Notification Service Extension: \(locale)")
          } else {
              print("Locale not found")
          }


Plugin(s)

@capacitor/preferences V 6.0.1

Capacitor Version

Im using Quasar so quasar info:

Operating System - Darwin(24.0.0) - darwin/arm64
NodeJs - 20.17.0

Global packages
  NPM - 10.8.2
  yarn - Not installed
  @quasar/cli - 2.4.1
  @quasar/icongenie - Not installed
  cordova - Not installed

Important local packages
  quasar - 2.17.0 -- Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time
  @quasar/app-vite - 1.10.0 -- Quasar Framework App CLI with Vite
  @quasar/extras - 1.16.12 -- Quasar Framework fonts, icons and animations
  eslint-plugin-quasar - Not installed
  vue - 3.5.11 -- The progressive JavaScript framework for building modern web UI.
  vue-router - 4.4.5
  pinia - 2.2.4 -- Intuitive, type safe and flexible Store for Vue
  vuex - Not installed
  vite - 2.9.18 -- Native-ESM powered web dev build tool
  eslint - 8.57.1 -- An AST-based pattern checker for JavaScript.
  electron - Not installed
  electron-packager - Not installed
  @electron/packager - Not installed
  electron-builder - Not installed
  register-service-worker - 1.7.2 -- Script for registering service worker, with hooks
  @capacitor/core - 6.1.0 -- Capacitor: Cross-platform apps with JavaScript and the web
  @capacitor/cli - 6.1.0 -- Capacitor: Cross-platform apps with JavaScript and the web
  @capacitor/android - 6.1.0 -- Capacitor: Cross-platform apps with JavaScript and the web
  @capacitor/ios - 6.1.2 -- Capacitor: Cross-platform apps with JavaScript and the web

Quasar App Extensions
  *None installed*

Networking
  Host - MacBook-Pro
  en0 - 192.168.188.39

Platform(s)

iOS only

Current Behavior

I don't receive any key/value pair buy trying to access via native code.

Expected Behavior

Get Key/Value by using native Code UserDefaults.standard.string(forKey: "locale") or UserDefaults.standard.string(forKey: "CapacitorStorage.locale") like it is on android

Other Technical Details

Testet with Xcode Version 15.1 on iPhone 16 Pro

Excel1 avatar Oct 22 '24 08:10 Excel1

This issue needs more information before it can be addressed. In particular, the reporter needs to provide a minimal sample app that demonstrates the issue. If no sample app is provided within 15 days, the issue will be closed.

Please see the Contributing Guide for how to create a Sample App.

Thanks! Ionitron 💙

Ionitron avatar Oct 30 '24 13:10 Ionitron

@Excel1 can you provide a GitHub repo with an app where the issue can be replicated?

alexgerardojacinto avatar Oct 30 '24 13:10 alexgerardojacinto

@alexgerardojacinto

The reason wyh this doesnt work is, that on iOS SharedPreferences are protected from access in outer Services like NotificationServiceExtension. So you can access SharedPreferences from the app set by the plugin not from the NotificationServiceExtension. You manually have to write a sync code - so on startup you save all preferences from your app in prefernces where both (Service Extensions and the App) can access. For this you have to create an App group.

AppDelegate.swift

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
      
        migrateUserDefaultsToAppGroups()
        return true
    }
// Workaround: cant access UserDefaults set by capacitor from an extension - only by using group
  func migrateUserDefaultsToAppGroups() {
          
          // User Defaults - Old
          let userDefaults = UserDefaults.standard
          
          // App Groups Default - New
          let groupDefaults = UserDefaults(suiteName: "group.mygroup.app")
          
          if let groupDefaults = groupDefaults {
                  for key in userDefaults.dictionaryRepresentation().keys {
                    if key == "CapacitorStorage.locale" {
                      groupDefaults.set(userDefaults.dictionaryRepresentation()[key], forKey: key)
                    }
                  }
                  groupDefaults.synchronize()
                  print("Successfully migrated defaults")
              
          } else {
              print("Unable to create NSUserDefaults with given app group")
          }
          
      }

So this issue can be closed. Maybe a hint in the documentation would be good.

Excel1 avatar Nov 14 '24 16:11 Excel1

How to call this swift func proactively within ionic web?

@alexgerardojacinto

The reason wyh this doesnt work is, that on iOS SharedPreferences are protected from access in outer Services like NotificationServiceExtension. So you can access SharedPreferences from the app set by the plugin not from the NotificationServiceExtension. You manually have to write a sync code - so on startup you save all preferences from your app in prefernces where both (Service Extensions and the App) can access. For this you have to create an App group.

AppDelegate.swift

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
      
        migrateUserDefaultsToAppGroups()
        return true
    }
// Workaround: cant access UserDefaults set by capacitor from an extension - only by using group
  func migrateUserDefaultsToAppGroups() {
          
          // User Defaults - Old
          let userDefaults = UserDefaults.standard
          
          // App Groups Default - New
          let groupDefaults = UserDefaults(suiteName: "group.mygroup.app")
          
          if let groupDefaults = groupDefaults {
                  for key in userDefaults.dictionaryRepresentation().keys {
                    if key == "CapacitorStorage.locale" {
                      groupDefaults.set(userDefaults.dictionaryRepresentation()[key], forKey: key)
                    }
                  }
                  groupDefaults.synchronize()
                  print("Successfully migrated defaults")
              
          } else {
              print("Unable to create NSUserDefaults with given app group")
          }
          
      }

So this issue can be closed. Maybe a hint in the documentation would be good.

Leslie-Wong-H avatar Sep 19 '25 03:09 Leslie-Wong-H

This doc helps. https://capacitorjs.com/docs/ios/custom-code

Leslie-Wong-H avatar Sep 19 '25 05:09 Leslie-Wong-H