Tulsa Dev Lunch iOS at Work

Preview:

DESCRIPTION

This the deck I used for a talk on integrating iOS into back office systems at the February 13, 2013 Tulsa Dev Lunch.

Citation preview

iOS at Work:Integrating iOS Apps with

Back End Systems

Matt Galloway

(Freelance Mobile Developer Extraordinaire)

Tulsa Dev LunchFebruary 13, 2013

Wednesday, February 13, 13

What about

Android?

Wednesday, February 13, 13

Android sucks.(Especially for business.)

Wednesday, February 13, 13

Most Consistent API

Consistent Hcxrdwcxre

--

Best Secur it .Y

1=eel G-ood Kumb·,cx Pseudo Open

Source-ness

Wednesday, February 13, 13

That said, most of what I’m gonna say about iOS applies to Android too.Meh.

Wednesday, February 13, 13

Think mobile!

Think now!

Wednesday, February 13, 13

Characteristics of Mobile

not a keyboard/mouse paradigm

unreliable low bandwidth high latency network connection

small screen

limited processing power and local storage

limited battery life

hostile work environment

untapped resources: camera(s), accelerometers, GPS, phone, speaker, mic, LED flash

Wednesday, February 13, 13

Wednesday, February 13, 13

Mobile web or die.

Wednesday, February 13, 13

Awesome Dashboard

"App"

Wednesday, February 13, 13

Natrve (iOS, Android, Blackberry, Windows Phone) App Window

Awesome Dashboard

"App"

Flll~d with a single We.bVIeW widget loaded

With your mobile web content.

Wednesday, February 13, 13

When the web won’t do.

Performance/Responsiveness/UX.

Complex local data store.

Network optional.

Hardware control.Sophisticated UI.

3D/accelerated graphics.

Wednesday, February 13, 13

How are enterprise mobile

apps different?

Complex local data stores.

Integration with back office

systems.

Wednesday, February 13, 13

,..__---------~----~~~

Mob.1le Inte_gr~t:1on J>os &- J>on'ts ,

OV\

Cove-r -the Y\et)

Wednesday, February 13, 13

0

I I I I I I I I I I I I I I I I I I I

Crf ~oLA he>. ve -t:.o) /

&-443 f>ov-ts B$Z>

I I I I I I I I I I I I

""' -+l a... . - • ~ (J H o ;.J (/) <U

:J cS ... >l-<Sw hZ.

_g ""'~ cu(/)V)

3~~ ""Q_.

_J (/) . ~h

l-I.

Protot _yp ·,ccxrash EV\terpr ·,se A rch.atecture

H H '"" ~ '"" • ~ LU

• LU (J (J

<(_ <(_ LU-tJ LU-tJ - ~ (U - ~ (U :s :s

h '"" h '"" 4- 4-'""~ '""~ r- r-r-~ r-~ (/) (/)

LU LU<!. LU LU<!. p!. z~ p!. z~

• •

Bus·aness Present~ t·aon D~t~ Access

Lo.9·ac

Wednesday, February 13, 13

sG.'-

D~t~

Stov-e

. -

...Q 0 ~

Wednesday, February 13, 13

EV\ tev-pv-·,se A v-c h ·,tectuv-e)(

D~t~ Access

-~

D~t~

Stov-e

Present~ t·aon )

Bus·aness Lo.9·ac;

&- D~ t~ Access

Wednesday, February 13, 13

D~t~

Stov-e

"EV\tev-pv-·ase" Av-ch.atectuv-e <;tu·ack·,e Mob·,r,z.cx t·aoV\ t=·,x

Present~ t·aon )

Bus·aness Lo.9·ac;

&- D~ t~ Access

Wednesday, February 13, 13

D~t~

Stov-e

. -

...Q 0 ~

'/ oLA'\\ V\eeO to •

bLA. ,\0 -t 'n \S.

Disclaimer: I’m

not .NET developer,

but I experimented a

little in college.

Wednesday, February 13, 13

In Visual Studio...1.) Create a Web Project

2.) Create a new Entity Model

3.) Reverse engineer Entity Model

from Database

4.) Create a WCF Data Service

5.) Add your Entity Model Class to

the Service class declaration

6.) Configure data access.

Wednesday, February 13, 13

http://www.hanselman.com/blog/CreatingAnODataAPIForStackOverflowIncludingXMLAndJSONIn30Minutes.aspxSource:

