istornz / flutter_live_activities

A Flutter plugin to use iOS 16.1+ Live Activities ⛹️ & iPhone Dynamic Island 🏝️ features
https://dimitridessus.fr/
MIT License
185 stars 56 forks source link

Can't I use LiveActivityIntent? #62

Open soulwawa opened 11 months ago

soulwawa commented 11 months ago

hi! Thanks for the great work! I want to use interactivity live activity using LiveActivityIntent.

But, I don't know how to refresh in swift code.

Can I update the live activity in swift (widget file) rather than flutter code? This is a sample code

struct RefreshIntent: LiveActivityIntent {

    static var title: LocalizedStringResource = "Refresh"
    static var description = IntentDescription("Refresh Description.")

    func perform() async throws -> some IntentResult {
        //  live activity update ??

        return .result()
    }
}

Thank you 🙇

https://developer.apple.com/documentation/AppIntents/LiveActivityIntent

Elliot727 commented 10 months ago

To update a Live Activity you need to do something in the perform function

struct RefreshIntent: LiveActivityIntent {

    static var title: LocalizedStringResource = "Refresh"
    static var description = IntentDescription("Refresh Description.")

    func perform() async throws -> some IntentResult {
        //  live activity update ??

        return .result()
    }
}

for example you could call an API and then update the UserDefaults

Heres an example:

@available(iOS 17.0, *)
struct ReloadDataAppIntent:AppIntent{

    @Parameter(title: "id")
    var id:String
    init() {
        id = ""
    }
    init(id: String) {
        self.id = id
    }

    static var title: LocalizedStringResource = "ReloadData"

    func perform() async throws -> some IntentResult {
        @StateObject var viewModel = ReloadDataViewModel()

        let sharedDefault = UserDefaults(suiteName: "YOURGROUPNAME")!

        data = await viewModel.fetchData(apiKey:"YOURAPIKEY)
        sharedDefault.set(YOURDATA, forKey: "YOURKEY")
        WidgetCenter.shared.reloadAllTimelines()
        return .result()
    }
}

Then in a button you can do

if #available(iOS 17.0, *) {
                Button(intent: ReloadDataAppIntent(id: "YOUR ID")) {
                    Image(systemName: "arrow.clockwise")
                        .font(.system(size: 20, weight: .semibold, design: .rounded))
                        .foregroundStyle(.white)
                }
                .buttonStyle(PlainButtonStyle())
            }
soulwawa commented 10 months ago

@Elliot727 Thank you reply, I don't understand maybe because I don't know the swift.

@StateObject var viewModel = ReloadDataViewModel()

let sharedDefault = UserDefaults(suiteName: "YOURGROUPNAME")!

data = await viewModel.fetchData(apiKey:"YOURAPIKEY)
sharedDefault.set(YOURDATA, forKey: "YOURKEY")
WidgetCenter.shared.reloadAllTimelines()
return .result()

Because it creates live activity with the flutter code.. It's hard to understand the code above 😭

Elliot727 commented 10 months ago

Let me explain. Hope this helps

  1. Creating a ViewModel with @StateObject:

    @StateObject var viewModel = ReloadDataViewModel()

    In SwiftUI, @StateObject is used to create an instance of a class that survives the entire view's lifecycle. In this case, a ReloadDataViewModel is being instantiated and managed as a state object. This is commonly used for data management in SwiftUI.

  2. Initializing UserDefaults with a specific suite name:

    let sharedDefault = UserDefaults(suiteName: "YOURGROUPNAME")!

    This code initializes a UserDefaults instance with a specified suite name. This can be useful when you want to share UserDefaults between different parts of your app or even between your app and its extensions.

  3. Asynchronous data fetching using async/await:

    data = await viewModel.fetchData(apiKey: "YOURAPIKEY")

    This code calls an asynchronous function fetchData from the ReloadDataViewModel class, using the new async/await syntax in Swift. It fetches data from an API and assigns it to the data variable.

  4. Setting data in UserDefaults:

    sharedDefault.set(YOURDATA, forKey: "YOURKEY")

    This line sets a value (YOURDATA) for a specified key (YOURKEY) in the UserDefaults instance (sharedDefault). UserDefaults is commonly used for storing simple data persistently.

  5. Reloading all timelines in a Widget:

    WidgetCenter.shared.reloadAllTimelines()

    If you are working with iOS widgets, this code triggers a reload of all the timelines associated with your app's widgets. It's useful when you want to update the content displayed in widgets dynamically.

  6. Returning a .result() value:

    return .result()

    This is a generic return statement, suggesting the function returns a result. The specific meaning of .result() depends on the context of the function and its return type.

Please replace placeholders like "YOURGROUPNAME," "YOURAPIKEY," "YOURDATA," and "YOURKEY" with your actual values as needed.

Let Me Know if you need anything else explained

Also its best to learn Swift / SwiftUI when building the iOS widgets

kvenn commented 1 month ago

This is a very helpful thread, thanks all! I'm trying to go a little further and wondering if anyone has cracked this.

In the example, ReloadDataViewModel is a Swift class. And anything run in perform runs in a separate process from your app (and I believe it's not even guaranteed your app's process will be running at the same time).

Is there any way to invoke Dart code (like a Dart ViewModel) from the perform function?

In the context of Flutter, it would be be great to reuse some of my request functionality that's already written in Dart than to have to recreate it in Swift.

Elliot727 commented 1 month ago

Hi Kvenn,

To invoke Dart code from the perform function in the Swift Live Activity, you would need to set up a communication channel between the Swift and Dart code. One approach could be to use platform channels, where the Swift code calls into the Dart code using a platform-specific method channel.

Alternatively, you could explore using a shared data store, like a database or shared preferences, where the Swift code updates the data and the Dart code observes the changes. This would allow you to reuse your existing Dart functionality without needing to directly call into it from the Swift code.

Both of these approaches would require some additional setup and integration work, but they could allow you to leverage your existing Dart code within the Live Activity implementation.

kvenn commented 1 month ago

I think both of those would require the app's process to be running, which isn't necessarily guaranteed. Does that sound right?

If that's the case, you'd need to be able to launch your app's process from the Live Activity process, which I don't think is possible to do without actually launching the app (like through an intent)

Elliot727 commented 1 month ago

Hi Kvenn,

You’re absolutely right that the solutions I mentioned earlier would require the app to be running, which isn’t guaranteed with Live Activities. However, starting with iOS 17.2, you can indeed update a Live Activity via push notifications, which opens up new possibilities.

Here's how it works:

Push Notifications for Live Activities: You can send a push notification that includes an update for the Live Activity. This means that even if your app is not running, you can still refresh the Live Activity's content.

Payload Structure: When you send a push notification to update the Live Activity, you'll include specific information in the payload that indicates which Live Activity to update and what data to refresh.

Handling Updates: In your app, you'll need to implement the handling of these push notifications to ensure that the Live Activity is updated correctly when the notification is received.

This approach allows you to leverage your existing Dart code without requiring the app to be running since the Live Activity can respond to notifications independently.

Let me know if you’d like more details on how to implement this or if there are any other aspects you’d like to discuss!

Maybe this Link might be more of what your trying to achieve ?