Skip to content

Feature/support null as default value (primitive types) #114

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

Open
wants to merge 2 commits into
base: RxJava1.x
Choose a base branch
from

Conversation

LeonardooBorges
Copy link

@LeonardooBorges LeonardooBorges commented Jul 7, 2017

Passing null as default value in get gives a NullPointerException when the key is already signed for primitive types. From my study of the code I see that it was passing the defValue (object of primitive) to SharedPreferences but it only accepts primitive types. This way sending a null gives the exception. I saw your comment in the #50 issue, but I think that sending null as default value make sense sometimes, even if using the primitive objects.
I also added some tests, you can run them in your version to see the problem by yourself.
You can see just the first commit also, it already solves the issue, but the second one improves the readability and comprehension of the changes.

… the readability and comprehension of the change of first commit.
@codecov-io
Copy link

Codecov Report

Merging #114 into RxJava1.x will decrease coverage by 0.03%.
The diff coverage is 100%.

Impacted file tree graph

@@              Coverage Diff              @@
##           RxJava1.x     #114      +/-   ##
=============================================
- Coverage      98.11%   98.07%   -0.04%     
=============================================
  Files              5        5              
  Lines            159      156       -3     
  Branches          11       10       -1     
=============================================
- Hits             156      153       -3     
  Misses             2        2              
  Partials           1        1
Impacted Files Coverage Δ
.../prefser/library/PreferencesAccessorsProvider.java 100% <100%> (ø) ⬆️
.../com/github/pwittchen/prefser/library/Prefser.java 98.83% <100%> (-0.04%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 63cff55...c10455f. Read the comment docs.

@LeonardooBorges LeonardooBorges changed the title Feature/support default value as null Feature/support null as default value (primitive types) Jul 7, 2017
@pwittchen
Copy link
Owner

pwittchen commented Jul 7, 2017 via email

assertThat(givenValue).isEqualTo(readValue);
}

// alteracoes comeca aqui
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 comment, after your review I can delete it.

Copy link
Owner

Choose a reason for hiding this comment

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

This comment should be deleted.

// then
assertThat(value).isNull();
// CustomClass givenObject = new CustomClass(23, "someText");
// CustomClass defaultObject = new CustomClass(67, "defaultText");
Copy link
Author

Choose a reason for hiding this comment

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

Also here.

Copy link
Owner

Choose a reason for hiding this comment

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

Commented code should be removed

// then
assertThat(value).isNull();
// CustomClass givenObject = new CustomClass(23, "someText");
// CustomClass defaultObject = new CustomClass(67, "defaultText");
Copy link
Owner

Choose a reason for hiding this comment

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

Commented code should be removed

assertThat(givenValue).isEqualTo(readValue);
}

// alteracoes comeca aqui
Copy link
Owner

Choose a reason for hiding this comment

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

This comment should be deleted.