[JSONPSupportBehavior]public class Service : DataService<YourEnterpriseEntities>{ // This method is called only once to initialize service-wide policies. public static void InitializeService(DataServiceConfiguration config) { // config.SetEntitySetAccessRule("*", EntitySetRights.AllRead); config.SetEntitySetAccessRule("Locations", EntitySetRights.AllRead); config.SetEntitySetAccessRule("Customers", EntitySetRights.All); config.SetEntitySetAccessRule("SalesOrders", EntitySetRights.All); config.SetEntitySetAccessRule("Secrets", EntitySetRights.None); //Set a reasonable paging site config.SetEntitySetPageSize("*", 25); config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2; }}

Wednesday, February 13, 13

http:LLyourhost.comLservice.svcLCustomers()

http:LLyourhost.comLservice.svcLCustomers(34)

http:LLyourhost.comLservice.svcLCustomers()? $filter=substringof('itactile',Name) or substringof('Galloway' ,ContactLastName)& $format=json

http:LLyourhost.comLservice.svcLCustomers(34)? $expand=Sales0rders$format=json

Wednesday, February 13, 13

http:LLyourhost.comLservice.svcL?~format=json

{ "d" • • { "Enti tySets" : [ "Batches", "Drawings", "DrawingTypes", "Elements", "ElernentAnswers", "ElernentAnswerPhotoes", "ElernentGroups", "Elernenticons", "ElernentQuestions", "ElernentRequirernents", "ElernentTypes", "LocationMetaDatas", "LocationMetaDataFields", "Locations", "PickListirnages", "Projects", "StoreAccesses", "sysdiagrarns", "TestTables", "tlkDivisions", "UpdElernents", "UpdElernentAnswers", "UpdElernentAnswerPhotoes", "UpdLocationMetaDatas", "Users" ]

} }

Wednesday, February 13, 13

http://yourhost.com/service.svc/ElementTypes?Sfor.mat=json

{ "d" : [ { " metadata": { "uri": "http: //yourhost.com/service.svc / ElementTypes(1)", "type": "YourDataModel.ElementType" }, "elementTypeid": 1, "name": "POS 1&2 Camera", "elementGroupid": 1, "lastModified": "\/Date(1340728631167)\/", "active": true, "elementiconid" : 3 4, "Elements" : { "_deferred": { "uri": "http: //yourhost.com/service.svc / ElementTypes(1) / Elements" } } , "ElementGroup": { "_deferred": { "uri": "http://yourhost.com/service.svc/ElementTypes(1)/ElementGroup" } } , "Elementicon" : { "_deferred": { "uri": "http: //yourhost.com/service.svc / ElementTypes(1) / Elementicon" } }, "ElementRequirements": { "_deferred": { "uri": "http: //yourhost.com/service.svc / ElementTypes(1) / ElementReguirements" } }, "DrawingTypes": { "_deferred": { "uri": "http: //yourhost.com/service.svc / ElementTypes(1) /DrawingTypes " } }, "ElementQuestions": { "_deferred": { "uri": "http: //yourhost.com/service.svc / ElementTypes(1) / ElementOuestions" } }

} ' { " metadata": { "uri": "http: //yourhost.com/service.svc / ElementTypes(2)", "type": "YourDataModel.ElementType" }, "elementTypeid": 2, "name": "POS 3&4 Camera", "elementGroupid": 1, "lastModified": "\/Date(1340728631167)\/",

Wednesday, February 13, 13

http://yourhost.com/service.svc/ElementTypes(l)?Sformat=json& Sexpand=ElementGroup { "d" : { " metadata" : { "uri": "http : //yourhost.com/service.svc/ElementTypes(1)", "type" : "QTSecurityModel . ElementType" }, "elementTypei d": 1, "name" : "POS 1&2 Camera", "elementGroupid" : 1 , "lastModif i ed" : "\/Date( 1340728631167) \/", "active": true, "elementiconi d" : 34, "Elements" : { "_ def erred" : { "uri": "http : //yourhost.com/service.svc/ElementTypes(1)/Elements" } } , "ElementGroup": { "_metadata": { "uri": "http://yourhost.com/service.svc/ElementGroups(1)", "type": "QTSecurityModel.ElementGroup" }, "elementGroupid": 1, "name": "Cameras", "sortOrder": 1, "lastModified": "\/Date(1340289282327)\/", "active": true, "ElementTypes": { "_deferred": { "uri": "http://yourhost.com/service.svc/ElementGroups(1)/ElementTypes" } } } , "Element i con" : { "_ def erred" : { "uri": "http : //yourhost.com/service.svc/ElementTypes(1)/Elementi con" } }, "ElementRequirements" : { "_ def erred" : { "uri": "http : //yourhost.com/service.svc/ElementTypes(1)/ElementReguirements" } } , "DrawingTypes" : { "_ def erred" : { "uri": "http : //yourhost.com/service.svc/ElementTypes(1)/DrawingTypes" } }, "ElementQuestions" : { "_ def erred" : { "uri": "http : //yourhost.com/service.svc/ElementTypes(1)/ElementOuestions" } } } }

Wednesday, February 13, 13

The Mobile Dev

POV

Wednesday, February 13, 13

+(id) syncRequest: (NSString *) urlString error:(NSError **) error { NSLog(@"syncRequest: %@",urlString); urlString=[SyncHelper addJsonToUri:urlString]; // Adds ?$format=json to URL NSURL *url = [NSURL URLWithString:urlString]; NSError *internalError = nil; NSURLResponse *response=nil; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; if (HTTP_USER!=nil && [HTTP_USER length]>0 && HTTP_PASSWORD!=nil && [HTTP_PASSWORD length]>0) { NSString *authStr = [NSString stringWithFormat:@"%@:%@",HTTP_USER,HTTP_PASSWORD]; NSData *authData = [authStr dataUsingEncoding:NSASCIIStringEncoding]; NSString *authValue = [NSString stringWithFormat:@"Basic %@", [authData base64EncodingWithLineLength:80]]; [request setValue:authValue forHTTPHeaderField:@"Authorization"]; }

NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&internalError]; if (!internalError) { internalError=nil; NSDictionary *interimDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers|NSJSONReadingAllowFragments error:&internalError]; if (internalError!=nil) { NSLog(@"Error parsing JSON from syncRequest: %@ ",[internalError debugDescription]); if (error!=nil) *error=internalError; return nil; } NSDictionary *errorDict = [interimDict objectForKey:@"error"]; if (errorDict!=nil) { NSDictionary *messageDict = [errorDict objectForKey:@"message"]; NSString *errorMessage = [messageDict objectForKey:@"value"]; if (error!=nil) *error=[NSError errorWithDomain:ERROR_DOMAIN code:4000 localizedDescription:[NSString stringWithFormat:@"Error received from server: %@",errorMessage]]; return nil; }

Reading Data

Wednesday, February 13, 13

id retVal = [interimDict objectForKey:@"d"]; if ([retVal isKindOfClass:[NSDictionary class]] && [((NSDictionary *)retVal) objectForKey:@"results"]!=nil) { return [((NSDictionary *)retVal) objectForKey:@"results"]; } else { return retVal; } } else { NSLog(@"Error: unable to complete web request because - %@",[internalError localizedDescription]); if (error!=nil) *error=internalError; return nil; }}

If result is a list, an NSArray

of NSMutableDictionary’s is

returned.

Otherwise, an NSMutableDictionary is returned.

Wednesday, February 13, 13

+(BOOL) insertEntity:(id) entity entityName:(NSString *)entityName error:(NSError **) error{ NSString *urlString = [SyncHelper urlStringForEntity:entityName]; // Turns “EntityName” into “http://yourserver/service.svc/EntityName NSURL *url = [NSURL URLWithString:urlString]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

[request setHTTPMethod:@"POST"];[request addValue:@"Application/json" forHTTPHeaderField:@"content-type"];[request addValue:@"Application/json" forHTTPHeaderField:@"accept"];[request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"];

if (HTTP_USER!=nil && [HTTP_USER length]>0 && HTTP_PASSWORD!=nil && [HTTP_PASSWORD length]>0) { NSString *authStr = [NSString stringWithFormat:@"%@:%@",HTTP_USER,HTTP_PASSWORD]; NSData *authData = [authStr dataUsingEncoding:NSASCIIStringEncoding]; NSString *authValue = [NSString stringWithFormat:@"Basic %@", [authData base64EncodingWithLineLength:80]]; [request setValue:authValue forHTTPHeaderField:@"Authorization"]; } NSError *internalError = nil; NSData *payload = [NSJSONSerialization dataWithJSONObject:entity options:NSJSONWritingPrettyPrinted error:&internalError]; if (internalError != nil) { if (error!=nil) *error = internalError; return NO; } [request setHTTPBody: payload]; NSHTTPURLResponse *response = nil;

internalError = nil; NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&internalError]; if (internalError != nil) { if (error!=nil) *error = internalError; return NO; } NSString *responseStatus = [NSHTTPURLResponse localizedStringForStatusCode:[response statusCode]]; if ([response statusCode]!=201) { if (error!=nil) *error = [NSError errorWithDomain:ERROR_DOMAIN code:100

localizedDescription:[NSString stringWithFormat:@"HTTP ERROR (%i) %@",[response statusCode],responseStatus]]; } return [response statusCode]==201; }

Inserting New Data

Wednesday, February 13, 13

+(BOOL) updateEntity:(NSMutableDictionary *)entity forKeys:(NSArray *)keys error:(NSError **) error { NSDictionary *metadata = [entity valueForKey:@"__metadata"]; if (metadata==nil) return NO; NSURL *url = [NSURL URLWithString:[metadata valueForKey:@"uri"]]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; [request setHTTPMethod:@"POST"];

[request addValue:@"MERGE" forHTTPHeaderField:@"X-HTTP-Method"]; [request addValue:@"Application/json" forHTTPHeaderField:@"content-type"];[request addValue:@"Application/json" forHTTPHeaderField:@"accept"];[request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"];

if (HTTP_USER!=nil && [HTTP_USER length]>0 && HTTP_PASSWORD!=nil && [HTTP_PASSWORD length]>0) { NSString *authStr = [NSString stringWithFormat:@"%@:%@",HTTP_USER,HTTP_PASSWORD]; NSData *authData = [authStr dataUsingEncoding:NSASCIIStringEncoding]; NSString *authValue = [NSString stringWithFormat:@"Basic %@", [authData base64EncodingWithLineLength:80]]; [request setValue:authValue forHTTPHeaderField:@"Authorization"]; } NSMutableDictionary *payloadDict = [NSMutableDictionary dictionaryWithCapacity:10]; [payloadDict setValue:metadata forKey:@"__metadata"]; for (NSString *key in keys) { [payloadDict setValue:[entity valueForKey:key] forKey:key]; } NSError *internalError = nil; NSData *payload = [NSJSONSerialization dataWithJSONObject:payloadDict options:NSJSONWritingPrettyPrinted error:&internalError]; if (internalError != nil) { if (error!=nil) *error = internalError; return NO; } [request setHTTPBody: payload]; NSHTTPURLResponse *response = nil;

internalError = nil; NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&internalError]; if (internalError != nil) { if (error!=nil) *error = internalError; return NO; } NSString *responseStatus = [NSHTTPURLResponse localizedStringForStatusCode:[response statusCode]]; if ([response statusCode]!=204) { if (error!=nil) *error = [NSError errorWithDomain:ERROR_DOMAIN code:5000

localizedDescription:[NSString stringWithFormat:@"HTTP ERROR (%i) %@",[response statusCode],responseStatus]]; } return [response statusCode]==204;}

Updating Data

Wednesday, February 13, 13

What to Do With an NSMutableDictionary?

1.) Parse into proper objects

