I was just playing with Alamofire framework and making few api calls. However I observed there are two request methods in alamofire
What is the difference between responseJSON and responseData of Alamofire.
public func responseData(
queue: DispatchQueue? = nil,
completionHandler: #escaping (DataResponse<Data>) -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: DataRequest.dataResponseSerializer(),
completionHandler: completionHandler
)
}
public func responseJSON(
queue: DispatchQueue? = nil,
options: JSONSerialization.ReadingOptions = .allowFragments,
completionHandler: #escaping (DataResponse<Any>) -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: DataRequest.jsonResponseSerializer(options: options),
completionHandler: completionHandler
)
}
responseJSON will pass a JSON object into its completion. i.e. it will be a dictionary or array with String keys and JSON compatible values.
responseData will pass a Data object into its completion. This may contain JSON data which can be deserialized into a JSON object but it also may contain any other type of data. Image data, HTML, video data, etc...
If you KNOW that you are getting JSON from an endpoint then use the responseJSON call.
Related
I have a list of Books.
open class Book(
#PrimaryKey
var id: String? = null,
var title: String? = null,
var author: String? = null
): RealmObject()
While for-looping, I filter some books and create an Observable with filtered ones. I add each observable to an array.
val listInserts = ArrayList<Observable<Book>>()
for loop(..) {
if (localBook condition) {
val postObservable = networkApiAdapter.insert(localBook)
listInserts.add(postObservable)
}
}
I merge (or concat) the observables hoping for some sequential POST requests.
Observable.concat(listInserts)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ s ->
println(s)
},
{ err ->
println(err)
},
{ println("onComplete") }
)
Always only one POST request is received at my MongoDb+Flask server and I also get this error:
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was NUMBER at line 1 column 4 path $
networkApiAdapter functions, Retrofit:
class NetworkAPIAdapter private constructor() {
fun insert(dto: Book): Observable<Book> {
println(dto.toString())
return bookService.insert(dto.title!!, dto.author!!)
}
interface BooksService {
#FormUrlEncoded
#POST(URL_ORDERS_ALL)
fun insert(
#Field("title") title: String,
#Field("author") author: String
): Observable<Book>
}
}
Any help would be welcomed. I don't know how to do multiples requests. I have tried many solutions with zip, repeatUntil, flatMap, but none worked.
After solving this, I have to delete all local books and do a GET request. All somehow working using RxJava.
Your BooksService.insert is defined as a POST request that expects a response from server in form of a Book JSON string (i.e. Observable<Book>). I think that is not what your server returns after the POST request goes through. Check what your POST request returns, I think it returns some Int (probably a response status) instead of a Book, which would explain your error:
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was NUMBER at line 1 column 4 path $
If that is the case, your concatenated Observable will terminate as soon as the error is thrown, i.e. right after your first POST request, therefore the remaining requests will not be executed.
If my assumptions were right, change your service to:
interface BooksService {
#FormUrlEncoded
#POST(URL_ORDERS_ALL)
fun insert(
#Field("title") title: String,
#Field("author") author: String
): Observable<Int>
}
Also, if you want your other requests to execute even if the previous throws an error, do:
val postObservable = networkApiAdapter.insert(localBook).onErrorReturn(_ -> SOME_INT_FLAG)
UPDATE
To execute your required logic after all the POST requests have been sent, try:
Observable.concat(listInserts)
// complete this observable after all POST requests have been sent
// not that it does not necesarily mean that all responses were 200, you should implement yourself what happens to items that were not successfully `POST`ed
.take(listInserts.size)
.doOnComplete {
// called when this observable completes, i.e. when all POST requests have been sent
executeFinalActions()
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ s->
println(s)
},
{ err ->
println(err)
},
{ println("onComplete") }
)
And implement the executeFinalActions() function:
private fun executeFinalActions() {
realm.executeTransaction(Realm::deleteAll)
networkApiAdapter.fetchAll()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ books ->
// todo: store books
},
{ err ->
println(err)
}
)
}
PS: you may want to have a look what Observable.take() does: http://reactivex.io/documentation/operators/take.html
I have the following method which simply fetches the data in ether SYNC or ASYNC way:
enum class CallType { SYNC, ASYNC }
suspend fun get( url: String, callType : CallType, mock : String? = null, callback: Callback? ): Response?
{
var response : okhttp3.Response ?= null
val request = Request.Builder()
.url( url )
.build()
val call = client.newCall( request )
if( mock != null )
{
// this works fine for SYNC, but how to make it work with ASYNC callback?
delay( 1000 )
return okhttp3.Response.Builder().body(
ResponseBody.create( "application/json".toMediaType(), mock )
).build()
}
if( callType == CallType.ASYNC && callback != null )
call.enqueue( callback )
else
response = call.execute()
return response
}
I would like to be able to mock/overwrite the response. I can do this fine when doing it the SYNC way, since I simply have to construct and return a fake okhttp3.response, like the snippet below, and the code execution stops and everything works out great:
if( mock != null )
{
delay( 1000 )
return okhttp3.Response.Builder().body(
ResponseBody.create( "application/json".toMediaType(), mock )
).build()
}
The problem is that I would like to be able to do the same for ASYNC calls, but I'm not sure where to go from here. I'm basically trying to replicate the enqueue() method so that after some delay my callback gets triggered (which was passed to the get() method) and my fake okhttp3.Response is returned via the callback, instead of return. Any suggestions on how to accomplish this? Thanks!
You're mixing different concepts with your implementation. Asynchrony should be controlled with the CoroutineContext instead of parameters. Like this you'll always return a non-null value. Also it's wise to hide the implementation details (here OkHttp) and not expose it.
You could use suspendCoroutine to bridge OkHttp with the couroutine.
suspend fun get(
url: String,
mock : String? = null
) = if(mock != null) {
delay( 1000 )
Response.Builder().body(
ResponseBody.create(
"application/json".toMediaType()
mock
)
).build()
} else suspendCoroutine { continuation ->
client.newCall(
Request.Builder()
.url(url)
.build()
).enqueue(
object : Callback {
override fun onFailure(call: Call, e: IOException) =
continuation.resumeWithException(e)
override fun onResponse(call: Call, response: Response) =
continuation.resume(response)
}
)
}
To access it synchronously just use
runBlocking { get(url, mock) }
If you really need to provide your own Callable, you could easily delegate to it. But you'd also have to create a call, even though you wouldn't need it when you're mocking the response.
one easy way is to just call the callback in a synchronous way:
if (mock != null) {
val response = ... // prepare mock response here
callback.onResponse(response)
}
in consequence the callback would be invoked even before your get function finishes.
If you want to achieve that the response actually is delivered asynchronously you need to execute the mock delivery from an extra coroutine
if (mock != null) {
GlobalScope.launch {
val response = ... // prepare mock response here
delay(1000)
callback.onResponse(response)
}
}
I am using Moya with RxSwift and I am trying to set the request timeout for the network call (API Calls) using below code as suggested :
which is including the custom Alamofire Manager when declaring your Provider
lazy var provider: RxMoyaProvider<CAPProviderAPI> = {
return RxMoyaProvider<CAPProviderAPI>(endpointClosure: Utility.getEndPointClosure(forProviderType: .authorized), manager: DefaultAlamofireManager.sharedManager, plugins: [NetworkActivityPlugin(networkActivityClosure: networkActivityClosure)])
}()
but I am getting an error saying : Use of unresolved identifier 'networkActivityClosure'
I would like to share with you the way I did it. It might not answer your question, but it shows the way to achieve the desired behavior using RxSwift operators.
I have some function which accepts timeout parameter and makes a request:
func request(timeout: TimeInterval, ...other params) -> Observable<...>
Inside this function I transform timeout to Observable this way:
func observableTimeout(timeout: TimeInterval, ...other params) -> Observable<...> {
return Observable<Int>
.timer(timeout, period: ..., scheduler: ...)
.take(1)
.map(to: ...) // map to timeout error
}
If the timeout takes place - I need to cancel the request. I have made some flatMapLatest analogue which also accepts a cancel signal:
public extension Observable {
public func flatMapLatest<T>(cancel: Observable<T>, factory: #escaping (E) throws -> Observable<T>) -> Observable<T> {
let observableNormal = self
.flatMap({ try factory($0) })
return Observable<Observable<T>>
.of(cancel, observableNormal)
.merge()
.take(1)
}
}
As a result, the request function will work this way:
func request(timeout: TimeInterval, ...other params) -> Observable<...> {
let cancel = observableTimeout(timeout: timeout, ...)
let factory = ...// your observable factory which makes a request using Moya
return Observable
.just((), scheduler: ...)
.flatMapLatest(cancel: cancel, factory: factory)
}
I want to pass a closure through another function via a selector. Here is what I am trying to do ideally:
#objc private func aFunction(_ firstParam: String, onComplete: (String) -> Void) {
//..some internal codes
onComplete("Done.")
}
func functionCaller(_ selectorString: String, paramString: String, onComplete: (String) -> Void) {
let selector : Selector = NSSelectorFromString(selectorString)
self.perform(selector, with: printString, with: onComplete)
}
functionCaller("aFunction:onComplete:", paramString: "anotherParameter", onComplete: { (_ myString String) -> Void in
print(myString)
})
Here the problem is when you try to compile this, Swift gives an error called "Segmentation Fault: 11"
I found the problematic line which is:
self.perform(selector, with: printString, with: onComplete)
when I change last with: onComplete parameter to a String (also changed related functions params) it is working. As far as I understand that the problem is sending closure via self.perform call doesn't work because the first function is an '#objc' marked function (I put this because otherwise perform selector did not work on Swift 3).
So any idea how can I pass a function or closure into a '#objc' marked function via performing selector?
Try to use Any instead of String in your function
func functionCaller(_ selectorString: String, paramString: String, onComplete: (Any) -> Void) {
let selector : Selector = NSSelectorFromString(selectorString)
let complete : Any = onComplete("complete")
self.perform(selector, with: complete)
}
functionCaller("aFunction:onComplete:", paramString: "anotherParameter", onComplete: { (_ myString ) -> Void in
let string = myString as! String
print(string)
})
I tested it and works in Swift 3
let completionHandler: (Any) -> Void = { value in
let js = "try {(callBackKey)('(value)'); delete window.(callBackKey);} catch(e){};"
(jsContext as? TYWKWebView)?.evaluateJavaScript(js, completionHandler: nil)
}
let handlerDict: [String: Any] = [TYJSBridge.COMPLETION_HANDLER_KEY: completionHandler]
let _ = jsInterfaceObject.perform(sel, with: parameters, with: handlerDict)
}
This code used to work in the previousversion of alamofire before swift 2. Now it gives a warning: cast from Result<AnyObject> to Dictionary<String, AnyObject> always fails.
Alamofire.Manager.sharedInstance.request(.POST, url, parameters:params)
.responseJSON { (request, response, data) -> Void in
var result = data as? Dictionary<String,AnyObject> //this gives an error cast from Result<AnyObject> to Dictionary<String, AnyObject> always fails
How can I get the cast to dictionary working?
You need to call:
Alamofire.request(.POST, url, parameters:params)
.responseJSON { request, response, result in
debugPrint(result)
if let value = result.value as? [String: AnyObject] {
print(value)
}
}
You should read through the updated README code samples.
I know it's bit too late to answer this but I share this because I feel like maybe this code can help someone:
Alamofire.request(url, method: .post, parameters: param, encoding: JSONEncoding.default, headers: nil).responseJSON
{
response in
SVProgressHUD.dismiss()
let data = response.result.value
let responseObject = data as? NSDictionary
switch (response.result)
{
case .success(_):
print(responseObject!["message"] as! NSString as String)
break
case .failure(_):
SVProgressHUD.showError(withStatus: (responseObject!["message"] as! NSString as String))
print(responseObject!["message"] as! NSString as String)
break
}
}
Thanks and Enjoy! Happy coding! :)