istornz / flutter_live_activities

A Flutter plugin to use iOS 16.1+ Live Activities ⛹ī¸ & iPhone Dynamic Island 🏝ī¸ features
https://dimitridessus.fr/
MIT License
175 stars 54 forks source link

Adds sinks unregister on engine end #49

Closed ggirotto closed 1 year ago

ggirotto commented 1 year ago

Summary: Unregister the sinks when the Flutter engine gets destroyed.

TLDR: The reason why this is necessary is because the activities can be updated and ended by using push notifications (as described in the project README). This makes possible that this function gets invoked when the app, and as a consequence Flutter engine, are killed:

https://github.com/istornz/flutter_live_activities/blob/c07dd45124b3a36a1d5583b01f5cd23c40b9b60a/ios/Classes/SwiftLiveActivitiesPlugin.swift#L341-L361

And why this is an issue? Because the app will raise the following exception:

Fatal Exception: NSInternalInconsistencyException Sending a message before the FlutterEngine has been run..

This doesn't produce any visual inconsistency in the app, but will pollute crash tools why this fatal exception.

A more in depth discussion can be found in the following links

istornz commented 1 year ago

Hi @ggirotto, thanks for your PR & your explanation 👍 I will release this fix ASAP on the next release :)

charlesRmajor commented 11 months ago

I think this added code is causing my app to crash on a hard close (or maybe this is just what's catching the real error)

SwiftLiveActivitiesPlugin.swift:389 is the 'activityEventSink?.self(response)' in this section:

        case .dismissed, .ended:
          response["status"] = "ended"
          activityEventSink?.self(response)

Stack trace:

Thread 4 Crashed:
0   libsystem_kernel.dylib                 0x104108a4c __pthread_kill + 8
1   libsystem_pthread.dylib                0x10424b1d0 pthread_kill + 256
2   libsystem_c.dylib                      0x18015f5ec abort + 104
3   libc++abi.dylib                        0x18028bc78 abort_message + 128
4   libc++abi.dylib                        0x18027d198 demangling_terminate_handler() + 300
5   libobjc.A.dylib                        0x18005fbf0 _objc_terminate() + 124
6   libc++abi.dylib                        0x18028b150 std::__terminate(void (*)()) + 12
7   libc++abi.dylib                        0x18028db74 __cxxabiv1::failed_throw(__cxxabiv1::__cxa_exception*) + 32
8   libc++abi.dylib                        0x18028db34 __cxa_throw + 132
9   libobjc.A.dylib                        0x1800841d4 objc_exception_throw + 368
10  Foundation                             0x180d1ba5c -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 184
11  Flutter                                0x106bf63f8 -[FlutterEngine sendOnChannel:message:binaryReply:] + 264
12  Flutter                                0x1071f9108 invocation function for block in SetStreamHandlerMessageHandlerOnChannel(NSObject<FlutterStreamHandler>*, NSString*, NSObject<FlutterBinaryMessenger>*, NSObject<FlutterMethodCodec>*, NSObject<FlutterTaskQueue>*) + 236
13  live_activities                        0x103ebd948 thunk for @escaping @callee_unowned @convention(block) (@unowned Swift.AnyObject?) -> () + 228
14  live_activities                        0x103ec8c4c closure #1 in SwiftLiveActivitiesPlugin.monitorLiveActivity<A>(_:) + 1224 (SwiftLiveActivitiesPlugin.swift:389)
15  live_activities                        0x103ece39d partial apply for closure #1 in SwiftLiveActivitiesPlugin.monitorLiveActivity<A>(_:) + 1
16  live_activities                        0x103ecdf55 thunk for @escaping @callee_guaranteed @Sendable @async () -> (@out A) + 1
17  live_activities                        0x103ece0c1 partial apply for thunk for @escaping @callee_guaranteed @Sendable @async () -> (@out A) + 1
18  libswift_Concurrency.dylib             0x1e2c399c5 completeTaskWithClosure(swift::AsyncContext*, swift::SwiftError*) + 1
ggirotto commented 11 months ago

I'm still experience this issue as well. The code certainly is not causing the crash, and neither this is causing a hard crash because the exception is thrown when the app is in background.

I still couldn't find a solution to this, because it seems that Flutter framework is not calling the detach function when the engine gets destroyed.

charlesRmajor commented 11 months ago

Maybe related to this

charlesRmajor commented 11 months ago

Commenting out the added code at the top of this conversation is stopping the "crash when hard closed" issue

(I'm not currently using push notifications to update the live activities, though. I will be in the future)

istornz commented 11 months ago

Maybe adding a try/catch can help? If you can test it could be very nice 😊

charlesRmajor commented 11 months ago

I tried a try/catch and still had the crash:

(been awhile since I was working in Swift very actively, so let me know if you see any issues with how I did that)

  @available(iOS 16.1, *)
  private func monitorLiveActivity<T : ActivityAttributes>(_ activity: Activity<T>) {
      do {
          try
          Task {
            for await state in activity.activityStateUpdates {
              var response: Dictionary<String, Any> = Dictionary()
              response["activityId"] = activity.id
              switch state {
              case .active:
                monitorTokenChanges(activity)
              case .dismissed, .ended:
                response["status"] = "ended"
                activityEventSink?.self(response)
              case .stale:
                response["status"] = "stale"
                activityEventSink?.self(response)
              @unknown default:
                response["status"] = "unknown"
                activityEventSink?.self(response)
              }
            }
          }
      } catch {
          print("error: ");
          print(error);
      }
  }

Also tried moving where the moving where the monitorLiveActivity function is called:

/// line 229:
    if (deliveryActivity != nil) {
//       monitorLiveActivity(deliveryActivity!) // TO HERE
      if removeWhenAppIsKilled {
        appLifecycleLifeActiviyIds.append(deliveryActivity!.id)
      }
//       monitorLiveActivity(deliveryActivity!) // FROM HERE
      result(deliveryActivity!.id)
    }

(SwiftLiveActivitiesPlugin.swift)

Same crash with both

ggirotto commented 11 months ago

@charlesRmajor Under what circumstances your app is crashing? The code of this PR only unregister the sinks when the app gets destroyed. Are you trying to update your live activity when your app goes to background/gets killed? If so the situation is similar as updating the live activity through push notifications and it won't work or it'll not be reliable if or without the added code.

The main question here is why the detach function is not being invoked when the Flutter engine gets destroyed.

charlesRmajor commented 11 months ago

It crashes when you hard close the app (swipe up to app selector and then swipe the app up to remove it)

I'm not currently using push notifications to update the Live Activity, but plan to before long

ggirotto commented 11 months ago

Added extra info to Flutter's thread https://github.com/flutter/flutter/issues/117523#issuecomment-1834547993

I don't think there's anything else we can do by now, unfortunately.