Native code runs fine on simulator and on macOS build but doesn't run at all on iOS build - Expo app

My apologies if the title is a bit ambiguous but I am honestly not sure if the issue is related to the native code not being executed, a DispatchQueue async task not being executed or an http request not being executed.

First of all, the Expo SDK version is 47.0.14.

Basically I have defined a function to send a POST request to an endpoint containing an image and some other data; that request is executed from another thread using the dispatch queue. When I build the app for macOs or when I test it on the simulator it works as intended (the server receives the data without issues), but when I build the app with EAS and release it on TestFlight it doesn’t work anymore (it doesn’t crash, it just doesn’t make the post request).

As you can see (the code is at the bottom) I added a lot of debug prints and os_log calls to be sure of the following three things:

  1. the function sendPhoto is actually called
  2. the async task is actually executed
  3. the post request is actually sent

I didn’t see any of the prints and logs in both the iPhone logs archive and console, so I started to believe that it fails at calling the function, but that should make the app crash when I call it from the javascript code.

The native module is written in Swift and I wrote the bindings for objective-C, in fact as I specified earlier, it works just fine on macOs and on the simulator.

I tested the build on an iPhone 12 Pro, an iPhone 14 Pro and an iPhone 7, all running the latest iOS version.

I was thinking that maybe I am missing some configuration that is needed by EAS (the online building platform of Expo) to properly link my native module to the final build? I didn’t find anything in the documentation :confused:

If you need more details please let me know, and if you need me to do some more debugging feel free to ask.
Meanwhile thanks for your help, very much appreciated.

@objc public func sendPhoto(
    _ token: String,
    photoName: String,
    metadata: String,
    sender: String,
    resolve: @escaping RCTPromiseResolveBlock,
    rejecter reject: @escaping RCTPromiseRejectBlock
  ) -> Void {
    
    let log = OSLog(subsystem: "it.myorg.manager", category: "ManagerDebugging")
    print("entered sendPhoto function")
    os_log("entered sendPhoto function", log: log, type: .default)
    DispatchQueue.global(qos: .userInitiated).async {
      print("entered dispatch queue")
      os_log("entered dispatch queue", log: log, type: .default)
      semaphore.wait()
      sentImagesCounter += 1
      semaphore.signal()
      print("incremented photos counter")
      os_log("incremented photos counter", log: log, type: .default)
      // URL del server a cui inviare la richiesta
      guard let url = URL(string: "https://api.myendpoint.it/app/uploader/png") else {
        print("Invalid endpoint URL")
        reject("0", "Invalid endpoint URL", nil)
        os_log("error creating the URL object", log: log, type: .default)
        return
      }
      
      // Dati dell'immagine PNG
      guard let image = UIImage(named: photoName),
            let imageData = image.pngData() else {
        print("Impossibile ottenere i dati dell'immagine.")
        reject("0", "Impossibile ottenere i dati dell'immagine.", nil)
        os_log("error fetching the image data", log: log, type: .default)
        return
      }
      
      let isLast = self.isLast(sender: sender)
      
      // Dati da inviare come variabili di tipo stringa
      let parameters: [String : Any] = [
        "metadata": metadata,
        "sender": sender,
        "last": isLast
      ]
      
      // Creazione della richiesta
      var request = URLRequest(url: url)
      request.httpMethod = "POST"
      print("created url request")
      os_log("created url request", log: log, type: .default)
      
      // Creazione del corpo della richiesta
      let boundary = "Boundary-\(UUID().uuidString)"
      request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
      request.setValue(token, forHTTPHeaderField: "Authorization")
      
      var data = Data()
      
      // Aggiunta dei dati dell'immagine al corpo della richiesta
      data.append("--\(boundary)\r\n".data(using: .utf8)!)
      data.append("Content-Disposition: form-data; name=\"image\"; filename=\"\(photoName)\"\r\n".data(using: .utf8)!)
      data.append("Content-Type: image/png\r\n\r\n".data(using: .utf8)!)
      data.append(imageData)
      data.append("\r\n".data(using: .utf8)!)
      
      // Aggiunta delle variabili di tipo stringa al corpo della richiesta
      for (key, value) in parameters {
        data.append("--\(boundary)\r\n".data(using: .utf8)!)
        data.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n".data(using: .utf8)!)
        data.append("\(value)\r\n".data(using: .utf8)!)
      }
      
      data.append("--\(boundary)--\r\n".data(using: .utf8)!)
      
      // Impostazione del corpo della richiesta
      request.httpBody = data
      
      // Eseguire la richiesta
      let task = URLSession.shared.dataTask(with: request) { data, response, error in
        if let error = error {
          os_log("error sending request", log: log, type: .default)
          print("Errore nella richiesta: \(error)")
          return
        }
        
        if let data = data {
          // Gestire la risposta del server
          os_log("request sent", log: log, type: .default)
          print("Risposta del server: \(String(data: data, encoding: .utf8) ?? "")")
        }
      }
      task.resume()
      print("post task.resume()")
      os_log("post task.resume()", log: log, type: .default)
      //Using semaphore to deal with racing condition
      semaphore.wait()
      resolve(sentImagesCounter)
      if(isLast){
        sentImagesCounter = 0
        print("reset photos counter")
        os_log("reset photos counter", log: log, type: .default)
      }
      semaphore.signal()
      print("finishing async call")
      os_log("finishing async call", log: log, type: .default)
    }
  }
  
  @objc private func isLast(sender: String) -> Bool{
    semaphore.wait()
    let isLast = sentImagesCounter == requiredPhotos
    semaphore.signal()
    return isLast
  }

The iOS folder containing all the native code is structured as follows:
iOS module structure

I have created a ReplateModule.swift file with the implementation of the sendPhoto function, a ReplateModule.m for defining which functions have to be exported and a bridging header file for linking the swift file with the objective c file. I haven’t touch AppDelegate.* and the other files which have been generated with npx expo prebuild.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.