Abstraction Builder

Building simple and elegant programming abstractions

Pico Tutorial 4 - Hello eBay Shopping

| Comments

This is the fourth tutorial of Pico tutorial series, in this tutorial, I will show you how to integrate Pico with eBay Shopping Web Service, if you are not familar with this service, just have a quick reivew on its official site, basically, eBay Shopping service allows you to search for eBay items, products and reviews, user info, and popular items and searches. In previous tutorials, I showed you how to integrate Pico with SOAP based services, while in this tutorial, I will show you how to interate Pico with XML based service, the eBay Shopping service just support XML message format.

The source of this tutorial is here.

Step 0 - Prerequisite

I suppose you have already read previous Pico tutorials, at least 1 and 2, and basically you should know:

  • The wsdl driven development process supported by Pico.
  • How to reference Pico as a static library in your project.
  • Or How to reference Pico Source directly in your project.

Step 1 - Generate Objective-C Proxy from WSDL

Download mwsc and run following command in terminal to generate the proxy:

1
bin/mwsc -pico -d generated -prefix Shopping_ -ebayshopping http://developer.ebay.com/webservices/latest/ShoppingService.wsdl

Depends on the network speed, you may need to wait a few moments to let the code generator download the wsdl and generate code, you may also download the wsdl and run the code generator with a local wsdl.

A few comments about the codegen options:

  • The code generator will throw many warnings, say that the wsdl violates some schema rule, just ignore them, it’s ok as long as the final code is generated correctly.
  • I have added a prefix Shopping_ as codegen option, such that all interfaces/types generated will be prefixed with Shopping_. Adding prefix to Objective-C types is a recommended best practice to avoid possible type name conflict with code generated from other services, for example, eBay has a couple of services, and they share a few commons types, without prefixing, you may get conflict if you use two or more eBay services in one application.
  • I have added a special -ebayshopping codegen option, this is because eBay Shopping service needs a per-call operation name HTTP header(eBay Finding service also needs a similar but different header), I added this special flag in the code generator to let it generate the header for me, since I don’t want to add this header everytime I call an eBay Shopping service, so this is just a special flag for eBay Shopping services and for demo only, or a hidden feature, not a generic codegen option.

Step 2 - Create New iOS Project, Add Pico Library and Generated Proxy into Your Project

Create a new simple iOS single view application named “HelloeBayShopping”, don’t choose ARC since Pico does not support ARC yet.

In this tutorial, we will reference Pico as a static library, suppose you have downloaded Pico source project from github site, then:

  1. Drag the Pico xcodeproj into your project,
  2. In the Build Phases of the target, add libPico.a and libxml2.dylib to “Link Binary With Libraries” section.
  3. In the Build Setting of the target, add [your path to Pico source] to “User Header Search Paths” setting, choose “recursive” seach path.

Build the project to ensure that it can build successfully.

Now drag the proxy generated in step 1 into the project, choose “Copy items to destination group’s folder” and “add to targets” when prompted.

Build the project again to ensure that it can build successfully.

The code generation will genenrate both SOAP and XML based interfaces from eBay Shopping wsdl for us, since we will use XML based interface in this tutorial, you may now review the generated eBay Shopping service XML interface to learn what kinds of functions are provided by eBay Shopping service, and what kinds of parameters are needed to call the serivce, the interface is posted below:

ShoppingInterface_XMLClient.h source
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
// Generated by wsdl compiler for ios/objective-c
// DO NOT CHANGE!


#import <Foundation/Foundation.h>
#import "PicoXMLClient.h"
#import "Shopping_GetCategoryInfoResponseType.h"
#import "Shopping_GetSingleItemResponseType.h"
#import "Shopping_GetMultipleItemsResponseType.h"
#import "Shopping_FindProductsRequestType.h"
#import "Shopping_GetMultipleItemsRequestType.h"
#import "Shopping_FindReviewsAndGuidesResponseType.h"
#import "Shopping_FindHalfProductsRequestType.h"
#import "Shopping_FindPopularSearchesResponseType.h"
#import "Shopping_FindProductsResponseType.h"
#import "Shopping_GetCategoryInfoRequestType.h"
#import "Shopping_GetUserProfileRequestType.h"
#import "Shopping_GeteBayTimeResponseType.h"
#import "Shopping_GetShippingCostsRequestType.h"
#import "Shopping_GetItemStatusResponseType.h"
#import "Shopping_GetUserProfileResponseType.h"
#import "Shopping_FindHalfProductsResponseType.h"
#import "Shopping_GetItemStatusRequestType.h"
#import "Shopping_FindPopularSearchesRequestType.h"
#import "Shopping_GetShippingCostsResponseType.h"
#import "Shopping_FindReviewsAndGuidesRequestType.h"
#import "Shopping_FindPopularItemsResponseType.h"
#import "Shopping_GetSingleItemRequestType.h"
#import "Shopping_GeteBayTimeRequestType.h"
#import "Shopping_FindPopularItemsRequestType.h"