2.) Create a wrapper object that stores NSMutableDictionary internally

3.) Use Objective-C Categories to add field-like methods to NSMutableDictionary

But don’t just [object objectForKey: @“propertyName“]

Wednesday, February 13, 13

//// NSMutableDictionary+Customer.m// Yale Cleaners//// Created by Matt Galloway on 8/31/12.// Copyright (c) 2012 Architactile LLC. All rights reserved.//

#import "NSMutableDictionary+Customer.h"

@implementation NSMutableDictionary (Customer)

#pragma mark - Customer Custom Getters

-(NSString *) mobileNumber { return [self filteredObjectForKey:@"Mobile_no"];}

-(NSString *) sendEmail { return [self filteredObjectForKey:@"SendEmail"];}

-(NSString *) sendReceipt { return [self filteredObjectForKey:@"SendReceipt"];}

-(NSString *) sendText { return [self filteredObjectForKey:@"SendText"];}

-(NSString *) username { return [self filteredObjectForKey:@"User_Name"];}

-(NSString *) uri { return [self filteredObjectForKey:@"uri"];}

-(NSString *) address { return [self filteredObjectForKey:@"address"];

-(NSString *) area { return [self filteredObjectForKey:@"area"];}

-(NSString *) charge { return [self filteredObjectForKey:@"charge"];}

-(NSString *) city { return [self filteredObjectForKey:@"city"];}...

Cate

gory

Exa

mple

Wednesday, February 13, 13

Use HTTPS +

Authentication

(at a minimum)

Wednesday, February 13, 13

Local Data Store?

Meet SQLite &

CoreData

Wednesday, February 13, 13

CoreData is one of iOS’s

greatest advantages over

Android for business apps.

Wednesday, February 13, 13

PrtmaryK_e_;y __ --1

OJeCt Pr • AttllbU folderNam j sonl.ast'li name: proJectld syn<Corn

IC!S e

~ocM1ed

!)lete nships Re ~l i O

