Jetpack Focus: Enable iOS14+ Stats Widgets and Intents for Jetpack - #19479
Conversation
- Duplicating WordPressStatsWidget target - Changing Display Name, Bundle Identifier for all the configurations, Info.plist file, Code Signing Entitlements, WPCOM_SCHEME value
… for different apps - Jetpack and WordPress apps share the same AppGroup - We need to differentiate between the keys of UserDefaults and file names to save different Widget preferences for Jetpack and WordPress apps
Duplicate and change displayName, bundleIdentifiers, set target dependencies, change schemes
…s widgets After updating from an old version of Jetpack app that doesn't support Stats widgets to a newer version, required UserDefault flags and other information is not already saved and cannot be accessed by Jetpack Stats Widgets. Detect if these flags are not set and initialize widgets after the app is launched.
You can test the changes in WordPress from this Pull Request by:
|
|
📝 I noticed there're some issues with provisioning profiles in the buildkite builds. Working on it. |
There was a problem hiding this comment.
Thanks for the PR @staskus! I went through the three test cases and things worked well.
Product bundle identifier
From what I see, the new intent target for Jetpack is what lets the user pick their site while configuring the widget. The product bundle identifier uses com.automattic. for debug/release build configs, but com.jetpack. for alpha and internal. Was this intentional? I also see a potential typo of com.jetpac. for the alpha build config:
Duplicate target dependencies
Looking at the Jetpack target's dependencies, a couple of entries look duplicated (some might pre-date this PR, but I haven't checked). I tested without these and the app seems to work fine.
Potential duplication
It feels like adding the widgets to Jetpack duplicated things (user default keys, localized strings, etc). I understand though that these need to be unique and for some things (esp. localized strings), it might not be desirable or practical to make these dynamic.
Open questions
A couple of open questions which I haven't found time to answer today:
- Have the changes been tested on iOS 13 (e.g. to ensure that the app handles gracefully the lack of widget support in iOS 13?)
- Are the intents exposed to Siri and if so does Siri differentiate properly between the apps?
- Should the widgets and their UI (e.g. for site selection) use a green accent/tint color instead of the current blue color?
I'll hold off approving since the product bundle identifier question looks to be a blocker, although the rest are not.
| ); | ||
| MTL_ENABLE_DEBUG_INFO = NO; | ||
| MTL_FAST_MATH = YES; | ||
| PRODUCT_BUNDLE_IDENTIFIER = com.jetpac.alpha.JetpackIntents; |
|
Thanks for the review!
It is intentional from my side. This is how code signing is set up for the main Jetpack target as well as new extensions we're creating.
Good observation, thanks! I explicitly added them there and then they were automatically added again when I included extensions into
I would call it configuration instead of duplication. We need certain things to be different widget targets.
In my opinion, we should try defining the differences between These are 2 commits I make these changes in
Let me know what you think about such changes. I can easily revert them if they look a bit overhead.
Yes. With these changes,
The SiriKit intents here are only used to have configurable widgets using "Edit Widget" menu. I don't see Siri voice commands being used in any way. |
You can test the changes in Jetpack from this Pull Request by:
|
|
What was done since the review:
Simulator.Screen.Recording.-.iPhone.14.-.2022-10-19.at.14.01.13.mp4 |
Yes, I could. Checking if there's some mismatch between the capabilities in release/alpha/internal profiles. |
|
I requested a review for myself because I wanted to look at the changes but don't have the time to do it right now. |
Generated by 🚫 dangerJS |
|
I found out that for Maybe it's the reason Widgets cannot load data in The other possible solution would be to use same groups for both I can confirm that now Widget data loads on AppCenter version |
I haven't looked at the Git history, but that sounds like an oversight to me. Something that was not noticed at the time of adding the alpha build. The fix you applied seems appropriate and something we should keep going forward. 👍 |
mokagio
left a comment
There was a problem hiding this comment.
None of my comments are blockers, but I'd like to know if you have any comments. Even just ideas on how to follow up later on.
The implementation at the project level is solid, as far as I can see.
I haven't tested the widgets locally, but consider this an approval for me once the other reviewers have a chance to look at the latest changes.
| @objc static let homeWidgetAllTimeFilename = "HomeWidgetAllTimeData.plist" | ||
| @objc static let homeWidgetThisWeekFilename = "HomeWidgetThisWeekData.plist" | ||
| } | ||
| } |
There was a problem hiding this comment.
It's neat to have dedicated settings for WordPress vs Jetpack. Have you thought about how this approach of duplicating a class definition will scale?
In particular, here's something that I'm concerned might happen:
- Developer working in Jetpack adds a new value to the
AppConfigurationWidgetversion from Jetpack - They use it in a file shared between Jetpack and WordPress
- Everything works fine for them because they are only building Jetpack
- The build fails in CI because the value is not available in WordPress
This behavior can be seen in #19502.
There was a problem hiding this comment.
Thank you for your comments.
Have you thought about how this approach of duplicating a class definition will scale?
Yes, I thought about it and this is not ideal. Your example is excellent. I had experience in other larger projects where we had to support multiple targets with different configurations. The use case was a bit different than we have with WordPress and Jetpack but technically the problem looks similar.
- The worst approach was having multiple control statements scattered in the codebase. This made understanding the differences between different configurations hard and any changes needed to be made directly in particular classes.
- We did try duplicating class definitions but we soon hit the problem you were describing. It's also not 100% transparent as well, which files are configuration files and which ones are included in which targets.
- It's possible to move the configuration to
xcconfigconfiguration files. It would allow us to have all the configuration, including build settings, in one place. Although that wouldn't guarantee we set all the required fields for all configurations. It could be used in tandem with some approaches in the code. - Our configurations could conform to one
protocoland the correct configuration be resolved at runtime. This way both configurations could be included in both targets, allowing us to show build error when one of the configurations doesn't conform to aprotocol. We ended up using a flavor of this approach in our previous project.
All in all, I think it deserves a wider discussion, how we could set up a configuration in a way that would support our needs going forward. Otherwise, we could end up with a messy solution.
There was a problem hiding this comment.
All in all, I think it deserves a wider discussion, how we could set up a configuration in a way that would support our needs going forward. Otherwise, we could end up with a messy solution.
Definitely. I'd be great to address the problem before it happens, but the kind of changes we are discussing are too far-reaching and it would make this PR go stale.
I'm glad to hear about the protocol idea, because that's something I'd been thinking about as well. We'd then need something that at runtime allocates the correct one but that seems simpler to achieve than keeping duplicated classes in sync.
Alternatively, and depending on what the plan long term is for these widgets, it might be beneficial to extract the bulk of the logic in a framework, then have each widget target be a thin wrapper to it and pass a struct with configurations. That would centralize things big time, but does come with an extra layer of complexity in isolating stuff.
We'll see...
| static let unconfiguredViewThisWeekTitle = LocalizableStrings.unconfiguredViewJetpackThisWeekTitle | ||
| static let unconfiguredViewAllTimeTitle = LocalizableStrings.unconfiguredViewJetpackAllTimeTitle | ||
| } | ||
| } |
There was a problem hiding this comment.
The same consideration about duplicating implementation mentioned for WidgetConfiguration.swift applies here.
| @objc static let statsTodayWidgetKeychainTokenKey = "OAuth2Token" | ||
| @objc static let statsTodayWidgetKeychainServiceName = "TodayWidget" | ||
| @objc static let statsTodayWidgetUserDefaultsSiteIdKey = "WordPressTodayWidgetSiteId" | ||
| @objc static let statsHomeWidgetsUserDefaultsSiteIdKey = "WordPressHomeWidgetsSiteId" |
There was a problem hiding this comment.
Have you considered increasing the granularity of these configurations?
@objc extension AppConfiguration {
class Widget: NSObject {
@objc(AppConfigurationWidgetStatsToday)
class StatsToday: NSObject {
@objc static let keychainTokenKey = "OAuth2Token"
@objc static let keychainServiceName = "TodayWidget"
@objc static let userDefaultsSiteIdKey = "WordPressTodayWidgetSiteId"
}
@objc(AppConfigurationWidgetStatsHome)
class StatsHome: NSObject {
@objc static let userDefaultsSiteIdKey = "WordPressHomeWidgetsSiteId"
...There was a problem hiding this comment.
I haven't but it would make sense since these keys are meant for different widgets. Although some of the keys are used for both iOS13 and iOS14+ widgets. I'll take a look what makes most sense.
| return | ||
| } | ||
|
|
||
| userDefaults.setValue(AccountHelper.isLoggedIn, forKey: AppConfiguration.Widget.Stats.userDefaultsLoggedInKey) |
There was a problem hiding this comment.
Note: Check if we could check existence of access token with a static key
There was a problem hiding this comment.
Unfortunately, the way the widgets are created we still rely on having defaultSiteId and preloaded widgetData when we initialize widgets for the first time. Only later StatsWidgetsService can be used inside of a widget to update the data.
Also, any refactoring we would do, we would still be unable to avoid user launching Jetpack at least once before adding widgets. After the launch correct token is set for AppConfiguration.Widget.Stats.keychainServiceName which is different between WordPress and Jetpack apps. Until then, the Widget wouldn't be able to display or fetch any information.
There was a problem hiding this comment.
we would still be unable to avoid user launching Jetpack at least once before adding widgets
nitpick: I thought the risk was that the user adds widgets before launching Jetpack (opposite of what's described here). Either way, it sounds like this idea was a dead-end.
There was a problem hiding this comment.
I thought the risk was that the user adds widgets before launching Jetpack
I don't see how can we avoid this risk, even if we make changes on what Widgets depends on. For those users who are updating the app from an older version that didn't support widgets, we would still need to set access token in the shared keychain with the expected key. Even though the key would be static, they would still differ from WordPress and would need to be set at least once.
guarani
left a comment
There was a problem hiding this comment.
Widgets couldn't be added now when building from Xcode, but work when installing the App Center builds.
| return | ||
| } | ||
|
|
||
| userDefaults.setValue(AccountHelper.isLoggedIn, forKey: AppConfiguration.Widget.Stats.userDefaultsLoggedInKey) |
There was a problem hiding this comment.
we would still be unable to avoid user launching Jetpack at least once before adding widgets
nitpick: I thought the risk was that the user adds widgets before launching Jetpack (opposite of what's described here). Either way, it sounds like this idea was a dead-end.
Update: After debugging with @staskus the widgets worked again after restarting the device, so I'm re-reviewing now. |
There was a problem hiding this comment.
I ran through the test cases and things are working now. It looks like my earlier difficulties adding widgets in #19479 (review), were due to a device quirk because they went away after re-starting the device as you suggested.
Case 1: Fresh install
This works as expected. I noticed a bug that isn't related to this PR and is reproducible on WPiOS 21.0 (TestFlight): On one account with a site with few stats, I saw the "Unable to load site stats" message. The app was showing data in Stats for the site (Most Popular Time and All Time had data, while Today had no data), and the widgets displayed the error message even after visiting Stats. I'm creating an issue for this now (update: see #19519).
Case 2: WordPress app with widgets exist
Works as expected.
Case 3: Migration
Works as expected.
Thanks for your awesome work getting these widgets into the Jetpack app, @staskus! 💯
| class Widget: NSObject { | ||
| @objc(AppConfigurationWidgetStats) | ||
| class Stats: NSObject { | ||
| @objc static let keychainTokenKey = "OAuth2Token" |
There was a problem hiding this comment.
@staskus I'm working on doing something similar to the constants used by Extensions. Is there a reason for opting to use the same token key for both apps?
There was a problem hiding this comment.
I didn't find it necessary, since it was used in tandem with keychainServiceName which was different for both apps. Did you encounter any problems with that?



Part of #19460
Description
This PR makes >=iOS 14 "Today", "This Week" and "This Year" Stats widgets available for the Jetpack app.
Solution
These are the main tasks accomplished to make Stats widgets work:
JetpackStatsWidgetstarget by duplicatingWordPressStatsWidgets.Constants.hinto separateAppConstants.hfor each app. It allows defining different UserDefault keys and different file names for saving widget information. Since Jetpack app and WordPress app share the same app group , we need to differentiate these parts.JetpackIntentsby duplicatingWordPressIntents. Perform similar steps as withJetpackStatsWidgets. It allows supporting site selection in theEdit widgetmenu.Testing instructions
Case 1: Fresh install
Case 2: WordPress app with widgets exist
Case 3: Migration
Regression Notes
Breaking WordPress stats widgets
Manual testing to ensure that WordPress widgets remain unaffected when interacting with Jetpack widgets
None
PR submission checklist:
TBD:
RELEASE-NOTES.txtif necessary.Images & Videos
Jetpack.-.schemes.MP4
Jetpack.-.add.widgets.mov