@@ -48,7 +48,7 @@ private void createAccessors() {
private void createBooleanAccessor() {
accessors.put(Boolean.class, new Accessor<Boolean>() {
@Override public Boolean get(String key, Boolean defaultValue) {
return preferences.getBoolean(key, defaultValue);
return preferences.getBoolean(key, (defaultValue == null) ? false : defaultValue);
Copy link
Owner

Choose a reason for hiding this comment

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

In this use case, we are defining default behavior for the user in the case of passing null. We don't know if a user wants true or false, but we're forcing using one of these. The same rule applies to all of the changes below.

Copy link
Author

Choose a reason for hiding this comment

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

No, we are not defining default behavior for the user, because if the user sends null in defValue, it would already return before in Prefser class (and it would return null).

Copy link
Owner

@pwittchen pwittchen Jul 10, 2017

Choose a reason for hiding this comment

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

Why would user pass the null? Why should the user expect false in the case of passing null, but not true? How could he or she guess such behavior just by looking at the library API without browsing source code or debugging?

Copy link
Author

Choose a reason for hiding this comment

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

The user will not get true or false if he/she passing null, the user will get null. Look the other comment I mention you, we are returning defValue before that check.

if (!contains(key)) {
  return defaultValue;
}

Line 250 of Prefser.

@@ -60,7 +60,7 @@ private void createBooleanAccessor() {
private void createFloatAccessor() {
accessors.put(Float.class, new Accessor<Float>() {
@Override public Float get(String key, Float defaultValue) {
return preferences.getFloat(key, defaultValue);
return preferences.getFloat(key, (defaultValue == null) ? 0f : defaultValue);
Copy link
Owner

Choose a reason for hiding this comment

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

Same issue as above.

@@ -72,7 +72,7 @@ private void createFloatAccessor() {
private void createIntegerAccessor() {
accessors.put(Integer.class, new Accessor<Integer>() {
@Override public Integer get(String key, Integer defaultValue) {
return preferences.getInt(key, defaultValue);
return preferences.getInt(key, (defaultValue == null) ? 0 : defaultValue);
Copy link
Owner

Choose a reason for hiding this comment

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

Same issue as above.

@@ -84,7 +84,7 @@ private void createIntegerAccessor() {
private void createLongAccessor() {
accessors.put(Long.class, new Accessor<Long>() {
@Override public Long get(String key, Long defaultValue) {
return preferences.getLong(key, defaultValue);
return preferences.getLong(key, (defaultValue == null) ? 0L : defaultValue);
Copy link
Owner

Choose a reason for hiding this comment

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

Same issue as above.

@@ -16,7 +16,7 @@
package com.github.pwittchen.prefser.library;

interface Accessor<T> {
T get(String key, T defaultValue);
T get(String key);
Copy link
Owner

Choose a reason for hiding this comment

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

I'd leave default value as a parameter inside the Accessor interface

@pwittchen
Copy link
Owner

pwittchen commented Jul 8, 2017

Hello @LeonardooBorges. I really appreciate your contribution, but I'd generally avoid forcing default behavior for accessing data. I think, in the case of this library, the user should explicitly define such behavior. Default Android SharedPreferences mechanism has almost the same API and behavior. Moreover, you wrote:

that sending null as default value make sense sometimes, even if using the primitive objects.

Can you provide any reasonable examples for that? I think passing null as a parameter or returning is generally a bad practice mentioned by the Uncle Bob in "Clean Code" book. If we can avoid it, we should do it. If the user provides null as a default value, he or she should expect NPE in some places if there will be no null-checks. We can add @NonNull annotation for default values or additional validation, but on the certain level, after passing null, the Exception will be thrown anyway. In addition, in the current design of the library, this behavior is correct.

PS. It's easier to review code when it's squashed into the single commit within the single PR. You can take a look at this tool: http://rebaseandsqua.sh/.

@@ -253,20 +247,20 @@ public boolean contains(String key) {
Preconditions.checkNotNull(key, KEY_IS_NULL);
Preconditions.checkNotNull(typeTokenOfT, TYPE_TOKEN_OF_T_IS_NULL);

if (!contains(key)) {
return defaultValue;
Copy link
Author

Choose a reason for hiding this comment

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

@pwittchen here we are returning the defValue, so if the execution reaches there (return preferences.getBoolean(key, (defaultValue == null) ? false : defaultValue);) SharedPreferences will contain the key and defValue will not be used.

@LeonardooBorges
Copy link
Author

Hello @pwittchen it is me that should be thanking you for what you did and share, many projects in the company I work uses your lib and we are thankful. I already fork and I'm using the changes I have done and as it works fine I decide to open this PR to contribute. I have some points to Prefser accept the null as defValue.

  1. Prefser can receive and return any object (because it is generics) and null is a value that any object can accept, so the lib could do this also.

  2. I wrote some generics code and the default null value was a good option to check if the app had already set some key, but in the way it is I can't, because an exception is thrown in SharedPreferences. I rather receive null (and it actually is the default value in the case) instead of get an exception in return.

The changes I did increases the Prefser scope to accept more values, so I cannot see a reason to not accept that. Please lets continue the discussion if you not agree.

@pwittchen
Copy link
Owner

pwittchen commented Jul 10, 2017

Ok, I see your point :). In the case of Java generics, we're not able to return null and NPE will be thrown. Nevertheless, I would prefer more elegant solution than simply returning some predefined values. I'd prefer to have some kind of Optional or Null Object Pattern implementation to make returned values more straightforward. I just don't want to hide "magic logic" from the users. I also don't want to add Guava dependency to this library just to add one Optional. I have to think about a possible solution that would be fine & consistent with my idea of the library code. Maybe I'll create some PoC and share it here later.

@LeonardooBorges
Copy link
Author

Thanks for your time, if you could share your solution later I will appreciate to see. 😄

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

Successfully merging this pull request may close these issues.

3 participants