9.7 C
New York
Tuesday, March 25, 2025

ios – URLSession backgroundSession not invoking urlSessionDidFinishEvents or handleEventsForBackgroundURLSession


I’m scuffling with backgroundSession.Set capabilities to background fetch and processing, set backgroundSession with URLSessionConfiguration.background.backgroundSession?.uploadTask is sweet, and

func urlSession(_ session: URLSession, activity: URLSessionTask, didCompleteWithError error: Error?)` is invoked ultimately with out error.

When attempting to check background (placing the app within the background), urlSessionDidFinishEvents(forBackgroundURLSession is just not triggered nor handleEventsForBackgroundURLSession in appDelegate.
(checked additionally getting back from background to foreground)

What am I lacking?

class AppDelegate: NSObject, UIApplicationDelegate {
    let gcmMessageIDKey = "gcm.message_id"
    var backgroundCompletionHandler: (() -> Void)?
    var userService: UserService?

    func software(_ software: UIApplication,
                     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        FirebaseApp.configure()
        FirebaseConfiguration.shared.setLoggerLevel(FirebaseLoggerLevel.debug)

        Messaging.messaging().delegate = self
        UNUserNotificationCenter.present().delegate = self
        
        AnalyticsManager.startMonitoringNetwork()

        return true
    }
    
    func software(_ software: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                       fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
          
          if let messageID = userInfo[gcmMessageIDKey] {
              print("Message ID software: (messageID)")
          }
          
          print(userInfo)
          
          completionHandler(UIBackgroundFetchResult.newData)
      }
    
    func software(_ app: UIApplication,
                     open url: URL,
                     choices: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
        return GIDSignIn.sharedInstance.deal with(url)
    }
    
    func software(_ software: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
        print("☠️ handleEventsForBackgroundURLSession")
        userService?.apiClient.backgroundSessionCompletionHandler = completionHandler
    }
}
@essential
struct WhisperApp: App {
    var userService = UserService()
    @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
    @StateObject var appCoordinator = AppCoordinator()
    @ObservedObject var loginViewModel = LogInViewModel()

    var physique: some Scene {
        WindowGroup {
            if appCoordinator.isLoggedIn {
                CustomTabBarView(userService: userService)
                    .environmentObject(appCoordinator)
                
            } else {
                LoginView(viewModel: loginViewModel)
                    .environmentObject(appCoordinator)
                    .onAppear {
                       delegate.userService = userService
              }
            }
            
        }
        
    }
}
last class URLSessionAPIClient: NSObject, ServiceProtocol {
    non-public let progress: PassthroughSubject<(id: Int, progress: Double), By no means> = .init()
    non-public var session: URLSession
    non-public var baseUrl: String
    non-public var decoder: JSONDecoder
    non-public let tokenExpiredSubject = PassthroughSubject()
    non-public var cancellables = Set()
    non-public var retryCount = 0
    non-public var fireBaseManager: FireBaseManager
    non-public var backgroundSession: URLSession?
    non-public var tempFileURL: URL?
    non-public var topic = PassthroughSubject()
    
    var tokenExpiredPublisher: AnyPublisher {
        tokenExpiredSubject.eraseToAnyPublisher()
    }
    
    var backgroundSessionCompletionHandler: (() -> Void)?
    
    
    init(
        baseUrl: String = GlobalConstants.baseURL,
        sessionConfiguration: URLSessionConfiguration = .default,
        decoder: JSONDecoder = JSONDecoder(),
        firebaseManager: FireBaseManager = FireBaseManager()
    ) {
        
        self.baseUrl = baseUrl
        self.session = URLSession(configuration: sessionConfiguration)
        self.decoder = decoder
        self.fireBaseManager = firebaseManager
        tremendous.init()
        
        let backgroundConfig = URLSessionConfiguration.background(withIdentifier: "backgroundUpload")
        
        self.backgroundSession = URLSession(configuration: backgroundConfig, delegate: self, delegateQueue: nil)
        
    }
    
    non-public func saveRequestBodyToTemporaryFile(_ physique: Information) throws -> URL {
        let tempDirectory = FileManager.default.temporaryDirectory
        let tempFileURL = tempDirectory.appendingPathComponent(UUID().uuidString)
        attempt physique.write(to: tempFileURL)
        
        self.tempFileURL = tempFileURL
        return tempFileURL
    }
    
    non-public func uploadLargeFile2(request: URLRequest, fileURL: URL) -> AnyPublisher {
        print("☠️ inside uploadLargeFile with native URLSession")
        
        let topic = PassthroughSubject()
        
        
        guard let httpBody = request.httpBody else {
            return Fail(error: NSError(area: "URLError", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"]))
                .eraseToAnyPublisher()
        }
        
        guard let temporaryFileURL = attempt? saveRequestBodyToTemporaryFile(httpBody) else {
            return Fail(error: NSError(area: "URLError", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"]))
                .eraseToAnyPublisher()
        }
        var requestTest = request
        requestTest.httpBody = nil
        
        let activity = backgroundSession?.uploadTask(with: requestTest, fromFile: temporaryFileURL)
        
        
        activity?.resume()
        
        return topic
            .decode(kind: R.self, decoder: decoder)
            .eraseToAnyPublisher()
    }
    
    non-public func sendDataTask(request: URLRequest) -> AnyPublisher {
        return session.dataTaskPublisher(for: request)
            .subscribe(on: DispatchQueue.world(qos: .background))
            .tryMap { [weak self] knowledge, response -> R in
                guard let self, let httpResponse = response as? HTTPURLResponse else {
                    throw APIError.invalidResponse
                }
                
                if httpResponse.statusCode == 401 {
                    throw APIError.expiredToken
                }
                
                guard (200...299).accommodates(httpResponse.statusCode) else {
                    throw APIError.invalidResponse
                }
                
                retryCount = 0
                return attempt self.decoder.decode(R.self, from: knowledge)
            }
            .tryCatch { [weak self] error -> AnyPublisher in
                guard let self else { throw error }
                
                if error as? APIError == .expiredToken, retryCount < 3 {
                    retryCount += 1
                    return self.refreshTokenAndRetryLarge(request: request)
                }
                
                throw error
            }
            .eraseToAnyPublisher()
    }
    
    func sendRequest(_ request: T) -> AnyPublisher {
        guard let urlRequest = urlRequest(from: request, baseURL: baseUrl) else {
            return Fail(error: APIError.invalidData)
                .eraseToAnyPublisher()
        }
        
        
        let isLargeFileR = request.urlPath.flatMap { isLargeFile(url: $0) } ?? false
        
        
        if isLargeFileR {
            // Use the trail because the file URL
            guard URL(string: request.path) != nil else {
                return Fail(error: APIError.invalidData).eraseToAnyPublisher()
            }
            guard let file = request.urlPath else { return Fail(error: APIError.invalidData)
                .eraseToAnyPublisher()}
            print("☠️ uploadLargeFile")
            
            return uploadLargeFile2(request: urlRequest, fileURL: file)
            // return uploadLargeFile(request: urlRequest, fileURL: file)
        } else {
            print("☠️ sendDataTask")
            
            return sendDataTask(request: urlRequest)
        }
    }
    
    non-public func refreshTokenAndRetryLarge(request: URLRequest) -> AnyPublisher {
        print("☠️ refreshTokenAndRetryLarge refreshTokenAndRetry")
        
        // Try to refresh the token
        return refreshToken()
            .flatMap { [weak self] _ -> AnyPublisher in
                guard let self else {
                    return Fail(error: APIError.invalidData).eraseToAnyPublisher()
                }
                
                // As soon as token is refreshed, retry the unique request
                return self.sendDataTask(request: request)
            }
            .eraseToAnyPublisher()
    }
    
    non-public func refreshTokenAndRetry(request: T) -> AnyPublisher {
        print("☠️ refreshTokenAndRetry")
        
        return refreshToken()
            .flatMap { [weak self] _ -> AnyPublisher in
                guard let self else {
                    return Fail(error: APIError.invalidData).eraseToAnyPublisher()
                }
                
                return self.sendRequest(request)
            }
            .eraseToAnyPublisher()
    }
    
    non-public func refreshToken() -> AnyPublisher {
        return Future { promise in
            let currentUser = Auth.auth().currentUser
            print("☠️ refreshToken")
            
            currentUser?.getIDTokenForcingRefresh(true) { idToken, error in
                if error != nil {
                    promise(.failure(APIError.invalidData))
                } else if let token = idToken {
                    CredentialManager.shared.setValue(token, kind: .token)
                    promise(.success(()))
                } else {
                    promise(.failure(APIError.invalidData))
                }
            }
        }
        .eraseToAnyPublisher()
    }
    
    
}
non-public extension URLSessionAPIClient {
    func urlRequest(from request: any HTTPRequest, baseURL: String) -> URLRequest? {
        guard let url = URL(string: baseURL + request.path) else {
            return nil
        }
        var urlRequest = URLRequest(url: url)
        urlRequest.timeoutInterval = GlobalConstants.requestTimeoutInterval
        urlRequest.httpMethod = request.methodology.rawValue
        request.headers?.forEach { urlRequest.addValue($0.worth, forHTTPHeaderField: $0.key) }
        
        if request.authRequirement == .requiresAuth {
            urlRequest.addValue(CredentialManager.shared.getValue(.token), forHTTPHeaderField: GlobalConstants.googleTokenHeaderKey)
            urlRequest.addValue(CredentialManager.shared.getValue(.gadget), forHTTPHeaderField: GlobalConstants.deviceTokenHeaderKey)
            urlRequest.addValue(CredentialManager.shared.getValue(.electronic mail), forHTTPHeaderField: GlobalConstants.userEmailKey)
        }
        
        urlRequest.httpBody = request.physique
        if let httpBody = urlRequest.httpBody {
            urlRequest.setValue("(String(describing: httpBody.rely))", forHTTPHeaderField: "Content material-Size")
        }
        
        return urlRequest
    }
}
extension URLSessionAPIClient {
    func isLargeFile(url: URL) -> Bool {
        // Examine if the request accommodates a legitimate file URL within the path
        
        // In any other case, examine the file dimension for big information
        do {
            let fileSize = attempt FileManager.default.attributesOfItem(atPath: url.path)[.size] as? NSNumber
            if let dimension = fileSize, dimension.intValue > 200 * 1024 * 1024 { // 200MB
                return true
            }
        } catch {
            print("Error checking file dimension: (error)")
        }
        
        return false
    }
}
extension URLSessionAPIClient: URLSessionDelegate, URLSessionTaskDelegate, 
     URLSessionDataDelegate {
    // Deal with the completion of the duty
    func urlSession(_ session: URLSession, activity: URLSessionTask, didCompleteWithError 
        error: Error?) {
        if let error = error {
            print("☠️ Add failed with error: (error.localizedDescription)")
            // Deal with the error right here (e.g., notify the consumer, retry the add, and so on.)
        } else {
            print("☠️ Add accomplished efficiently.")
            // You may deal with the profitable add right here
        }
        
        if let tempFileURL = self.tempFileURL {
            attempt? FileManager.default.removeItem(at: tempFileURL)
        }
        
        // Name the background session completion handler to complete the duty
        if let completionHandler = backgroundSessionCompletionHandler {
            completionHandler()
        }
    }
    
    func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
        print("☠️ Background duties completed")
        self.backgroundSessionCompletionHandler?()
        self.backgroundSessionCompletionHandler = nil
        
    }
    
    func urlSession(_ session: URLSession, uploadTask: URLSessionUploadTask, didSendBodyData bytesSent: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
        let progress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)
        print("☠️ Add Progress: (progress * 100)%")
    }
}

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles