Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Convert Shared Preferences Android to Pigeon #7015

Closed

Conversation

AmanNegi
Copy link

Changes

Converted the Android Implementation of shared_preferences to Pigeon. The files messages.g.dart and Messages.java are autogenerated by Pigeon according to the template provided in pigeons/messages.dart.

  • I took reference from the path_provider package as it is already converted to pigeon.
  • The tests were heavily inspired by the SharedPreferences for MacOS as it had been already converted using Pigeon.
  • All the tests are running successfully and the example is also working as expected.

Issue

Part of flutter/flutter#117914

Pre-launch Checklist

  • [✅] I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • [✅] I read the Tree Hygiene wiki page, which explains my responsibilities.
  • [✅] I read and followed the relevant style guides and ran the auto-formatter. (Unlike the flutter/flutter repo, the flutter/plugins repo does use dart format.)
  • [✅] I signed the CLA.
  • [✅] The title of the PR starts with the name of the plugin surrounded by square brackets, e.g. [shared_preferences]
  • [✅] I listed at least one issue that this PR fixes in the description above.
  • [✅] I updated pubspec.yaml with an appropriate new version according to the pub versioning philosophy, or this PR is exempt from version changes.
  • [✅] I updated CHANGELOG.md to add a description of the change, following repository CHANGELOG style.
  • [✅] I updated/added relevant documentation (doc comments with ///).
  • [✅] I added new tests to check the change I am making, or this PR is test-exempt.
  • [✅] All existing and new tests are passing.

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

@AmanNegi
Copy link
Author

@tarrinneal Could you please guide me with how can I pass through the CI/CD Pipeline, the tests were running and the example is also running fine as expected.

Copy link
Contributor

@stuartmorgan-g stuartmorgan-g left a comment

Choose a reason for hiding this comment

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

Thanks for the contribution!

@tarrinneal
Copy link
Contributor

tarrinneal commented Jan 24, 2023

@tarrinneal Could you please guide me with how can I pass through the CI/CD Pipeline, the tests were running and the example is also running fine as expected.

Seems like you got it, nice work. I'll go ahead and review after you fix Stuarts requests. Thank you for contributing!

@AmanNegi
Copy link
Author

I have made the changes but I am facing issues with File Formatting, however I'm trying to fix it. Once I fix it I will raise a review check. Sorry for the delay

@AmanNegi
Copy link
Author

Hey, there I successfully fixed the error regarding Code Formatting.

@AmanNegi AmanNegi requested review from stuartmorgan-g and removed request for tarrinneal January 25, 2023 17:54
@AmanNegi
Copy link
Author

Hey, there I reverted the unwanted changes caused by linting.

@AmanNegi AmanNegi requested review from tarrinneal and removed request for stuartmorgan-g January 26, 2023 05:58
AmanNegi

This comment was marked as duplicate.

Copy link
Author

@AmanNegi AmanNegi left a comment

Choose a reason for hiding this comment

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

Fixed all reviews successfully :)

@AmanNegi AmanNegi requested review from stuartmorgan-g and removed request for tarrinneal January 27, 2023 07:26
@AmanNegi
Copy link
Author

Hey, is there some issue with the PR? Can I help with it?


/** Helper class to save data to `android.content.SharedPreferences` */

// Rename class and file to match it's purpose, preferably SharedPreferencesHelper
Copy link
Contributor

Choose a reason for hiding this comment

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

If you would still like to make this change, I think another pr that does only this would be a good idea.

Copy link
Author

@AmanNegi AmanNegi Jan 27, 2023

Choose a reason for hiding this comment

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

Should I change the name perhaps? I was asked to make minimal changes, so I didn't change the file name as well.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think both in a follow up pr would be ideal

Copy link
Author

Choose a reason for hiding this comment

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

Yeah sure, do I have to do it after this PR is merged? I am sorry for the lame question😔, this is my first time contributing.

Copy link
Contributor

Choose a reason for hiding this comment

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

you can put the pr up whenever, I think it would be easier to wait till this one is landed though

Copy link
Author

Choose a reason for hiding this comment

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

Okay sure! Thanks a lot again(🙂).

@stuartmorgan-g
Copy link
Contributor

Hey, is there some issue with the PR?

What issue are you referring to?

@AmanNegi
Copy link
Author

Hey, is there some issue with the PR?

What issue are you referring to?

I thought it wasn't getting merged perhaps I am missing something at my end.

@stuartmorgan-g
Copy link
Contributor

I thought it wasn't getting merged perhaps I am missing something at my end.

PRs don't get merged before they are approved by reviewers; please read https://github.com/flutter/flutter/wiki/Tree-hygiene#getting-a-code-review (and 11 hours since your last update is not a reasonable window of time before pinging reviewers; re-review can take a while depending on people's schedules).

@AmanNegi
Copy link
Author

I am really sorry, I will make sure not to tag any contributor/maintainer.

@AmanNegi
Copy link
Author

Hey there, any updates regarding this PR? 😊

Copy link
Contributor

@stuartmorgan-g stuartmorgan-g left a comment

Choose a reason for hiding this comment

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

Apparently I hadn't published my review comments; they were still in Pending mode.

