Swift 3, RxAlamofire and mapping to custom object - swift

I created simple project to check libraries like RxAlamofire and AlamofireObjectMapper. I have simple ApiService with one endpoint where is PHP script which works properly and returns JSON. I want call to recipeURL and I use flatMap operator to get response and provide it into Mapper where I should get Recipe object. How I can to do that?
Or is there other way?
class ApiService: ApiDelegate{
let recipeURL = "http://example.com/test/info.php"
func getRecipeDetails() -> Observable<Recipe> {
return request(.get, recipeURL)
.subscribeOn(MainScheduler.asyncInstance)
.observeOn(MainScheduler.instance)
.flatMap({ request -> Observable<Recipe> in
let json = ""//request.??????????? How to get JSON response?
guard let recipe: Recipe = Mapper<Recipe>().map(JSONObject: json) else {
return Observable.error(ApiError(message: "ObjectMapper can't mapping", code: 422))
}
return Observable.just(recipe)
})
}
}

From RxAlamofire's readme, it seems a method json(_:_:) exists in the library.
Typically, you'd rather use map instead of flatMap to transform the returned data to another format. flatMap would be useful if you needed to subscribe to a new observable (for example, doing a second request using part of the result from the first one).
return json(.get, recipeURL)
.map { json -> Recipe in
guard let recipe = Mapper<Recipe>().map(JSONObject: json) else {
throw ApiError(message: "ObjectMapper can't mapping", code: 422)
}
return recipe
}

Related

Code improvement - how to serve use pictures in Vapor?

