Hockey app did crash on last session - swift

I'm using this function to detect if the app did crashed on last session, but in always returns the same result, no matter how and where i put "fatalError", or any other errors. What i'm doing wrong?
private func didCrashInLastSessionOnStartup() -> Bool {
//returns false
NSLog("\(BITHockeyManager.sharedHockeyManager().crashManager.didCrashInLastSession)")
//returns -1
NSLog("\(BITHockeyManager.sharedHockeyManager().crashManager.timeIntervalCrashInLastSessionOccurred)")
return (BITHockeyManager.sharedHockeyManager().crashManager.didCrashInLastSession) &&
(BITHockeyManager.sharedHockeyManager().crashManager.timeIntervalCrashInLastSessionOccurred < 5)
}
Here is my didFinishLaunchingWithOptions:
BITHockeyManager.sharedHockeyManager().configureWithIdentifier("<id>", delegate: self)
BITHockeyManager.sharedHockeyManager().crashManager.crashManagerStatus = .AutoSend;
BITHockeyManager.sharedHockeyManager().debugLogEnabled = true
BITHockeyManager.sharedHockeyManager().startManager()
BITHockeyManager.sharedHockeyManager().authenticator.authenticateInstallation();
if self.didCrashInLastSessionOnStartup() {
NSLog("Crashed on last session")
} else {
self.setupApplication()
}
And my delegate functions:
func crashManagerWillCancelSendingCrashReport(crashManager: BITCrashManager!) {
if self.didCrashInLastSessionOnStartup() {
self.setupApplication()
}
}
func crashManager(crashManager: BITCrashManager!, didFailWithError error: NSError!) {
if self.didCrashInLastSessionOnStartup() {
self.setupApplication()
}
}
func crashManagerDidFinishSendingCrashReport(crashManager: BITCrashManager!) {
if self.didCrashInLastSessionOnStartup() {
self.setupApplication()
}
}

The problem is that you are using additional 3rd party SDKs which incorporate a crash reporting feature and initialize those after the HockeySDK in your code. (Found that out via your support request and that information was never part of your question describing the situation)
You can only use one 3rd party crash reporting library in your app, the last one you initialize will always be the only one that works.

Related

Swift How to handle Auto-renewable Subscription receipt and validation