@HostApi(dartHostTestHandler: 'TestSharedPreferencesApi')
abstract class SharedPreferencesApi {
@TaskQueue(type: TaskQueueType.serialBackgroundThread)
bool remove(String key);
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add documentation comments on each method explaining what they do.

@@ -25,3 +25,4 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
pigeon: ^7.0.2
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be updated to ^8.0.0.

import '../test/messages_test.g.dart';

// Mock Implementation of the SharedPreferencesApi
class _MockAPI extends TestSharedPreferencesApi {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a fake, not a mock, so should be named accordingly.

Copy link
Author

@AmanNegi AmanNegi Feb 15, 2023

Choose a reason for hiding this comment

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

Okay, I will rename it. 😊


/** Helper class to save data to `android.content.SharedPreferences` */

// Rename class and file to match it's purpose, preferably SharedPreferencesHelper
Copy link
Contributor

Choose a reason for hiding this comment

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

.remove(key)
.putString(key, LIST_IDENTIFIER + encodeList(listValue))
.commit();
} catch (RuntimeException e) {
Copy link
Contributor

Choose a reason for hiding this comment

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

It doesn't look like you addressed my question from before: why are you throwing an exception for the sole purpose of rethrowing it? This doesn't accomplish anything unless I'm missing something.

Copy link
Author

Choose a reason for hiding this comment

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

Sorry for that, I have now fixed it. Now I have mitigated the functions to handle IOException and the wrapper functions then convert it to RuntimeException.

api.setDouble("Pie", 3.14);
api.setStringList("Names", Arrays.asList("Flutter", "Dart"));
api.setBool("NewToFlutter", false);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm confused by the changes in this file; you created a mock, and then you wrote unit tests of the mock. There is absolutely no production code being tested in these tests.

The point of unit tests is to test the actual code. If you need to mock out the underlying system API that's one thing (although it's not clear to me that that's necessary here), but you can't mock out everything.

Copy link
Author

Choose a reason for hiding this comment

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

I wrote these tests so that the user can easily understand what all functions are available and what all values can be passed. I am using Fake Tests because we can't access the SharedPreferencesPlugin in Unit Tests. If I am wrong about it, please let me know.

Copy link
Contributor

Choose a reason for hiding this comment

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

I wrote these tests so that the user can easily understand what all functions are available and what all values can be passed.

The purpose of unit tests is to test code; documentation belongs in comments.

I am using Fake Tests because we can't access the SharedPreferencesPlugin in Unit Tests.

Why wouldn't unit tests be able to access code under test?

Copy link
Author

Choose a reason for hiding this comment

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

Because under SharedPreferencesPlugin.java we are using android.content.SharedPreferences and other libraries which can only execute when we test on an Emulator/device.

I took inspiration for tests from here.

Let me know if I'm wrong here.

Copy link
Contributor

Choose a reason for hiding this comment

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

Because under SharedPreferencesPlugin.java we are using android.content.SharedPreferences and other libraries which can only execute when we test on an Emulator/device.

Internal implementation details that aren't available in unit tests can be mocked out, per my comment above.

I took inspiration for tests from here.

Let me know if I'm wrong here.

That is testing a small utility, not the plugin itself, so isn't going to be very useful as an example. But even then, that's testing actual code, not testing test mocks, so is serving a useful test purpose.

Examples of substantive unit tests include this and this.

Copy link
Author

Choose a reason for hiding this comment

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

Hey there, I went through the examples and I was able to create some tests using MockedSharedPreferencesPlugin.

  • I tried, mocking android.content.SharedPreferences: I tried this method but it results in a chain of mapping methods similar to the approach I have followed.
  • I don't think we will be able to test the production code like the code under MethodCallHandlerImpl because it requires several mocked objects with chained methods Mockito.when() to map methods.

Here, is the updated test. I am really unsure about how to write tests for this because there are not many references of tests with packages using Pigeon.

plugin.setup(registrar.messenger(), registrar.context());
}

@SuppressLint("LongLogTag")
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are you using a tag so long that it violates linter rules?

return preferences;
}

// Call the function according to the type of value provided
Future<bool?> _handleSetValue(
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is the future type nullable?

'set$valueType',
<String, dynamic>{'key': key, 'value': value},
))!;
return await _handleSetValue(valueType, key, value) ?? false;
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the purpose of extracting the helper here?

Copy link
Author

Choose a reason for hiding this comment

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

Earlier setDataType was used, as at the Android end we used to get its name from callMethod. However, it makes more sense to filter that at the Dart End. Either way, this function has to be created at either Dart or Android end (which maps setDataType string to a function according to the dataType).

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not asking why the code that's currently in the helper exists, I'm asking why it's in a helper that's only called from one place, where that place ends up being just a single line calling that helper, rather than that code being here. I don't see what value the indirection is providing.

List<String> data = (List<String>) stream.readObject();
if (stream != null) {
stream.close();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Why did you remove the cleanup steps from finally blocks? That regresses behavior.

Copy link
Author

Choose a reason for hiding this comment

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

Yes, sorry for removing the cleanup process. I have updated the finally block in all required scenarios.

@stuartmorgan-g
Copy link
Contributor

We've just completed the migration of the plugin code to the flutter/packages repository, as described in https://flutter.dev/go/flutter-plugins-repo-migration, and this repository is now being archived. Unfortunately that means that all in-progress PRs here must be moved to flutter/packages.

Please see our instructions for an explanation of how to move your PR, and if you have any issues moving your PR please don't hesitate to reach out in the #hackers-ecosystem channel in Discord.

Our apologies that your PR was caught in this one-time transition. We're aware that it's disruptive in the short term, and appreciate your help in getting us to a better long-term state!

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

Successfully merging this pull request may close these issues.

3 participants