draw-ng­elementRe

ypes qul rements fc::

1

• Anr butt~ hc: dlypc: JSOnW~t~Od i fied loatoorMet~DJ.tJfield ld ,~e

pockl stCho ces required • Rclattonships location~c:tt~Oata <

--:

Louuonvet.lO.lt.l-, Annb~o~tcs

jsonlilst Y.od I fled ocatton~ctOJDiltald vo~lutBool

v.llueno~t

VJ.Iuelnt value Text

Rc ta11onsh1 ps

!ocatton

-

-.;

C Locat_oo_n __ ----,~ 19 Auro butes OJddrusl .lddreu2 City folde r'\ arne htghCr.mc:Locat.on J S.onuu~od oticd

loutoOf'lld loutlonNu m ber l'ame state ~urveyCiodT me surveyor sut\leySct'leduled-IIM

:~:~::~:d~~ cotN\ple-Le syt'I(Complete I J ' ~ 2op

Re :ltoonshlps >dto'lwings Elemc:ntRc:qu rc:rrc:f'll

e emet~tRequiremc:nu Anrobutes D•aw r.g ::__j 0r.Jwingiy_JX ___ 1 • Attribute~ ·--..-j~ ~----+ locattonl\'etaOata e ementRequirementld

Attributes 1---------drolw n!lld project JSOt'll...lS tMOCifled dr.w,,ngTypeld fa en~rne m.lXAllcw.ed JSOnl...lstMOOif1ed m nRc:qu•rcd name heoght Relauonshops

