I've an Flutter iOS app which is assosciated with watch app target. Basic functionality is that the current watch app is tracking live heart rate of the user through watch app
Issue: While Debugging I'm able to compile the app and it is working fine on both end, but when I wanted to archive the build these issues are appearing.Not Sure how to Fix this or from where it is apearing.
Xcode : Version 15.0 (15A240d)
iOS :
Watch App :
Issue Attachaments/References : Link
AppDelegate :
import UIKit
import Flutter
import WatchConnectivity
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
var session: WCSession?
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
initFlutterChannel()
if WCSession.isSupported() {
print("Watch Session Supported")
session = WCSession.default;
session?.delegate = self;
session?.activate();
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
private func initFlutterChannel() {
if let controller = window?.rootViewController as? FlutterViewController {
let channel = FlutterMethodChannel(
name: "com.org.productName",
binaryMessenger: controller.binaryMessenger)
channel.setMethodCallHandler({ [weak self] (
call: FlutterMethodCall,
result: @escaping FlutterResult) -> Void in
switch call.method {
case "flutterToWatch":
print("flutterToWatch")
guard let watchSession = self?.session, watchSession.isPaired, watchSession.isReachable, let methodData = call.arguments as? [String: Any], let method = methodData["method"], let data = methodData["data"] as? Any else {
result(false)
return
}
let watchData: [String: Any] = ["method": method, "data": data]
print(watchData)
// Pass the receiving message to Apple Watch
// watchSession.sendMessage(watchData, replyHandler: nil, errorHandler: )
watchSession.sendMessage(watchData) { (replaydata) in
print("\(replaydata)")
} errorHandler: { (err) in
print("\(err)")
}
result(true)
default:
result(FlutterMethodNotImplemented)
}
})
}
}
}
extension AppDelegate: WCSessionDelegate {
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
}
func sessionDidBecomeInactive(_ session: WCSession) {
}
func sessionDidDeactivate(_ session: WCSession) {
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
DispatchQueue.main.async {
if let method = message["method"] as? String, let controller = self.window?.rootViewController as? FlutterViewController {
let channel = FlutterMethodChannel(
name: "com.org.productName",
binaryMessenger: controller.binaryMessenger)
channel.invokeMethod(method, arguments: message)
}
}
}
}
Watch App / WatchViewModel.Swift :
import SwiftUI
import WatchConnectivity
import HealthKit
import WatchKit
class WatchViewModel:NSObject, ObservableObject,HKWorkoutSessionDelegate,HKLiveWorkoutBuilderDelegate {
func workoutSession(_ workoutSession: HKWorkoutSession, didChangeTo toState: HKWorkoutSessionState, from fromState: HKWorkoutSessionState, date: Date) {
print("workoutSession>>didChangeTo \(toState)")
// Wait for the session to transition states before ending the builder.
if toState == .ended {
builder?.endCollection(withEnd: date) { (success, error) in
self.builder?.finishWorkout { (workout, error) in
}
}
}
}
func workoutSession(_ workoutSession: HKWorkoutSession, didFailWithError error: Error) {
print("workoutSession>>didFailWithError \(error)")
}
// WKInterfaceController, HKWorkoutSessionDelegate,
func workoutBuilder(_ workoutBuilder: HKLiveWorkoutBuilder, didCollectDataOf collectedTypes: Set<HKSampleType>) {
for type in collectedTypes {
if type == HKQuantityType.quantityType(forIdentifier: .heartRate)! {
// Handle heart rate data
if let statistics = workoutBuilder.statistics(for: type as! HKQuantityType) {
let heartRateUnit = HKUnit.count().unitDivided(by: HKUnit.minute())
let value = statistics.mostRecentQuantity()?.doubleValue(for: heartRateUnit) ?? 0
counter = Int(value)
sendDataMessage(for: .sendHRToFlutter, data: ["counter": counter])
print("Workout Heart Rate: \(value) BPM")
// You can update UI or perform other actions with the heart rate data
}
}
// Handle other collected data types if needed
}
}
func workoutBuilderDidCollectEvent(_ workoutBuilder: HKLiveWorkoutBuilder) {
//if workoutBuilder.workoutEvents.count > 0{
let workoutEvents = workoutBuilder.workoutEvents
for event in workoutEvents {
print("Work Builder Collect Data : ",event)
}
//}
}
var session: WCSession
let healthStore = HKHealthStore()
var builder: HKLiveWorkoutBuilder?
var workOutSession: HKWorkoutSession?
let queue = OperationQueue()
@Published var counter = 0
// Start the workout.
func startWorkout(workoutType: HKWorkoutActivityType) {
let configuration = HKWorkoutConfiguration()
configuration.activityType = workoutType
configuration.locationType = .indoor
// Create the session and obtain the workout builder.
do {
workOutSession = try HKWorkoutSession(healthStore: healthStore, configuration: configuration)
builder = workOutSession?.associatedWorkoutBuilder()
builder?.delegate = self
builder?.dataSource = HKLiveWorkoutDataSource(healthStore: healthStore,
workoutConfiguration: configuration)
// Start the workout session
workOutSession?.startActivity(with: Date())
} catch {
// Handle any exceptions.
print("Error starting workout session: \(error.localizedDescription)")
return
}
// Setup session and builder.
// session1?.delegate = self
builder?.delegate = self
// Set the workout builder's data source.
builder?.dataSource = HKLiveWorkoutDataSource(healthStore: healthStore,
workoutConfiguration: configuration)
// Start the workout session and begin data collection.
let startDate = Date()
workOutSession?.startActivity(with: startDate)
builder?.beginCollection(withStart: startDate) { (success, error) in
// The workout has started.
print("beginCollection :", success)
}
}
// Add more cases if you have more receive method
enum WatchReceiveMethod: String {
case sendHRToNative
}
// Add more cases if you have more sending method
enum WatchSendMethod: String {
case sendHRToFlutter
}
init(session: WCSession = .default) {
self.session = session
// let wkManagerModel = WKManagerModel()
super.init()
self.session.delegate = self
session.activate()
requestAuthorization()
}
func sendDataMessage(for method: WatchSendMethod, data: [String: Any] = [:]) {
sendMessage(for: method.rawValue, data: data)
}
// Request authorization to access HealthKit.
func requestAuthorization() {
// The quantity type to write to the health store.
let typesToShare: Set = [
HKQuantityType.workoutType()
]
// The quantity types to read from the health store.
let typesToRead: Set = [
HKQuantityType.quantityType(forIdentifier: .heartRate)!,
HKObjectType.activitySummaryType()
]
// Request authorization for those quantity types.
healthStore.requestAuthorization(toShare: typesToShare, read: typesToRead) { [self] (success, error) in
print(success)
print(error ?? "No Error")
print(self.dataTypesToRead())
var dataType : HKSampleType?
dataType = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)!
print("readHealthKitData : ", self.readHealthKitData(type: dataType!))
// Serial queue for sample handling and calculations.
queue.maxConcurrentOperationCount = 1
queue.name = "MotionManagerQueue"
startWorkout(workoutType: .running)
}
}
private func queryForUpdates(type: HKObjectType) {
switch type {
case HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)!:
debugPrint("HKQuantityTypeIdentifierHeartRate")
default: debugPrint("Unhandled HKObjectType: \(type)")
}
}
private func dataTypesToRead() -> Set<HKSampleType> {
return Set(arrayLiteral:
HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)!,
HKObjectType.workoutType()
)
}
/// Types of data that this app wishes to write to HealthKit.
///
/// - returns: A set of HKSampleType.
private func dataTypesToWrite() -> Set<HKSampleType> {
return Set(arrayLiteral:
HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)!,
HKObjectType.workoutType()
)
}
func readHealthKitData(type : HKSampleType) {
let query = HKSampleQuery(sampleType: type, predicate: nil, limit: 1, sortDescriptors: nil) { (query, results, error) in
if let error = error {
// Handle query error
print("Error querying heart rate: \(error.localizedDescription)")
return
}
if let heartRateSample = results?.first as? HKQuantitySample {
// Access heart rate value
let heartRate = heartRateSample.quantity.doubleValue(for: HKUnit(from: "count/min"))
// counter = heartRate
self.counter = Int(heartRate)
self.sendDataMessage(for: .sendHRToFlutter, data: ["counter": self.counter])
print("Heart Rate self.counter: \(self.counter) BPM")
// You can update UI or perform other actions with the heart rate data
print("Heart Rate Query: \(heartRate)")
}
}
healthStore.execute(query)
}
}
extension WatchViewModel: WCSessionDelegate {
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
}
// Receive message From AppDelegate.swift that send from iOS devices
func session(_ session: WCSession, didReceiveMessage message: [String : Any],replyHandler: @escaping ([String : Any]) -> Void) {
DispatchQueue.main.async {
guard let method = message["method"] as? String, let enumMethod = WatchReceiveMethod(rawValue: method) else {
return
}
switch enumMethod {
case .sendHRToNative:
self.counter = (message["data"] as? Int) ?? 0
}
}
}
func sendMessage(for method: String, data: [String: Any] = [:]) {
guard session.isReachable else {
return
}
let messageData: [String: Any] = ["method": method, "data": data]
session.sendMessage(messageData, replyHandler: nil, errorHandler: nil)
}
}
The problem might be the "WatchViewModel" is a member of both the iOS app and the WatchKit app.
You can click on the "WatchViewModel.Swift", and check if it's a member of both the iOS app (Runner) and the WatchKit app. If it is, then untick the iOS app (Runner) in the target membership section.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With