I am testing the auto renewable In-app purchases in swift, I found out that there are some strange problems with my code.
I am testing these functions in sandbox environment
User can purchase either one month, one year auto renewable subscription or permanent permission
App should check if the subscription is still valid every time when user open app, if not, lock all premium functions
User is able to restore the purchased plan, app should get the previous purchased type ie. one month, one year, or permanent.
After long research on the tutorials, I am still confused about the validation
I see that there are two ways to validate receipt, one is locally the other is on the server.
But I don't have a server, does that mean I can only validate it locally
Every time the auto-renewal subscription expires, the local receipt is not updated, so when I reopen the app I got a subscription expiration alert (The method I defined by my self for validation check ), when I click the restore button, the app restored successfully and receipt was updated
After 6 times manually restored and refresh the receipt (the sandbox user can only renew 6 times), when I click the restore button, the part transaction == .purchased is till called, and my app unlocks premium function, however when I reopen my app, my app alerts that the subscription is expired, which is it should.
My core problem is how can I check the validation of subscriptions with Apple every time when I open the app, I don't have a server, and I don't know why the receipt is not refreshing automatically
Here are some parts of my code, I call checkUserSubsriptionStatus() when I open the app, I am using TPInAppReceipt Library
class InAppPurchaseManager {
static var shared = InAppPurchaseManager()
init() {
}
public func getUserPurchaseType() -> PurchaseType {
if let receipt = try? InAppReceipt.localReceipt() {
var purchaseType: PurchaseType = .none
if let purchase = receipt.lastAutoRenewableSubscriptionPurchase(ofProductIdentifier: PurchaseType.oneMonth.productID) {
purchaseType = .oneMonth
}
if let purchase = receipt.lastAutoRenewableSubscriptionPurchase(ofProductIdentifier: PurchaseType.oneYear.productID) {
purchaseType = .oneYear
}
if receipt.containsPurchase(ofProductIdentifier: PurchaseType.permanent.productID) {
purchaseType = .permanent
}
return purchaseType
} else {
print("Receipt not found")
return .none
}
}
public func restorePurchase(in viewController: SKPaymentTransactionObserver) {
SKPaymentQueue.default().add(viewController)
if SKPaymentQueue.canMakePayments() {
SKPaymentQueue.default().restoreCompletedTransactions()
} else {
self.userIsNotAbleToPurchase()
}
}
public func checkUserSubsriptionStatus() {
DispatchQueue.main.async {
if let receipt = try? InAppReceipt.localReceipt() {
self.checkUserPermanentSubsriptionStatus(with: receipt)
}
}
}
private func checkUserPermanentSubsriptionStatus(with receipt: InAppReceipt) {
if let receipt = try? InAppReceipt.localReceipt() { //Check permsnent subscription
if receipt.containsPurchase(ofProductIdentifier: PurchaseType.permanent.productID) {
print("User has permament permission")
if !AppEngine.shared.currentUser.isVip {
self.updateAfterAppPurchased(withType: .permanent)
}
} else {
self.checkUserAutoRenewableSubsrption(with: receipt)
}
}
}
private func checkUserAutoRenewableSubsrption(with receipt: InAppReceipt) {
if receipt.hasActiveAutoRenewablePurchases {
print("Subsription still valid")
if !AppEngine.shared.currentUser.isVip {
let purchaseType = InAppPurchaseManager.shared.getUserPurchaseType()
updateAfterAppPurchased(withType: purchaseType)
}
} else {
print("Subsription expired")
if AppEngine.shared.currentUser.isVip {
self.subsrptionCheckFailed()
}
}
}
private func updateAfterAppPurchased(withType purchaseType: PurchaseType) {
AppEngine.shared.currentUser.purchasedType = purchaseType
AppEngine.shared.currentUser.energy += 5
AppEngine.shared.userSetting.hasViewedEnergyUpdate = false
AppEngine.shared.saveUser()
AppEngine.shared.notifyAllUIObservers()
}
public func updateAfterEnergyPurchased() {
AppEngine.shared.currentUser.energy += 3
AppEngine.shared.saveUser()
AppEngine.shared.notifyAllUIObservers()
}
public func purchaseApp(with purchaseType: PurchaseType, in viewController: SKPaymentTransactionObserver) {
SKPaymentQueue.default().add(viewController)
if SKPaymentQueue.canMakePayments() {
let paymentRequest = SKMutablePayment()
paymentRequest.productIdentifier = purchaseType.productID
SKPaymentQueue.default().add(paymentRequest)
} else {
self.userIsNotAbleToPurchase()
}
}
public func purchaseEnergy(in viewController: SKPaymentTransactionObserver) {
SKPaymentQueue.default().add(viewController)
let productID = "com.crazycat.Reborn.threePointOfEnergy"
if SKPaymentQueue.canMakePayments() {
let paymentRequest = SKMutablePayment()
paymentRequest.productIdentifier = productID
SKPaymentQueue.default().add(paymentRequest)
} else {
self.userIsNotAbleToPurchase()
}
}
}
If you do not have the possibility to use a server, you need to validate locally. Since you are already included TPInAppReceipt library, this is relatively easy.
To check if the user has an active premium product and what type it has, you can use the following code:
// Get all active purchases which are convertible to `PurchaseType`.
let premiumPurchases = receipt.activeAutoRenewableSubscriptionPurchases.filter({ PurchaseType(rawValue: $0.productIdentifier) != nil })
// It depends on how your premium access works, but if it doesn't matter what kind of premium the user has, it is enough to take one of the available active premium products.
// Note: with the possibility to share subscriptions via family sharing, the receipt can contain multiple active subscriptions.
guard let product = premiumPurchases.first else {
// User has no active premium product => lock all premium features
return
}
// To be safe you can use a "guard" or a "if let", but since we filtered for products conforming to PurchaseType, this shouldn't fail
let purchaseType = PurchaseType(rawValue: product.productIdentifier)!
// => Setup app corresponding to active premium product type
One point I notice in your code, which could lead to problems, is that you constantly add a new SKPaymentTransactionObserver. You should have one class conforming to SKPaymentTransactionObserver and add this only once on app start and not on every public call. Also, you need to remove it when you no longer need it (if you created it only once, you would do it in the deinit of your class, conforming to the observer protocol.
I assume this is the reason for point 2.
Technically, the behavior described in point 3 is correct because the method you are using asks the payment queue to restore all previously completed purchases (see here).
Apple states restoreCompletedTransactions() should only be used for the following scenarios (see here):
If you use Apple-hosted content, restoring completed transactions gives your app the transaction objects it uses to download the content.
If you need to support versions of iOS earlier than iOS 7, where the app receipt isn’t available, restore completed transactions instead.
If your app uses non-renewing subscriptions, your app is responsible for the restoration process.
For your case, it is recommended to use a SKReceiptRefreshRequest, which requests to update the current receipt.
Get the receipt every time when the app launches by calling the method in AppDelegate.
getAppReceipt(forTransaction: nil)
Now, below is the required method:
func getAppReceipt(forTransaction transaction: SKPaymentTransaction?) {
guard let receiptURL = receiptURL else { /* receiptURL is nil, it would be very weird to end up here */ return }
do {
let receipt = try Data(contentsOf: receiptURL)
receiptValidation(receiptData: receipt, transaction: transaction)
} catch {
// there is no app receipt, don't panic, ask apple to refresh it
let appReceiptRefreshRequest = SKReceiptRefreshRequest(receiptProperties: nil)
appReceiptRefreshRequest.delegate = self
appReceiptRefreshRequest.start()
// If all goes well control will land in the requestDidFinish() delegate method.
// If something bad happens control will land in didFailWithError.
}
}
Here is the method receiptValidation:
func receiptValidation(receiptData: Data?, transaction: SKPaymentTransaction?) {
guard let receiptString = receiptData?.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0)) else { return }
verify_in_app_receipt(with_receipt_string: receiptString, transaction: transaction)
}
Next is the final method that verifies receipt and gets the expiry date of subscription:
func verify_in_app_receipt(with_receipt_string receiptString: String, transaction: SKPaymentTransaction?) {
let params: [String: Any] = ["receipt-data": receiptString,
"password": "USE YOUR PASSWORD GENERATED FROM ITUNES",
"exclude-old-transactions": true]
// Below are the url's used for in app receipt validation
//appIsInDevelopment ? "https://sandbox.itunes.apple.com/verifyReceipt" : "https://buy.itunes.apple.com/verifyReceipt"
super.startService(apiType: .verify_in_app_receipt, parameters: params, files: [], modelType: SubscriptionReceipt.self) { (result) in
switch result {
case .Success(let receipt):
if let receipt = receipt {
print("Receipt is: \(receipt)")
if let _ = receipt.latest_receipt, let receiptArr = receipt.latest_receipt_info {
var expiryDate: Date? = nil
for latestReceipt in receiptArr {
if let dateInMilliseconds = latestReceipt.expires_date_ms, let product_id = latestReceipt.product_id {
let date = Date(timeIntervalSince1970: dateInMilliseconds / 1000)
if date >= Date() {
// Premium is valid
}
}
}
if expiryDate == nil {
// Premium is not purchased or is expired
}
}
}
case .Error(let message):
print("Error in api is: \(message)")
}
}
}

