Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to use this with alamofireobjectmapper? #34

Closed
rlam3 opened this issue Aug 10, 2016 · 16 comments
Closed

How to use this with alamofireobjectmapper? #34

rlam3 opened this issue Aug 10, 2016 · 16 comments

Comments

@rlam3
Copy link

rlam3 commented Aug 10, 2016

How to use this with alamofireobjectmapper?

Please provide short example

I'm trying to nest several alamofire requests. Is it possible that you provide an example with the documentation? Thanks!

@kciter
Copy link
Member

kciter commented Aug 11, 2016

Below is simple example.
https://gist.github.com/kciter/e6f13119cbf8ad0963a68a5ebc421069

import UIKit
import RxSwift
import RxAlamofire
import ObjectMapper

class Post: Mappable {
    var id: Int = 0
    var title: String = ""

    required init?(_ map: Map) {
    }

    func mapping(map: Map) {
        id <- map["id"]
        title <- map["title"]
    }
}

extension ObservableType {
    public func mapObject<T: Mappable>(type: T.Type) -> Observable<T> {
        return flatMap { data -> Observable<T> in
            let json = data as? AnyObject
            guard let object = Mapper<T>().map(json) else {
                throw NSError(
                    domain: "",
                    code: -1,
                    userInfo: [NSLocalizedDescriptionKey: "ObjectMapper can't mapping"]
                )
            }

            return Observable.just(object)
        }
    }

    public func mapArray<T: Mappable>(type: T.Type) -> Observable<[T]> {
        return flatMap { data -> Observable<[T]> in
            let json = data as? AnyObject
            guard let objects = Mapper<T>().mapArray(json) else {
                throw NSError(
                    domain: "",
                    code: -1,
                    userInfo: [NSLocalizedDescriptionKey: "ObjectMapper can't mapping"]
                )
            }

            return Observable.just(objects)
        }
    }
}

class ViewController: UIViewController {
    var disposeBag: DisposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()

        JSON(.GET, "http://jsonplaceholder.typicode.com/posts").mapArray(Post.self).subscribeNext { (posts) in
            for post in posts {
                print(post.title)
            }
        }.addDisposableTo(disposeBag)
    }
}

@rlam3
Copy link
Author

rlam3 commented Aug 12, 2016

@kciter Thanks. But what if I need to do nested alamofire requests? Do I make both request an observable? or just one observable with nested alamofire?

@kciter
Copy link
Member

kciter commented Aug 12, 2016

@rlam3 What do you think is below to use?

Observable.just()
    .flatMap {
        return JSON(.GET, "http://jsonplaceholder.typicode.com/posts").mapArray(Post.self)
    }
    .flatMap { posts in
        return JSON(.GET, "http://jsonplaceholder.typicode.com/comments").mapArray(Comment.self).map { comments in
                    return (posts, comments)
        }
    }
    .subscribeNext { result in
        // ...
    }.addDisposableTo(disposeBag)

@rlam3
Copy link
Author

rlam3 commented Aug 12, 2016

@kciter From what it looks like, you're mapping the comments to the post themselves correct? How do I get the values out of result? result.post? result.comment?

Say for example. I need to asynchronously obtain a JSON web token and use this token in the next request like retrieving a post. Is there a way similar to promisekit in rxswift for this? Like

getjsonwebtoken().then{
    jwt in
    getPosts(jwt:jwt){
       posts in
       // update ui with posts
    }
}

@rlam3
Copy link
Author

rlam3 commented Aug 15, 2016

Found http://www.finneycanhelp.com/tag/rxswift/ ... I guess what was returned for result should be more like (posts, comments) and from there we can do what we need

@kciter
Copy link
Member

kciter commented Aug 16, 2016

@rlam3

getjsonwebtoken().
    .flatMap { jwt in
        return getPosts(jwt: jwt).mapArray(Post.self) // mapArray is ObservableType extension.
    }
    .subscribeNext { posts in
        // update ui...
    }.addDisposableTo(disposeBag)

@rlam3
Copy link
Author

rlam3 commented Aug 18, 2016

@kciter When I use flatMap to chain the second time. It gave me the following error:

Cannot convert value of type '(_) -> Post' to expect argument type '(Post) -> _' 

What does this mean and how should I resolve this? Thanks!

@kciter
Copy link
Member

kciter commented Aug 18, 2016

@rlam3 Please show me your code. :)

@rlam3
Copy link
Author

rlam3 commented Aug 19, 2016

@kciter Looks like I was able to get it to work now. I believe it was a mapping issue. Thanks! My next problem is that Swift doesn't recognize my JSON parameters? Is there a specific way to implement headers and parameters when using Alamofire.JSON this way? Everything is installed correctly.

When I cmd + left-click on "JSON", it does not take me to the function. It returns back to me a question mark. Whereas the second one does take me to the function.

// This Doesnt work like this

