Friday, July 30, 2010

iPhone Development: Accessing SOAP Services with WSDL2ObjC Part II

In my last post we built a basic exerciser for WSDL2ObjC. It's time to talk about complex SOAP data types. What we're going to do today is build a program which uses complex SOAP types. I'll start this tutorial from scratch, so you don't have to go through the first tutorial to get here.

Here's what you need to get started:
  • WSDL2ObjC. I'm going to use WSDL2Objc-0.7-pre1.zip for this tutorial. You can get this from the project's home page.
  • A web service to consume. For this tutorial, we'll use the "Barcode" service (http://www.webservicex.net/genericbarcode.asmx). This service will accept a complex SOAP datatype defining various options to generate a barcode, and will return data which represents the barcode image.

Let's go.

1. Generate the stubs with WSDL2Objc.
Start WSDL2ObjC. In the first field, enter the name of the source WSDL. You can specify a local file, or a web address. For the barcode service, enter http://www.webservicex.net/genericbarcode.asmx?wsdl In the second field, specify a directory into which WSDL2ObjC will create the new files. You can use the Browse button to specify a particular directory. When you click "Parse WSDL," you will see a few messages, one of which should say it is generating Objective C Code into the output directory. Check the target directory to make sure your files are there.
2. Create the project in Xcode.Start Xcode and create a new View-based Application. Call it anything you'd like. In the .h file for the controller, add an action method called buttonPressed, and then two outlets called dataToEncode and barcodeImage.

... UIViewController {
UITextField *dataToEncode;
UIImageView *barcodeImage;
}

@property (nonatomic, retain) IBOutlet UITextField *dataToEncode;
@property (nonatomic, retain) IBOutlet UIImageView *barcodeImage;

-(IBAction)buttonPressed:(id)sender;


In the .m, add the lines to @synthesize dataToEncode and barcodeData, then add the boilerplate for buttonpressed:

-(IBAction)buttonPressed:(id)sender
{
}


Save these files, then open the controller's NIB with Interface Builder. Place a text field and a button on the View. Call the button "Test" or whatever you want, place the field anywhere you want, and add "Data to encode" as a placeholder, if you want. Add a UIImageView. Mine looks like this:

Now hook the button's Touch Up Inside event to the buttonPressed action, and connect the dataToEncode outlet to the UITextField, and connect the barcodeImage outlet to the UIImageView. Save the file and close Interface Builder.

Go ahead and build and run, just to make sure everything's OK so far. I expect everything to be OK, but it doesn't hurt to check.

3. Pull in the WSLD2ObjC generated code.
Let's pull in the code WSDL2ObjC wrote for us: in the project add a new Group named "BarCode SOAP classes". With Finder, drag all the files that it created into the new group. Tell Xcode to copy the items into the destination folder.

Take a look at Barcode.h. There's a ton of stuff here: definitions for bindings, responses, requests, etc. This code corresponds to the data that WSDL2ObjC pulled out of BarCode's WSDL.

Before compiling from this point we have to do two things to our build target: open the Targets group, right-click your target, then select "Get Info," and then "Build." Do the following:

  • In the Linking section, find "Other Linker Flags." Add the -lxml2 to the Linker Flags.
  • In the GCC 4.2 - Code Generation section, find "Other C Flags." Add -I/usr/include/libxml2 to Other C Flags.

4. Write.
Let's write some code. Open the .m for your controller. #import "Barcode.h". Now let's write our buttonPressed method:


