-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Split Omnibar: Data type & storage update #6991
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
Conversation
This stack of pull requests is managed by Graphite. Learn more about stacking. |
aa9f4d2 to
6b0dfce
Compare
cee2296 to
86457ba
Compare
6b0dfce to
0f2ea5d
Compare
86457ba to
e376ab4
Compare
d7dd418 to
69f6484
Compare
e376ab4 to
be9ddaf
Compare
044f781 to
31606a2
Compare
app/src/main/java/com/duckduckgo/app/browser/omnibar/LegacyOmnibarLayout.kt
Outdated
Show resolved
Hide resolved
app/src/main/java/com/duckduckgo/app/browser/omnibar/OmnibarLayout.kt
Outdated
Show resolved
Hide resolved
app/src/main/java/com/duckduckgo/app/browser/omnibar/Omnibar.kt
Outdated
Show resolved
Hide resolved
| // safety check: if the omnibar type is SPLIT but the feature is disabled, reset to SINGLE_TOP | ||
| if (settingsDataStore.omnibarType == OmnibarType.SPLIT && | ||
| (!browserFeatures.useUnifiedOmnibarLayout().isEnabled() || !browserFeatures.splitOmnibar().isEnabled()) | ||
| ) { | ||
| settingsDataStore.omnibarType = OmnibarType.SINGLE_TOP | ||
| } |
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.
settingsDataStore can be mutated at any point in time, from a lot of places, so having this safety check in the BrowserActivity only looks fragile.
Maybe consider adding an OmnibarTypeResolver or something along these lines to inject into all places that initialize the omnibar, and do the flag checks there?
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.
Thanks @LukasPaczos, that's a good point. However, there are a lot of places where this resolver would litter the code. Considering that this is just a temporary measure while the flags exist, I was thinking maybe we could avoid that and move this type reset somewhere higher up.
What do you think of adding a PrivacyConfigCallbackPlugin and checking/resetting in onPrivacyConfigDownloaded()?
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.
Maybe using MainProcessLifecycleObserver would be better, because onStart() would be run every time the app starts, as opposed to just when the config is downloaded. This would take care of internal builds when the feature is toggled off in the dev settings.
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 downside of this is that if:
- We release the feature and a number of users enable it.
- We have to temporarily disable it remotely.
- We re-enable the feature.
- All of the the users that previously opted into using the split omnibar would have to go and enable it again.
There's no friction-less way to manage a situation like that, but I think the preference should persist.
What if we added the flag check before returning the value from the SettingsSharedPreferences?
override var omnibarType: OmnibarType
get() {
val prefValue = OmnibarType.fromString(
preferences.getString(KEY_OMNIBAR_TYPE, OmnibarType.SINGLE_TOP.typeName) ?: OmnibarType.SINGLE_TOP.typeName,
)
return if (prefValue == OmnibarType.SPLIT && browserFeatures.splitOmnibar().isEnabled()) {
OmnibarType.SPLIT
} else {
OmnibarType.SINGLE_TOP
}
}
set(value) = preferences.edit { putString(KEY_OMNIBAR_TYPE, value.typeName) }There could be a concern about synchronous feature flag check. That said, pref access can already go to disk so all responsible callers should already move this off of the main thread 😁 We can also look into caching the flag value.
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.
I already thought of your suggestion but unfortunately, it creates a dependency cycle. I've added a flag to remeber if the split omnibar was used when a feature is disabled. It's then used to restore the choice when it's re-enabled.
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.
@LukasPaczos You can test this fallback behavior in a subsequent PR where you can see the selection in the settings.
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.
Continuing in #6991 (comment).
cdf44c0 to
a863754
Compare
83ce28b to
1965900
Compare
| class SplitOmnibarFallbackObserver @Inject constructor( | ||
| private val settingsDataStore: SettingsDataStore, | ||
| private val browserFeatures: AndroidBrowserConfigFeature, | ||
| ) : MainProcessLifecycleObserver { | ||
| override fun onStart(owner: LifecycleOwner) { | ||
| super.onStart(owner) | ||
|
|
||
| if (settingsDataStore.omnibarType == OmnibarType.SPLIT && | ||
| (!browserFeatures.useUnifiedOmnibarLayout().isEnabled() || !browserFeatures.splitOmnibar().isEnabled()) | ||
| ) { | ||
| settingsDataStore.isSplitOmnibarSelected = true | ||
| settingsDataStore.omnibarType = OmnibarType.SINGLE_TOP | ||
| } else if (settingsDataStore.isSplitOmnibarSelected && | ||
| browserFeatures.useUnifiedOmnibarLayout().isEnabled() && | ||
| browserFeatures.splitOmnibar().isEnabled() | ||
| ) { | ||
| // Restore user's choice if the feature is re-enabled | ||
| settingsDataStore.omnibarType = OmnibarType.SPLIT | ||
| settingsDataStore.isSplitOmnibarSelected = false | ||
| } | ||
| } | ||
| } |
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 approach feels a bit backwards to me. As discussed in this comment, whether the split omnibar is used isn’t determined solely by the user preference - it also depends on resolving a feature flag. That means it should be checked every time.
In this case, we effectively need a small "repository" layer in front of the settings data store to handle that logic cleanly. I understand the hesitation to add it since the flag might be short-lived, but I suspect it could stay around for a while during rollout.
My recommendation would still be to introduce this resolver/repository class to keep the code clean, but the current approach also works right now, so I won’t block the PR.
If you decide to move forward with the current version, please just make sure the isEnabled feature flag checks don’t run on the main thread.
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.
@LukasPaczos I understand the concern. However, the flags are used during omnibar initialization in the BrowserTabFragment, so a flag update would not take effect until a new tab is created. In fact, using the live value might create an inconsistent state when one tab might use a split omnibar and the other one would not. So using a "resolver" to get the most up-to-date omnibar type (based on the latest flag value) might create even more issues this omnibar type is used all over the place.
I think the easiest solution is to simply cache the latest known flag values at the start (and change the omnibar type, if needed) and then use them throughout the lifetime of the app. If there's a new config downloaded while the app's running, it will take effect the next time the app restarts.
I added OmnibarFeatureRepoitory, which loads the flags (on the background thread) when the app starts.
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.
However, the flags are used during omnibar initialization in the BrowserTabFragment, so a flag update would not take effect until a new tab is created. In fact, using the live value might create an inconsistent state when one tab might use a split omnibar and the other one would not
That same issue can occur with the proposed OmnibarFeatureRepository. It updates the preference state in onStart, so if the user resumes the app after a config change, new tabs will fallback to top omnibar while existing tabs will still use the split version.
Either way, each fragment already checks for changes to the omnibar position/type in BrowserTabFragment#onResume, so they should recover correctly in both cases.
Let's merge and iterate as needed.
…ibarLayout.kt Co-authored-by: Łukasz Paczos <lpaczos@duckduckgo.com>
…yout.kt Co-authored-by: Łukasz Paczos <lpaczos@duckduckgo.com>
cc81f57 to
cff6c4f
Compare

Task/Issue URL: https://app.asana.com/1/137249556945/task/1211565479656353?focus=true
Description
This PR updates the storage layer and replaces the
OmnibarPosition->OmnibarType.New
OmnibarTypeenum has the following values:SINGLE_TOPSINGLE_BOTTOMSPLIT_TOPThere's also a new
splitOmnibarfeature flag, although it's unused at the moment.Steps to test this PR
Change position
Omnibar behavior