While I am in the app, everything works great. I put the app in the background and come back even 30 seconds later and it’s no longer updating etc. I am fairly new at parse and even newer at LiveQuery so I am not sure what is needed to post but this is my basic setup:
ParseSwift Version: 4.0.1
import ParseSwift
var queryM = Message.query()
let subscriptionM = queryM.subscribeCallback!
struct MessagingInterface: View {
...
.onAppear(perform: {
let delegate = LiveQueryDelegate()
if let socket = ParseLiveQuery.getDefault() {
socket.receiveDelegate = delegate
}
queryM = queryM.where("chatKey" == finalChatID)
subscriptionM.handleSubscribe { subscribedQuery, isNew in
//: You can check this subscription is for this query
if isNew {
print("Successfully subscribed to messages query \(subscribedQuery)")
subscribedQuery.find { result in
switch result {
case .success(let messages):
self.messages = messages
case .failure(let error):
print(error)
}
}
} else {
print("Successfully updated subscription to messages query \(subscribedQuery)")
}
subscriptionM.handleEvent { newQuery, event in
newQuery.find { result in
switch result {
case .success(let messages):
self.messages = messages
case .failure(let error):
print(error)
}
}
switch event {
case .entered(let object):
print("Entered: \(object)")
case .left(let object):
print("Left: \(object)")
case .created(let object):
messages?.append(object)
print("Created: \(object)")
case .updated(let object):
let lastMessage = messages?.last
if lastMessage?.id == object.id {
if messages?.count ?? 0 > 0 {
messages?[messages!.count - 1] = object
print(messages)
}
}
print("Updated: \(object)")
case .deleted(let object):
print("Deleted: \(object)")
}
})
}
You should look at the second link in the comment referring to requesting permissions as this is on the developer end of any Apple OS app and not the Swift SDK:
I looked at this, but I don’t really need the app to get data in the background, let me explain a bit better.
What I am expecting:
Livequery loads the messages for a given chat
I switch apps to anything else
Come back (even 30 seconds later) and it keeps loading where we left off
What is happening:
Livequery loads the messages for a given chat
I switch to anything else
Come back, it no longer updates at all without reloading the entire app/view.
So I am not sure what I am doing wrong. Currently, I have both background fetch and remote notifications. Would I still need to do background refresh and how/what would I do to keep the connection alive (if I even need to)
The Swift SDK can only do what the OS allows it to do, so if the OS cuts communication based on what your app is authorized to do, there’s nothing the SDK can do. You can look online for iOS restricting apps when the app leaves the foreground. You can also add print statements to see when the OS cuts access and allows it. You can also look at the following thread:
I think I solved the issue, originally I was subscribing etc within the views .onAppear. When the app goes to the background I have no idea what happens, but something happens to the view that permanently breaks the connection.
I switched it over to a ViewModel (to follow MVVM, as I should have done from the beginning). So yea folks don’t be lazy like me, make a ViewModel and ParseSwift will work as it should, here’s my code if anyone is interested:
import Foundation
import ParseSwift
class MessageViewModel: ObservableObject {
@Published var messages = [Message]()
@Published var queryM = Message.query()
func loadMessages() {
//Set up the delegate
let delegate = LiveQueryDelegate()
if let socket = ParseLiveQuery.getDefault() {
socket.receiveDelegate = delegate
}
//Begin subscribing
let subscriptionM = queryM.subscribeCallback!
subscriptionM.handleSubscribe { subscribedQuery, isNew in
//: You can check this subscription is for this query
if isNew {
print("Successfully subscribed to messages query \(subscribedQuery)")
subscribedQuery.find { result in
switch result {
case .success(let messages):
self.messages = messages
case .failure(let error):
print(error)
}
}
} else {
print("Successfully updated subscription to messages query \(subscribedQuery)")
}
}
//Handle Subscription Events
subscriptionM.handleEvent { newQuery, event in
newQuery.find { result in
switch result {
case .success(let messages):
self.messages = messages
case .failure(let error):
print(error)
}
}
switch event {
case .entered(let object):
print("Entered: \(object)")
case .left(let object):
print("Left: \(object)")
case .created(let object):
self.messages.append(object)
print("Created: \(object)")
case .updated(let object):
let lastMessage = self.messages.last
if lastMessage?.id == object.id {
if self.messages.count ?? 0 > 0 {
self.messages[self.messages.count - 1] = object
print(self.messages)
}
}
print("Updated: \(object)")
case .deleted(let object):
print("Deleted: \(object)")
}
}
}
func closeConnection() {
do {
try queryM.unsubscribe()
} catch {
print(error)
}
}
}
The Swift SDK is designed for SwiftUI and MVVM out-of-the-box. A LiveQuery Subscription is already a View Model as discussed in the documentation, so using subscribeCallback in the manner you are using it is the long way:
Cloud Functions and every Query can be used directly as a View Model without any additional code as well. The Playgrounds shows how to use a LiveQuery as a View Model directly:
I discuss and show examples on how to make sophisticated View Models leveraging subscriptions and standard queries by conforming and subclassing to what the Swift SDK provides:
I realized the issue actually has not been fixed, when testing on a physical device the same issue happens.
If the app goes to the background for even 30 seconds, the livequery will unsubscribe and cannot be resubscribed to.
I have rewritten my file and even directly used what was used in the playground example to the exact same result. No matter what I attempt after restoring the app I get
“Unsubscribed from query!”
query.subscribe seems to do nothing. How do I resubscribe to a query once the app is no longer in the background/inactive.
When you reopen you app, you should check isSubscribed() and isPendingSubscription, if your query isn’t in either it means the OS cleared the memory of your app as I stated in:
If your query isn’t present in either, it is your responsibility to save the state of your app before the OS terminates it, not the SDK (there’s a ton of info about this online). If the query is present, but the connection is disconnected, you need to make the request by calling open or resubscribing. To learn more about how the OS makes decisions you should read here:
The OS does not clear the memory within 2 seconds of going home then going back to the app.
As I keep asking how? There is nothing in the documentation nor can I find anything detailing what to do when your app has momentarily been switched from and back to.
I already attempted query.subscribe! xcode throws the following warning:
Expression of type 'Subscription<GameScore>' is unused
I posted my example project above but here is the entire class so you can see:
import SwiftUI
import ParseSwift
//: Create a query just as you normally would.
var query = GameScore.query()
//: To use subscriptions inside of SwiftUI
struct ContentView: View {
//: A LiveQuery subscription can be used as a view model in SwiftUI
@ObservedObject var subscription = query.subscribe!
@Environment(\.scenePhase) var scenePhase
var body: some View {
VStack {
if subscription.subscribed != nil {
Text("Subscribed to query!")
} else if subscription.unsubscribed != nil {
Text("Unsubscribed from query!")
} else if let event = subscription.event {
//: This is how you register to receive notifications of events related to your LiveQuery.
switch event.event {
case .entered(let object):
Text("Entered with points: \(String(describing: object.points))")
case .left(let object):
Text("Left with points: \(String(describing: object.points))")
case .created(let object):
Text("Created with points: \(String(describing: object.points))")
case .updated(let object):
Text("Updated with points: \(String(describing: object.points))")
case .deleted(let object):
Text("Deleted with points: \(String(describing: object.points))")
}
} else {
Text("Not subscribed to a query")
}
Text("Update GameScore in Parse Dashboard to see changes here:")
Button(action: {
try? query.unsubscribe()
}, label: {
Text("Unsubscribe")
.font(.headline)
.background(Color.red)
.foregroundColor(.white)
.padding()
.cornerRadius(20.0)
})
Spacer()
.onChange(of: scenePhase, perform: { newPhase in
if newPhase == .background {
print("in background")
} else {
query.subscribe!
// print(test?.results)
}
})
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I even made a video highlighting the issue:
Its important to note this only seems to occur on a physical device, on simulator it works perfectly.
.onChange(of: scenePhase, perform: { newPhase in
if newPhase == .background {
print("in background")
} else {
ParseLiveQuery.getDefault()?.open(completion: { error in
if let error = error {
print(error.localizedDescription)
}
})
// print(test?.results)
}
})
In my test app this now works correctly. Is there anyway this can be the default behavior of the SDK. When the app leaves it disconnects, when it comes back it re-establishes the connection. Seems like a very easy thing to impliment
However this is what I get when I check ParseLiveQuery:
Is Subscribed: Optional(true)
Is Pending Subscription: Optional(false)
Is Socket Established: Optional(true)
Is Connected: Optional(true)
Is Connecting: Optional(false)
I am attempting to use open but I have been unable to resubscribe in any way.
It looks like however you setup your app, you created a weird situation the OS breaks the connection and didn’t report the broken connection back to ParseLiveQuery.
You can probably address that by: closing the connection first, then opening the connection, a simple, “off/on”.
At any rate, the issues you are facing are related to you not properly handling your apps live cycle and managing state (why you need to off/on and others) as I’ve mentioned a few times already:
In addition, some of your issues from the code you provided are SwiftUI based and not Parse SwiftUI based. I recommend looking more into SwiftUI on https://www.hackingwithswift.com or on StackOverFlow along with looking into understanding more about the iOS app life cycle:
Hey! Did you ever figure this out? I’m having the same issue. The moment I go to the background for a significant amount of time and go back to the app, liveQuery stops working.