- (IBAction)buttonPressed:(id)sender
{
  BarCodeSoapBinding *binding = [[BarCode BarCodeSoapBinding] initWithAddress:@"http://www.webservicex.net/genericbarcode.asmx"];
  binding.logXMLInOut = YES;

  BarCode_BarCodeData *params = [[BarCode_BarCodeData alloc] init];
  params.Height = [NSNumber numberWithInt:125];
  params.Width = [NSNumber numberWithInt:225];
  params.Angle = [NSNumber numberWithInt:0];
  params.Ratio = [NSNumber numberWithInt:5];
  params.Module = [NSNumber numberWithInt:0];
  params.Left = [NSNumber numberWithInt:25];
  params.Top = [NSNumber numberWithInt:0];   params.CheckSum = NO;   params.FontName = @"Arial";   params.BarColor = @"Black";   params.BGColor = @"White";   params.FontSize = [NSNumber numberWithFloat:10.0];   params.barcodeOption = BarCode_BarcodeOption_Both;   params.barcodeType = BarCode_BarcodeType_Code_2_5_interleaved;   params.checkSumMethod = BarCode_CheckSumMethod_None;   params.showTextPosition = BarCode_ShowTextPosition_BottomCenter;   params.BarCodeImageFormat = BarCode_ImageFormats_PNG;
   BarCode_GenerateBarCode *request = [[BarCode_GenerateBarCode alloc] init];   request.BarCodeText = [dataToEncode text];   request.BarCodeParam = params;
   BarCodeSoapBindingResponse *resp = [binding GenerateBarCodeUsingParameters:request];   for (id mine in resp.bodyParts)   {      if ([mine isKindOfClass:[BarCode_GenerateBarCodeResponse class]])      {         UIImage *barcode = [UIImage imageWithData:[mine GenerateBarCodeResult]];
         [barcodeImage setImage:barcode];      }   }
   [request release];   [params release];
}

We create a binding, then associate it with the Barcode Generator service endpoint. We then create a request, fill in the blanks, then call the service. The service returns a BarCodeSoapBindingResponse which we save in resp.

Now about that request. If you look at the definition of BarCode_GenerateBarCode you'll see it has two parts: BarCodeText, a NSString representing the data to encode, and BarCodeParam, which is an object representing a set of parameters affecting the barcode I'm going to get. BarCodeParam covers things like dimensions of the image, the barcode symbology to use, etc. You can see how I set the parameters. Now how did I know what to set each to? In the WSDL definition you can see each element and its type, so I know which parameters need a number, which need a string, and so forth. If you look in Barcode.h that WSDL2ObjC generated, you will also find enum values for options like which barcode symbology to use, image format to return, etc.

The response type has a field called bodyParts. The code runs through bodyParts, looking for the response to the call. It then builds a UIImage based on the PNG data I asked for, and sets barcodeImage's data to that.

5. Run.
Save everything. Under the Run menu, bring up the console, then build and run. Enter data into the field, then click the "Test" button. In the console, you should see the outbound request, and then the response. And in the simulator, of course, you should see your barcode.


And that's all there is to it. Next time, we'll talk about asynchronous calls.

As always, let me know what you think.

Wednesday, May 5, 2010

iPhone Development: Accessing SOAP Services with WSDL2ObjC

I needed to access a SOAP-based web service from the iPhone. There are a wide variety of opinions on how to go about this: "One word: Don't" was one poster's reply to a related question at Stackoverflow.com. Some people suggest writing one's own routines, while others suggest trying various code-generating tools. One I found was called gSoap, but apparently there is a fair amount of work yet to do to get it working with the iPhone. I found wsdl2objc which generates Objective-C code from a WSDL so you can call SOAP services. I want to show you how I used WSDL2ObjC to access a SOAP based web service.

Here's what you need to get started:
  • WSDL2ObjC. I'm going to use WSDL2ObjC-0.7-pre1.zip for this tutorial. You can get this from the project's home page.
  • A web service to consume. For this tutorial, we'll use the "LocalTime" service (http://www.ripedevelopment.com/webservices/LocalTime.asmx) which will, given a zip code, provide the local time for that zip code.

Let's go.

1. Generate the stubs with WSDL2ObjC.
Start WSDL2ObjC. In the first field, enter the name of the source WSDL. You can specify a local file, or a web address. For here, enter http://www.ripedevelopment.com/webservices/LocalTime.asmx?wsdl In the second field, specify a directory in which WSDL2ObjC will create the new files. You can use the Browse button to specify a directory. When you click "Parse WSDL", you will see a few messages, one of which should say it is generating Objective C Code into the output directory. Check the target directory to make sure your files are there.


2. Create the project in Xcode.
Start Xcode and create a new View-based Application. Call it anything you want. In the .h for the controller, add an action method called buttonPressed, and an outlet called field:

... UIViewController {

UITextField *field;
}

@property (nonatomic, retain) IBOutlet UITextField *field;
- (IBAction)buttonPressed:(id)sender;
@end

In the .m, add the line to @synthesize field, then add boilerplate for buttonpressed:

- (IBAction)buttonPressed:(id)sender
{
}