/**
 This class is the XML client to the ShoppingInterface Web Service.
*/
@interface ShoppingInterface_XMLClient : PicoXMLClient {

}

/**
 public method
*/
-(void)findHalfProducts:(Shopping_FindHalfProductsRequestType *) requestObject
      success:(void (^)(Shopping_FindHalfProductsResponseType *responseObject))success
      failure:(void (^)(NSError *error))failure;

/**
 public method
*/
-(void)findPopularItems:(Shopping_FindPopularItemsRequestType *) requestObject
      success:(void (^)(Shopping_FindPopularItemsResponseType *responseObject))success
      failure:(void (^)(NSError *error))failure;

/**
 public method
*/
-(void)findPopularSearches:(Shopping_FindPopularSearchesRequestType *) requestObject
      success:(void (^)(Shopping_FindPopularSearchesResponseType *responseObject))success
      failure:(void (^)(NSError *error))failure;

/**
 public method
*/
-(void)findProducts:(Shopping_FindProductsRequestType *) requestObject
      success:(void (^)(Shopping_FindProductsResponseType *responseObject))success
      failure:(void (^)(NSError *error))failure;

/**
 public method
*/
-(void)findReviewsAndGuides:(Shopping_FindReviewsAndGuidesRequestType *) requestObject
      success:(void (^)(Shopping_FindReviewsAndGuidesResponseType *responseObject))success
      failure:(void (^)(NSError *error))failure;

/**
 public method
*/
-(void)getCategoryInfo:(Shopping_GetCategoryInfoRequestType *) requestObject
      success:(void (^)(Shopping_GetCategoryInfoResponseType *responseObject))success
      failure:(void (^)(NSError *error))failure;

/**
 public method
*/
-(void)getItemStatus:(Shopping_GetItemStatusRequestType *) requestObject
      success:(void (^)(Shopping_GetItemStatusResponseType *responseObject))success
      failure:(void (^)(NSError *error))failure;

/**
 public method
*/
-(void)getMultipleItems:(Shopping_GetMultipleItemsRequestType *) requestObject
      success:(void (^)(Shopping_GetMultipleItemsResponseType *responseObject))success
      failure:(void (^)(NSError *error))failure;

/**
 public method
*/
-(void)getShippingCosts:(Shopping_GetShippingCostsRequestType *) requestObject
      success:(void (^)(Shopping_GetShippingCostsResponseType *responseObject))success
      failure:(void (^)(NSError *error))failure;

/**
 public method
*/
-(void)getSingleItem:(Shopping_GetSingleItemRequestType *) requestObject
      success:(void (^)(Shopping_GetSingleItemResponseType *responseObject))success
      failure:(void (^)(NSError *error))failure;

/**
 public method
*/
-(void)getUserProfile:(Shopping_GetUserProfileRequestType *) requestObject
      success:(void (^)(Shopping_GetUserProfileResponseType *responseObject))success
      failure:(void (^)(NSError *error))failure;

/**
 public method
*/
-(void)geteBayTime:(Shopping_GeteBayTimeRequestType *) requestObject
      success:(void (^)(Shopping_GeteBayTimeResponseType *responseObject))success
      failure:(void (^)(NSError *error))failure;


@end

All the methods in the interface follow same calling paradigm - you call the service with required request object and register success callback(for success handling logic) and failure callback(for error handling logic) using Objective-C block.

You may also compare the XML interface with the SOAP interface, they are almost similar, except that SOAP interface will give you an additional SOAPFault object in the failure callback, this is obvious, since XML based service has no concept of SOAPFault, usually, the error is returned as response resident error(RRE) in the XML response message.

Step 3 - Implement Appliction Logic and UI, Call Proxy to Invoke Web Service as Needed.

First, create a shared service client as below:

ShoppingInterface_XMLClient.h source
1
2
3
4
5
6
7
#import "ShoppingInterface_XMLClient.h"

@interface EBayShoppingServiceClient : ShoppingInterface_XMLClient

+ (EBayShoppingServiceClient *)sharedClient;

@end

Note, unlike shared clients created in previous tutorials, the EBayShoppingServiceClient extends ShoppingInterface_XMLClient, which means we are using XML service interface supported by Pico.

