Skip to content

Commit

Permalink
[shared_preferences] Add shared preferences devtools (#6749)
Browse files Browse the repository at this point in the history
This PR adds the shared_preferences_tools package. This package user the [devtools_extension](https://pub.dev/packages/devtools_extensions) tooling to create a tool for shared preferences. The idea of this PR came from @kenzieschmoll on this [issue](flutter/flutter#145433). Initially I've published this tool as a [separate package](https://pub.dev/packages/shared_preferences_tools), but this PR aims to bring the functionality to the main shared_preferences package. Once this PR gets merged I'll archive the `shared_preferences_tools` package. 

https://github.com/flutter/packages/assets/11666470/fcf71145-c330-4397-b62e-c0c4c8bc9f01
  • Loading branch information
adsonpleal authored Dec 16, 2024
1 parent 645621e commit 2fc3390
Show file tree
Hide file tree
Showing 39 changed files with 5,156 additions and 3 deletions.
4 changes: 4 additions & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,7 @@ packages/local_auth/local_auth_windows/** @cbracken
packages/path_provider/path_provider_windows/** @cbracken
packages/shared_preferences/shared_preferences_windows/** @cbracken
packages/url_launcher/url_launcher_windows/** @cbracken

# - DevTools extensions
# @adsonpleal is the actual maintainer of shared_preferences_tool but is not yet a committer, so can't be listed as the owner.
packages/shared_preferences/shared_preferences_tool/** @tarrinneal
4 changes: 4 additions & 0 deletions packages/shared_preferences/shared_preferences/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.4.0

* Adds shared preferences devtools extension

## 2.3.3

* Clarifies scope of prefix handling in README.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
!build
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
name: shared_preferences
issueTracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22
version: 1.0.0
materialIconCodePoint: '0xe683'
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import 'package:flutter/foundation.dart';
import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart';
import 'package:shared_preferences_platform_interface/types.dart';

import 'shared_preferences_devtools_extension_data.dart';

/// Provides a persistent store for simple data.
///
/// Data is persisted to and fetched from the disk asynchronously.
Expand Down Expand Up @@ -401,3 +403,10 @@ class SharedPreferencesWithCache {
return _cacheOptions.allowList?.contains(key) ?? true;
}
}

// Include an unused import to ensure this library is included
// when running `flutter run -d chrome`.
// Check this discussion for more info: https://github.com/flutter/packages/pull/6749/files/6eb1b4fdce1eba107294770d581713658ff971e9#discussion_r1755375409
// ignore: unused_element
final bool _fieldToKeepDevtoolsExtensionReachable =
fieldToKeepDevtoolsExtensionLibraryAlive;
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:convert';
import 'dart:developer' as developer;

import 'package:flutter/foundation.dart';

import '../shared_preferences.dart';

const String _eventPrefix = 'shared_preferences.';

/// A typedef for the post event function.
@visibleForTesting
typedef PostEvent = void Function(
String eventKind,
Map<String, Object?> eventData,
);

/// A helper class that provides data to the DevTools extension.
///
/// It is only visible for testing and eval.
@visibleForTesting
class SharedPreferencesDevToolsExtensionData {
/// The default constructor for [SharedPreferencesDevToolsExtensionData].
///
/// Accepts an optional [PostEvent] that should only be overwritten when testing.
const SharedPreferencesDevToolsExtensionData([
this._postEvent = developer.postEvent,
]);

final PostEvent _postEvent;

/// Requests all legacy and async keys and post an event with the result.
Future<void> requestAllKeys() async {
final SharedPreferences legacyPrefs = await SharedPreferences.getInstance();
final Set<String> legacyKeys = legacyPrefs.getKeys();
final Set<String> asyncKeys = await SharedPreferencesAsync().getKeys();

_postEvent('${_eventPrefix}all_keys', <String, List<String>>{
'asyncKeys': asyncKeys.toList(),
'legacyKeys': legacyKeys.toList(),
});
}

/// Requests the value for a given key and posts an event with the result.
Future<void> requestValue(String key, bool legacy) async {
final Object? value;
if (legacy) {
final SharedPreferences legacyPrefs =
await SharedPreferences.getInstance();
value = legacyPrefs.get(key);
} else {
value = await SharedPreferencesAsync().getAll(allowList: <String>{
key
}).then((Map<String, Object?> map) => map.values.firstOrNull);
}

_postEvent('${_eventPrefix}value', <String, Object?>{
'value': value,
// It is safe to use `runtimeType` here. This code
// will only ever run in debug mode.
'kind': value.runtimeType.toString(),
});
}

/// Requests the value change for the given key and posts an empty event when finished.
Future<void> requestValueChange(
String key,
String serializedValue,
String kind,
bool legacy,
) async {
final Object? value = jsonDecode(serializedValue);
if (legacy) {
final SharedPreferences legacyPrefs =
await SharedPreferences.getInstance();
// we need to check the kind because sometimes a double
// gets interpreted as an int. If this was not an issue
// we'd only need to do a simple pattern matching on value.
switch (kind) {
case 'int':
await legacyPrefs.setInt(key, value! as int);
case 'bool':
await legacyPrefs.setBool(key, value! as bool);
case 'double':
await legacyPrefs.setDouble(key, value! as double);
case 'String':
await legacyPrefs.setString(key, value! as String);
case 'List<String>':
await legacyPrefs.setStringList(
key,
(value! as List<Object?>).cast(),
);
}
} else {
final SharedPreferencesAsync prefs = SharedPreferencesAsync();
// we need to check the kind because sometimes a double
// gets interpreted as an int. If this was not an issue
// we'd only need to do a simple pattern matching on value.
switch (kind) {
case 'int':
await prefs.setInt(key, value! as int);
case 'bool':
await prefs.setBool(key, value! as bool);
case 'double':
await prefs.setDouble(key, value! as double);
case 'String':
await prefs.setString(key, value! as String);
case 'List<String>':
await prefs.setStringList(
key,
(value! as List<Object?>).cast(),
);
}
}
_postEvent('${_eventPrefix}change_value', <String, Object?>{});
}

/// Requests a key removal and posts an empty event when removed.
Future<void> requestRemoveKey(String key, bool legacy) async {
if (legacy) {
final SharedPreferences legacyPrefs =
await SharedPreferences.getInstance();
await legacyPrefs.remove(key);
} else {
await SharedPreferencesAsync().remove(key);
}
_postEvent('${_eventPrefix}remove', <String, Object?>{});
}
}

/// Include a variable to keep the library alive in web builds.
/// It must be a `final` variable.
/// Check this discussion for more info: https://github.com/flutter/packages/pull/6749/files/6eb1b4fdce1eba107294770d581713658ff971e9#discussion_r1755375409
// ignore: prefer_const_declarations
final bool fieldToKeepDevtoolsExtensionLibraryAlive = false;
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import 'package:flutter/foundation.dart' show visibleForTesting;
import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart';
import 'package:shared_preferences_platform_interface/types.dart';

import 'shared_preferences_devtools_extension_data.dart';

/// Wraps NSUserDefaults (on iOS) and SharedPreferences (on Android), providing
/// a persistent store for simple data.
///
Expand Down Expand Up @@ -285,3 +287,10 @@ Either update the implementation to support setPrefix, or do not call setPrefix.
_completer = null;
}
}

// Include an unused import to ensure this library is included
// when running `flutter run -d chrome`.
// Check this discussion for more info: https://github.com/flutter/packages/pull/6749/files/6eb1b4fdce1eba107294770d581713658ff971e9#discussion_r1755375409
// ignore: unused_element
final bool _fieldToKeepDevtoolsExtensionReachable =
fieldToKeepDevtoolsExtensionLibraryAlive;
6 changes: 3 additions & 3 deletions packages/shared_preferences/shared_preferences/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
name: shared_preferences
description: Flutter plugin for reading and writing simple key-value pairs.
Wraps NSUserDefaults on iOS and SharedPreferences on Android.
description: Flutter plugin for reading and writing simple key-value pairs. Wraps NSUserDefaults on iOS and SharedPreferences on Android.
repository: https://github.com/flutter/packages/tree/main/packages/shared_preferences/shared_preferences
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22
version: 2.3.3
version: 2.4.0

environment:
sdk: ^3.4.0
Expand Down Expand Up @@ -40,6 +39,7 @@ dev_dependencies:
sdk: flutter
integration_test:
sdk: flutter
path: ^1.9.0

topics:
- persistence
Expand Down
Loading

0 comments on commit 2fc3390

Please sign in to comment.