I have a working directory that contains every user's picture and I am trying to implement a call that returns data containing the user's picture, defined in this structure:
struct ImageData: Content {
var picture: Data // UIImage data
}
I tried to implement a solution also partially using what I found in the book 'Server Side Swift with Vapor' (version 3) in chapter 26 but that's different for me because I am not using Leaf and I need to return the data directly.
I came up with this function to return the user picture, which does its job but I am trying to improve it.
func getProfilePictureHandler(_ req: Request) throws -> EventLoopFuture<ImageData> {
return User.find(req.parameters.get("userID"), on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { user in
// To do: throw error (flatMapThrowing?)
let filename = user.profilePicture!
let path = req.application.directory.workingDirectory
+ imageFolder
+ filename
// Improvement: Do I need this?
var data = Data()
return req.fileio.readFile(at: path) { buffer -> EventLoopFuture<Void> in
let additionalData = Data(buffer: buffer)
data.append(contentsOf: additionalData)
return req.eventLoop.makeSucceededVoidFuture()
}.map {
return ImageData(picture: data)
}
}
}
First:
How to implement this using flatMapThrowing? If I replace flatMap with flatMapThrowing I get this error: "Cannot convert return expression of type 'EventLoopFuture' to return type 'ImageData'". Which doesn't make sense to me considering that flatMap allows returning a future and not a value.
I didn't find any solution other than using a Data variable and appending chunks of data as more data is read. I am not sure that this is thread-safe, FIFO and I don't consider it an elegant solution. Does anybody know any better way of doing it?
The short answer is that as soon as you have the file path, Vapor can handle it all for you:
func getProfilePictureHandler(_ req: Request) throws -> EventLoopFuture<Response> {
return User.find(req.parameters.get("userID"), on: req.db)
.unwrap(or: Abort(.notFound))
.tryflatMap { user in
// To do: throw error (flatMapThrowing?)
guard let filename = user.profilePicture else {
throw Abort(.notFound)
}
let path = req.application.directory.workingDirectory
+ imageFolder
+ filename
return req.fileio.streamFile(at: path)
}
}
You can use tryFlatMap to have a flatMap that can throw and you want to return a Response. Manually messing around with Data is not usually a good idea.
However, the better answers are use async/await and the FileMiddleware as two tools to clean up your code and remove the handler altogether

Returning parsed JSON data using Alamofire?

Hello new to Swift and Alamofire,
The issue i'm having is when I call this fetchAllUsers() the code will return the empty users array and after it's done executing it will go inside the AF.request closure and execute the rest.
I've done some research and I was wondering is this is caused by Alamofire being an Async function.
Any suggestions?
func fetchAllUsers() -> [User] {
var users = [User]()
let allUsersUrl = baseUrl + "users/"
if let url = URL(string: allUsersUrl) {
AF.request(url).response { response in
if let data = response.data {
users = self.parse(json: data)
}
}
}
return users
}
You need to handle the asynchrony in some way. This this means passing a completion handler for the types you need. Other times it means you wrap it in other async structures, like promises or a publisher (which Alamofire also provides).
In you case, I'd suggest making your User type Decodable and allow Alamofire to do the decoding for you.
func fetchAllUsers(completionHandler: #escaping ([User]) -> Void) {
let allUsersUrl = baseUrl + "users/"
if let url = URL(string: allUsersUrl) {
AF.request(url).responseDecodable(of: [User].self) { response in
if let users = response.value {
completionHandler(users)
}
}
}
}
However, I would suggest returning the full Result from the response rather than just the [User] value, otherwise you'll miss any errors that occur.

Unable to infer complex closure return type; add explicit type to disambiguate in RxSwift

I need to make multiple calls.
1. Delete Document Upload
2. Image 1 & server returns URL
3. Upload Image 2 & server returns URL
4. Create Document API contains both URLs & extra
parameters.
The code which I tried to write is in RxSwift,& MVVM.
let resultOfDocumentUpdateWithDelete =
donepressed
.filter{ $0 }
.withLatestFrom(self.existingDocumentIDChangedProperty)
.flatMapLatest {id in
let deleted_document = apiClient.deleteDocument(id).asObservable().materialize()
let upload_frontImage = deleted_document
.withLatestFrom(self.frontImageNameChangedProperty)
.flatMapLatest {image in
apiClient.uploadImage(image: image!).asObservable().materialize()
}
let upload_backImage = upload_frontImage
.withLatestFrom(self.backImageChangedProperty)
.flatMapLatest {image in
apiClient.uploadImage(image: image!).asObservable().materialize()
}
let upload_document = upload_backImage
.withLatestFrom(self.parametersChangedProperty)
.flatMapLatest {parameters in
apiClient.uploadDocument(parameters: parameters)
}
return upload_document.materialize()
}
.share(replay: 1)
Make sure, two responses of server are input in last API, so all of these will be called in a sequence.
how to do in RxSwift.
This was an interesting one! The take-away here is that when you are in doubt, go ahead and make your own operator. If it turns out that you later figure out how to do the job using the built-in operators, then you can replace yours. The only thing with making your own is that they require a lot more testing.
Note, to use the below, you will have to combineLatest of your observables and then flatMap and pass their values into this function.
// all possible results from this job.
enum ProcessResult {
case success
case deleteFailure(Error)
case imageFailue(Error)
case backImageFailure(Error)
case documentFailure(Error)
}
func uploadContent(apiClient: APIClient, documentID: Int, frontImage: UIImage, backImage: UIImage, parameters: Parameters) -> Single<ProcessResult> {
// instead of trying to deal with all the materializes, I decided to turn it into a single process.
return Single.create { observer in
// each api call happens in turn. Note that there are no roll-back semantics included! You are dealing with a very poorly written server.
let deleted = apiClient.deleteDocument(id: documentID)
.asObservable()
.share()
let imagesUploaded = deleted
.flatMap { _ in Observable.zip(apiClient.uploadImage(image: frontImage).asObservable(), apiClient.uploadImage(image: backImage).asObservable()) }
.share()
let documentUploaded = imagesUploaded
.flatMap { arg -> Single<Void> in
let (frontURL, backURL) = arg
var updatedParams = parameters
// add frontURL and backURL to parameters
return apiClient.uploadDocument(parameters: updatedParams)
}
.share()
let disposable = deleted
.subscribe(onError: { observer(.success(ProcessResult.deleteFailure($0))) })
let disposable1 = imagesUploaded
.subscribe(onError: { observer(.success(ProcessResult.imageFailue($0))) })
let disposable2 = documentUploaded
.subscribe(
onNext: { observer(.success(ProcessResult.success)) },
onError: { observer(.success(ProcessResult.documentFailure($0))) }
)
return Disposables.create([disposable, disposable1, disposable2])
}
}

Swift dealing with classes, UIButtons and tableView

This might sound like a very stupid question but I am fairly new to swift and cannot think how to go about this. As you can see in this Screenshot I have a search recipes textfield in RecipesViewController where the user enters a food item (which I use in the api call). After the user hits the button I make a call to an api and get data from that api and store that data in instance variable (searchRecipe array) in my RecipesViewController class. Now I am trying to show the data that I received from the api in a table view so I have another class called SearchRecipeTViewController. n this class I want to populate the table with the data I received from the api however when I try to access the searchRecipe array (which stores the elements received from the api) I get a blank value which I understand is due to the instance variable being initialized as "". But now how do I go about this so that I can get data from the api and display it on the table view when the user hits the button. Any suggestions would be appreciated.
Code to call and get data from api when button is clicked
#IBAction func SearchButton(sender: UIButton) {
if let recipe = RecipeSearchBar.text {
searchRecipe = recipe
}
//search recipe API call
endpoint = "http://api.yummly.com/v1/api/recipes? _app_id=apiID&_app_key=apiKey&q=\(searchRecipe)"
Alamofire.request(.GET, endpoint).responseJSON { response in
if response.result.isSuccess {
let data = response.result.value as! NSDictionary
if let matches = data["matches"] as? [[String: AnyObject]] {
for match in matches {
if let name = match["recipeName"] as? String {
self.recipeName.append(name);
}
}
}
}
else if response.result.isFailure {
print("Bad request")
}
}
}
Try using SwiftyJSON to manipulate the JSON the API returns. SwiftyJSON makes API calls that use JSON much easier. Here is the code I used that uses both Alamofire and SwiftyJSON.
//Use alamofire to connect to the web service and use GET on it
Alamofire.request(url).responseJSON { response in
if let error = response.result.error {
print("Error: \(error)")
return
}
//Value is the response the webservice gives, given as a Data Obkect
if let value = response.result.value {
//The information given from the webservice in JSON format
let users = JSON(value)
print("The user information is: \(users)")
//Get each username in the JSON output and print it
for username in users.arrayValue{
print(username["username"].stringValue)
}
}
}
Forgot to add a link to SwiftJSON: https://github.com/SwiftyJSON/SwiftyJSON

