Skip to content

Commit 41493df

Browse files
committed
feat: support passing appGroupId directly with functions
1 parent ac84f31 commit 41493df

4 files changed

Lines changed: 213 additions & 19 deletions

File tree

packages/home_widget/example/integration_test/ios_test.dart

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'package:integration_test/integration_test.dart';
1111

1212
void main() {
1313
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
14+
const integrationAppGroupId = 'group.es.antonborri.integrationTest';
1415

1516
group('Need Group Id', () {
1617
testWidgets('Save Data needs GroupId', (tester) async {
@@ -46,7 +47,7 @@ void main() {
4647

4748
setUp(() async {
4849
// Add Group Id
49-
await HomeWidget.setAppGroupId('group.es.antonborri.integrationTest');
50+
await HomeWidget.setAppGroupId(integrationAppGroupId);
5051
// Clear all Data
5152
for (final key in cleanupKeys) {
5253
await HomeWidget.saveWidgetData(key, null);
@@ -199,7 +200,7 @@ void main() {
199200
testWidgets(
200201
'Initially Launched completes and returns null if not launched from widget',
201202
(tester) async {
202-
await HomeWidget.setAppGroupId('group.es.antonborri.integrationTest');
203+
await HomeWidget.setAppGroupId(integrationAppGroupId);
203204
final retrievedData =
204205
await HomeWidget.initiallyLaunchedFromHomeWidget();
205206
expect(retrievedData, isNull);
@@ -211,7 +212,7 @@ void main() {
211212
final deviceInfo = await DeviceInfoPlugin().iosInfo;
212213
final hasInteractiveWidgets =
213214
double.parse(deviceInfo.systemVersion.split('.').first) >= 17.0;
214-
await HomeWidget.setAppGroupId('group.es.antonborri.integrationTest');
215+
await HomeWidget.setAppGroupId(integrationAppGroupId);
215216
if (hasInteractiveWidgets) {
216217
final registerCallbackResult =
217218
await HomeWidget.registerInteractivityCallback(
@@ -242,7 +243,7 @@ void main() {
242243

243244
group('Android Configurable Widgets', () {
244245
testWidgets('Android-specific APIs complete on iOS stubs', (tester) async {
245-
await HomeWidget.setAppGroupId('group.es.antonborri.integrationTest');
246+
await HomeWidget.setAppGroupId(integrationAppGroupId);
246247
await expectLater(
247248
HomeWidget.isRequestPinWidgetSupported(),
248249
completion(false),
@@ -266,6 +267,70 @@ void main() {
266267
);
267268
});
268269
});
270+
271+
group('Per-call app group without global setup', () {
272+
const keyValueKey = 'integration_per_call_key';
273+
const fileKey = 'integration_per_call_file_key';
274+
275+
setUp(() async {
276+
HomeWidget.groupId = null;
277+
await HomeWidget.saveWidgetData(
278+
keyValueKey,
279+
null,
280+
appGroupId: integrationAppGroupId,
281+
);
282+
await HomeWidget.saveWidgetData(
283+
fileKey,
284+
null,
285+
appGroupId: integrationAppGroupId,
286+
);
287+
});
288+
289+
testWidgets('save/get widget data with appGroupId override',
290+
(tester) async {
291+
await HomeWidget.saveWidgetData(
292+
keyValueKey,
293+
'value',
294+
appGroupId: integrationAppGroupId,
295+
);
296+
297+
final value = await HomeWidget.getWidgetData<String>(
298+
keyValueKey,
299+
appGroupId: integrationAppGroupId,
300+
);
301+
expect(value, 'value');
302+
});
303+
304+
testWidgets('saveFile and clear with appGroupId override', (tester) async {
305+
final path = await HomeWidget.saveFile(
306+
fileKey,
307+
Uint8List.fromList(utf8.encode(jsonEncode({'per': 'call'}))),
308+
extension: 'json',
309+
appGroupId: integrationAppGroupId,
310+
);
311+
expect(await File(path).exists(), isTrue);
312+
313+
final storedPath = await HomeWidget.getWidgetData<String>(
314+
fileKey,
315+
appGroupId: integrationAppGroupId,
316+
);
317+
expect(storedPath, path);
318+
319+
await HomeWidget.saveWidgetData(
320+
fileKey,
321+
null,
322+
appGroupId: integrationAppGroupId,
323+
);
324+
expect(
325+
await HomeWidget.getWidgetData<String>(
326+
fileKey,
327+
appGroupId: integrationAppGroupId,
328+
),
329+
isNull,
330+
);
331+
expect(await File(path).exists(), isFalse);
332+
});
333+
});
269334
}
270335

271336
Future<void> interactivityCallback(Uri? uri) async {}

packages/home_widget/ios/home_widget/Sources/home_widget/HomeWidgetPlugin.swift

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,19 @@ public class HomeWidgetPlugin: NSObject, FlutterPlugin, FlutterStreamHandler {
3232
private let notInitializedError = FlutterError(
3333
code: "-7", message: "AppGroupId not set. Call setAppGroupId first", details: nil)
3434

35+
private func callArguments(_ call: FlutterMethodCall) -> [String: Any?]? {
36+
return call.arguments as? [String: Any?]
37+
}
38+
39+
private func resolvedAppGroupId(from call: FlutterMethodCall) -> String? {
40+
if let args = callArguments(call),
41+
let appGroupId = args["appGroupId"] as? String
42+
{
43+
return appGroupId
44+
}
45+
return HomeWidgetPlugin.groupId
46+
}
47+
3548
private static func isRunningInAppExtension() -> Bool {
3649
let bundleURL = Bundle.main.bundleURL
3750
let bundlePathExtension = bundleURL.pathExtension
@@ -75,7 +88,7 @@ public class HomeWidgetPlugin: NSObject, FlutterPlugin, FlutterStreamHandler {
7588
details: nil))
7689
}
7790
} else if call.method == "saveWidgetData" {
78-
if HomeWidgetPlugin.groupId == nil {
91+
guard let resolvedGroupId = resolvedAppGroupId(from: call) else {
7992
result(notInitializedError)
8093
return
8194
}
@@ -86,7 +99,7 @@ public class HomeWidgetPlugin: NSObject, FlutterPlugin, FlutterStreamHandler {
8699
let id = myArgs["id"] as? String,
87100
let data = myArgs["data"]
88101
{
89-
let preferences = UserDefaults.init(suiteName: HomeWidgetPlugin.groupId)
102+
let preferences = UserDefaults.init(suiteName: resolvedGroupId)
90103
if data != nil {
91104
if let binaryData = data as? FlutterStandardTypedData {
92105
preferences?.setValue(Data(binaryData.data), forKey: id)
@@ -104,7 +117,7 @@ public class HomeWidgetPlugin: NSObject, FlutterPlugin, FlutterStreamHandler {
104117
details: nil))
105118
}
106119
} else if call.method == "getWidgetData" {
107-
if HomeWidgetPlugin.groupId == nil {
120+
guard let resolvedGroupId = resolvedAppGroupId(from: call) else {
108121
result(notInitializedError)
109122
return
110123
}
@@ -115,7 +128,7 @@ public class HomeWidgetPlugin: NSObject, FlutterPlugin, FlutterStreamHandler {
115128
let id = myArgs["id"] as? String,
116129
let defaultValue = myArgs["defaultValue"]
117130
{
118-
let preferences = UserDefaults.init(suiteName: HomeWidgetPlugin.groupId)
131+
let preferences = UserDefaults.init(suiteName: resolvedGroupId)
119132
result(preferences?.value(forKey: id) ?? defaultValue)
120133
} else {
121134
result(

packages/home_widget/lib/src/home_widget.dart

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ class HomeWidget {
2525
String id,
2626
T? data, {
2727
bool deleteFile = true,
28+
String? appGroupId,
2829
}) async {
2930
if (deleteFile && data == null) {
30-
final raw = await getWidgetData<dynamic>(id);
31+
final raw = await getWidgetData<dynamic>(id, appGroupId: appGroupId);
3132
if (raw is String && _isHomeWidgetManagedFilePath(raw)) {
3233
final file = File(raw);
3334
if (await file.exists()) {
@@ -40,10 +41,12 @@ class HomeWidget {
4041
}
4142
}
4243

43-
return _channel.invokeMethod<bool>('saveWidgetData', {
44+
final arguments = <String, dynamic>{
4445
'id': id,
4546
'data': data,
46-
});
47+
if (appGroupId != null) 'appGroupId': appGroupId,
48+
};
49+
return _channel.invokeMethod<bool>('saveWidgetData', arguments);
4750
}
4851

4952
/// Updates the HomeScreen Widget
@@ -99,11 +102,17 @@ class HomeWidget {
99102
/// Returns Data saved with [saveWidgetData]
100103
/// [id] of Data Saved
101104
/// [defaultValue] value to use if no data was found
102-
static Future<T?> getWidgetData<T>(String id, {T? defaultValue}) {
103-
return _channel.invokeMethod<T>('getWidgetData', {
105+
static Future<T?> getWidgetData<T>(
106+
String id, {
107+
T? defaultValue,
108+
String? appGroupId,
109+
}) {
110+
final arguments = <String, dynamic>{
104111
'id': id,
105112
'defaultValue': defaultValue,
106-
});
113+
if (appGroupId != null) 'appGroupId': appGroupId,
114+
};
115+
return _channel.invokeMethod<T>('getWidgetData', arguments);
107116
}
108117

109118
/// Required on iOS to set the AppGroupId [groupId] in order to ensure
@@ -229,6 +238,7 @@ class HomeWidget {
229238
String key,
230239
Uint8List bytes, {
231240
String extension = 'bin',
241+
String? appGroupId,
232242
}) async {
233243
final ext = _normalizeExtension(extension);
234244
_validateKey(key);
@@ -238,17 +248,18 @@ class HomeWidget {
238248
// coverage:ignore-start
239249
if (Platform.isIOS) {
240250
final PathProviderFoundation provider = PathProviderFoundation();
251+
final resolvedGroupId = appGroupId ?? HomeWidget.groupId;
241252
assert(
242-
HomeWidget.groupId != null,
253+
resolvedGroupId != null,
243254
'No groupId defined. Did you forget to call `HomeWidget.setAppGroupId`',
244255
);
245256
directory = await provider.getContainerPath(
246-
appGroupIdentifier: HomeWidget.groupId!,
257+
appGroupIdentifier: resolvedGroupId!,
247258
);
248259

249260
if (directory == null) {
250261
throw StateError(
251-
'Widget storage directory is null for group "${HomeWidget.groupId}". '
262+
'Widget storage directory is null for group "$resolvedGroupId". '
252263
'Verify App Group configuration and HomeWidget.setAppGroupId.',
253264
);
254265
}
@@ -264,7 +275,7 @@ class HomeWidget {
264275
}
265276
await file.writeAsBytes(bytes);
266277

267-
await saveWidgetData<String>(key, path);
278+
await saveWidgetData<String>(key, path, appGroupId: appGroupId);
268279

269280
return path;
270281
} catch (e) {
@@ -279,6 +290,7 @@ class HomeWidget {
279290
String key,
280291
ImageProvider imageProvider, {
281292
ImageConfiguration configuration = ImageConfiguration.empty,
293+
String? appGroupId,
282294
}) async {
283295
_validateKey(key);
284296
final completer = Completer<Uint8List>();
@@ -320,7 +332,7 @@ class HomeWidget {
320332
);
321333
stream.addListener(listener);
322334
final bytes = await completer.future;
323-
return saveFile(key, bytes, extension: 'png');
335+
return saveFile(key, bytes, extension: 'png', appGroupId: appGroupId);
324336
}
325337

326338
/// Generate a screenshot based on a given widget.
@@ -335,6 +347,7 @@ class HomeWidget {
335347
required String key,
336348
Size logicalSize = const Size(200, 200),
337349
double? pixelRatio,
350+
String? appGroupId,
338351
}) async {
339352
pixelRatio ??=
340353
PlatformDispatcher.instance.implicitView?.devicePixelRatio ?? 1;
@@ -417,6 +430,7 @@ class HomeWidget {
417430
key,
418431
byteData.buffer.asUint8List(),
419432
extension: 'png',
433+
appGroupId: appGroupId,
420434
);
421435
} catch (e) {
422436
throw Exception('Failed to save screenshot to app group container: $e');

0 commit comments

Comments
 (0)