ShoppingInterface_XMLClient.m source
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#import "EBayShoppingServiceClient.h"

static NSString *const eBayAppId = @"YOUR APPID HERE";

// production
static NSString *const eBayShoppingServiceURLString = @"http://open.api.ebay.com/shopping?";
// sandbox
//static NSString *const eBayShoppingServiceURLString = @"http://open.api.sandbox.ebay.com/shopping";

static NSString *const targetAPIVersion = @"809";
/**
 for site id list, see http://developer.ebay.com/DevZone/shopping/docs/CallRef/types/SiteCodeType.html
 */
static NSString *const targetSiteId = @"0"; // 0 for US

@implementation EBayShoppingServiceClient

+ (EBayShoppingServiceClient *)sharedClient {
    static EBayShoppingServiceClient *_sharedClient = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedClient = [[EBayShoppingServiceClient alloc] initWithEndpointURL:[NSURL URLWithString:eBayShoppingServiceURLString]];
    });

    return _sharedClient;
}

- (id)initWithEndpointURL:(NSURL *)URL {

    self = [super initWithEndpointURL:URL];
    if (!self) {
        return nil;
    }

    [super setDefaultHeader:@"X-EBAY-API-APP-ID" value:eBayAppId];
    [super setDefaultHeader:@"X-EBAY-API-REQUEST-ENCODING" value:@"XML"];
    [super setDefaultHeader:@"X-EBAY-API-VERSION" value:targetAPIVersion];
    [super setDefaultHeader:@"X-EBAY-API-SITE-ID"value:targetSiteId];

    return self;
}

@end

Like eBay Finding service, the eBay Shopping service needs a few HTTP headers set to work, let me give more comments:

  1. eBay Shopping service needs to set a few HTTP headers to work, for a list of required headers, please refer to doc here.
  2. One mandatory header for eBay Shopping service is eBayAppId, you need to register on eBay developer site as an eBay developer then get this id, before your can run this demo, you must fill in your own eBayAppId in the shared client.
  3. Another mandatory header for eBay Shopping service is targetAPIVersion, aka the API version you want to use, usually, you use the latest one, at the time of this writing, the latest version is 809, you may update this according to your real needs.
  4. We also set X-EBAY-API-REQUEST-ENCODING to XML, indicating we want to call XML service supported by eBay Shopping service.

Now the UI part, we will find one popular item from eBay by keywords, since this is a hello world like sample, we just need a UITextField for keyword input and UIButton to trigger eBay search by invoking method searchButtonPressed which will indirectly call eBay Shopping findPopularItems API through the proxy, fairly simple, see definition in header file ViewController.h and instantiation in implementation file ViewController.m.

Now implement the searchButtonPressed method by invoking service as below:

ViewController.m source
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#import "ViewController.h"
#import "Shopping_CommonTypes.h"
#import "EBayShoppingServiceClient.h"
#import "Toast+UIView.h"

- (void)searchButtonPressed:(id)sender
{
    // Hide the keyboard.
    [_searchText resignFirstResponder];

    if (_searchText.text.length > 0) {

        // start progress activity
        [self.view makeToastActivity];

        // Get shared service client
        EBayShoppingServiceClient *shoppingClient = [EBayShoppingServiceClient sharedClient];
        shoppingClient.debug = YES; // enable request/response message logging

        // Build request object
        Shopping_FindPopularItemsRequestType *request = [[[Shopping_FindPopularItemsRequestType alloc] init] autorelease];
        request.queryKeywords = _searchText.text;
        // only need one item for demo
        request.maxEntries = [NSNumber numberWithInt:1];

        // make API call and register callbacks
        [shoppingClient findPopularItems:request success:^(Shopping_FindPopularItemsResponseType *responseObject) {

            // stop progress activity
            [self.view hideToastActivity];

            if ([Shopping_AckCodeType_SUCCESS isEqualToString:responseObject.ack]) {

                if (responseObject.itemArray.item.count > 0) {
                    // show the title of the first found item
                    Shopping_SimpleItemType *item = [responseObject.itemArray.item objectAtIndex:0];

                    // start image downloading progress activity
                    [self.view makeToastActivity];
                    // get gallery image
                    NSURL *imageURL = [NSURL URLWithString:item.galleryURL];
                    NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
                    // stop progress activity
                    [self.view hideToastActivity];

                    UIImage *image = [UIImage imageWithData:imageData];
                    [self.view makeToast:item.title duration:3.0 position:@"center" title:@"Success" image:image];
                } else {

                    // no result
                    [self.view makeToast:@"No result" duration:3.0 position:@"center"];
                }

            } else { // response resident error
                Shopping_ErrorType *error = [responseObject.errors objectAtIndex:0];
                [self.view makeToast:error.shortMessage duration:3.0 position:@"center" title:@"Error"];
            }
        } failure:^(NSError *error) {

            [self.view makeToast:[error localizedDescription] duration:3.0 position:@"center" title:@"Error"];
        }];

    }
}

