Yelp Fusion API - ios

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

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
}
}

How to add a function that automatically moves to the second API key in Rxswift

One API key can only make 100 requests per day. So one API key can't handle a lot of requests per day. There are other ways to solve this problem, but I would like to solve this problem by entering various API keys. For example, if the first API key makes 100 requests and the request value returns as an error, I want to add a function that automatically moves to the second API key.
Can you tell me how to make it with Rxswift?
I would appreciate any help you can provide.
The code is as below.
private func loadTopNews() {
let resource = Resource<ArticleResponse>(url: URL(string: "https://newsapi.org/v2/top-headlines?country=\(selectedLanguagesCode[0])&sortBy=%20popularity&apiKey=\(apiKey[0])")!)
URLRequest.load(resource: resource)
.subscribe(onNext: { articleResponse in
let topArticle = articleResponse.articles.first
self.articleVM = ArticleViewModel(topArticle!)
}).disposed(by: disposeBag)
}
struct Resource<T: Decodable> {
let url: URL
}
extension URLRequest {
static func load<T>(resource: Resource<T>) -> Observable<T> {
return Observable.just(resource.url)
.flatMap { url -> Observable<Data> in
let request = URLRequest(url: url)
return URLSession.shared.rx.data(request: request)
}.map { data -> T in
return try JSONDecoder().decode(T.self, from: data)
}
}
}
struct ArticleResponse: Decodable {
let articles: [Article]
}
struct Article: Decodable {
let title: String
let publishedAt: String
let urlToImage: String?
let url: String
}
struct ArticleListViewModel {
let articlesVM: [ArticleViewModel]
}
extension ArticleListViewModel {
init(_ articles: [Article]) {
self.articlesVM = articles.compactMap(ArticleViewModel.init)
}
}
extension ArticleListViewModel {
func articleAt(_ index: Int) -> ArticleViewModel {
return self.articlesVM[index]
}
}
struct ArticleViewModel {
let article: Article
init(_ article: Article) {
self.article = article
}
}
extension ArticleViewModel {
var title: Observable<String> {
return Observable<String>.just(article.title)
}
var publishedAt: Observable<String> {
return Observable<String>.just(article.publishedAt)
}
var urlToImage: Observable<String> {
return Observable<String>.just(article.urlToImage ?? "NoImage")
}
var url: Observable<String> {
return Observable<String>.just(article.url)
}
}
I wrote an article covering this very thing (albeit in a different context): RxSwift and Handling Invalid Tokens
The above article will help if you are making multiple requests at the same time and need to restart all of them with the new token. It might be overkill in this specific case.
To solve this problem, you need:
A function that will build a resource with a given api key
An Observable that emits a different API key whenever it's subscribed to.
Once you have those two pieces, you can just retry your subscription until one of the keys works.
Solution
I suggest you use the above as clues and try to solve the problem yourself. Then you can check your answer against the solution below...
For item 1, I see that you need two arguments to create a resource. So I suggest making a function factory that will produce a function that takes an apiKey. Like this:
func makeResource(selectedLanguagesCode: String) -> (String) -> Resource<ArticleResponse> {
{ apiKey in
Resource<ArticleResponse>(url: URL(string: "https://newsapi.org/v2/top-headlines?country=\(selectedLanguagesCode)&sortBy=%20popularity&apiKey=\(apiKey)")!)
}
}
Note that this function is not part of the class. It doesn't need self.
For item 2, we need a function that takes the array of apiKeys and produces an Observable that will emit a different key each time it's subscribed to:
Something like this should work:
func produceApiKey(apiKeys: [String]) -> Observable<String> {
var index = 0
return Observable.create { observer in
observer.onNext(apiKeys[index % apiKeys.count])
observer.onCompleted()
index += 1
return Disposables.create()
}
}
Again, this function doesn't need self so it's not part of the class.
Now that you have these two elements, you can use them in your loadTopNews() method. Like this:
private func loadTopNews() {
produceApiKey(apiKeys: apiKey)
.map(makeResource(selectedLanguagesCode: selectedLanguagesCode[0]))
.flatMap(URLRequest.load(resource:))
.retry(apiKey.count - 1)
.subscribe(onNext: { articleResponse in
let topArticle = articleResponse.articles.first
self.articleVM = ArticleViewModel(topArticle!)
})
.disposed(by: disposeBag)
}

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.

Construct Moya TargetType in 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]"
}
}

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.

Resources