How to post composite key data parameters with Alamofire? - ios

I have data like this on server side.
For UserImagesTable, these are my parameters
{
"status" : false,
"id" : {
"userId" : 1,
"imageId" : 1
}
I need to post data through alamofire and retrofit.
I tried like this: I created one model class for userImages Table and one more model class for userImages.id table
My model classes are:
class UserImages
{
var status : Bool? = nil
var id : ImagesId? = nil
func toJson() -> [String:Any] {
return[
"status" : status as Any!,
"id" : id as ImagesId!
]
}
}
class ImagesId
{
var userId : Int16? = nil
var imageId : Int16? = nil
func toJson() -> [String:Any] {
return[
"userId" : userId as Any!,
"imageId" : imageId as Any!,
]
}
}
Then my Alamofire
let ImageParams = UserImages()
let ImageIdParams = ImagesId()
ImageIdParams.imageId = 2
ImageIdParams.userId = 3
ImageParams.status = true
favouriteVideoParams.id = ImageIdParams.toJson()-->this line coming some error like -->Cannot assign value of type '[String:any]' to type ImageId?
Alamofire.request(url, method: .post, parameters: ImageParams.toJson(), encoding: JSONEncoding.default, headers: Defines.Constants.Headers)
What I'm doing for post, is this approach correct or not? If it's possible give me some idea about retrofit post also.

For Alamofire, your .toJSON functions aren't really required a simpler solution would be that you create a parameter variable in your function that shapes the parameters with pre-defined keys and variable values given via the function, this would look something like this:
func postUserImageTable(with status: Bool?, and userImagesId: ImagesId?) {
let url = "your url"
let parameters = [
"status": status!
"id": [
"userId": userImagesId?.userId
"imageId": userImagesId?.imageId
]
]
Alamofire.request(url, method: .post, parameters: parameters)
}
as per the official documentation of Alamofire (Alamofire Post with JSON encoded Params)
For Retrofit you can make use of the #body tag to POST pojo classes to your server. So if you have equivalent java classes as you showed in Swift, being this:
public class UserImage {
private Boolean status;
private ImagesId imagesId;
//constructor
//getters and setters
}
public class ImagesId {
private int userId, imageId;
//constructor
//getters and setters
}
then you can add this call to your retrofit service
#Post("your/post/url")
Response postUserImageTable(#Body UserImage body);
And you can use this in your call with retrofit:
Call<Response> call = Service.postUserImageTable(new UserImage(false, new ImagesId(1,2));
call.enqueue(New Callback<Response> {
//obligatory overrides from retrofit
})

Related

Use switch cases based on condition

I have made a file called Constants.swift. Within this, I have made a class like so...
public class WebServices {
static let getMyPlants : String = "plant/getPlants"
static let getMyOrganizations: String = "organization/getOrganizations"
}
Now whenever, I use an api anywhere in my project, I do Webservices.getMyPlants.
Now I also have a base-url for each of the API's. That is mentioned below public class WebServices.... like so..
struct envDev {
var BASEURL : String = "http://api-proj-dev.ii.the-co.com/api/"
}
Now, the base-url for Webservices.getMyOrganizations is different. I want to use a condition within struct envDev that if I have selected Webservices.getMyOrganizations, then I can give a different BASEURL. Something like...
//The below code isn't right. I just wrote it to represent the kind of solution I wish to have.
struct envDev {
var BASEURL : String = "http://api-proj-dev.ii.the-co.com/api/"
if Webservices.getMyOrganizations {
var BASEURL : String = "http://my second base-url.."
}
}
EDIT 1 Giving below the signature of APIHelper
class APIHelper: NSObject {
var API: NSString
var json: NSString
var receivedJSON: NSString?
var arrResult: NSMutableArray = []
let esmatBaseUrl = AppDelegate().currentUser //This is given in AppDelegate as `var currentUser = envDev()`
()
EDIT 2 Inclusion of baseUrl computed property in APIHelper & the error.
class APIHelper: NSObject {
var API: NSString
var json: NSString
var receivedJSON: NSString?
var arrResult: NSMutableArray = []
let esmatBaseUrl = AppDelegate().currentUser //This is given in AppDelegate as `var currentUser = envDev()`
()
var baseUrl: String {
esmatBaseUrl.baseUrl(forApi: API as String) // Here I'm getting the error as `Value of type 'envDev' has no member 'baseUrl'`
}
envDev has no way of knowing what happens in APIHelper, so you need a way to pass in the API from APIHelper to envDev. This means that BASEURL should not be a property, but a method:
func baseUrl(forApi api: String) -> String {
switch api {
case WebServices.getMyPlants: return "some url"
case WebServices.getMyOrganizations: return "some other url"
default: fatalError()
}
}
Then in APIHelper, you can add a baseUrl computed property that calls the above method:
var baseUrl: String {
esmatBaseUrl.baseUrl(forApi: API as String)
}
This would mean that you need to change all occurrences of esmatBaseUrl.BASEURL in your existing code to just baseUrl.
Also, I would suggest not using NSString, NSArray, etc in Swift. You should their Swift counterparts: String and [T].
I understood your query. You want to create an ENUM for your server-environment's, instead of hard-coding baseUrl's you probably want to use ENUMS to select different environments, right.
So accordingly, I've created an ENUM for you to add different server-environments so it will be feasible for you to use it frequently every-where.
private enum ServerEnv: String {
case stage, prod, test, my_plants, my_organization
var domainValue: String {
switch self {
case .test, .my_plants: return "http://api-testing-proj-dev.ii.the-co.com/api/"
case .stage: return "http://api-staging-proj-dev.ii.the-co.com/api/"
case .prod: return "http://api-production-proj-dev.ii.the-co.com/api/"
case .my_organization: return "http://api-my_organization-proj-dev.ii.the-co.com/api/"
}
}
}
Example :
let baseUrl = ServerEnv.my_organization.domainValue
Output => baseURL = "http://api-my_organization-proj-dev.ii.the-co.com/api/"
let baseUrl = ServerEnv.my_plants.domainValue
Output => baseURL = "http://api-testing-proj-dev.ii.the-co.com/api/"
I hope, I've solved your query here.
Happy Coding :-)

Converting Dictionary Key type in Swift 5 with Dictionary(uniqueKeysWithValues:)

I am writing an application with networking capabilities for iOS 13.4 (Swift 5, Xcode 11) using Alamofire 5. I have created my custom type typealias KeyedParameters = [ParameterKeys: Any] to be able to use my API Parameter Keys in a 'swifty' short way (i.e. .login instead of KeyedParameters.login.rawValue).
The problem is when I try do convert this type back to default Alamofire's Parameters, I receive following error: Cannot convert return expression of type 'Dictionary<ParameterKeys, Any>' to return type 'Parameters' (aka 'Dictionary<String, Any>').
Casting:
extension KeyedParameters {
var parameters: Parameters {
Dictionary(uniqueKeysWithValues: map { ($0.key.rawValue, $0.value) })
}
}
ParameterKeys:
enum ParameterKeys: String {
// MARK: - Auth and User
case id, login, password, email, name
case createdAt = "created_at"
...
}
How error looks:
I think this might be just a case of a bad error message.
Your extension KeyedParameters (a typealias for [ParameterKeys: Any]) is actually equivalent to:
extension Dictionary where Key == ParameterKeys, Value: Any { ...
Swift has some odd behaviour when calling an initializer for a generic type within the declaration of that type itself. If the generic types are different, it won't handle that properly.
Here's a simpler example without so many red herrings (type aliases, enum raw values, etc.) and dependencies:
extension Dictionary {
func returnADifferentDict() -> [Character: String] {
let words = [
"apple", "anchovies",
"bacon", "beer",
"celery"
]
return Dictionary(uniqueKeysWithValues:
words.map { ($0.first!, $0) }
)
// fixed:
// return Dictionary<Character, String>(uniqueKeysWithValues:
// words.map { ($0.first!, $0) }
// )
}
}
The solution is to explicitly specify the generic type parameters of the generic type you're initializing. In your case,
extension KeyedParameters {
var parameters: Parameters {
Dictionary<String, Any>(uniqueKeysWithValues: map { ($0.key.rawValue, $0.value) })
}
}
You'd better explicitly highlight type like this:
extension KeyedParameters {
var parameters: Parameters {
return Parameters(uniqueKeysWithValues:
self.map { (key, value) in (key.rawValue, value) }
)
}
}
Worked for me.

Archiving Custom Object in Swift - Extended

I am working on an iOS App on Swift 4.0. The app uses an 3rd party SDK where there is a model lets say,
class Customer: NSCopying, NSObject {
var name: String!
var age: Int!
var address: Address!
}
At that point I have no control to modify any properties and signature for the model as its inside SDK. But I need to store the object in disk/user defaults and load when needed.
Is it possible? If it is then how can I do that?
One way is to use SwiftyJSON to convert the model object to JSON data:
extension Customer {
func toJSON() -> JSON {
return [
"name": name
"age": age
"address": address.toJSON() // add a toJSON method the same way in an Address extension
]
}
static func fromJSON(_ json: JSON) -> Customer {
let customer = Customer()
customer.name = json["name"].string
customer.age = json["age"].int
customer.address = Address.fromJSON(json["address"]) // add a fromJSON method the same way
}
}
Now you can do something like saving to UserDefaults
UserDefaults.standard.set(try! Customer().toJSON().rawData(), forKey: "my key")
let customer = Customer.fromJSON(JSON(data: UserDefaults.standard.data(forKey: "my key")!))

How to pass the JSON body with POST request in moya

I'm using the moya library to make a POST request. In TargetType, I couldn't able to see any property to pass the parameters[JSON body] along with POST request. Here, I attach the TargetType
public protocol TargetType {
/// The target's base `URL`.
var baseURL: URL { get }
/// The path to be appended to `baseURL` to form the full `URL`.
var path: String { get }
/// The HTTP method used in the request.
var method: Moya.Method { get }
/// Provides stub data for use in testing.
var sampleData: Data { get }
/// The type of HTTP task to be performed.
var task: Task { get }
/// Whether or not to perform Alamofire validation. Defaults to `false`.
var validate: Bool { get }
/// The headers to be used in the request.
var headers: [String: String]? { get }
}
public extension TargetType {
var validate: Bool {
return false
}
}
Finally,I got the solution for my problem. In Moya 10.0, we can pass the http body JSON payload in task property [TargetType].
click here for reference
var task: Task {
switch self {
case .zen, .showUser, .showAccounts: // Send no parameters
return .requestPlain
case let .updateUser(_, firstName, lastName): // Always sends parameters in URL, regardless of which HTTP method is used
return .requestParameters(parameters: ["first_name": firstName, "last_name": lastName], encoding: URLEncoding.queryString)
case let .createUser(firstName, lastName): // Always send parameters as JSON in request body
return .requestParameters(parameters: ["first_name": firstName, "last_name": lastName], encoding: JSONEncoding.default)
}
}

Cannot assign to 'parameters' in 'self' error - from CS193p Stanford Online Course

I am currently working on the Smashtag application which can be downloaded here: http://web.stanford.edu/class/cs193p/cgi-bin/drupal/
I have been following along with the iTunes U video (YouTube link: https://www.youtube.com/watch?v=ZIwaIEfPAh8&spfreload=10 ) and I can't get past the errors I have been getting.
Even after downloading the final copy of the app from the Stanford website I am seeing these Swift Compiler Errors still popping up.
It continues to say Cannot assign to 'parameters' in 'self'
Could this be from Xcode being updated to 6.3 after this Smashtag app learning course was released in February/March 2015?
I appreciate your help, not sure how to solve this at the moment.
private var twitterAccount: ACAccount?
public class TwitterRequest
{
public let requestType: String
public let parameters = Dictionary<String, String>()
// designated initializer
public init(_ requestType: String, _ parameters: Dictionary<String, String> = [:]) {
self.requestType = requestType
**self.parameters = parameters**
}
// convenience initializer for creating a TwitterRequest that is a search for Tweets
public convenience init(search: String, count: Int = 0, _ resultType: SearchResultType = .Mixed, _ region: CLCircularRegion? = nil) {
var parameters = [TwitterKey.Query : search]
if count > 0 {
parameters[TwitterKey.Count] = "\(count)"
}
switch resultType {
case .Recent: parameters[TwitterKey.ResultType] = TwitterKey.ResultTypeRecent
case .Popular: parameters[TwitterKey.ResultType] = TwitterKey.ResultTypePopular
default: break
}
if let geocode = region {
parameters[TwitterKey.Geocode] = "\(geocode.center.latitude),\(geocode.center.longitude),\(geocode.radius/1000.0)km"
}
self.init(TwitterKey.SearchForTweets, parameters)
}
public enum SearchResultType {
case Mixed
case Recent
case Popular
}
As of Swift 1.2, giving your constant variables an initial value (not in init) means they cannot be reassigned a value in init. Therefore, because you're setting an initial value of parameters to be an empty dictionary, you cannot give parameters a new value in init. To solve this change your code to:
public class TwitterRequest {
public let requestType: String
// Note - now we just define the type here.
public let parameters: Dictionary<String, String>
public init(_ requestType: String, _ parameters: Dictionary<String, String> = [:]) {
self.requestType = requestType
self.parameters = parameters
}
// Other stuff
}
Alternatively you could change parameters to be a var, as pointed out by #Thomas Kilian in his comment. However, if you're not going to change the values stored in parameters it makes more sense to declare it as let and use the code above.

Resources