Observable.just()
    .flatMap{

        let csrf: String = api.getCSRFTokenFromAPI()
        return JSON(.POST,"http://localhost:5000/api/auth", parameters: ["username": "XXXX@gmail.com", "password": "XXXX"], encoding: .JSON, headers: ["X-CSRFToken" : csrf])

    .subscribeNext{

        (auth_token) in

        print("Auth TOKEN !! \(auth_token)")

    }.addDisposableTo(disposeBag) <<<<< ERROR HERE

// Cannot invoke 'addDisposableTo' with an argument list of type '(DisposeBag)'

// This works down here

        Observable.just()
            .flatMap{

                return JSON(.GET,"http://localhost:5000/api/v1/csrf_token")

            }.subscribeNext{

                csrf_token in

                print("CSRF TOKEN !! \(csrf_token)")

            }.addDisposableTo(disposeBag)

Really new to RxSwift haha.
Thanks!

@rlam3
Copy link
Author

rlam3 commented Aug 19, 2016

URL Strings are great and all. But I'd like to get more control over my requests that I build when I am doing "Alamofire.request" instead of RxAlamofire.request because I'm trying to update the body of my json POST. Is there a way around this? This seems much more complicated than it should be since all I'm trying to do is obtain a token and then use that token in the next request. I'm observing a patter that Observables run on different sessions and don't conincide when I use the Alamofire.request way so I'm using a using a Manager.sharedInstance way now to test if the cookies/session is in the same.

But I'm running into the following print statement error where it says

ERROR: "Ambiguous reference to member 'print(_:seperator:terminator)'"

let manager = APIClient().sharedInstance

// GET JWT
Observable.just()
    .flatMap{

        // CSRF
        return manager
            .rx_responseJSON(.GET, "http://localhost:5000/api/v1/csrf_token", parameters: nil, encoding: .JSON, headers: nil)
    }
    .flatMap{

        (r,d) in

        let csrf = d["csrf_token"]

        // JWT

                return manager.rx_responseJSON(.POST,"http://localhost:5000/api/auth", parameters: ["username": "XXXX@gmail.com", "password": "XXXX"], encoding: ParameterEncoding.JSON, headers: ["X-CSRFToken" : csrf, "Content-Type": "application/json"]).map{

                    (response) in

                    return response

                }

    }
    .subscribeNext{

        (response) in

        print(response) <<<<<< ERROR HERE, not sure how to debug this.

    }.addDisposableTo(disposeBag) <<<< Generic element error

But If I remove print statement. it will tell me:

ERROR: "Generic parameter 'Element' could not be inferred"

@kciter
Copy link
Member

kciter commented Aug 22, 2016

Hello, @rlam3
I don't know APIClient object. but if write the code in my way as below.

Observable.just()
            .flatMap{
                return JSON(.GET, "http://localhost:5000/api/v1/csrf_token", parameters: nil, encoding: .JSON, headers: nil)
            }
            .map { response in
                let csrf = response["csrf_token"] as! String
                return JSON(.POST, "http://localhost:5000/api/auth", parameters: ["username": "XXXX@gmail.com", "password": "XXXX"], encoding: .JSON, headers: ["X-CSRFToken" : csrf, "Content-Type": "application/json"])
            }
            .subscribeNext{ response in
                print(response)
            }.addDisposableTo(disposeBag)

The biggest difference is changed flatMap to map.

@rlam3
Copy link
Author

rlam3 commented Aug 22, 2016

@kciter Okay. I tried your following code but my response only give me back the following::

<FlatMap<Request, Observable<AnyObject>>: 0x7fbc1b647990>

when it prints out and it doesn't have any properties or attributes I can call onto from the observable. How do I interact with the second JSON response? Thanks!

Reference:
https://www.natashatherobot.com/swift-2-flatmap/

@kciter
Copy link
Member

kciter commented Aug 23, 2016

@rlam3 flatMap and map operator has only one parameter. If you want to return 2 params, you can use to Set Type.

@rlam3
Copy link
Author

rlam3 commented Aug 23, 2016

@kciter i see... also I'd like to mention that print statements inside of "flatMap{}" usually triggers

// Ambiguous reference to member 'print'

Any reason why this may happen or why it is not a good idea to put a print statement inside flatMap before the return statement?

@stanislavprokopov
Copy link

@kciter I am trying to use the example you provided in the second comment to this issue with latest versions of Swift, ObjectMapper and RxAlamofire and it fails during "map(json)". Could you please provide an updated example?
The only changes I did are related to compiler complaining about "map(json)" so i had to change it to "map(JSONObject: json)", not sure if this is correct, what is the actual data type that is coming to flatmap?

@stanislavprokopov
Copy link

I have resolved the issue i was having by changing the

let json = data as? AnyObject
guard let object = Mapper<T>().map(json) else {

to

let (_, json) = (data as? (HTTPURLResponse, Any))!
guard let object = Mapper<T>().map(JSONObject: json) else {

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants