Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 69 additions & 4 deletions packages/home_widget/example/integration_test/ios_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:integration_test/integration_test.dart';

void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
const integrationAppGroupId = 'group.es.antonborri.integrationTest';

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

setUp(() async {
// Add Group Id
await HomeWidget.setAppGroupId('group.es.antonborri.integrationTest');
await HomeWidget.setAppGroupId(integrationAppGroupId);
// Clear all Data
for (final key in cleanupKeys) {
await HomeWidget.saveWidgetData(key, null);
Expand Down Expand Up @@ -199,7 +200,7 @@ void main() {
testWidgets(
'Initially Launched completes and returns null if not launched from widget',
(tester) async {
await HomeWidget.setAppGroupId('group.es.antonborri.integrationTest');
await HomeWidget.setAppGroupId(integrationAppGroupId);
final retrievedData =
await HomeWidget.initiallyLaunchedFromHomeWidget();
expect(retrievedData, isNull);
Expand All @@ -211,7 +212,7 @@ void main() {
final deviceInfo = await DeviceInfoPlugin().iosInfo;
final hasInteractiveWidgets =
double.parse(deviceInfo.systemVersion.split('.').first) >= 17.0;
await HomeWidget.setAppGroupId('group.es.antonborri.integrationTest');
await HomeWidget.setAppGroupId(integrationAppGroupId);
if (hasInteractiveWidgets) {
final registerCallbackResult =
await HomeWidget.registerInteractivityCallback(
Expand Down Expand Up @@ -242,7 +243,7 @@ void main() {

group('Android Configurable Widgets', () {
testWidgets('Android-specific APIs complete on iOS stubs', (tester) async {
await HomeWidget.setAppGroupId('group.es.antonborri.integrationTest');
await HomeWidget.setAppGroupId(integrationAppGroupId);
await expectLater(
HomeWidget.isRequestPinWidgetSupported(),
completion(false),
Expand All @@ -266,6 +267,70 @@ void main() {
);
});
});

group('Per-call app group without global setup', () {
const keyValueKey = 'integration_per_call_key';
const fileKey = 'integration_per_call_file_key';

setUp(() async {
HomeWidget.groupId = null;
await HomeWidget.saveWidgetData(
keyValueKey,
null,
appGroupId: integrationAppGroupId,
);
await HomeWidget.saveWidgetData(
fileKey,
null,
appGroupId: integrationAppGroupId,
);
});

testWidgets('save/get widget data with appGroupId override',
(tester) async {
await HomeWidget.saveWidgetData(
keyValueKey,
'value',
appGroupId: integrationAppGroupId,
);

final value = await HomeWidget.getWidgetData<String>(
keyValueKey,
appGroupId: integrationAppGroupId,
);
expect(value, 'value');
});

testWidgets('saveFile and clear with appGroupId override', (tester) async {
final path = await HomeWidget.saveFile(
fileKey,
Uint8List.fromList(utf8.encode(jsonEncode({'per': 'call'}))),
extension: 'json',
appGroupId: integrationAppGroupId,
);
expect(await File(path).exists(), isTrue);

final storedPath = await HomeWidget.getWidgetData<String>(
fileKey,
appGroupId: integrationAppGroupId,
);
expect(storedPath, path);

await HomeWidget.saveWidgetData(
fileKey,
null,
appGroupId: integrationAppGroupId,
);
expect(
await HomeWidget.getWidgetData<String>(
fileKey,
appGroupId: integrationAppGroupId,
),
isNull,
);
expect(await File(path).exists(), isFalse);
});
});
}

Future<void> interactivityCallback(Uri? uri) async {}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,19 @@ public class HomeWidgetPlugin: NSObject, FlutterPlugin, FlutterStreamHandler {
private let notInitializedError = FlutterError(
code: "-7", message: "AppGroupId not set. Call setAppGroupId first", details: nil)

private func callArguments(_ call: FlutterMethodCall) -> [String: Any?]? {
return call.arguments as? [String: Any?]
}

private func resolvedAppGroupId(from call: FlutterMethodCall) -> String? {
if let args = callArguments(call),
let appGroupId = args["appGroupId"] as? String
{
return appGroupId
}
return HomeWidgetPlugin.groupId
}

private static func isRunningInAppExtension() -> Bool {
let bundleURL = Bundle.main.bundleURL
let bundlePathExtension = bundleURL.pathExtension
Expand Down Expand Up @@ -75,7 +88,7 @@ public class HomeWidgetPlugin: NSObject, FlutterPlugin, FlutterStreamHandler {
details: nil))
}
} else if call.method == "saveWidgetData" {
if HomeWidgetPlugin.groupId == nil {
guard let resolvedGroupId = resolvedAppGroupId(from: call) else {
result(notInitializedError)
return
}
Expand All @@ -86,7 +99,7 @@ public class HomeWidgetPlugin: NSObject, FlutterPlugin, FlutterStreamHandler {
let id = myArgs["id"] as? String,
let data = myArgs["data"]
{
let preferences = UserDefaults.init(suiteName: HomeWidgetPlugin.groupId)
let preferences = UserDefaults.init(suiteName: resolvedGroupId)
if data != nil {
if let binaryData = data as? FlutterStandardTypedData {
preferences?.setValue(Data(binaryData.data), forKey: id)
Expand All @@ -104,7 +117,7 @@ public class HomeWidgetPlugin: NSObject, FlutterPlugin, FlutterStreamHandler {
details: nil))
}
} else if call.method == "getWidgetData" {
if HomeWidgetPlugin.groupId == nil {
guard let resolvedGroupId = resolvedAppGroupId(from: call) else {
result(notInitializedError)
return
}
Expand All @@ -115,7 +128,7 @@ public class HomeWidgetPlugin: NSObject, FlutterPlugin, FlutterStreamHandler {
let id = myArgs["id"] as? String,
let defaultValue = myArgs["defaultValue"]
{
let preferences = UserDefaults.init(suiteName: HomeWidgetPlugin.groupId)
let preferences = UserDefaults.init(suiteName: resolvedGroupId)
result(preferences?.value(forKey: id) ?? defaultValue)
} else {
result(
Expand Down
36 changes: 25 additions & 11 deletions packages/home_widget/lib/src/home_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ class HomeWidget {
String id,
T? data, {
bool deleteFile = true,
String? appGroupId,
}) async {
if (deleteFile && data == null) {
final raw = await getWidgetData<dynamic>(id);
final raw = await getWidgetData<dynamic>(id, appGroupId: appGroupId);
if (raw is String && _isHomeWidgetManagedFilePath(raw)) {
final file = File(raw);
if (await file.exists()) {
Expand All @@ -40,10 +41,12 @@ class HomeWidget {
}
}

return _channel.invokeMethod<bool>('saveWidgetData', {
final arguments = <String, dynamic>{
'id': id,
'data': data,
});
if (appGroupId != null) 'appGroupId': appGroupId,
};
return _channel.invokeMethod<bool>('saveWidgetData', arguments);
}

/// Updates the HomeScreen Widget
Expand Down Expand Up @@ -99,11 +102,17 @@ class HomeWidget {
/// Returns Data saved with [saveWidgetData]
/// [id] of Data Saved
/// [defaultValue] value to use if no data was found
static Future<T?> getWidgetData<T>(String id, {T? defaultValue}) {
return _channel.invokeMethod<T>('getWidgetData', {
static Future<T?> getWidgetData<T>(
String id, {
T? defaultValue,
String? appGroupId,
}) {
final arguments = <String, dynamic>{
'id': id,
'defaultValue': defaultValue,
});
if (appGroupId != null) 'appGroupId': appGroupId,
};
return _channel.invokeMethod<T>('getWidgetData', arguments);
}

/// Required on iOS to set the AppGroupId [groupId] in order to ensure
Expand Down Expand Up @@ -229,6 +238,7 @@ class HomeWidget {
String key,
Uint8List bytes, {
String extension = 'bin',
String? appGroupId,
}) async {
final ext = _normalizeExtension(extension);
_validateKey(key);
Expand All @@ -238,17 +248,18 @@ class HomeWidget {
// coverage:ignore-start
if (Platform.isIOS) {
final PathProviderFoundation provider = PathProviderFoundation();
final resolvedGroupId = appGroupId ?? HomeWidget.groupId;
assert(
HomeWidget.groupId != null,
resolvedGroupId != null,
'No groupId defined. Did you forget to call `HomeWidget.setAppGroupId`',
);
directory = await provider.getContainerPath(
appGroupIdentifier: HomeWidget.groupId!,
appGroupIdentifier: resolvedGroupId!,
);

if (directory == null) {
throw StateError(
'Widget storage directory is null for group "${HomeWidget.groupId}". '
'Widget storage directory is null for group "$resolvedGroupId". '
'Verify App Group configuration and HomeWidget.setAppGroupId.',
);
}
Expand All @@ -264,7 +275,7 @@ class HomeWidget {
}
await file.writeAsBytes(bytes);

await saveWidgetData<String>(key, path);
await saveWidgetData<String>(key, path, appGroupId: appGroupId);

return path;
} catch (e) {
Expand All @@ -279,6 +290,7 @@ class HomeWidget {
String key,
ImageProvider imageProvider, {
ImageConfiguration configuration = ImageConfiguration.empty,
String? appGroupId,
}) async {
_validateKey(key);
final completer = Completer<Uint8List>();
Expand Down Expand Up @@ -320,7 +332,7 @@ class HomeWidget {
);
stream.addListener(listener);
final bytes = await completer.future;
return saveFile(key, bytes, extension: 'png');
return saveFile(key, bytes, extension: 'png', appGroupId: appGroupId);
}

/// Generate a screenshot based on a given widget.
Expand All @@ -335,6 +347,7 @@ class HomeWidget {
required String key,
Size logicalSize = const Size(200, 200),
double? pixelRatio,
String? appGroupId,
}) async {
pixelRatio ??=
PlatformDispatcher.instance.implicitView?.devicePixelRatio ?? 1;
Expand Down Expand Up @@ -417,6 +430,7 @@ class HomeWidget {
key,
byteData.buffer.asUint8List(),
extension: 'png',
appGroupId: appGroupId,
);
} catch (e) {
throw Exception('Failed to save screenshot to app group container: $e');
Expand Down
Loading
Loading