Construct Moya TargetType in iOS - ios

I'm trying to use Moya with RxSwift in my project
I'm facing the problem with url contain "?"
This TargetType I have create
private extension String {
var URLEscapedString: String {
return self.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlHostAllowed)!
}
}
enum TMDb {
case discoverMovieList(page: Int)
case discoverMovieDetail(moive: Movie)
}
extension TMDb: TargetType {
var baseURL: URL { return URL(string: BASE_URL)! }
var path: String {
switch self {
case .discoverMovieList(page: let page):
return "discover/movie?api_key=\(API_KEY)&sort_by=release_date.desc&page=\(page)"
}
}
var method: Moya.Method {
return .get
}
var parameters: [String: Any]? {
return nil
}
var sampleData: Data {
switch self {
case .discoverMovieList(page: _):
return "test".data(using: .utf8)!
case .discoverMovieDetail(moive: _):
return "test1".data(using: .utf8)!
}
}
var task: Task {
return .request
}
var parameterEncoding: ParameterEncoding {
return URLEncoding.default
}
}
The problem is when I make the request. The url path return is somehow not correct
This is the url i got from console
Optional("https://api.themoviedb.org/3/discover/movie%3Fapi_key=58da429caf2e25e8ff9436665e2f0e36&sort_by=release_date.desc&page=1")
But the correct one should be
https://api.themoviedb.org/3/discover/movie?api_key=58da429caf2e25e8ff9436665e2f0e36&sort_by=release_date.desc&page=1
There something wrong when handle the "?" (it become %3F" character in the url. How can we make it work normally?
Update
This is how I call my Moya
let provider: RxMoyaProvider<TMDb>
let persistentContainer: PersistentContainer
func discoverMoiveList(for page: Int) {
self.provider.request(TMDb.discoverMovieList(page: 1)) { (result) in
print(result.value?.request?.url?.absoluteString ?? "no url")
}
}
}

Here you've got a topic how to get an optional variable without Opt(). I prefer that way where you create an extension:
extension Optional {
func asStringOrNilText() -> String {
switch self {
case .some(let value):
return String(describing: value)
case _:
return "(nil)"
}
}
}
print(result.value?.request?.url?.absoluteString.asStringOrNilText())

The problem is not about "?". It occurred because of you used optional values in your path URL. Control BASE_URL and API_KEY, they must not be optional values.
There is no problem with the Moya. Here Moya-Logger result for your provider:
Moya_Logger: [29/04/2017 15:48:22] Request: https://api.themoviedb.org/3/discover/movie%3Fapi_key=58da429caf2e25e8ff9436665e2f0e36&sort_by=release_date.desc&page=1
Moya_Logger: [29/04/2017 15:48:22] Request Headers: [:]
Moya_Logger: [29/04/2017 15:48:22] HTTP Request Method: GET
Moya_Logger: [29/04/2017 15:48:23] Response: <NSHTTPURLResponse: 0x618000220360> { URL: https://api.themoviedb.org/3/discover/movie%3Fapi_key=58da429caf2e25e8ff9436665e2f0e36&sort_by=release_date.desc&page=1 } { status code: 200, headers {
Connection = "keep-alive";
"Content-Length" = 89;
"Content-Type" = "application/octet-stream";
Date = "Sat, 29 Apr 2017 12:48:23 GMT";
Server = openresty;
"X-RateLimit-Limit" = 40;
"X-RateLimit-Remaining" = 39;
"X-RateLimit-Reset" = 1493470113;
} }

You should pass anything after the ? as parameter, this way Moya knows how to construct the URL correctly.
var path: String {
switch self {
case .discoverMovieList(page: let page):
return "discover/movie"
}
}
var method: Moya.Method {
return .get
}
var parameters: [String: Any]? {
switch self {
case .discoverMovieList(page: let page):
return "["api_key":"\(API_KEY)",
"sort_by":"release_date.desc",
"page":page]"
}
}

Related

iOS Networking - Moya & Protocol Buffer Serialisation