Re atton~h ps JSOf'll.astMod•f1ed ,.----+-----------1-- ,..> drav. n!jType

c:oc:mc:ntRcquirc:mcnts scale 1,..:::::1

OementTyoe E ementCroup 9 Annblltes

dril'Wings A::llamc: i---1-~---+----------+--+~c: cmc:ntTypc

c c:mentType5 wlctth l := ===------:!-------------lL...--~~~ocatoon ----'

ects J Re ·" •onship~ d ~ !---t-~PI'OJ ____ __,. d t.lWlngTypt "

• Ann b"tc:s c:lememTypeld elementGroupld ,conl tlename JsonLut'-1oct'lc:d Jsonl..ut'Aocif,c:d n;amc: narrc: sortOrder Rc: o'!t on\hops

Rel.'lt on·""s~h"'t pc;s===i drilv.ongTypu /\'.,.» elementCroup

c:lementQ .. estlons c:lementRequ lremen ts c:lc:mc:nts

~ Eltmc:ntQue stlon

Attrobutes

eemenu _ 1,_ ~ t.~ ~ cc Element l ess

AUt ib'->ttS dra\~o, ng(()Q(dX 1\.J ~;::·~~;=~:n V\ er w ·,t. h elementld clcmentll.umber

jsonl..ut'-1oditied l c: c:mc:ntQI.esbonld nelpPnotofo ename

n.1me ~ 'f RelatoOnSh pS m 0 t. ~~ s !-------\c:iementAnswc:rs no help-tltt

nchesMax nches\4ln json~t\lod • tied pnoto.AI owNOte~

:>hotoM.1xCount ohotoM nCount photoRc:qu rel'l.otcs p cl(L siC no cc:s quest Of'ITtxt quest on- 'fpe required sortOrdc:r tc:xtMaxlength

Rc: iltooqhips

E c:mentAnswc:r Auro butes

OJnswc: rlnt .'lnswerText created On e emen!An~"erld gpslatttudc: gpslong tude jsonLmVoct ofoed IJ.stl'-'\od ned

Re iltoons"'lps e ement

0 emc ntA.11s"'..: rPhoto Aur iba.tes

crc;atcdOn etementAnswerPhotold gpsUtotude

gpslottgltude J t'ludong jsonLOJst'Aodoficd .'lnMod fied ~otorden~e

photoNotts

Code ~nd no

SQL •

: :::~~~;~;:rs "<;_-------------------,fmentAn~"-C!rPhotos +u

Wednesday, February 13, 13

To Recap...

Android sucks.

Mobilize your web assets.

Consider the mobile web first.

Use RESTful APIs.

Avoid SQL & SOAP.

CoreData is way worth it.Wednesday, February 13, 13

Matt Galloway

(Freelance Mobile Developer Extraordinaire)

matt@architactile.com

918-808-3072

Wednesday, February 13, 13

Recommended