In-App Feedback Prompt UI - #22052
Conversation
|
| App Name | WordPress Alpha |
|
| Configuration | Release-Alpha | |
| Build Number | pr22052-46e2143 | |
| Version | 24.0 | |
| Bundle ID | org.wordpress.alpha | |
| Commit | 46e2143 | |
| App Center Build | WPiOS - One-Offs #8479 |
|
| App Name | Jetpack Alpha |
|
| Configuration | Release-Alpha | |
| Build Number | pr22052-9b8db01 | |
| Version | 24.0 | |
| Bundle ID | com.jetpack.alpha | |
| Commit | 9b8db01 | |
| App Center Build | jetpack-installable-builds #7499 |
c71a915 to
fbb5165
Compare
|
I've been going trough PRs to update them via I did not work on this one because it's based off another PR. |
a65af62 to
6a98364
Compare
100425c to
9bdd9c8
Compare
| if !unreadNotificationIds.isEmpty { | ||
| // Track as significant event for App Rating calculations | ||
| AppRatingUtility.shared.incrementSignificantEvent() | ||
| } |
There was a problem hiding this comment.
This code was supposed to increment the significant events count when the user visits the Notifications screen and has unread notifications. But that didn't work, the unreadNotificationIds was empty when I visit the screen even though there are unread notifications.
After some digging, I realized the unreadNotificationIds is populated when the user switches to Unread tab, then it is emptied when switching to another tab.
This behavior is buggy in my opinion, and I replaced it with the "User taps on an unread notification" action.
a9fa8b9 to
41fa80f
Compare
| /// uses the app in a broad fashion. | ||
| /// | ||
| @objc var systemWideSignificantEventCountRequiredForPrompt: Int = 1 | ||
| @objc var systemWideSignificantEventCountRequiredForPrompt: Int = 5 |
There was a problem hiding this comment.
I'll checkout to your branch and verify
| } | ||
|
|
||
| func presentIfNeeded() { | ||
| guard FeatureFlag.compliancePopover.enabled/*, !defaults.didShowCompliancePopup */else { |
There was a problem hiding this comment.
The change /*, !defaults.didShowCompliancePopup */ was accidentally merged in release/24.0. This PR reverts the change but I'll follow up with another PR targeting 24.0 and adding a Unit Test for the presentIfNeeded method.
| // MARK: - Properties | ||
|
|
||
| private let appRatingUtility: AppRatingUtility | ||
| private let userDefaults: UserDefaults |
There was a problem hiding this comment.
UserPersistentRepository can be used instead of UserDefaults as type. It simply adds an abstraction over UserDefaults to allow us injecting.
There was a problem hiding this comment.
Resolved in cb10a4a. I've removed the userDefaults property as it was not used.
| let yes = UIAlertAction(title: Strings.FeedbackAlert.yes, style: .default) { _ in | ||
| self.handlePositiveFeedback(in: controller) | ||
| } |
There was a problem hiding this comment.
I suppose there's no retain cycle as UIAlertController will be deallocated once the function ends. Though I thought it could make it less prone for a cycle in the future. Up to you.
There was a problem hiding this comment.
Good catch, I'll add [weak self] to this closure and all other UIAlertAction closures.
There was a problem hiding this comment.
I got this error when adding [weak self]:
'weak' may only be applied to class and class-bound protocol types, not 'InAppFeedbackPromptCoordinator'
That makes sense since InAppFeedbackPromptCoordinator is a struct.
There was a problem hiding this comment.
Oh, that actually may not be such a great idea as it contains other reference types. So when you pass it around, its value type properties will be copied but the reference types will be modified throughout every copy.
| deinit { | ||
| guard !feedbackWasSubmitted else { | ||
| return | ||
| } | ||
| WPAnalytics.track(.appReviewsCanceledFeedbackScreen) | ||
| } |
There was a problem hiding this comment.
Shouldn't we do this in viewDidDisappear or some other view lifecycle function rather than deinit which is about memory deallocation?
| private var complianceCoordinator: CompliancePopoverCoordinator? | ||
| private var inAppFeedbackCoordinator: InAppFeedbackPromptPresenting? |
There was a problem hiding this comment.
I am not sure what kind of structure you have followed but if VC retains Coordinators (whether directly or via ViewModel), these should be unowned variables rather than strongly held. Since we would wish the children Coordinators to be deallocated once their respective VCs are.
I just wanted to point that out.
There was a problem hiding this comment.
these should be unowned variables rather than strongly held. Since we would wish the children Coordinators to be deallocated once their respective VCs are.
Their respective VCs don't hold a reference to these children coordinators, so if we mark them as unowned , they will be deallocated immediately.
I have considered instantiating those children coordinators in MySiteOverlaysCoordinator.presentOverlayIfNeeded, but I'm planning to write unit tests for MySiteOverlaysCoordinator and mock those children coordinators dependencies.
There was a problem hiding this comment.
I see. So how are the Coordinators retained? A parent coordinator retains a child coordinator and that one retains its children coordinators? If that's the case, do we also need to deallocate them once the screen is dismissed?
So if Coordinator A (CA) is the parent:
CA -> CB -> CC and the ViewController that is linked to CC is dismissed, is there a mechanism to deallocate CC in place?
There was a problem hiding this comment.
@alpavanoglu MySiteViewController retains MySiteOverlaysCoordinator, which in turns retains those child coordinators.
MySiteVC > MySiteOverlaysCoordinator > Child Coordinators
Also, none of the coordinators retain a View Controller. And the coordinators will be deallocated when MySiteVC is deallocated.
Keep in mind that the coordinators are only responsible for displaying their respective VCs, but they don't retain them.
There was a problem hiding this comment.
Okay. So if a child coordinator's ViewController is dismissed, the child coordinator itself will not be deallocated because it is being referenced by the parent coordinator. If you think in terms of navigation stack, this would be like a whole flow being retained until their root VC is dismissed/deallocated.
Because of this problem, I personally prefer that each respective VC retains their Coordinator rather than a parent Coordinator retaining children coordinators. Otherwise coordinators have to replicate a deallocation chain that is provided by Navigation Stack out of the box.
Of course all of this is with the assumption that Coordinators are class and not struct. Since they often contain states that change over its lifetime, I find class to be a better fit.
There was a problem hiding this comment.
I personally prefer that each respective VC retains their Coordinator rather than a parent Coordinator retaining children coordinators.
I think this is not always possible. In cases like the In-App Feedback coordinator, where the VC is just a UIAlertViewController, we have to create a subclass to retain the Coordinator.
Additionally, the InAppFeedbackCoordinator was inspired by the CompliancePopoverCoordinator, and the latter doesn't retain its VC.
I'm not against your suggestion, but I still don't know what to change exactly. Could you provide a code example to clarify this approach?
There was a problem hiding this comment.
If you think in terms of navigation stack, this would be like a whole flow being retained until their root VC is dismissed/deallocated.
I understand, but this is not happening in our case. MySiteViewController retains ONLY the coordinators of the screens it is responsible for presenting.
The coordinators are just an alternative to writing the presentation logic within the MySiteViewController.
There was a problem hiding this comment.
I see. The structure confused me to think otherwise. MySiteOverlaysCoordinator is merely a helper to ensure only one coordinator is active at a given time. No action needed then.
Additionally, the InAppFeedbackCoordinator was inspired by the CompliancePopoverCoordinator, and the latter doesn't retain its VC.
Yeah I think it is better that the Coordinator does not retain VCs. Maybe I wasn't articulate enough. Navigation stack already retains VCs down the chain. When Coordinators do it too, we need to deal with deallocating them.
🧪 Test Results 🧪Positive Feedback Flow❓ Since I cannot Submit (on simulator), I had to press Cancel. After that, the view is dismissed. And the rest of steps passed. Negative Feedback Flow✅ All steps work as expected. Cancel Negative Feedback Flow✅ All steps work as expected. Regression: Compliance Popover✅ All steps work as expected. |
1162ccb to
650d08e
Compare
650d08e to
cb10a4a
Compare
alpavanoglu
left a comment
There was a problem hiding this comment.
Approving to not block the progress. However I think that my last 2 comments might be important. If after considering them, you think it is safe to go ahead, please feel free to merge.
Thanks for taking care of my earlier comments!
Generated by 🚫 dangerJS |



Part of #22341
Related PR
This PR depends on:
Description
This PR introduces the In-App Feedback Prompt UI. The prompt becomes visible after a combination of any of these actions is performed at least 20 times:
CleanShot.2024-01-09.at.22.28.11.mp4
CleanShot.2024-01-09.at.22.30.37.mp4
Test Instruction
Positive Feedback Flow
WordPressAppDelegate.swiftand on line 411, setutility.systemWideSignificantEventCountRequiredForPromptto 5.app_reviews_saw_promptto be tracked.app_reviews_liked_appto be tracked.Negative Feedback Flow
InAppFeedbackPromptCoordinator.swiftand on line 25, forceshouldShowPromptForAppReviewto returntrue.app_reviews_didnt_like_appto be tracked.app_reviews_feedback_screen_openedto be tracked.app_reviews_feedback_sent <feedback: The app should be improved!>to be tracked.Cancel Negative Feedback Flow
app_reviews_declined_to_rate_appto be trackedapp_reviews_feedback_screen_canceledto be trackedRegression: Compliance Popover
CompliancePopoverCoordinator.swiftdefaults.didShowCompliancePopuptrue.Regression Notes
Potential unintended areas of impact
The presentation of the Compliance Popover should be tested.
What I did to test those areas of impact (or what existing automated tests I relied on)
Manual Testing.
What automated tests I added (or what prevented me from doing so)
None.
PR submission checklist:
RELEASE-NOTES.txtif necessary.UI Changes testing checklist: