PhoneGap and Xamarin - What an iOS Dev Learned About Hybrid Solutions
Tulsa Dev Lunch iOS at Work
-
Upload
matt-galloway -
Category
Technology
-
view
227 -
download
5
description
Transcript of Tulsa Dev Lunch iOS at Work
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-ngelementRe
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)
918-808-3072
Wednesday, February 13, 13