Can anyone elaborate me on the possibility of using Moya for communicating with web services that use ProtoBuf Serialisation instead of JSON notation? Is it possible? Is it already implemented or is there any extensions for it?
Any information is really appreciated :)
Yes it is possible
Here is the process how you can use protobuf with Moya.
import Foundation
import Moya
import SwiftProtobuf
enum Currency {
case exchangeRate(param: SwiftProtobuf.Message)
}
extension Currency: TargetType {
var validationType: ValidationType {
return .successCodes
}
var headers: [String: String]? {
return [
"Accept": "application/octet-stream",
"Content-Type": "application/octet-stream",
"Authorization": [use auth token here if any],
]
}
var baseURL: URL {
return URL(string:"https:www.currency.com")
}
var path: String {
switch self {
case .exchangeRate:
return "/exchangeRate"
}
}
var method: Moya.Method {
switch self {
case .exchangeRate:
return .post
}
}
var parameters: Data? {
switch self {
case let .exchangeRate(param):
return try! param.serializedData()
}
}
var task: Task {
switch self {
case .exchangeRate:
return .requestData(self.parameters!)
}
}
}
You can access an API like this:
provider = MoyaProvider<GitHub>()
provider.request(.exchangeRate(param:[here will be protobuf model object])) {
result in
switch result {
case let .success(moyaResponse):
//do something with the response data
case let .failure(error):
//error occured
}
}

Yelp Fusion API

I am a beginner trying to incorporate yelp's fusions API into my app but I couldn't find any proper resources on how to use the API. I am trying to use the business Search endpoint. can someone help me? Thanks.
I am using yelp for my location based restaurant viewing application.
I use Moya for the network requests. I have example for you:
In your codebase define your own api key!
import Foundation
import Moya
private let apiKey = MyConstants.shared.apiKey
enum YelpService {
enum BusinessProvider: TargetType {
case search(lat: Double, long: Double)
case details(id: String)
var baseURL: URL {
return URL(string: "https://api.yelp.com/v3/businesses")!
}
var path: String {
switch self {
case .search:
return "/search"
case let .details(id):
return "/\(id)"
}
}
var method: Moya.Method {
return .get
}
var sampleData: Data {
return Data()
}
var task: Task {
switch self {
case let .search(lat, long):
return .requestParameters(parameters: ["latitude": lat, "longitude": long, "limit": 10], encoding: URLEncoding.queryString)
case .details:
return .requestPlain
}
}
var headers: [String: String]? {
return ["Authorization": "Bearer \(apiKey)"]
}
}
}
Your api link should look like this : https://api.yelp.com/v3/businesses/search

Enum element cannot be referenced as an instance member