More comments to the serivce call code:

  1. I’ve added comments in the code so the whole service call flow should be easy to understand.
  2. We used the eBay Shopping findPopularItems call, which takes a keyword as input, and will return a list of matched popular items on eBay, for demo, we just need one item to display, so we set maxEntries to 1.
  3. In the success handling logic, we show the title and the image of the returned item, for demo, the image is downloaded synchronously, but in practice, you should download image asynchronously in order not to block main UI.
  4. eBay Shopping service supports response resident error(RRE), so even we get a success response, we still need to check the response for resident error and handle it accordingly.
  5. We used a thrid party library called “Toast” for producing toast like message, this is just for the convenience of demo, not necessary in your real project.
  6. The maxEntries property of Shopping_FindPopularItemsRequestType is of type NSNumber, you may get confused what kind of number should be put in maxEntries, short, int or long? please just consult the source of Shopping_FindPopularItemsRequestType.h, it provides type hint as code comments, see [NSNumber type hint] below. Indeed, every type generated from wsdl has sufficient type hint to assist your development. Xsd annotations in wsdl/schema are also generated into corresponding interfaces/types, further facilitating your development.
  7. Similarly, in response handling, the item property of Shopping_SimpleItemArrayType is of type NSMutableArray, you may get confused what is the actual entry type? please just consult the source of Shopping_SimpleItemArrayType.h for type hint, see [NSMuatableArray entry type hint] below.
  8. Xsd enumeration is mapped to Objective-C NSString in Pico framework, for example, the ack property of Shopping_FindPopularItemsResponseType is of type NSString, in wsdl, it’s an xsd enumeration of type AckCodeType, the type hint in Shopping_AbstractResponseType.h(from which Shopping_FindPopularItemsResponseType.h extends) will tell you where to find the enum constants allowed by ack property, in this case, the allowed enum constants are in Shopping_AckCodeType.h, see [Enum type hint below] :

NSNumber type hint:

Shopping_FindPopularItemsRequestType.h source
1
2
3
4
5
/**
Specifies the maximum number of entries to return in a single call.
type : NSNumber, wrapper for primitive int
*/
@property (nonatomic, retain) NSNumber *maxEntries;

NSMutableArray entry type hint:

Shopping_SimpleItemArrayType.h source
1
2
3
4
5
6
/**
Contains data for an item listing.
entry type : class Shopping_SimpleItemType
*/

@property (nonatomic, retain) NSMutableArray *item;

Enum type hint:

Shopping_AbstractResponseType.h source
1
2
3
4
5
6
7
8
9
 /**
  
  Indicates whether the call was successfully processed by eBay.
  
  
  type: string constant in Shopping_AckCodeType.h
 */
  @property (nonatomic, retain) NSString *ack;
  

Please don’t forget to include the shared client header file, it’s a best practice to include the generated Shopping_CommonTypes.h file which can free you from writing many import statements required by request building and response handling.

Final Step - Run the Demo

Let’s run the demo in iPhone simulator, see a sceen shot below:

and the debug output:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
2013-03-30 11:50:48.084 HelloeBayShopping[3746:c07] Sending request to : http://open.api.ebay.com/shopping?
2013-03-30 11:50:48.086 HelloeBayShopping[3746:c07] Request message:
2013-03-30 11:50:48.086 HelloeBayShopping[3746:c07] <?xml version="1.0" encoding="UTF-8" ?>
<FindPopularItemsRequest xmlns="urn:ebay:apis:eBLBaseComponents">
  <QueryKeywords>Ipad mini</QueryKeywords>
  <MaxEntries>1</MaxEntries>