Alamofire retry request - reactive way

I was looking at those two:
http://sapandiwakar.in/refresh-oauth-tokens-using-moya-rxswift/
Using retryWhen to update tokens based on http error code
And trying to create similiar thing, but without Moya, using Alamofire + RxSwift.
First of all is obviously where should I stick this, since my implementation is divided into a couple smaller parts.
First of all I have my custom method for generating reactive requests:
static func rx_request<T>(requestConvertible: URLRequestConvertible, completion: (Request) -> Observable<T> ) -> Observable<T> {
let manager: Manager = Manager.sharedInstance
return manager
.rx_request { manager -> Request in
return Alamofire.request(requestConvertible)
}
.flatMap { request -> Observable<T> in
return completion(request)
}
.shareReplay(1)
}
Which is later used by specific Requests convenience classes. For example my UserRequests has this private extension to extract some common code from it's methods:
private extension Request {
func rx_userRequest() -> Observable<User> {
return self
.validate()
.rx_responseJSON()
.flatMap{ (request, json) -> Observable<User> in
guard
let dict = json as? [ String: AnyObject ],
let parsedUser: User = try? Unbox(dict) else {
return Observable.error(RequestError.ParsingError)
}
return Observable.just(parsedUser)
}
.rx_storeCredentials()
}
}
Because of how things looks like I wonder whare's the right place to put a retry method and also how to implement it? Because depending on the location I can get different input arguments.
The retry code has to go after the first try, which is rx_responseJSON so the way you have things setup now, it must go between that and the flatMap after it.