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.
Related
We have noticed in our project, we are getting allot of exceptions logged in Xcode (->Organiser->Crashes) logged in production...
However these Share crashes don't give any extra debug infomation, nor are they logged in our Firebase exceptions.
This is how network API calls are being made using ktor 1.6:
#Throws(CancellationException::class, ResponseException::class, Exception::class)
suspend fun getProfileGoals(profileId : Int) : ProfileGoal{
val url = "$base/api/goals/$profileId"
var client = HttpClient {
install(JsonFeature) {
serializer = KotlinxSerializer(kotlinx.serialization.json.Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
useAlternativeNames = false
coerceInputValues = true
encodeDefaults = true
})
}
return client.get(url){
headers {
header(DEVICE_ID, deviceId)
}
}
}
And this is how it is used in iOS:
func getGoals(){
profilesApi?.getProfileGoals(profileId: Int32(selfProfile!.profileId), completionHandler: { profileGoal, error in
if let profileGoal = profileGoal {
self.profileGoals = profileGoal
....
}
}
self.collectionView.reloadData()
})
}
There are obviously lots of other API calls, but they are all done in a similar way.
I guess my question is, is anything obviously wrong or done the incorrect way or how to get these exceptions appearing in Firebase Crashlytics, which may hopefully have more info for debugging
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 have a working REST API based on this API Gateway tutorial. I'm able to successfully invoke it via the test functionality of the AWS Console; and I'm able to successfully invoke it via my simple iOS Swift 4.2 Xcode application using the iPhone XR simulator.
I know it's working via a real, live external call because I can see the Cloudwatch logs which always register a 200 response and is sending the results back to the Client.
My problem is really in understanding the Swift code, and I'm hoping that a Swift expert can help me understand how to unpack result in the code below.
Here's my code in ViewController.swift for invoking the REST API and attempting to print result to the console:
#IBAction func userInvokeApi(_ sender: UIButton) {
print("You clicked invoke api...")
let client = SVTLambdaGateClient.default()
client.calcGet(operand2: "3", _operator: "+", operand1: "5").continueWith{ (task: AWSTask?) -> AnyObject? in
if let error = task?.error {
print("Error occurred: \(error)")
return nil
}
if let result = task?.result {
// Do something with result
print("The result is... \(result)")
}
return nil
}
}
As pointed out in the comments below, I'm getting the following result because it's printing out the address of the object:
You clicked invoke api...
The result is... <AmplifyRestApiTest.Empty: 0x600002020770> {
}
(where AmplifyRestApiTest is the name of my Xcode project.)
UPDATE When I set a breakpoint on the print statement, this is what I see in the Debug pane:
UPDATE 2
When I type task?.result there are two viable properties as per this answer from the Amplify team: error and result. So, since my API responds successfully I am assuming I just don't know how to view result.
Can someone help me understand what steps I must take to access members of this class object?
Here is the corresponding method in the API Gateway-generated iOS Swift SDK code:
/*
#param operand2
#param _operator
#param operand1
return type: Empty
*/
public func calcGet(operand2: String, _operator: String, operand1: String) -> AWSTask<Empty> {
let headerParameters = [
"Content-Type": "application/json",
"Accept": "application/json",
]
var queryParameters:[String:Any] = [:]
queryParameters["operand2"] = operand2
queryParameters["operator"] = _operator
queryParameters["operand1"] = operand1
let pathParameters:[String:Any] = [:]
return self.invokeHTTPRequest("GET", urlString: "/calc", pathParameters: pathParameters, queryParameters: queryParameters, headerParameters: headerParameters, body: nil, responseClass: Empty.self) as! AWSTask<Empty>
}
I'm fairly certain this return type of Empty refers to the Empty model defined for the REST API as shown in the screenshot below. I think it's "empty" because the API doesn't alter the response from the Lambda function back to the Client. So, it's all pass-through. Indeed, the tutorial explains that the other models -- Output and Result -- are not used because it "relies on the passthrough behavior and does not use this model."
Any thoughts?
So I'm playing around with go-ethereum in iOS and I'm having quite a bit of trouble trying to interact with a contract deployed to Rinkeby testnet, I'm very new to the whole blockchain technology so any help is appreciated.
All I'm trying to do is access a deployed contract and get the value of a string but the issue I'm having is I get this error when I try to make a call to a bound contract:
Thread 1: Fatal error: 'try!' expression unexpectedly raised an error: Error Domain=go Code=1 "abi: cannot unmarshal string in to []interface {}" UserInfo={NSLocalizedDescription=abi: cannot unmarshal string in to []interface {}}
this is the code I'm using to make the call.
// Declare the error variables
var clientError: NSErrorPointer;
var addressError: NSErrorPointer;
var contractError: NSErrorPointer;
// Get the bindContract from Rinkeby test network.
let client = GethNewEthereumClient("https://rinkeby.infura.io/v3/398ed56d211646faaf010ca183de11f2", clientError);
let contractAddress = GethNewAddressFromHex("0x7259667715d671Ee370d7788647f95Fe7C3B532d", addressError);
guard let contractABI = ReadJsonResourceAsString(fileName: "InboxContractInterface", fileType: "json") else {
print("[ViewController] failed to read the abi json as string.")
return;
}
let boundContract = GethBindContract(contractAddress, contractABI, client, contractError);
// Prepare the callOpts
let callOpts = GethNewCallOpts();
callOpts?.setGasLimit(300000);
callOpts?.setContext(GethNewContext());
// Prepare the results & params interfaces
let results = GethNewInterfaces(1);
let params = GethNewInterfaces(0);
let stringResult = GethNewInterface();
stringResult?.setDefaultString();
try! results?.set(0, object: stringResult);
// Make the call
let methodName = "message";
try! boundContract?.call(callOpts, out_: results, method: methodName, args: params);
// Show results.
print("[ViewController] message call result: " + (stringResult?.getString())!);
And this is my contract's code:
pragma solidity ^0.4.17;
contract Inbox {
string public message;
function Inbox (string initialMessage) public {
message = initialMessage;
}
function setMessage (string newMessage) public {
message = newMessage;
}
}
For anyone that might find the same issue after digging a bit more I found this issue for android: https://github.com/ethereum/go-ethereum/issues/14832
Luckily this is already fixed, so it was totally my fault for not using the latest version.
I was using Geth v1.5.9 so after updating to v1.8.2 it finally worked, not sure which version in-between got fixed tho.
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
}