Kotlin Multiplaform iOS Exceptions showing in Xcode, not in Firebase - ios

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

Related

InvalidMutabilityException: mutation attempt of frozen kotlin.native.internal Exception

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.

iOS - TwilioVideo - Unable to connect on call with remote participant

I am developing app with video calling functionality and I am using Twilio Video for that.
Currently using TwilioVideo SDK v4.6.2 and iOS Version 14.x and Above
I am not able to connect to the TwilioVideo room
Below is my code:
func connect() {
guard let accessToken = self.accessToken, let roomName = self.roomName else {
return
}
prepareAudio()
prepareCamera()
let connectOptions = ConnectOptions(token: accessToken) { (builder) in
builder.isDominantSpeakerEnabled = true
builder.isNetworkQualityEnabled = true
if let localAudioTrack = self.localAudioTrack {
builder.audioTracks = [localAudioTrack]
}
if let localVideoTrack = self.localVideoTrack {
builder.videoTracks = [localVideoTrack]
}
if let preferredAudioCodec = TwiloVideoSettingsManager.shared.audioCodec {
builder.preferredAudioCodecs = [preferredAudioCodec]
}
if let preferredVideoCodec = TwiloVideoSettingsManager.shared.videoCodec {
builder.preferredVideoCodecs = [preferredVideoCodec]
}
if let encodingParameters = TwiloVideoSettingsManager.shared.getEncodingParameters() {
builder.encodingParameters = encodingParameters
}
builder.region = "gll"
builder.roomName = roomName
}
self.room = TwilioVideoSDK.connect(options: connectOptions, delegate: self)
UIApplication.shared.isIdleTimerDisabled = true
}
Response in not received from Twilio in either of the methods mentioned below
func didConnect(to room: Room) {
NSLog("Room: \(room.name) SID: \(room.sid)")
if (room.remoteParticipants.count > 0) {
self.remoteParticipant = room.remoteParticipants[0]
self.remoteParticipant.delegate = self
}
self.delegate.videoServiceManagerDidConnectToRoom(name:room.name)
}
func roomDidFailToConnect(room: Room, error: Error) {
NSLog("Failed to connect to a Room: \(error).")
self.delegate.videoServiceManagerFailToConnectRoom(error: error.localizedDescription)
self.leaveRoom()
}
I am not able connect to the room every time and sometimes I get the error mentioned below :
Failed to connect to a Room: Error Domain=com.twilio.video Code=53000 "Signaling connection error" UserInfo={NSLocalizedDescription=Signaling connection error, NSLocalizedFailureReason=SIP error 408}.
When I check the Twilio logs in debug mode I am not getting any error.
Please guide me to rectify if there is any mistake in my code
Twilio employee here. Error 53000 is somewhat vague and could occur due to different things: https://www.twilio.com/docs/api/errors/53000
I'd suggest the following next steps:
try reproducing this error using the iOS Quickstart app (https://github.com/twilio/video-quickstart-ios)
try running our Networktest on the problematic device: https://www.networktest.twilio.com

Stuck with Api response Ktor

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.

Swift: how to detect the error type using the debugger?

I'm new in iOS development, so maybe I'm thinking in the wrong way. I coded a view model with a function that calls an API, and everything works fine.
class SearchCityViewModel : ViewModelProtocol {
//OBSERVABLES
var cities = PublishSubject<[City]>()
var networkError = PublishSubject<Void>()
var generalError = PublishSubject<Void>()
init(){
print("Init SearchCityViewModel")
reinit()
}
func reinit(){}
func searchCity(stringToSearch: String){
async {
do {
if stringToSearch.count>=2 {
let cities = try await(api.getCities(cityToSearch: stringToSearch)).payload!
self.cities.onNext(cities)
}
else {
self.cities.onNext([])
}
}
catch {
self.generalError.onNext(Void())
}
}
}
Now I want to handle errors. In the catch block I want to distinguish all the errors I want to handle gracefully, and for the other ones I just want to emit a general error. To do that, firstly I need to know which error is thrown when the situation I want to handle occurs. I usually do this with the debugger. For instance, I disable the internet connection, and i create a breakpoint inside the catch block. The idea is to check which error is thrown when the internet connection is disabled, in order to create a catch block for that kind of error.
Image of the debugger
I'm struggling because with the debugger I only see that is an AFError instance, but it's not telling me nothing more that can help me to catch it.
What is wrong with my workflow? Do I really need to read all the docs every time? For each library I use?
Thank you!
Perhaps you can read the articles and then you will know how to do it better, you can use the framework -oslog instead of using print function.
debugging your logging info
I found the way. What I was missing is casting the error as NSError. In this way, with the debugger is possible to see the domain and the code of the error. In the case of Alamofire, the real error is wrapped, and it's accessible through the underlyingError attribute. Once I had the domain and the code of the error, I wrote the following code:
class SearchCityViewModel : ViewModelProtocol {
//OBSERVABLES
var cities = PublishSubject<[City]>()
var networkError = PublishSubject<Void>()
var generalError = PublishSubject<Void>()
init(){
print("Init SearchCityViewModel")
reinit()
}
func reinit(){}
func searchCity(stringToSearch: String){
async {
do {
if stringToSearch.count>=2 {
let cities = try await(api.getCities(cityToSearch: stringToSearch)).payload!
self.cities.onNext(cities)
}
else {
self.cities.onNext([])
}
}
catch {
if let afError = asAFError, let underlyingError = afError.underlyingError as NSError?, underlyingError.domain == NSURLErrorDomain, underlyingError.code == NSURLErrorNotConnectedToInternet || underlyingError.code == NSURLErrorTimedOut {
self.networkError.onNext(Void())
}
else {
self.generalError.onNext(Void())
}
}
}
}

GRPC-Swift Send HTTPProtocolVersion from Client

I'm trying GRPC-Swift for Client-Server application.
I'm using GRPC-Swift for both Client and Server
Client is an iPhone application, which I tried with iPhone Simulator.
I followed this link for Client-side streaming RPC.
When I send message to Server from Client, I got the following error message in the console from Server,
error io.grpc.server_channel_call : unable to determine http version
From the Server in the
HTTPProtocolSwitcher.swift
inside the function func channelRead(context: ChannelHandlerContext, data: NIOAny), it is checking for HTTPProtocolVersion, and it is missing.
How to send the HTTPVersion from the Client code?
Update:
Client Code
import GRPC
import NIO
class HTTPClient {
private let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
private var channel: ClientConnection?
private var client: ChatGuide_ChatGuideClient?
private var clientCall: ClientStreamingCall<ChatGuide_TextMessage, ChatGuide_TextMessage>?
func connect(host: String, port: Int) throws {
let channel = ClientConnection.secure(group: self.group)
.connect(host: host, port: port)
self.channel = channel
self.client = ChatGuide_ChatGuideClient(channel: channel)
}
func disconnect() {
do {
self.clientCall?.sendEnd(promise: nil)
_ = try self.clientCall?.status.wait()
try self.group.syncShutdownGracefully()
} catch let error {
print("\(type(of: self)): Could not shutdown gracefully -", error.localizedDescription)
}
}
func initiateClient() {
let timeAmount = TimeAmount.minutes(1)
let timeLimit = TimeLimit.timeout(timeAmount)
let options = CallOptions(timeLimit: timeLimit)
let call = self.client?.chat(callOptions: options)
call?.response.whenSuccess { (message) in
print("\(type(of: self)): Message from server -", message.text)
}
call?.response.whenFailure { (error) in
print("\(type(of: self)): Response error -", error.localizedDescription)
}
self.clientCall = call
}
func send(text: String) {
if self.clientCall == nil {
self.initiateClient()
}
let message = ChatGuide_TextMessage.with {
$0.text = text
}
self.clientCall?.sendMessage(message, promise: nil)
}
}
Hey Vignesh,
I am currently learning gRPC-Swift myself, so I hope I will be of service and not muck things further.
However, it looks to me that you are not configuring the HTTP/1.x layer in order to transfer Protobuf packets, if you take a look at the HTTP1ToGRPCServerCodec.swift file Here
I think you will have a much clearer idea of how to adjust your code, I am sorry I can't provide more details, however not being too sure myself without further testing and reviewing the codebase.
Best regards and keep me posted if indeed i was helpful,
cheers
From the Server I have initiated insecure Server as,
let server = Server.insecure(group: self.group)
From the Client I have initiated secure ClientConnection as,
let channel = ClientConnection.secure(group: self.group)
And I got this clarification from here
So I made the ClientConnection also insecure as,
let channel = ClientConnection.insecure(group: self.group)
And after this it is working now.

Resources