I've create a workable Cloud Function using Firebase in which works using my browser. Now, I'm working with my iOS Swift code, and have successfully installed all dependencies.
However, I'm new to iOS/Swift and try to figure out where to call the URL from the Cloud Function? Here is the code Firebase provides to call from within an iOS App:
functions.httpsCallable("addMessage").call(["text": "test"]) { (result, error) in
if let error = error as NSError? {
if error.domain == FunctionsErrorDomain {
let code = FunctionsErrorCode(rawValue: error.code)
let message = error.localizedDescription
let details = error.userInfo[FunctionsErrorDetailsKey]
}
// ...
}
if let text = (result?.data as? [String: Any])?["text"] as? String {
print(text) // WOULD EXPECT A PRINT OF THE CALLABLE FUNCTION HERE
}
}
Here's the callable Cloud Function (which is deployed):
exports.addMessage = functions.https.onCall((data, context) => {
const text = data.text;
return {
firstNumber: 1,
secondNumber: 2,
operator: '+',
operationResult: 1 + 2,
};
});
As of now, I see nothing printed in my XCode console, expect the callable function. Thank you!
It sounds like you may be using an HTTP request Cloud Function. HTTP callable Cloud Functionss are not the same thing as HTTP request Cloud Functions.
Notice the signature of HTTP callable Cloud Functions:
exports.addMessage = functions.https.onCall((data, context) => {
// ...
});
versus HTTP request Cloud Functions:
exports.date = functions.https.onRequest((req, res) => {
// ...
});
If you're using onRequest, you will have to make an HTTP request from the client. If you're using a callable function, then you just pass the function name and data as shown in the sample. Judging from the link you showed, it would be something like
functions.httpsCallable("testFunction").call(["foo": "bar"]) { (result, error) in
//...
}
I figured about the problem. I had to update my Cloud Function return key to match my Swift function. Here is TypeScript code:
exports.addMessage = functions.https.onCall((data, context) => {
const text = data.text;
console.log(text)
return {
text: "100"
};
Hope this works. Make sure to add a 'text' element on the return part of your callable Cloud Function, for example:
exports.addMessage = functions.https.onCall((data,
context) => {
const text = data.text;
return {
text: text
firstNumber: 1,
secondNumber: 2,
operator: '+',
operationResult: 1 + 2,
};
});
In your code, you're returning variables which you're not using, such as 'firstNumber', 'secondNumber', 'operator', and 'operationResult', and your forgetting to add the important variable, which is 'text'.
instead of (result?.data as? [String: Any])?["text"] as? String
use result?.data
finally it look something like this
if let text = (result?.data) {
print(text) // WOULD EXPECT A PRINT OF THE CALLABLE FUNCTION HERE
}
Related
I'm very new to the Kotlin Multiplatform and Swift language, I have a problem with KMM only the iOS part, I have successfully run this on Android but it fails on IOS due to concurrency issues.
Kotlin code snippet :
#Throws(Exception::class)
suspend fun getResponse(data: String): String {
var response: String
client.responsePipeline.intercept(HttpResponsePipeline.Transform) { (_, body) ->
when (context.response.status) {
HttpStatusCode.OK -> response = body as String
}
}
response = client.post(BASE_URL) {
contentType(ContentType.Application.Json)
body = data
}
return response
}
iOS code snippet :
#State var response: String = ""
Button("Click") {
Repository().getResponse(data: "hello world") { data, error in
if data != nil {
response.self = "\(data)"
}
}
}
I get HttpClient: {"output":"...","statusCode":200 } from the Api which I want but it fails anyways.
I tried wrap the post request with CoroutineScope(Dispatchers.Main){
withContext(Dispatchers.Default){}}
But no luck, any idea why?
Assuming you're on recent Kotlin and library versions, you should enable the new memory model. Put this in gradle.properties
kotlin.native.binary.memoryModel=experimental
See KaMP Kit for an example.
Migrating to Ktor 2.0.0 solved many problems for me.
I am trying to build a KMM application using Ktor for our ApiServices. I have created a BaseApiClass where I have all of the api related code.
Code for BaseApiClass :-
class BaseAPIClass {
//Create Http Client
private val httpClient by lazy {
HttpClient {
defaultRequest {
host = ApiEndPoints.Base.url
contentType(ContentType.Application.Json)
header(CONNECTION, CLOSE)
}
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.ALL
}
install(HttpTimeout) {
requestTimeoutMillis = NETWORK_REQUEST_TIMEOUT
}
expectSuccess = false
// JSON Deserializer
install(JsonFeature) {
val json = Json {
ignoreUnknownKeys = true
coerceInputValues = true
}
serializer = KotlinxSerializer(json)
}
}
}
// Api Calling Functions I have few more similar to this but issue is random and comes in any of the api
#Throws(Exception::class)
suspend fun sampleApi(requestBody: RequestBody?) : Either<CustomException, BaseResponse<EmptyResponseModel>> {
return try {
val response = httpClient.post<BaseResponse<EmptyResponseModel>> {
url(ApiEndPoints.sample.url)
if (requestBody != null) {
body = requestBody
}
}
Success(response)
}
catch (e: Exception) {
Failure(e as CustomException)
}
}
Here's how I call the api from iOS app :-
val apiClass = BaseApiClass()
func callApi() {
apiClass.sampleApi(requestBody: .init(string: "value here")) { (result, error) in
result?.fold(failed: { (error) -> Any? in
// Error here
}, succeeded: { (result) -> Any? in
// Success here
})
}
}
Now here if I try to call similar few more api's with the same object i.e apiClass then after few calls it get stuck inside my function callApi it don't send even api request (Because I can't see Request Logs printed in my console) and because of that I cannot do any other operations as I don't get anything from api.
As soon as I change my screen or close the app and try to call the same api then it works good.
But instead of creating a object only at one time like this apiClass = BaseApiClass() if I try to do with BaseApiClass().sampleApi(request params here) {// completion handler here} it works fine I don't get any issues with this.
I am not sure what causes this to happen everything works good in Android this is faced only with iOS.
Try to set LogLevel.NONE in the install(Logging) block.
At the moment I resolved in this way because it seems a bug of Ktor.
See: https://youtrack.jetbrains.com/issue/KTOR-2711
It should be fixed in the version 1.6.0.
Are you using the multithreaded variant of the Coroutines library? The official docs state that you should use this variant when working with Ktor. See here
After all the efforts and trying a lot of debugging skills I got to understand that my completion handler in the shared module is never called even if I receive the response the response from api.
The only solution I have achieved is creating the different HTTP Client using expect and actual mechanism. By making separate clients I have not encountered the issue yet.
If you have any other answers or solutions I would be happy to have a look at it.
I am trying to call a cloud function from firebase by using the following code.
Client code -
func checkUserStatus() {
let functions = Functions.functions(region: "us-central1")
let argument = [
"currentUser":
[
"email": "test#email.com",
"uid": "LP8R4yZroyMTj"
]
]
functions.httpsCallable("subscriptionStatus").call(argument) { (result, error) in
if error != nil {
print("FAILED")
print(error)
} else {
print("PASSED")
print(result)
}
}
}
Cloud Function code -
exports.subscriptionStatus = functions.https.onRequest(async (request: Request<RequestBody>, response) => {
const {
currentUser,
} = request.body
// Logic goes here
}
But getting the following error when running it
Error Domain=com.firebase.functions Code=3 "INVALID ARGUMENT" UserInfo={NSLocalizedDescription=INVALID ARGUMENT}
The function takes in a parameter called currentUser which further comprises of user's email and uid.
Any lead would be highly appreciated on the matter.
Just to have an answer to the question for anyone else having a similar issue.
To call an onRequest() cloud function you need to use the URL where it's deployed at i.e. https://us-central1-<project-id>.cloudfunctions.net/<function-name>?<var-name>=<var-value>
If you're wanting to call it in the client app using call, then you'll need to use an onCall() cloud function.
Firebase has a one of the best documentation on their services: https://firebase.google.com/docs/functions/get-started
OnCall functions:
https://firebase.google.com/docs/functions/callable
onRequest functions:
https://firebase.google.com/docs/functions/http-events
I created a nodejs lambda function in AWS and exposed it using APIGateway with methods GET, PUT, POST, and DELETE (all setup with proxy). All methods have been tested and work in AWS using APIGateway, and then outside of AWS using Postman.
First, I called the GET method for the endpoint in my Swift 4 project, and it is successful.
BUT I have tried just about everything to call the POST method in swift and cannot get it to execute successfully. This is what I am currently trying after researching online:
let awsEndpoint: String = "https://host/path"
guard let awsURL = URL(string: awsEndpoint) else {
print("Error: cannot create URL")
return
}
var postUrlRequest = URLRequest(url: awsURL)
postUrlRequest.httpMethod = "POST"
postUrlRequest.addValue("John Doe", forHTTPHeaderField: "name")
postUrlRequest.addValue("imageurl.com", forHTTPHeaderField: "imageUrl")
URLSession.shared.dataTask(with: postUrlRequest) { data, response, error in
guard let data = data else { return }
do {
guard let receivedTodo = try JSONSerialization.jsonObject(with: data,
options: []) as? [String: Any] else {
print("Error")
return
}
} catch let err{
print(err)
}
}.resume()
The response I get is ["message":"Internal Server Error"]. When I look at the logs in CloudWatch they are not very descriptive. The error log for the post call is:
"Execution failed due to configuration error: Malformed Lambda proxy response"
After researching this issue aws suggests to format the response in a specific way and I have updated my nodejs lambda function to mimmic this.
case "POST":
pool.getConnection(function(err, connection) {
const groupName = event.headers.name;
const imageUrl = event.headers.imageUrl;
var group = {Name: groupName, ImageUrl: imageUrl, IsActive:true, Created:date, Updated:date};
var query = "INSERT INTO Groups SET ?";
connection.query(query,group, function (error, results, fields) {
var responseBody = {
"key3": "value3",
"key2": "value2",
"key1": "value1"
};
var response = {
"statusCode": 200,
"headers": {
"my_header": "my_value"
},
"body": JSON.stringify(responseBody),
"isBase64Encoded": true
};
if (error) callback(error);
else callback(null, response)
connection.release();
});
});
break;
Like I said previously, this works when testing everywhere except swift 4. My GET call works with swift 4, so I do not think it is an issue with allowing anything in the info.plist but I could be wrong. I have tried just about everything, but cannot seem to get past this error.
I fixed this issue myself. After allowing ALL log output in API Gateway for that endpoint, I found that somewhere along the way my headers were being converted to all lowercase.
'imageUrl' became 'imageurl'
It was throwing an error because in my lambda function, it could not find 'imageUrl'
I think this is a conversion that is happening in APIGateway because I have never come across this issue with swift.
Im using SMS verification to verify users. My problem is that when I enter a code to verify I get invalid code. I can't for the life of me figure out why.
Calling cloud code function:
#IBAction func verifyCodeButtonTapped(sender: AnyObject) {
var verificationCode: String = verificationCodeTextField.text!
let textFieldText = verificationCodeTextField.text ?? ""
if verificationCode.utf16.count < 4 || verificationCode.utf16.count > 4 {
displayAlert("Error", message: "You must entert the 4 digit verification code sent yo your phone")
} else {
let params = ["verificationCode" : textFieldText]
PFCloud.callFunctionInBackground("verifyPhoneNumber", withParameters: params, block: { (object: AnyObject?, error) -> Void in
if error == nil {
self.performSegueWithIdentifier("showVerifyCodeView", sender: self)
} else {
self.displayAlert("Sorry", message: "We couldnt verify you. Please check that you enterd the correct 4 digit code sent to your phone")
}
})
}
}
Cloud code to verify code:
Parse.Cloud.define("verifyPhoneNumber", function(request, response) {
var user = Parse.User.current();
var verificationCode = user.get("phoneVerificationCode");
if (verificationCode == request.params.phoneVerificationCode) {
user.set("phoneNumber", request.params.phoneNumber);
user.save();
response.success("Success");
} else {
response.error("Invalid verification code.");
}
});
Your parameter names are mismatched between the iOS and JS code.
verificationCode vs phoneVerificationCode
Change
let params = ["verificationCode" : textFieldText]
To use the same parameter name:
let params = ["phoneVerificationCode" : textFieldText]
EDIT
Other issues I see with the code:
The first two lines of the iOS code create a variable and a constant from the textField's text value. Get rid of the verificationCode variable and just use the textFieldText constant.
I would add some more error states to the Cloud Code before you check if the codes are equivalent. First check if the parameter exists and the expected type and length:
var requestCode = request.params.phoneVerificationCode;
if ((typeof requestCode !== "string") || (requestCode.length !== 4)) {
// The verification code did not come through from the client
}
Then perform the same checks on the value from the user object:
else if ((typeof verificationCode !== "string) || (verificationCode.length !== 4)) {
// There is not a verification code on the Parse User
}
Then you can continue to checking if requestCode and verificationCode are equivalent.