Latest 2.3.1
Homepage https://github.com/michaelhenry/RxRetroSwift
License MIT
Platforms ios 9.0
Dependencies RxSwift, RxCocoa
Authors

CI Status
Version
License
Platform

What does it do?

It simplifies your RESTful API calls, automatically convert the HttpResponse into specified Model as well as the Error using the new apple ’s Codable feature.

For example in a request for fetching specific user information and you have a User model, all you have to do is make the User model conforms to Codable and specify it when using the RequestCaller.

{
  "name":"kel",
  "email":"[email protected]"
}

User model that conforms to Codable.

struct User: Codable {
  var name:String
  var email:String
}

This will automatically convert the response into an instance User model.

Example:


let caller = RequestCaller(config: URLSessionConfiguration.default)

func fetchUser(byUserId userId) -> Observable<Result<User, ErrorModel>> {
    let request:URLRequest = RequestModel(
      httpMethod: .get,
      path: "v1/users/(userId)")
      .asURLRequest()
    return caller.call(request)
  }

Let say it’s an array of users; since Array conforms to Codable, all you have to do is specify the type to be [User].

Example:

func fetchUsers() -> Observable<Result<[User], ErrorModel>> {
    let request:URLRequest = RequestModel(
      httpMethod: .get,
      path: "v1/users")
      .asURLRequest()
    return caller.call(request)
  }

About handling ResponseError:

RxRetroSwift provided a typealias ErrorCodable which is a combination of HasErrorInfo and Decodable protocol:

public typealias DecodableError = Decodable & HasErrorInfo

For example, the json error response of your login request is

{
  "message": "Unable to login."
  "details": {
    "password": "You changed your password 2 months ago."
  }
}

And you have this as Model:

struct ErrorModel {

  var errorCode:Int?
  var message:String
  var details:[String:String]
}

How about dealing to a request that don’t expect to return an object or model?

RxRetroSwift provide a method that will return Observable<Result<RawResponse>, DecodableErrorModel>>.


public func call<DecodableErrorModel:DecodableError>(_ request: URLRequest)
  -> Observable<Result<RawResponse, DecodableErrorModel>>

public struct RawResponse {

  public var statusCode:Int
  public var data:Data?

  init(statusCode:Int, data:Data?) {
    self.statusCode = statusCode
    self.data       = data
  }
}

Example

To run the example project, clone the repo, and run pod install from the Example directory first.

Requirements

Features

  • Easy to use and simple, Just few lines of codes (excluding RxSwift).
  • Clean and Neat implementation.
  • Flexible error handling.
  • Simplify your rest API client.

TODO

  • Unit and integration test (done)
  • Add Example (done)
  • Support different authentication method for your URLRequest.

Installation

Cocoapods

RxRetroSwift is now available through CocoaPods. To install
it, simply add the following line to your Podfile:

pod 'RxRetroSwift'

Swift Package Manager

.package(url: "https://github.com/michaelhenry/RxRetroSwift", from: "2.1"),

Sample implementation

Using JSONPlaceholder API.
You can also check the Sample Project

class APIClient {

  static var shared = APIClient()
  var caller = RequestCaller.shared

  private init() {

    RequestModel.defaults.baseUrl = "https://jsonplaceholder.typicode.com"
  }

  func fetchPosts() -> Observable<Result<[Post], ErrorModel>> {
    let request = RequestModel(
      httpMethod: .get,
      path: "posts")
      .asURLRequest()

    return caller.call(request)
  }

  func insertPost(post:Post) -> Observable<Result<Post, ErrorModel>> {
    let request = RequestModel(
      httpMethod: .post,
      path: "posts",
      payload: post.dictionaryValue)
      .asURLRequest()

    return caller.call(request)
  }

  func fetchComments() -> Observable<Result<[Comment], ErrorModel>> {
    let request = RequestModel(
      httpMethod: .get,
      path: "comments")
      .asURLRequest()

    return caller.call(request)
  }

  func fetchAlbums() -> Observable<Result<[Album], ErrorModel>> {
    let request = RequestModel(
      httpMethod: .get,
      path: "albums")
      .asURLRequest()

    return caller.call(request)
  }

  func fetchPhotos() -> Observable<Result<[Photo], ErrorModel>> {
    let request = RequestModel(
      httpMethod: .get,
      path: "photos")
      .asURLRequest()

    return caller.call(request)
  }

  func fetchTodos() -> Observable<Result<[Todo], ErrorModel>> {
    let request = RequestModel(
      httpMethod: .get,
      path: "todos")
      .asURLRequest()

    return caller.call(request)
  }

  func fetchUsers() -> Observable<Result<[User],ErrorModel>> {

    let request = RequestModel(
      httpMethod: .get,
      path: "users")
      .asURLRequest()

    return caller.call(request)
  }
}

Testing


class TestAPIClient:QuickSpec {

  override func spec() {

    describe("Using JSONPlaceholder API") {

      let apiClient = APIClient.shared

      it("Check Posts result count"){
        let observable = apiClient.fetchPosts()
        expect(observable.map { $0.value!.count }).first == 100
      }

      it("Can insert post"){
        var post = Post()
        let title = "This is my post"
        let userId = 101
        let body = "This is a message body"

        post.title = title
        post.userId = userId
        post.body = body
        let observable = apiClient.insertPost(post: post)
        expect(observable.map { $0.value?.title ?? "" }).first == title
        expect(observable.map { $0.value?.userId ?? 0 }).first == userId
        expect(observable.map { $0.value?.body ?? "" }).first == body
      }

      it("Check Comments result count"){
        let observable = apiClient.fetchComments()
        expect(observable.map { $0.value!.count }).first == 500
      }

      it("Check Albums result count"){
        let observable = apiClient.fetchAlbums()
        expect(observable.map { $0.value!.count }).first == 100
      }

      it("Check Photos result count"){
        let observable = apiClient.fetchPhotos()
        expect(observable.map { $0.value!.count }).first == 5000
      }

      it("Check Todos result count"){
        let observable = apiClient.fetchTodos()
        expect(observable.map { $0.value!.count }).first == 200
      }

      it("Check Users result count"){
        let observable = apiClient.fetchUsers()
        expect(observable.map { $0.value!.count }).first == 10
      }
    }
  }
}

Contributions

Just feel free to submit pull request or suggest anything that would be useful.

Author

Michael Henry Pantaleon, [email protected]

License

RxRetroSwift is available under the MIT license. See the LICENSE file for more info.

Latest podspec

{
    "name": "RxRetroSwift",
    "version": "2.3.1",
    "summary": "A Simple, Neat and Clean, Easy to use Reactive RetroFit inspired implementation in swift.",
    "description": "A Simple, Neat and Clean, Easy to use Reactive RetroFit inspired implementation in swift. nAll you have to do is create models( and viewModels) and let the RxRetroSwift handle the difference response objects for you.",
    "homepage": "https://github.com/michaelhenry/RxRetroSwift",
    "license": {
        "type": "MIT",
        "file": "LICENSE"
    },
    "authors": {
        "Michael Henry Pantaleon": "[email protected]"
    },
    "source": {
        "git": "https://github.com/michaelhenry/RxRetroSwift.git",
        "tag": "2.3.1"
    },
    "social_media_url": "https://twitter.com/michaelhenry119",
    "platforms": {
        "ios": "9.0"
    },
    "source_files": "Sources/RxRetroSwift/**/*",
    "dependencies": {
        "RxSwift": [
            "~> 4.1"
        ],
        "RxCocoa": [
            "~> 4.1"
        ]
    }
}

Pin It on Pinterest

Share This