Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[shared_preferences] Add shared preferences devtools #6749

Open
wants to merge 80 commits into
base: main
Choose a base branch
from

Conversation

adsonpleal
Copy link

@adsonpleal adsonpleal commented May 16, 2024

This PR adds the shared_preferences_tools package. This package user the devtools_extension tooling to create a tool for shared preferences. The idea of this PR came from @kenzieschmoll on this issue. Initially I've published this tool as a separate package, 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.

shared_preferences_tools.mp4

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

Copy link
Member

@kenzieschmoll kenzieschmoll left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all the new dart files in this PR need the Flutter copyright:

// 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.

@adsonpleal
Copy link
Author

adsonpleal commented May 21, 2024

@kenzieschmoll Done, all the requested changes were applied. One thing that I'd like to do, though, is some integrations tests. I was exploring the devtools github repo and saw that you already have a devtools_test package, but it doesn't have the fixture tooling used in the devtools/devtools package. The best scenario would be to be able to create fixture tests like this one.

Do you have plans of extracting this tooling to the devtools_test package? Or is there some docs on how to do fixture tests for devtools_extensions packages?

@kenzieschmoll
Copy link
Member

How long does that build take?

@stuartmorgan depending on the size of the app, maybe a minute or two? The build command builds the extensions flutter web app in release mode, and then copies the assets to the extension/devtools/build folder.

@stuartmorgan
Copy link
Contributor

Adding a minute or two to the publish step is fine. We'll need to add repo tooling for packages to be able to specify a pre-publish hook, probably via a tools/some_known_name.dart script that does whatever needs to be done.

@adsonpleal
Copy link
Author

@kenzieschmoll and @stuartmorgan also the tests need to run with the --platform chrome flag since I'm doing some UI tests and the devtools_extensions won't run without this flag.

flutter test --platform chrome

@kenzieschmoll
Copy link
Member

@kenzieschmoll Done, all the requested changes were applied. One thing that I'd like to do, though, is some integrations tests. I was exploring the devtools github repo and saw that you already have a devtools_test package, but it doesn't have the fixture tooling used in the devtools/devtools package. The best scenario would be to be able to create fixture tests like this one.

Do you have plans of extracting this tooling to the devtools_test package? Or is there some docs on how to do fixture tests for devtools_extensions packages?

We are not planning on publishing the devtools_test package, but devtools_shared does have a library devtools_test_utils.dart that should what you are looking for.

You can use the integration test of the simulated environment in package:devtools_extensions as a guide: https://github.com/flutter/devtools/tree/master/packages/devtools_extensions/integration_test.

@kenzieschmoll
Copy link
Member

@stuartmorgan WRT to testing, is there a way to add an integration test hook to the CI? For example, the flutter/devtools CI has this code to run the flutter web integration tests for the devtools_extensions package: https://github.com/flutter/devtools/blob/master/tool/ci/bots.sh#L91-L94

@stuartmorgan
Copy link
Contributor

@stuartmorgan WRT to testing, is there a way to add an integration test hook to the CI? For example, the flutter/devtools CI has this code to run the flutter web integration tests for the devtools_extensions package: https://github.com/flutter/devtools/blob/master/tool/ci/bots.sh#L91-L94

It's possible to run arbitrary bespoke tests with tool/run_tests.dart, but that's generally an approach of last resort. What specifically do the tests here need that isn't covered by standard integration test invocation?

Copy link
Member

@kenzieschmoll kenzieschmoll left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great! Done with first round of review on the extension content itself.

@adsonpleal
Copy link
Author

adsonpleal commented Nov 8, 2024

@kenzieschmoll and @stuartmorgan I can't make both Linux analyze stable and Linux analyze master pass. The flutter_test SDK package has the vm_service version pinned. And the version has changed in the master version. So, if I pin the version to ^14.3.0 it breaks on stable, if I change to ^14.2.5 it breaks on master, since I'm using mockito to mock the vm VmService class. This breaks the compilation, since ^14.3.0 changed the api and mockito is generating mocks for the old api.

I see two options here:

1 - Drop mockito altogether.
2 - Create my own VmService mock class, but keep mockito for the other.

WDYT?

I'll start with 2 and see if it fixes this issue.

@adsonpleal
Copy link
Author

@kenzieschmoll this PR is ready for review again.

///
/// It is only visible for testing and eval.
@visibleForTesting
class DevtoolsExtension {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we rename this DevToolsExtensionDataProvider or SharedPreferencesDevToolsExtensionData or similar? This class name is very close to DevToolsExtension, which is the wrapper widget from package:devtools_extensions, which is a bit confusing.

Map<String, Object?> eventData,
);

/// A helper class that provides data to the devtool extension.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit r/devtools/DevTools


const String _eventPrefix = 'shared_preferences:';

/// A typedef for the post event function
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super-nit: period at the end of sentences

/// The default constructor for [DevtoolsExtension].
///
/// Accepts an optional [PostEvent] that should only be overwritten when testing.
DevtoolsExtension([this._postEvent = developer.postEvent]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be a const constructor?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question, just followed @sigmundch's suggestion. I'll try the const constructor to see if the web runner keeps the library.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be const, just changed it.

});
}

/// Requests the value for a given key and post and event with the result.
Copy link
Member

@kenzieschmoll kenzieschmoll Nov 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: r/and event/an event. Also r/post/posts

});
}

/// Requests the a value change for the give key and post an empty event when finished;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typos:

  • remove first "a"
  • r/give/given
  • r/post/posts
  • period at end of sentence instead of semi colon

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 and issue
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

r/and/an

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dyslexia hitting hard 😬

// we'd only need to do a simple pattern matching on value.
switch (kind) {
case 'int':
await legacyPrefs.setInt(key, value! as int);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will value ever be null? if so, should we handle the null case instead of null asserting?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not in the way it is implemented. There is no way to set it as null. The only option is removing it, but this is another method.

} else {
final SharedPreferencesAsync prefs = SharedPreferencesAsync();
// we need to check the kind because sometimes a double
// gets interpreted as an int. If this was not and issue
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

r/and/an


import '../shared_preferences.dart';

const String _eventPrefix = 'shared_preferences:';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: should this be shared_preferences. instead of shared_preferences:? Using dot is a more common convention for event names than using a colon.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I copied this from somewhere, gonna change it to ..

_postEvent('${_eventPrefix}change_value', <String, Object?>{});
}

/// Requests a key removal and post an empty event when removed.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: r/post/posts

);
});

test('should request value from async api', () async {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for this test case and the test below, can you include what type the value is in the test name so that the tests are easy to differentiate (like you did for the async api test below)? Also, it looks like we are only testing bool and int values for the legacy API. Should we be testing every type like you do for the async api below?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're using the get method in the requestValue implementation, which returns a Object? I thought that doing a test for each type would be redundant. But I see the value of doing a test for each type, we could check if the 'kind' value is being set accordingly. I'll create a helper function and write tests for all types.

);
});

test('should request value from async api', () async {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider putting all legacy API tests in a testing group and all async API tests in a testing group

Future<void> _runCommand({
required String message,
required String executable,
required List<String> arguments,
}) async {
stdout.write(message);
final Directory rootDir = Directory(
Copy link
Member

@kenzieschmoll kenzieschmoll Nov 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you rename this to sharedPreferencesToolParent or similar so that it is clear that the .parent.parent call is to walk up to the shared_preferences_tool parent? adding a comment could help too

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! I was struggling to find a good name for this.

Comment on lines +40 to +45
serviceManager.registerLifecycleCallback(
ServiceManagerLifecycle.afterCloseVmService,
(_) {
setState(() {});
},
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this have any implications when not using the simulated environment?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gonna double check

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just tested it again and we have no implications when running on the actual DevTools.

@@ -150,8 +162,8 @@ sealed class SharedPreferencesData implements _SharedPreferencesData<Object> {
};
}

/// The type of the value formatted in a pretty way.
String get prettyType {
/// The kind of the value as string.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: "as a String."

import 'package:devtools_app_shared/service.dart';

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: remove unnecessary new line between package imports


late final StreamSubscription<Event> streamSubscription;
streamSubscription = _service.onExtensionEvent.listen((Event event) {
if (event.extensionKind == 'shared_preferences:$eventKind') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a comment describing where the event prefix and event kinds are defined

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants