Last active
April 5, 2020 14:42
-
-
Save diamantidis/7c615a886acb04b87428a81f9c1029b8 to your computer and use it in GitHub Desktop.
Code snippets for the post 'Help your user rate and review your iOS app!' (https://diamantidis.github.io/2020/04/05/app-reviews-for-iOS-apps)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
enum StorageKey: String { | |
case lastVersionPromptedReview | |
case dayOfFirstAction | |
case actionCount | |
} | |
protocol Storage { | |
func integer(forKey key: StorageKey) -> Int | |
func set(_ value: Int, forKey key: StorageKey) | |
func object(forKey key: StorageKey) -> Any? | |
func set(_ value: NSDate, forKey key: StorageKey) | |
func string(forKey key: StorageKey) -> String? | |
func set(_ value: String, forKey key: StorageKey) | |
} | |
extension UserDefaults: Storage { | |
func integer(forKey key: StorageKey) -> Int { | |
return self.integer(forKey: key.rawValue) | |
} | |
func set(_ value: Int, forKey key: StorageKey) { | |
self.set(value, forKey: key.rawValue) | |
} | |
func object(forKey key: StorageKey) -> Any? { | |
return self.object(forKey: key.rawValue) | |
} | |
func set(_ value: NSDate, forKey key: StorageKey) { | |
self.set(value, forKey: key.rawValue) | |
} | |
func string(forKey key: StorageKey) -> String? { | |
return self.string(forKey: key.rawValue) | |
} | |
func set(_ value: String, forKey key: StorageKey) { | |
self.set(value, forKey: key.rawValue) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
protocol BundleType { | |
func object(forInfoDictionaryKey key: String) -> Any? | |
} | |
extension Bundle: BundleType {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
class InAppReviewManager { | |
// MARK: - Initializers | |
init(storage: Storage = UserDefaults.standard, bundle: BundleType = Bundle.main) { | |
self.storage = storage | |
self.bundle = bundle | |
} | |
// MARK: - Public methods | |
func shouldAskForReview() -> Bool { | |
// Count the number of actions | |
var actionCount = storage.integer(forKey: .actionCount) | |
actionCount += 1 | |
storage.set(actionCount, forKey: .actionCount) | |
// Store the day of first action | |
let dateOfFirstActionObj = storage.object(forKey: .dayOfFirstAction) | |
if dateOfFirstActionObj == nil { | |
storage.set(NSDate(), forKey: .dayOfFirstAction) | |
} | |
// Calculate if there have been three weeks since the first action | |
let threeWeeksInSeconds: TimeInterval = 3 * 7 * 24 * 60 * 60 | |
let threeWeeksAgo = Date(timeIntervalSinceNow: -threeWeeksInSeconds) | |
guard let dateOfFirstAction = dateOfFirstActionObj as? Date else { | |
return false | |
} | |
let comparisonResult = dateOfFirstAction.compare(threeWeeksAgo) | |
// Check if user has reviewed this version | |
let bundleVersionKey = kCFBundleVersionKey as String | |
guard let bundleVersion = bundle.object(forInfoDictionaryKey: bundleVersionKey) as? String else { | |
return false | |
} | |
let lastVersion = storage.string(forKey: .lastVersionPromptedReview) | |
guard case .orderedAscending = comparisonResult, actionCount >= 3, lastVersion == nil || lastVersion != bundleVersion else { | |
return false | |
} | |
storage.set(bundleVersion, forKey: .lastVersionPromptedReview) | |
return true | |
} | |
// MARK: - Private properties | |
private let storage: Storage | |
private let bundle: BundleType | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
import StoreKit | |
... | |
let manager = InAppReviewManager() | |
if manager.shouldAskForReview() { | |
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2)) { | |
SKStoreReviewController.requestReview() | |
} | |
} | |
... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
@testable import In_App_Review_Demo | |
class MockStorage: Storage { | |
var actionCount: Int? | |
var setActionCallback: ((_ count: Int)->Void)? | |
var dayOfFirstAction: NSDate? | |
var setDayCallback: ((_ day: NSDate)->Void)? | |
var latestVersion: String? | |
var setVersionCallback: ((_ version: String)->Void)? | |
func integer(forKey key: StorageKey) -> Int { | |
return actionCount ?? -1 | |
} | |
func set(_ value: Int, forKey key: StorageKey) { | |
setActionCallback?(value) | |
} | |
func object(forKey key: StorageKey) -> Any? { | |
return dayOfFirstAction | |
} | |
func set(_ value: NSDate, forKey key: StorageKey) { | |
setDayCallback?(value) | |
} | |
func string(forKey key: StorageKey) -> String? { | |
return latestVersion | |
} | |
func set(_ value: String, forKey key: StorageKey) { | |
setVersionCallback?(value) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
@testable import In_App_Review_Demo | |
class MockBundle: BundleType { | |
var version: String? | |
func object(forInfoDictionaryKey key: String) -> Any? { | |
return version | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import XCTest | |
@testable import In_App_Review_Demo | |
class InAppReviewManagerTests: XCTestCase { | |
// MARK: - Overriden methods | |
override func setUp() { | |
storage = MockStorage() | |
bundle = MockBundle() | |
sut = InAppReviewManager(storage: storage, bundle: bundle) | |
} | |
// MARK: - Test methods | |
func testFirstTimeAction__ShouldNotShowReview() { | |
// Arrange | |
storage.actionCount = 0 | |
storage.dayOfFirstAction = nil | |
storage.latestVersion = nil | |
bundle.version = "1" | |
let actionCountExpectation = expectation(description: "func `set(value:Int, forKey: StorageKey)` is called") | |
storage.setActionCallback = { _ in | |
actionCountExpectation.fulfill() | |
} | |
let dayExpectation = expectation(description: "func `set(value:NSDate, forKey: StorageKey)` is called") | |
storage.setDayCallback = { _ in | |
dayExpectation.fulfill() | |
} | |
let versionExpectation = expectation(description: "func `set(value:String, forKey: StorageKey)` is called") | |
versionExpectation.isInverted = true | |
storage.setVersionCallback = { _ in | |
versionExpectation.fulfill() | |
} | |
// Act | |
let response = sut.shouldAskForReview() | |
// Assert | |
XCTAssertFalse(response) | |
waitForExpectations(timeout: 1, handler: nil) | |
} | |
func testThirdActionAfterThreeWeeksOnNewVersion__ShouldShowReview() { | |
// Arrange | |
storage.actionCount = 2 | |
storage.dayOfFirstAction = NSDate(timeIntervalSinceNow: -3 * 7 * 24 * 60 * 60 ) | |
storage.latestVersion = "1" | |
bundle.version = "2" | |
let actionCountExpectation = expectation(description: "func `set(value:Int, forKey: StorageKey)` is called") | |
storage.setActionCallback = { _ in | |
actionCountExpectation.fulfill() | |
} | |
let dayExpectation = expectation(description: "func `set(value:NSDate, forKey: StorageKey)` is called") | |
dayExpectation.isInverted = true | |
storage.setDayCallback = { _ in | |
dayExpectation.fulfill() | |
} | |
let versionExpectation = expectation(description: "func `set(value:String, forKey: StorageKey)` is called") | |
storage.setVersionCallback = { _ in | |
versionExpectation.fulfill() | |
} | |
// Act | |
let response = sut.shouldAskForReview() | |
// Assert | |
XCTAssertTrue(response) | |
waitForExpectations(timeout: 1, handler: nil) | |
} | |
// MARK: - Private properties | |
private var sut: InAppReviewManager! | |
private var storage: MockStorage! | |
private var bundle: MockBundle! | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment