I have successfully figured out push notifications in Cloud Code in the basic sense. I can send a test message from my app and it shows up on my phone.
But I don't understand how to change the values of request.params.message in my Cloud Code.
Also, what I have below causes a Cloud Code error of "invalid type for key message, expected String, but got array." What array?
In case it isn't clear, I am trying to send a push notification to users who subscribe to a particular channel.
My Swift code:
import UIKit
import Parse
import Bolts
class ViewController: UIViewController {
var channels = "B08873"
var minyanID = "bethel08873"
var message = "This is a test message."
override func viewDidLoad() {
super.viewDidLoad()
let currentInstallation = PFInstallation.currentInstallation()
currentInstallation.addUniqueObject( (minyanID), forKey:"channels")
currentInstallation.addUniqueObject(message, forKey: "message")
currentInstallation.saveInBackground()
}
#IBAction func sendPush(sender: AnyObject) {
if minyanID == channels {
PFCloud.callFunctionInBackground("alertUser", withParameters: [channels:message], block: {
(result: AnyObject?, error: NSError?) -> Void in
if ( error === nil) {
NSLog("Rates: \(result) ")
}
else if (error != nil) {
NSLog("error")
}
});
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
My Cloud Code
Parse.Cloud.define("alertUser", function(request,response){
var query = new Parse.Query(Parse.Installation);
var theMessage = request.params.channels;
var theChannel = request.params.message;
query.equalTo(theChannel)
Parse.Push.send({
where: query,
data : { alert: theMessage, badge: "Increment", sound: "", } },
{ success: function() { response.success() },
error: function(error) { response.error(err) }
});
});
Because you are sending B08873 as key and This is a test message. as its value. If you want to send both channel and message key/value pairs you need to do it like this instead:
PFCloud.callFunctionInBackground("alertUser", withParameters: ["channels": channels , "message": message], .....
In your Cloud function you should be bale to access these paramerts like this:
var theChannels = request.params.channels; // returns B08873
var theMessage = request.params.message; // returns This is a test message.
Then call the Push function like this:
Parse.Push.send({
channels: [ theChannels ],
data: {
alert: theMessage ,
badge: "Increment"
}}, {
success: function() {
response.success();
},
error: function(error) {
response.error(error);
}
});
Related
I have Apple Pay implemented in my SwiftUI app and I'd like to call a function after payment status returns a success. However, when I try to call the function, I run into error "Type () cannot conform to View". Relevant code:
PaymentView.swift (SwiftUI view where Apple Pay button is displayed):
#StateObject var applePayModel = ApplePayModel()
//....code....//
var body: some View {
PaymentButton(){
// Apple Pay Model
applePayModel.pay(clientSecret: PaymentConfig.shared.paymentIntentClientSecret)
}
if let paymentStatus = applePayModel.paymentStatus {
switch paymentStatus {
case .success:
postPaymentSetup() // trying to call this function!!
Text("Payment complete!")
case .error:
Text("Payment failed")
case .userCancellation:
Text("Payment canceled")
#unknown default:
Text("Unknown status")
}
}
}
ApplePayModel.swift
import Foundation
import Stripe
import PassKit
class ApplePayModel : NSObject, ObservableObject, STPApplePayContextDelegate {
#Published var paymentStatus: STPPaymentStatus?
#Published var lastPaymentError: Error?
var clientSecret: String?
func pay(clientSecret: String?) {
self.clientSecret = clientSecret
// Configure a payment request
let pr = StripeAPI.paymentRequest(withMerchantIdentifier: "merchant.com.app", country: "US", currency: "USD")
// You'd generally want to configure at least `.postalAddress` here.
// We don't require anything here, as we don't want to enter an address
// in CI.
pr.requiredShippingContactFields = []
pr.requiredBillingContactFields = []
// Configure shipping methods
pr.shippingMethods = []
// Build payment summary items
// (You'll generally want to configure these based on the selected address and shipping method.
pr.paymentSummaryItems = [
PKPaymentSummaryItem(label: "Widget)", amount: NSDecimalNumber(string: "0.00")),
PKPaymentSummaryItem(label: "Merchant", amount: NSDecimalNumber(string: "0.00")),
]
// Present the Apple Pay Context:
let applePayContext = STPApplePayContext(paymentRequest: pr, delegate: self)
applePayContext?.presentApplePay()
}
func applePayContext(_ context: STPApplePayContext, didCreatePaymentMethod paymentMethod: STPPaymentMethod, paymentInformation: PKPayment, completion: #escaping STPIntentClientSecretCompletionBlock) {
// Confirm the PaymentIntent
if (self.clientSecret != nil) {
// Call the completion block with the PaymentIntent's client secret.
completion(clientSecret, nil)
} else {
completion(nil, NSError())
}
}
func applePayContext(_ context: STPApplePayContext, didCompleteWith status: STPPaymentStatus, error: Error?) {
// When the payment is complete, display the status.
self.paymentStatus = status
self.lastPaymentError = error
}
}
ApplePayModel is an observable object so I know there should be a way to check when its paymentStatus has changed. Just haven't figured out a way yet. Any ideas?
Thanks!
EDIT:
Here's the function I'm trying to call, located in PaymentView.swift:
func postPaymentSetup(){
DataService.instance.updateUser(userID: currentUserID)
// send notifications to seller
AuthService.instance.getUserOnesignalID(forUserID: selectedSellerID) { onesignalID in
OneSignal.postNotification(["contents": ["en": "Your payment has succeeded!"], "include_player_ids": ["\(onesignalID ?? "")"]])
}
showToast = true
toastMessage = "Payment Completed ✅"
isShowingSheet = false
isShowingSheet_Reservation = false
showSelectButton = false
presentationMode.wrappedValue.dismiss()
}
I am trying to make an independent class/library that runs on its own and handles errors internally, but also gives feedback (status) to the frontend for possible user interaction. In Android I'd solve this with a Listener:
class Status(errCode:Int=0, msg:String=""){
var errCode:Int=0
var text:String=""
private var listener: ChangeListener? = null
init {
this.errCode=errCode
this.text=msg
}
fun set(errCode:Int, msg:String) {
this.errCode=errCode
this.text=msg
if (listener != null) listener!!.onChange()
}
fun getListener(): ChangeListener? {
return listener
}
fun setListener(listener: ChangeListener?) {
this.listener = listener
}
interface ChangeListener {
fun onChange()
}
}
Whenever I want to update it, e.g. on Exception, I call:
catch (e: IOException) {
this.status.set(109,"someException: $e")
}
And in the MainActivity I just have to handle these changes:
myObj.status.setListener(object : Status.ChangeListener {
override fun onChange() {
when(myObj!!.status.errCode) {
//errors
109 -> log(myObj!!.status.text) //some Exception
111 -> myObj!!.restart() //another exc
112 -> myObj!!.checkAll() //some other error
...
In Swift I found didSet that seems similar. I read you can attach it to a struct so whenever something in the struct changes, it is called. So I added my "struct Status" to my "struct myObj" like this:
struct myObj {
var status:Status=Status(errCode: 0, msg: "Init")
struct Status {
var errCode:Int
var msg:String
mutating func set(err:Int, txt:String){
errCode=err
msg=txt
}
}
and in my code I initialize a new instance of myObj
var mObj:myObj=myObj.init() {
didSet{
switch myObj.status.errCode{
case 1:
promptOkay()
case 113:
obj.errorHandling()
default:
log(txt: mObj.status.msg)
}
}
}
However, it never trigger didSet, even though internal functions should change the Status. I read that didSet is not triggered on init, but I do not run anything right now after initializing the class object that should run quite independently. I want to verify that the approach is okay before I go further and have to unravel everything again.
didSet must be declared on the property, not during initialization:
class MyObj {
var status: Status = Status(errCode: 0, msg: "Init") {
didSet {
// status did change
print("new error code: \(status.errCode)")
}
}
struct Status {
var errCode:Int
var msg:String
mutating func set(err:Int, txt:String){
errCode = err
msg = txt
}
}
}
let obj = MyObj()
obj.status.set(err: 10, txt: "error 10") // prints "new error code: 10"
At this point you can react to every changes made to obj.status in the didSetClosure.
Edit — React to didSet from outside the class
If you want to respond to changes from outside the MyObj, I would recommend using a closure:
class MyObj {
var status: Status = Status(errCode: 0, msg: "Init") {
didSet {
statusDidChange?(status)
}
}
// closure to call when status changed
var statusDidChange: ((Status) -> Void)? = nil
struct Status {
var errCode:Int
var msg:String
mutating func set(err:Int, txt:String){
errCode = err
msg = txt
}
}
}
This way, you can assign a closure from outside to perform custom actions:
let obj = MyObj()
obj.statusDidChange = { status in
// status did change
print("new error code: \(status.errCode)")
}
obj.status.set(err: 10, txt: "error 10") // prints "new error code: 10"
Edit 2 — Call didSet closure directly from initialization
You also can manually call the statusDidChange closure during the init.
class MyObj {
var status: Status = Status(errCode: 0, msg: "Init") {
didSet {
statusDidChange(status)
}
}
// closure to call when status changed
var statusDidChange: (Status) -> Void
init(status: Status, statusDidChange: #escaping (Status) -> Void) {
self.status = status
self.statusDidChange = statusDidChange
self.statusDidChange(status)
}
}
let obj = MyObj(status: MyObj.Status(errCode: 9, msg: "error 09")) { status in
// status did change
print("new error code: \(status.errCode)")
}
obj.status.set(err: 10, txt: "error 10")
This will print
new error code: 9
new error code: 10
I want to add a value to Firestore. When finished I want to return the added value. The value does get added to Firestore successfully. However, the value does not go through sink.
This is the function that does not work:
func createPremium(user id: String, isPremium: Bool) -> AnyPublisher<Bool,Never> {
let dic = ["premium":isPremium]
return Future<Bool,Never> { promise in
self.db.collection(self.dbName).document(id).setData(dic, merge: true) { error in
if let error = error {
print(error.localizedDescription)
} else {
/// does get called
promise(.success(isPremium))
}
}
}.eraseToAnyPublisher()
}
I made a test function that works:
func test() -> AnyPublisher<Bool,Never> {
return Future<Bool,Never> { promise in
promise(.success(true))
}.eraseToAnyPublisher()
}
premiumRepository.createPremium(user: userID ?? "1234", isPremium: true)
.sink { receivedValue in
/// does not get called
print(receivedValue)
}.cancel()
test()
.sink { recievedValue in
/// does get called
print("Test", recievedValue)
}.cancel()
Also I have a similar code snippet that works:
func loadExercises(category: Category) -> AnyPublisher<[Exercise], Error> {
let document = store.collection(category.rawValue)
return Future<[Exercise], Error> { promise in
document.getDocuments { documents, error in
if let error = error {
promise(.failure(error))
} else if let documents = documents {
var exercises = [Exercise]()
for document in documents.documents {
do {
let decoded = try FirestoreDecoder().decode(Exercise.self, from: document.data())
exercises.append(decoded)
} catch let error {
promise(.failure(error))
}
}
promise(.success(exercises))
}
}
}.eraseToAnyPublisher()
}
I tried to add a buffer but it did not lead to success.
Try to change/remove .cancel() method on your subscriptions. Seems you subscribe to the publisher, and then immediately cancel the subscription. The better option is to retain and store all your subscriptions in the cancellable set.
I have an array of appointments and I'm trying to grab all of the photos for these appointments from our windows azure blob storage. First, I want to get the list of blobs with the associated appointmentId so I can download and store them properly afterwards.
I'm using PromiseKit but I'm not at all sure about how to use PromiseKit in a loop:
for appointment in appointments {
// Get blobs
}
Here's my code so far. Any help is greatly appreciated!
func getBlobsPromise(appointmentId: Int32) -> Promise<[BlobDownload]> {
return Promise { seal in
var error: NSError?
var blobDownloads = [BlobDownload]()
container = AZSCloudBlobContainer(url: URL(string: containerURL)!, error: &error)
if ((error) != nil) {
print("Error in creating blob container object. Error code = %ld, error domain = %#, error userinfo = %#", error!.code, error!.domain, error!.userInfo)
seal.reject(error!)
}
let prefix: String = "AppointmentFiles/\(appointmentId)"
container?.listBlobsSegmented(with: nil, prefix: prefix, useFlatBlobListing: true, blobListingDetails: AZSBlobListingDetails(), maxResults: 150) { (error : Error?, results : AZSBlobResultSegment?) -> Void in
if error != nil {
seal.reject(error!)
}
for blob in results!.blobs!
{
let blobInfo = blob as! AZSCloudBlob
if blobInfo.blobName.lowercased().contains("jpg") || blobInfo.blobName.lowercased().contains("jpeg") {
let blobDownload: BlobDownload = BlobDownload(appointmentId: Int(jobId), blob: blobInfo)
blobDownloads.append(blobDownload)
}
}
seal.fulfill(blobDownloads)
}
}
}
That returns the blobs as expected but I want to get all of the blobs for all of the appointments before proceeding. Here's what I tried (among other things):
func getBlobsForAllJobs(appointmentIds: [Int32]) -> Promise<[BlobDownload]> {
return Promise { seal in
let count = appointmentIds.count - 1
let promises = (0..<count).map { index -> Promise<[BlobDownload]> in
return getBlobsPromise(agencyCode: agencyCode, appointmentId: appointmentIds[index])
}
when(fulfilled: promises).then({ blobDownloads in
seal.fulfill(blobDownloads)
})
}
}
EDIT 1
I solved this using a DispatchGroup and completion handler. Here's the code in case someone is interested. If there are alternate (better) ways of doing this I'd love to hear them. I'm a c# guy just getting into Swift.
func getBlobsToDownload(appointmentIds: [Int32], completion: #escaping ([BlobDownload]) -> Void) {
var myBlobsToDownload = [BlobDownload]()
let myGroup = DispatchGroup()
for apptId in appointmentIds {
myGroup.enter()
getBlobs(appointmentId: apptId) { (blobDownloads) in
print("Finished request \(apptId)")
print("Blobs fetched from apptId \(apptId) is \(blobDownloads.count)")
for blobDownload in blobDownloads {
myBlobsToDownload.append(blobDownload)
}
myGroup.leave()
}
}
myGroup.notify(queue: .main) {
print("Finished all requests.")
completion(myBlobsToDownload)
}
}
I am trying out Google's nearby messages API, which seems to be easy to use, but it's for some reason not working as expected. I suspect that the problem is something trivial, but I have not been able to solve this.
I double-checked that the API-key is correct and I have also added permissions for NSMicrophoneUsageDescription and NSBluetoothPeripheralUsageDescription in the Info.plist.
The Nearby Messages API is enabled in Google's developer console and the API keys has been set to be restricted to the app's bundle identifier. It won't work either if this restrictions is removed.
class ViewController: UIViewController {
private var messageManager: GNSMessageManager?
override func viewDidLoad() {
super.viewDidLoad()
GNSMessageManager.setDebugLoggingEnabled(true)
messageManager = GNSMessageManager(apiKey: "<my-api-key>", paramsBlock: { (params: GNSMessageManagerParams?) -> Void in
guard let params = params else { return }
params.microphonePermissionErrorHandler = { hasError in
if hasError {
print("Nearby works better if microphone use is allowed")
}
}
params.bluetoothPermissionErrorHandler = { hasError in
if hasError {
print("Nearby works better if Bluetooth use is allowed")
}
}
params.bluetoothPowerErrorHandler = { hasError in
if hasError {
print("Nearby works better if Bluetooth is turned on")
}
}
})
// publish
messageManager?.publication(with: GNSMessage(content: "Hello".data(using: .utf8)))
// subscribe
messageManager?.subscription(messageFoundHandler: { message in
print("message received: \(String(describing: message))")
}, messageLostHandler: { message in
print("message lost: \(String(describing: message))")
})
}
}
Did anybody else have issues setting this up?
Ok, for whoever has the same problem, the solution was quite simple and almost embarrassing. It is necessary to hold the publication and the subscription result in a class variable:
private var publication: GNSPublication?
private var subscription: GNSSubscription?
override func viewDidLoad() {
super.viewDidLoad()
messageManager = GNSMessageManager(apiKey: "<my-api-key>")
// publish
publication = messageManager?.publication(with: GNSMessage(content: "Hello".data(using: .utf8)))
// subscribe
subscription = messageManager?.subscription(messageFoundHandler: { message in
print("message received: \(String(describing: message))")
}, messageLostHandler: { message in
print("message lost: \(String(describing: message))")
})
}