I am using a widget in my capacitor app (currently this question only focuses on iOS).
I want to save an integer from my webview code into Preferences and then be able to read it from the iOS widget.
Maybe I could also use SQLIte for this as pointed out here: https://forum.ionicframework.com/t/will-app-clips-be-available-in-ionic/194768/10?u=folsze
but I think I would still need to use app groups, even for sqlite, which complicates the writing process for capacitor? Or am I wrong?
Is this even possible or am I trying to accomplish something that is impossible? (is it not possible in both iOS AND Android?). I am especially skeptic that the write from my webview code is able to write into a specifc group, since I am not sure the Preferences.ConfigureOptions.group referes to the same concept as a group for iOS app extensions
I created a group for my native app and my native widget in xcode, with the same name:
I tried to do it with configureoptions group but the integer never arrives at the widget, it is non existent:
https://capacitorjs.com/docs/apis/preferences#configureoptions
Here is my webview code:
const key = 'widgetStreak';
const value = String(this.streak);
console.log("AAA", key, value);
await Preferences.configure({group: "group.com.company.name"});
await Preferences.set({ key, value });
Here is my widget code:
import WidgetKit
import SwiftUI
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), streak: 0)
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date(), streak: fetchStreak())
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
let currentDate = Date()
for hourOffset in 0 ..< 5 {
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate, streak: fetchStreak())
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
private func fetchStreak() -> Int {
guard let userDefaults = UserDefaults(suiteName: "group.com.company.name") else {
print("Failed to initialize UserDefaults.")
return 0
}
// Ensure that UserDefaults are synchronized
userDefaults.synchronize()
// Check if the key exists
if userDefaults.object(forKey: "widgetStreak") == nil {
print("Key 'widgetStreak' does not exist.")
return 0
}
let ret = userDefaults.integer(forKey: "widgetStreak")
if ret == 0 {
print("The value for 'widgetStreak' is zero.")
} else {
print("The value for 'widgetStreak' is: \(ret)")
}
return ret
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
let streak: Int
}
struct TestWidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
VStack {
Text("Daily Streak")
Text("\(entry.streak) days")
.font(.largeTitle)
}
}
}
struct TestWidget: Widget {
let kind: String = "TestWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
if #available(iOS 17.0, *) {
TestWidgetEntryView(entry: entry)
.containerBackground(.fill.tertiary, for: .widget)
} else {
TestWidgetEntryView(entry: entry)
.padding()
.background()
}
}
.configurationDisplayName("Daily Streak Widget")
.description("Displays your daily streak.")
}
}
#Preview(as: .systemSmall) {
TestWidget()
} timeline: {
SimpleEntry(date: .now, streak: 0)
SimpleEntry(date: .now, streak: 5)
}
Here is the output that I am getting for my native widget code:
Couldn't read values in CFPrefsPlistSource<0x101c854a0> (Domain: group.com.felixolszewski.geochamp1, User: kCFPreferencesAnyUser, ByHost: Yes, Container: (null), Contents Need Refresh: Yes): Using kCFPreferencesAnyUser with a container is only allowed for System Containers, detaching from cfprefsd
Key 'widgetStreak' does not exist.
Key 'widgetStreak' does not exist.
Key 'widgetStreak' does not exist.
Key 'widgetStreak' does not exist.
Key 'widgetStreak' does not exist.
Key 'widgetStreak' does not exist.
Key 'widgetStreak' does not exist.
Key 'widgetStreak' does not exist.
Key 'widgetStreak' does not exist.
Key 'widgetStreak' does not exist.
Apparently the warning can be ignored: https://stackoverflow.com/a/39923879/20009330
I also tried all the advices on this post: Failed to read values in CFPrefsPlistSource iOS 10
But none helped, still getting the Key 'widgetStreak' does not exist.
, which makes me think it's just not possible with Capacitor. Maybe someone could suggest some alternative workarounds? Maybe the webview localStorage can be accessed from the widget? I found some clues to this here, that localStorage or indexedDB is not a good options, for whatever reason:
https://forum.ionicframework.com/t/will-app-clips-be-available-in-ionic/194768/10?u=folsze
Rather than getting your JS to write the number, have your JS pass the number to a plugin on the iOS side that writes it. I have done exactly this recently and learned everything I needed from Capacitor's documentation
https://capacitorjs.com/docs/plugins/ios
The basic outline is
You then need to use Xcode's app groups to make sure both App and Widget can read the same files