Save these files, then open the controller's NIB file with Interface builder. Place a text field and a button on the View. Title the button "Test" or whatever you want; stretch the field out about 3/4 the way across the view. Here's what mine looks like:



Now wire the button's Touch Up Inside event to the buttonPressed action, and connect the field's outlet to field. Save and close Interface Builder.

Go ahead and build and run, just to make sure everything's OK so far. I expect it is, but it doesn't hurt to check.

3. Pull in the WSDL2ObjC generated code.
Let's pull in the code WSDL2ObjC wrote for us: in "Other Sources," add a new Group named "LocalTime". With Finder, drag all the files that it created into the new group. Tell Xcode to copy the items into the destination folder.

Take a look at LocalTime.h. There's a ton of stuff there: definitions for bindings, responses, requests, etc. This code corresponds to the data that WSDL2ObjC pulled out of LocalTime's WSDL.

Before compiling from this point, make sure you have followed the instructions in the wiki page UsageInstructions (ie, the linker flags properties and frameworks). You find UsageInstructions at the WSDL2ObjC project home page. Incidentally, the first character of the Other C Flags property is the capital letter I as in India. You're welcome.

4. Write.
Let's write some code. Open the .m for your controller. First thing you need to do is #import "LocalTime.h" Now let's fill in the buttonPressed function. Here it is:

- (IBAction)buttonPressed:(id)sender {
  LocalTimeSoapBinding *binding = [[LocalTime LocalTimeSoapBinding] initWithAddress:@"http://www.ripedevelopment.com/webservices/LocalTime.asmx"];
  binding.logXMLInOut = YES;  // to get logging to the console.

  LocalTime_LocalTimeByZipCode *request = [[LocalTime_LocalTimeByZipCode alloc] init];
  request.ZipCode = @"29687";  // insert your zip code here.

  LocalTimeSoapBindingResponse *resp = [binding LocalTimeByZipCodeUsingParameters:request];
  for (id mine in resp.bodyParts)
  {
          if ([mine isKindOfClass:[LocalTime_LocalTimeByZipCodeResponse class]])
          {
                  field.text = [mine LocalTimeByZipCodeResult];
          }
  }
}


The first thing we do is create a binding, and associate it with ripedevelopment's web service endpoint. We then create a request object, fill in the blanks, and then call the service. The service returns a LocalTimeSoapBindingResponse, which we save in resp.

LocalTimeSoapBindingResponse has a field called bodyParts. The code runs through bodyParts, looking for the response to the call. It then stores the result in the field for us to see.

5. Run.
Save everything. Under the Run menu, bring up the Console, then build and run. Click the "Test" button on the Simulator. You should see the outbound request, a response of 200, then... Uh oh, "Unexpected response MIME type to SOAP call:text/xml"

What we're going to do here is switch the MIME type from application/soap+xml to text/xml. You can use Xcode's search and replace; you should find four occurrences of application/soap+xml Change them all to text/xml.

Now when you rerun the app, and click the "Test" button, you should see the messages in the console, and then you should see the date appear in the field.


So there you have it. Now I know I didn't show how to make an asynchronous call, nor did I talk about complex types. That's what next time is for.

Let me know what you think.

Wednesday, April 14, 2010

Me and my cholesterol

In consideration of the hyperlitigiousness of many in our society, I am NOT suggesting that you or anyone else go fiddling with their blood chemistry outside of the close supervision of their doctor. My only intent in this particular post is to relate a personal anecdote.

Cholesterol and triglycerides are fatty substances that exist in the body. They are used by the body for several vital functions: cell building, myelin-sheaths for your nerves, body metabolism, and so on. They're supposed to be there.

The problem is when there is too much there. In short, excess cholesterol and triglycerides make a mess of things, and can lead to heart attack and stroke. These fats come from two sources: the food we eat, and the body itself.

Some of us, like well, me, tend to run high on these numbers, despite halfway decent eating habits. I knew I had a tendency to high cholesterol and high triglycerides on account of my parents. When my 40-year-old cousin suffered a stroke, I decided to get my blood checked. The results were not good:

Total cholesterol: 212
HDL: 36
LDL: 121
Triglycerides: 311

Yikes. Time to do something.

I tend to be cautious of brand-name pharmaceuticals in general, especially heavily-marketed brand-name pharmaceuticals. If I had a chronic condition, I would prefer to try to control the condition with diet and exercise, then OTC offerings, then generic pharmaceuticals, then brand-name. (Perhaps I'll try to explain that in a blog post someday). I was doing most of the right things pertaining to diet and exercise. The doctor I had at the time (note on the past tense of the verb) said I basically didn't have much choice, and signed me up for a heavy dose of a particular prescription. I wasn't comfortable with this, and decided to see if there was something else I could do.

I did a little digging and discovered research involving Niacin, and discovered it has been used for decades as a heart medication, and I also found studies examining its effects on hyperlipidemia. The studies I read referred to tests of dosages from 1500mg/day up to 3000mg/day, and so I figured if I tried it at 1000mg/day, I'd be below even the minimum dosage seen in the tests.

I discovered that straight niacin causes a temporary, though unpleasant, reaction called "the flush" similar to a sunburn. I also found that "slo-release niacin" really wasn't recommended because of its effects on the liver. I found reference to "Inositol Hexanicotinate" (also called "No-flush") is a type of niacin which doesn't cause the flush, and doesn't irritate the liver. I found 500mg capsules at the local supermarket, and started taking one at breakfast, and one at dinner.

I started going to a new doctor, and told him what I was up to. He sent me to get a blood profile to see how I was faring.

Total cholesterol: 217
HDL: 52
LDL: 118
Triglycerides: 233.

Nice. About a 40% increase in my HDL, and a 30% decrease in my triglycerides.

I had my blood checked again a few months ago, and I'm still doing pretty good:

Total: 197
HDL: 53
LDL: 102
Triglycerides: 210.

The triglycerides could still spare to come down a couple of notches, but I'm working on that.

I don't know why I feel compelled to tell you all about me and my cholesterol, but there you go.

Friday, March 19, 2010

Church Life: Assembling or Shopping?

Going to church this Sunday? Yeah? Good for you. Why are you going?

  • To worship
  • To hear God's word preached
  • To see my friends
  • To hear some good music
  • To assemble, after all, Hebrews 10:25 says to not forsake assembling with each other.

Assembling... Assembling...

I think that for the most part, we don't assemble anymore. I think we shop. I think this because of the reasons one might hear from people who, instead of going to church, are leaving it.

  • Doctrine is so dull.
  • Preaching is so dull.
  • The music is so dull.
  • I'm not being fed.
  • I can't thrive.
  • They don't have puppet shows for the kids.
  • I hate quail: it tastes like chicken.
  • Manna again? how boring, you'd think God could do something more spectacular than that.

I think in large part we have no more commitment to our local church than we do to the place where we buy our milk and eggs: "I'm going to shop here today, and I'll leave the second I find a better deal, or something more convenient, or better customer service."

Going to church this Sunday? Yeah? Good for you. Why are you going?

Wednesday, February 10, 2010

Convenience: Fee vs Free

My daughter's school bill was due. Ordinarily, my wife would drive over to campus and drop a check off at the business office. "Can't we pay it online?" she asked. "Don't see a reason why not," I said. So I went to the college website and followed the steps for paying a bill online. Right up to the point when they said they were going to charge me $15.00 as a "convenience fee." That's not very convenient.

What a stupid idea: "convenience fee."

Digressing for a moment, it reminds me of the time I was replacing something in my wife's car, which was supposed to be located in what the manufacturer termed the "convenience center." I envisioned a nice friendly little compartment, easily accessible, where I would be able to locate and replace the gadget in question. I turned out to be buried deep in the dashboard, behind various wires, levers, and other junk, and I about dislocated my arms trying to reach around everything to get to it. "Convenience Center." Hardy-har. Good one. I bet the engineers had a great laugh coming up with that name.

Anyway, not long after this (the convenience fee episode) I saw an example of real convenience, for free. I had a handful of Starbucks giftcards of varying amounts, most of which I had received as gifts from the recent holidays. I don't go to Starbucks all that often, so cards can wind up riding around in my wallet for months at a time. However, I do ordinarily go and register the cards at their website. Recently I noticed the website allows you to transfer balances from one card to another, so you can "roll" all your cards into one. "That's convenient, I wonder how much they charge you?" the cynic in me thought. Turns out, it's free. No charge. Glad we could help.

What a great idea: Convenience. For Free.