Why am I still able to fetch data, even with deleting FireStore object in Swift?

I deleted an entry in the Firestore and also checked it manually to confirm that. However, as long as I do not close the application, I can send a request to fetch the data and I still get the result. This should not be the case.
If you imagine having a shared photo with some textual information and you delete those information, this would mean, other users can still see the textual information (fetched from the Firestore) but not the image anymore (store in Firestorage).
I want to display a message on the UI, something like "The content does not exist anymore".
How I can achieve that? I used the following approach so far but it does not work at the moment:
public func checkIfChallengeObjectExists(completionHandler:#escaping(Bool)->(), challengeId:String) {
CHALLENGE_COLLECTION?.document(challengeId).getDocument(completion: { (querySnapshot, error) in
if (error != nil) {
print(error?.localizedDescription as Any)
}
if (querySnapshot?.documentID == "" || querySnapshot!.metadata.isFromCache) {
completionHandler(false)
}
else {
completionHandler(true)
}
})
}
Any solutions?
Non-existent documents will still return document snapshots, but they will be empty. Therefore, you must check the contents of the snapshot for the document, not the snapshot itself. Also, you should handle errors and the overall flow of the return better.
public func checkIfChallengeObjectExists(completionHandler:#escaping(Bool)->(), challengeId:String) {
CHALLENGE_COLLECTION?.document(challengeId).getDocument(completion: { (querySnapshot, error) in
if let doc = querySnapshot,
doc.exists {
completionHandler(true) // only one possible true condition
} else {
if let error = error {
print(error.localizedDescription)
}
completionHandler(false) // all else false
}
})
}
As a side note, I recommend reordering the parameters of the function to make it easier to read when called (conventionally, the completion handler comes last) and giving the boolean argument a name so it's easier to read when referencing (sometime later or by other developers).
public func verifyChallengeObject(ID: String, _ completion: #escaping (_ exists: Bool) -> Void) {
...
}
verifyChallengeObject(ID: "abc123", { (exists) in
if exists {
...
} else {
...
}
})

UICollectionView.reloadData() Design Error

When i did cv.reloadData my cells is broken.
First
Last
When i write answer and click send button run this func.
func checkPlay(playedWord : String) {
if WordCheck(word: playedWord) {
DispatchQueue.main.async {
self.collectionView.reloadData()
}
if isGameOver() {
//alert end save user default if score is max? and do proccess
}
}
}
If i get to comment cv.reloadData() func. My design is be safe but not writing answer.
I'm so sory for my bad English

