-
Notifications
You must be signed in to change notification settings - Fork 226
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
Add pref-key
to FML feature variables
#5862
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #5862 +/- ##
==========================================
- Coverage 36.81% 36.65% -0.16%
==========================================
Files 347 347
Lines 33447 33380 -67
==========================================
- Hits 12313 12236 -77
- Misses 21134 21144 +10 ☔ View full report in Codecov by Sentry. |
9643ee2
to
cd9fd8e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Self-review
} | ||
|
||
var userDefaults = UserDefaults.standard | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't been used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is now used!
} | ||
} ?: getter() | ||
} | ||
{%- endif %} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the heart of the patch for kt. It produces code like:
val myBoolean: Boolean
get() {
fun getter() = _variables.getBool("my-boolean") ?: _defaults.myBoolean
return this._prefs?.let {
if (it.contains("my-boolean-pref-key")) {
it.getBoolean("my-boolean-pref-key", getter())
} else {
null
}
} ?: getter()
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commented:
val myBoolean: Boolean
get() {
fun getter() = _variables.getBool("my-boolean") ?: _defaults.myBoolean
return this._prefs?.let {
// We have to provide a default value, so we need to use contains
// to detect if a value has been provided. It doesn't check the type,
// so we have to assume that the app isn't overwriting the prefs
// with the wrong type.
if (it.contains("my-boolean-pref-key")) {
// The prefs API needs us to provide a default value; in practice it will never
// return the default because of the contains check.
it.getBoolean("my-boolean-pref-key", getter())
} else {
null
}
} ?: getter()
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Following self-review:
val myBoolean: Boolean
get() =
this._prefs?.let {
if (it.contains("my-boolean-pref-key")) {
try {
it.getBoolean("my-boolean-pref-key", false)
} catch (e: ClassCastException) {
// This only gets to here if the app has written the
// wrong type to this preference.
null
}
} else {
null
}
} ?: _variables.getBool("my-boolean") ?: _defaults.myBoolean
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly I really like the chained ?:
return {{ prop_swift }} | ||
} | ||
return {{ getter }} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Swift's user defaults API isn't as clever as Android's. It doesn't do the defaulting for us, and provides a catchall func object(forKey:)->Any?
. This makes it much easier to generate!
Generated code:
public var myBoolean: Bool {
if let prefs = self._prefs,
let myBoolean = prefs.object(forKey: "my-boolean-pref-key") as? Bool {
return myBoolean
}
return self._variables.getBool("my-boolean") ?? _defaults.myBoolean
}
doc: "".into(), | ||
typ: TypeRef::String, | ||
}], | ||
props: vec![PropDef::new("button-color", TypeRef::String, json!("blue"))], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A significant number of lines changed in this PR is changing these PropDef {
construction to a convenience test helper constructor.
} | ||
} | ||
Ok(()) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is the beginning of a larger refactor to split out feature structure validation which needs to be done when the manifest is being written (e.g. is this a valid schema) from feature defaults validation (i.e. does this JSON conform to the schema).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah that's a great refactor
/// App developers should use the generated concrete classes, which | ||
/// implement this interface. | ||
/// | ||
public protocol FMLFeatureInterface: FMLObjectInterface { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The FMLFeatureInterface
/FMLFeatureObject
was introduced in Kotlin when we did the toJSONObject
work.
This is the Swift version, now we need it to implement isModified()
.
It still does not do the JSON object creation.
26f4b36
to
1d53f2f
Compare
1d53f2f
to
426a907
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall looks good, I'm loving the refactors and tests. I ran into issues trying to test it in Fenix and didn't want to hold up the review process any longer so I'm approving now. Since it's not a breaking change I'm not particularly concerned about merging it now to get builds going with it and making changes again later if we need to.
/// | ||
/// This may be `true` if a `pref-key` has been set in the feature manifest and the user has | ||
/// set that preference. | ||
func isModified() -> Bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need the public modifier?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding public
gives:
❌ error: public
modified cannot be used in protocols.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool I just wasn't sure — iirc we had a few prs in recent history just adding privacy modifiers
// There may be a chance that we can get Self::Option to work, but not at this time. | ||
// This may be done by adding a branch to this match and adding a `preference_getter` to | ||
// the `OptionalCodeType`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense 👍
} | ||
} | ||
Ok(()) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah that's a great refactor
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a thought — I'm not the biggest fan of having unnamed tests like this, at some point I think it would be nice to refactor the kts files into more standard junit tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please file an issue! I agree, it's getting unwieldy adding these to workflows.rs
and a kts
and swift
file.
} | ||
} ?: getter() | ||
} | ||
{%- endif %} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly I really like the chained ?:
} | ||
|
||
public extension FMLFeatureInterface { | ||
func isModified() -> Bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding public
gives:
public
modifier is redundant for instance method declared in a public extension.
Fixes EXP-2822.
This PR adds to the FML a pref key to feature variables. For example
The
NimbusBuilder
now accepts auserDefaults
orsharedPreferences
which stores the prefs.The generated code for
myFeature.isEnabled
now checks the prefs for a keymy-feature.enabled
.This allows app code to toggle on or off features, via a pref rather than just experiment.
There are several restrictions for this feature:
Boolean
,Int
,String
, andText
types are supported.To detect if a user's preference has been set, a new
isModified()
method is provided. This returnstrue
if any prefs in the feature have been set.When
isModified
istrue
, exposure events are suppressed.NB. It's not clear (without seeing usage) if this suppressing exposure events is enough before the prefs can be changed by a general population. Alternatives may be to unenroll from any experiments involving the feature. Until this is clear, we advise only exposing such user preferences in a secret settings context.
Pull Request checklist
[ci full]
to the PR title.Branch builds: add
[firefox-android: branch-name]
to the PR title.