tjc
tjc

Reputation: 89

Dismiss Live Activities on App Termination

I support Live Activities in my app, and am unable to dismiss Live Activities when the app gets closed from the background. I understand that I can send an update from the backend but am unsure how to determine when the app gets terminated in order to send it.

Currently, I subscribe to the willTerminateNotification notification in order to perform actions when the app gets terminated. This gets triggered when the app is terminated in the foreground. However, if it gets terminated from the background, it does not get triggered and my Live Activity stays after the app gets closed. How can I prevent this from happening?

Upvotes: 5

Views: 1932

Answers (2)

Uch
Uch

Reputation: 244

Found a solution! It's a little involved across a few files. I'm using React Native but since this is purely iOS code, the solution still applies.

Note: For whatever reason, in XCode, the logger only fires in applicationWillTerminate once per app initialization (hitting the play button in XCode), but this code actually does fire every time the user re-opens the app and swipes away.

Keep in mind, this function is reliable if the app is RUNNING and the user opens up the multi-app viewer and kills the app from there. The behavior may be unreliable if the app has already been suspended by the Apple system due to memory issues or because of the watch dog.

Apple Developer docs didn't help at all.

  1. (React Native) Ensure you have your LiveActivityModule.m file set up properly
  2. Add an applicationWillTerminate function in your AppDelegate.mm file
  3. Import your Project App Bundle into the AppDelegate.mm file so you can call your EndActivity Function
  4. Modify your End Activity Function in Swift Code to ensure that Tasks will finish even when app is killed suddenly

Step 1: (React Native only): In LiveActivityModule.m Make sure your LiveActivity is properly declared.

#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>

// Important to note that the name "LiveActivity" was the name I declared as an Objective C module in my LiveActivityModule.swift file. It ensures the class can be referenced in react native
@interface RCT_EXTERN_MODULE(LiveActivity, NSObject)

RCT_EXTERN_METHOD(startActivity)
RCT_EXTERN_METHOD(updateActivity: (NSString *) name)
RCT_EXTERN_METHOD(endActivity)

// I don't think this is mandatory
+ (BOOL)requiresMainQueueSetup
{
  return NO;
}
  
@end

Step 2 & 3: In AppDelegate.mm, create your App Delegate applicationWillTerminate function and reference your LiveActivity EndActivity function.

// other imports
#import <os/log.h>
// (React Native) You DO NOT need your bridging header to be imported, just the entire folder name of your app with '-Swift.h'. NOT the file name of the file that holds the EndActivity function
#import <AppName-Swift.h>

// I like to have a logger in this file, not mandatory
// Make sure you create the logger object in EACH FUNCTION BODY
os_log_t logger = nil;

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  // other initialization
  logger = os_log_create("com.bundle.id", "AppDelegate");
  os_log(logger, "In AppDelegate Initializing the app");
  // final stuff you want to run
}

- (void)applicationWillTerminate:(UIApplication *)application {
  logger = os_log_create("com.bundle.id", "AppDelegate");
  os_log(logger, "App terminated! Good bye!");
  // Create an instance of the LiveActivityModule class
  if (@available(iOS 16.2, *)) {
    os_log(logger, "Starting call to LiveActivity! Good bye!");
    LiveActivity *liveActivityModule = [[LiveActivity alloc] init];
    // Call the endActivity method
    [liveActivityModule endActivity];
  } else {
    // Fallback on earlier versions
  }
}

@end

Step 4. Modify your End Activity Function in Swift Code to ensure that Tasks will finish even when app is killed suddenly. This is in my file named LiveActivityModule.swift

import SwiftUI
import ActivityKit
import OSLog
@available(iOS 16.2, *)
// Declaring this LiveActivity as an objective-c function
@objc(LiveActivity) 
class LiveActivityModule: NSObject {
  
  private var content: ActivityContent<LiveAttributes.ContentState>?
  let logger = Logger(subsystem: "com.bundle.id", category: "dynamicIsland")
  
  @objc(startActivity)
  func startActivity() {
    // other code
    logger.debug("Starting activity")
  }
  
  @objc(updateActivity:)
  func updateActivity(name: String) {
    // other code
    logger.debug("Updating activity")
  }
    
    @objc(endActivity)
    func endActivity() {
      logger.debug("Ending Live Activities")
          // You need to add this Sephamore code. This way the Task will actually run
          let semaphore = DispatchSemaphore(value: 0)
          Task
          {
            logger.debug("We're in that task before the for loop")
            for activity in Activity<LiveAttributes>.activities
            {
              logger.debug("Ending Live Activity: \(activity.id)")
              await activity.end(nil, dismissalPolicy: .immediate)
            }
            semaphore.signal()
          }
          semaphore.wait()
    }  
}

Upvotes: 0

Sergio
Sergio

Reputation: 1922

// To kill the live activity immediately:
await liveActivity.end(nil, dismissalPolicy: .immediate)

Upvotes: -3

Related Questions