GoogleCast iOS sender, v4, not sending messages

On version 2, the sender app was able to send messages.
func deviceManager(_ deviceManager: GCKDeviceManager!,
didConnectToCastApplication
applicationMetadata: GCKApplicationMetadata!,
sessionID: String!,
launchedApplication: Bool) {
deviceManager.add(self.textChannel)
}
However, the API says that we are now using GCKSessionManager instead of GCKDeviceManager.
The API says I must have a GCKSession add the textChannel, which I did here:
Once the session starts, I add the textChannel (because sessionManager.currentCastSession was nil before the session started).
func sessionManager(_ sessionManager: GCKSessionManager, didStart session: GCKSession) {
if session.device == connectionQueue {
connectionQueue = nil
}
self.sessionManager!.currentCastSession!.add(textChannel)
print("")
}
Meanwhile, I send the text message in another function:
let result = self.textChannel.sendTextMessage("\(self.textField.text)", error: &error)
But the result is always false, and the error is always "Channel is not connected or is not registered with a session".
In addition, when I do:
print("isConnected1 \(self.textChannel.isConnected)")
the result is false.
Do you know what other steps I am missing for it to be connected?
Just learned that it was an issue of my namespace. It connects now.
Problem was the namespace wasn't matching the namespace from my receiver code.
fileprivate lazy var textChannel:TextChannel = {
return TextChannel(namespace: NAMESPACE)
}()

Is it iCloud or is it my code?

I am using a slightly updated code of this question: Method for downloading iCloud files? Very confusing?
Here is an excerpt of the code:
private func downloadUbiquitiousItem(atURL url: URL) -> Void {
do {
try FileManager.default.startDownloadingUbiquitousItem(at: url)
do {
let attributes = try url.resourceValues(forKeys: [URLResourceKey.ubiquitousItemDownloadingStatusKey])
if let status: URLUbiquitousItemDownloadingStatus = attributes.allValues[URLResourceKey.ubiquitousItemDownloadingStatusKey] as? URLUbiquitousItemDownloadingStatus {
if status == URLUbiquitousItemDownloadingStatus.current {
self.processDocument(withURL: url)
return
} else if status == URLUbiquitousItemDownloadingStatus.downloaded {
self.processDocument(withURL: url)
return
} else if status == URLUbiquitousItemDownloadingStatus.notDownloaded {
do {
//will go just fine, if it is unnecessary to download again
try FileManager.default.startDownloadingUbiquitousItem(at: url)
} catch {
return
}
}
}
} catch {
}
//only happens if the try does not fail
self.documentsQuery = NSMetadataQuery()
self.documentsQuery.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]
self.documentsQuery.valueListAttributes = [NSMetadataUbiquitousItemPercentDownloadedKey,NSMetadataUbiquitousItemDownloadingStatusKey]
self.documentsQuery.predicate = NSPredicate(format: "%K like 'backup.json'", argumentArray: [NSMetadataItemFSNameKey])
NotificationCenter.default.addObserver(self, selector: #selector(self.queryUpdate(notification:)), name: NSNotification.Name.NSMetadataQueryDidUpdate, object: self.documentsQuery)
DispatchQueue.main.async {
if self.documentsQuery.start() {
if self.restoreHUD != nil {
self.restoreHUD.show(animated: true)
//timeout for restoring
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(30), execute: {
if self.restoreHUD != nil {
self.restoreHUD.hide(animated: true)
}
})
}
}
}
} catch {
//file does not exist in icloud most likely
}
}
So this works sometimes, but it is really unstable, for example we tested the following cases:
Backup to iCloud
Check that we have a valid document in Settings -> iCloud -> Storage -> Manage Storage -> MyApp -> backup.json
Force a first launch, so that the app restores backup.json (aka executes the code above)
This sometimes works and sometimes doesn't. Sometimes the query won't update.
We also tested the following scenario:
Remove backup from iCloud manually via settings
Uninstall the app and reinstall it to provide a first launch
the startDownloadingUbiquitousItem function does not seem to throw, even though nothing is in iCloud because I think that iCloud still hasn't synced the local file or deleted the local data, but it also does not download properly... yet the status is notDownloaded.
Maybe users are not supposed to wipe the stuff via Settings? I'd like to know if my code is missing a case that could happen, or if this API is just really unhandy for developers...
Thanks!
Probably, we call to adding notification in "main thread".
NotificationCenter.default.addObserver(self, selector: #selector(self.queryUpdate(notification:)), name: NSNotification.Name.NSMetadataQueryDidUpdate, object: self.documentsQuery)