I'm creating an API layer using Moya and keep getting the above mentioned error for the .updateMyWeightGoal target when I'm creating a request for an endpoint.
goalAPI.request(target: .updateMyWeightGoal(weightGoalData: goalInfo), success: { (response) in
//
}){ (response: [String : Any]) in
print(response)
}
I've created another Moya API of the same type and am calling it using the same goalAPI as this and it's working fine.
Any ideas what might be causing this issue
For reference here is the class definition for the weightGoalData type
class UpdatedWeightGoalInfo: Mappable {
var consumerUserID: Int?
var height: String?
var weight: String?
var goalWeight: String?
init() {
}
convenience init(userId: Int, weightGoalData: WeightGoalResponse) {
self.init()
consumerUserID = userId
height = "\(weightGoalData.currentHeight)"
weight = "\(weightGoalData.currentWeight)"
goalWeight = "\(weightGoalData.goalWeight)"
}
required init?(map: Map) {
}
func mapping(map: Map) {
consumerUserID <- map["consumerUserId"]
height <- map["height"]
weight <- map["weight"]
goalWeight <- map["goalWeight"]
}
}
And the definition of the API:
enum GoalSettingAPI: AccessTokenAuthorizable {
case updateMyWeightGoal(weightGoalData: UpdatedWeightGoalInfo)
}
extension GoalSettingAPI: TargetType {
var parameterEncoding: ParameterEncoding {
switch self {
default:
return JSONEncoding.default
}
}
var baseURL: URL { return URL(string: appBaseURL + "*hidden*/")! }
var path: String {
switch self {
case .updateMyWeightGoal(_):
return "updateMyWeightGoal"
}
}
var method: Moya.Method {
switch self {
case .updateMyWeightGoal(_):
return .post
}
}
var parameters: [String: Any]? {
switch self {
case .updateMyWeightGoal(let weightGoalData):
return weightGoalData.toJSON()
}
}
var sampleData: Data {
switch self {
default:
return Data()
}
}
var task: Task {
switch self {
default:
return .request
}
}
var shouldAuthorize: Bool {
switch self {
default:
return false
}
}
}
This is the stupidest thing.
As it turns out the error was coming, not from the enum, but from the success block. It was expecting an object of type Mappable, which I wasn't providing.
You're referring to .updateMyWeightGoal as an instance member (.) when it is declared as an enum. Try changing:
goalAPI.request(target: .updateMyWeightGoal(weightGoalData: goalInfo)
To
goalAPI.request(target: GoalSettingAPI.updateMyWeightGoal(weightGoalData: goalInfo)
Had the same error. In my case the issue was that I had typed:
if someVariable = .AnEnumValue
What I meant was:
if someVariable == .AnEnumValue
The key difference being = vs ==.
Not sure how the compiler landed on this particular error message, but this fixed the problem.

Using an enum in generic function

I want to create a generic function with an enum as generic parameters.
I have the following generic function:
func buildProvider<T>(service: T) -> RxMoyaProvider<T> {
let endpointClosure = { (target: T) -> Endpoint<T> in
let url = target.baseURL.appendingPathComponent(target.path).absoluteString
let endpoint = Endpoint<T>(URL: url, sampleResponseClosure: {.networkResponse(200, target.sampleData)}, method: target.method, parameters: target.parameters)
return endpoint
}
return RxMoyaProvider<T>(endpointClosure: endpointClosure)
}
I call it like this:
self.userProvider = buildProvider(service: UserService)
Below you see the declaration of userProvider:
var userProvider: RxMoyaProvider?
And below you see an example of UserService:
import Foundation
import Moya
enum UserService {
case login(qrKey: String, language: String, fcmToken: String)
}
extension UserService: TargetType {
var baseURL: URL {
let appConfig = AppConfig()
let hostname = try! appConfig.hostname()
return URL(string: hostname)!
}
var path: String {
switch self {
case .login(_,_,_):
return "/v1/login"
}
}
var method: Moya.Method {
switch self {
case .login(_,_,_):
return .post
}
}
var parameters: [String: Any]? {
switch self {
case .login(let qrKey, let language, let fcmToken):
let params = ["qr-key": qrKey, "language": language, "os": "ios", "fcm-token": fcmToken]
return params
}
}
var sampleData: Data {
switch self {
case .login(_, _, _):
return "".data(using: .utf8)!
}
}
var task: Task {
return .request
}
}
And I get the following error:
Cannot convert value of type 'UserService.Type' to expected argument type 'UserService'
Can someone help me with this?
Thanks !
I have no experience with Moya and can not know for sure how it works and what you're trying to achieve, but it seems like you're trying to pass type into function. If so you must do something like:
func buildProvider<T>(service: T.Type) -> RxMoyaProvider<T> {
...
return RxMoyaProvider<T>(endpointClosure: endpointClosure)
}
and call it with .self:
self.userProvider = buildProvider(service: UserService.self)
but it's not your only problem. How do you know without context that type <T> would have baseURL property:
let endpointClosure = { (target: T) -> Endpoint<T> in
let url = target.baseURL.appendingPathComponent(target.path).absoluteString
?
Seems like you've messed up with overall architecture, and I would strongly advice to reconsider it.

how to use a property from a class to another class in swift

I have a model class like this
import Foundation
import SwiftyJSON
class SpecificCategoryJSON {
var _product_id: Int?
var _product_title: String?
var _product_sku: String?
var _product_image: URL?
init(items: JSON){
self._product_id = items["product_id"].int
self._product_title = items["product_title"].stringValue
self._product_sku = items["product_sku"].stringValue
let post_imgAA = items["product_image"].arrayValue
for itemsIMG in post_imgAA {
self._product_image = itemsIMG["guid"].URL
}
}
var product_id: Int {
if _product_id == nil {
_product_id = 0
}
return _product_id!
}
var product_title: String {
if _product_title == nil {
_product_title = ""
}
return _product_title!
}
var product_sku: String {
if _product_sku == nil {
_product_sku = ""
}
return _product_sku!
}
var product_image: URL {
if _product_image == nil {
let myURL = "http://www.clker.com/cliparts/d/L/P/X/z/i/no-image-icon-hi.png"
let noImage: URL = URL(string: myURL)!
_product_image = noImage
}
return _product_image!
}
}
I want to use the product_id to send as a parameter to make a POST request using Alamofire . Here is the POST request where I have kept the product_id static .
class ProductDetailsManager {
//........
func printPublicGists() -> Void {
let url: String = "http://xxx/get_product_informations/"
let parameter = ["product_id": 33]
Alamofire.request(url, method: .post, parameters: parameter, encoding: URLEncoding.default, headers: nil)
.responseJSON { (response) in
guard response.result.error == nil else {
print(response.result.error!)
return
}
guard let value = response.result.value else {
print("no product to show")
return
}
print(value)
}
}
//.......
}
I want to get the product_id from the class SpecificCategoryJSON and use it in class ProductDetailsManager to make a call . What should I do , inherit ? override ? UserDefaults.standard.set ? Please help .
Maybe I misunderstood the question, but you should instantiate your SpecificCategoryJSON class and access it's property :
func printPublicGists() -> Void {
let url: String = "http://xxx/get_product_informations/"
let cat = SpecificCategoryJSON(items: someJSON)
let parameter = ["product_id": cat.product_id]
// ....
}

Resources