</FindPopularItemsRequest>
2013-03-30 11:50:48.086 HelloeBayShopping[3746:c07] Request HTTP Headers : 
{
    Accept = "text/xml";
    "Accept-Language" = "en, fr, de, ja, nl, it, es, pt, pt-PT, da, fi, nb, sv, ko, zh-Hans, zh-Hant, ru, pl, tr, uk, ar, hr, cs, el, he, ro, sk, th, id, ms, en-GB, ca, hu, vi, en-us;q=0.8";
    "Content-Type" = "text/xml";
    "User-Agent" = "HelloeBayShopping/1.0 (iPhone Simulator; iOS 6.0; Scale/1.00)";
    "X-EBAY-API-APP-ID" = **************;
    "X-EBAY-API-CALL-NAME" = FindPopularItems;
    "X-EBAY-API-REQUEST-ENCODING" = XML;
    "X-EBAY-API-SITE-ID" = 0;
    "X-EBAY-API-VERSION" = 809;
}
2013-03-30 11:50:49.057 HelloeBayShopping[3746:1a03] Response HTTP headers : 
{
    "Cache-Control" = "no-cache";
    Connection = "keep-alive";
    "Content-Type" = "text/xml;charset=utf-8";
    Date = "Sat, 30 Mar 2013 03:50:59 GMT";
    Expires = "Sat, 25 Dec 1999 00:00:00 GMT";
    "Last-Modified" = "Sat, 30 Mar 2013 03:51:00 GMT";
    Pragma = "no-cache";
    Server = "Apache-Coyote/1.1";
    "Transfer-Encoding" = Identity;
    "X-EBAY-API-BUILD-TAG" = "E817_CORE_APILW2_15902151_R1";
    "X-EBAY-API-POOL-NAME" = "___cDRidW9rdDdlaHFg";
    "X-EBAY-API-SERVER-NAME" = "___dWtgMWQ3MmYrNTE1YygyNSg+Nys3MDUrNDM5PTY/Mw==";
    "X-EBAY-ESB-GUID" = "urn:uuid:A94C3221C939E291D6329443603678959471486594422";
}
2013-03-30 11:50:49.057 HelloeBayShopping[3746:4303] Response message : 
2013-03-30 11:50:49.057 HelloeBayShopping[3746:4303] <?xml version="1.0" encoding="UTF-8"?>

  <FindPopularItemsResponse xmlns="urn:ebay:apis:eBLBaseComponents">
   <Timestamp>2013-03-30T03:51:00.322Z</Timestamp>
   <Ack>Success</Ack>
   <Build>E817_CORE_APILW2_15902151_R1</Build>
   <Version>817</Version>
   <ItemArray>
    <Item>
     <ItemID>350692808075</ItemID>
     <EndTime>2013-04-15T17:23:27.000Z</EndTime>
     <ViewItemURLForNaturalSearch>http://www.ebay.com/itm/Apple-iPad-Mini-NEW-360-Degree-Rotating-PU-Leather-Case-Cover-w-Swivel-Stand-/350692808075</ViewItemURLForNaturalSearch>
     <ListingType>StoresFixedPrice</ListingType>
     <GalleryURL>http://thumbs4.ebaystatic.com/pict/3506928080758080_4.jpg</GalleryURL>
     <PrimaryCategoryID>176973</PrimaryCategoryID>
     <PrimaryCategoryName>Computers/Tablets & Networking:iPad/Tablet/eBook Accessories:Cases, Covers, Keyboard Folios</PrimaryCategoryName>
     <BidCount>9529</BidCount>
     <ConvertedCurrentPrice currencyID="USD">7.75</ConvertedCurrentPrice>
     <ListingStatus>Active</ListingStatus>
     <TimeLeft>P16DT13H32M27S</TimeLeft>
     <Title>For Apple iPad Mini NEW 360 Degree Rotating PU Leather Case Cover w Swivel Stand</Title>
     <ShippingCostSummary>
      <ShippingServiceCost currencyID="USD">0.0</ShippingServiceCost>
      <ShippingType>Flat</ShippingType>
      <ListedShippingServiceCost currencyID="USD">0.0</ListedShippingServiceCost>
     </ShippingCostSummary>
     <WatchCount>1681</WatchCount>
    </Item>
   </ItemArray>
  </FindPopularItemsResponse>

let’s also try a error case, for example, if you forget to fill in your eBayAppId in the shared client, then you will get:

This is just a bare minimum eBay Shopping service based application, for a demo with more functions, please see the eBayDemoApp sample in the Examples folder of Pico source, eBayDemoApp is a composite app which calls two eBay services behind, this app searches eBay by calling eBay Finding service, shows a list of matched items on UI, when an item is clicked, it will show item details by calling eBay Shopping service, see a screen shot below.

Now it’s your turn to create iOS applications based on eBay Shopping and eBay Finding web services, see your next great service based app.

Update 1

The eBay Shopping Service Proxy has been extracted as a standalone project, hosted here, and the corresponding appledoc is hosted here, the appledoc is a useful programming reference. By the way, the doc annotations in wsdl are not only generated into the proxy code, but into the appledoc, assisting your development.

Comments