SLIDE 1 Cameron Barrie
Client Principal at Bilue cameron@bilue.com.au @whalec
SLIDE 2
Mobile at Warp Speed
SLIDE 3
If you’re not disrupting, you’re likely being disrupted
SLIDE 4
A quick survey
SLIDE 5 – JJ&LJ2013
Worst upgrade ever!!!!! Now I can’t even log in, most useless app out of all the banks, really angry about this can’t even transfer money let alone opening the app. FIX IMMEDIATELY
SLIDE 6
Tried to move fast
SLIDE 7 –Anon friend
We don’t read the reviews anymore, the only people who leave reviews are the people who complain.
SLIDE 8 – Serenity-1105
I have spent the last week trying to get up to the bank to
- rder a new card. I looked on the app and it wasn’t there,
updated today and wow, how convenient I am now able to order another card through the app. You guys sure make my life easier. Very pleased with this new update…
SLIDE 9
Can move fast
SLIDE 10
How do we get there?
SLIDE 11
- Can you make decisions quickly?
- Are you using source control(I know right)?
- Are you doing Test Driven Development?
- Does the business understand the risk/reward?
- Do you have genuine buy in from the business?
Some Prerequisites
SLIDE 12
Why is it a challenge?
SLIDE 13
It’s not the web
SLIDE 14
Can’t just refresh
SLIDE 15
Walled Garden
SLIDE 16
5 day release cycle
SLIDE 17
1-2 days for expedited
SLIDE 18
No update guarantees
SLIDE 19
Forced updates are bad
SLIDE 20
On going support
SLIDE 21
Business Logic
SLIDE 22 The Challenge
{ self.collectionViewDataSource.cellFactory = [WOWCollectionViewProductCellFactory class]; self.collectionViewDelegate.cellFactory = [WOWCollectionViewProductCellFactory class]; self.collectionViewLayoutArrangement = [WOWProductsViewController layoutArrangementForUserInterfaceIdiom: [UIDevice currentDevice].userInterfaceIdiom]; self.collectionViewLayoutDelegate.layoutSpacingClass = [WOWCollectionViewAutoSpacingLayoutDelegate sizeClassForUserInterfaceIdiom:[UIDevice currentDevice].userInterfaceIdiom]; self.collectionViewLayoutDelegate.cellSizeClass = [WOWProductsViewController sizeClassForLayoutArrangement:self.collectionViewLayoutArrangement] ; }
- (void)viewWillAppear:(BOOL)animated
{ [super viewWillAppear:animated]; self.barAnimatingScrollViewDelegate.animatableHeaderBar = (UIView <WOWAnimatableBar> *)self.navigationController.navigationBar; }
{ [self showLoadingIndicator]; [self.productsDataStore fetchDataWithRequestURL:nil errorHandler:^(NSError *error) { [self dataStore:self.productsDataStore didReceiveError:error]; }]; }
- (void)showLoadingIndicator
{ self.collectionView.hidden = YES; self.loadingIndicatorView.hidden = NO; } }
SLIDE 23 Social
{ self.collectionViewDataSource.cellFactory = [WOWCollectionViewProductCellFactory class]; self.collectionViewDelegate.cellFactory = [WOWCollectionViewProductCellFactory class]; self.collectionViewLayoutArrangement = [WOWProductsViewController layoutArrangementForUserInterfaceIdiom: [UIDevice currentDevice].userInterfaceIdiom]; self.collectionViewLayoutDelegate.layoutSpacingClass = [WOWCollectionViewAutoSpacingLayoutDelegate sizeClassForUserInterfaceIdiom:[UIDevice currentDevice].userInterfaceIdiom]; self.collectionViewLayoutDelegate.cellSizeClass = [WOWProductsViewController sizeClassForLayoutArrangement:self.collectionViewLayoutArrangement] ; }
- (void)viewWillAppear:(BOOL)animated
{ [super viewWillAppear:animated]; self.barAnimatingScrollViewDelegate.animatableHeaderBar = (UIView <WOWAnimatableBar> *)self.navigationController.navigationBar; }
{ [self showLoadingIndicator]; [self.productsDataStore fetchDataWithRequestURL:nil errorHandler:^(NSError *error) { [self dataStore:self.productsDataStore didReceiveError:error]; }]; }
- (void)showLoadingIndicator
{ self.collectionView.hidden = YES; self.loadingIndicatorView.hidden = NO; } }
SLIDE 24
Recipe for disaster
SLIDE 25
Remove Business Logic
SLIDE 26 { title: "Stop killing unicorns", user_id: 67 }
Remove Business Logic
/post.json
SLIDE 27 @implementation BILPost
- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
self = [self init]; if (self) { self.title = dictionary[@"title"]; NSString *urlString = [NSString stringWithFormat:@"%@users/%@.json", kAPIBaseURL, dictionary[@"user_id"]]; self.userURL = [NSURL URLWithString:urlString]; } return self; }
Remove Business Logic
BILPost.m
SLIDE 28 @implementation BILPost
- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
self = [self init]; if (self) { self.title = dictionary[@"title"]; NSString *urlString = [NSString stringWithFormat:@"%@users/%@.json", kAPIBaseURL, dictionary[@"user_id"]]; self.userURL = [NSURL URLWithString:urlString]; } return self; }
Remove Business Logic
BILPost.m
SLIDE 29 { title: "Stop killing unicorns", user_id: 67 }
Craft APIs
/post.json
SLIDE 30 { title: "Stop killing unicorns", user_id: 67 }
Craft APIs
/post.json
SLIDE 31 { title: "Stop killing unicorns", user: “http://service.com/api/v2/users/1.json” }
Use hypermedia
/post.json
SLIDE 32 { title: "Stop killing unicorns", user: “http://service.com/api/v2/users/1.json” }
Use hypermedia
/post.json
SLIDE 33 @implementation BILPost
- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
self = [self init]; if (self) { self.title = dictionary[@"title"]; NSString *urlString = [NSString stringWithFormat:@"%@users/%@.json", kAPIBaseURL, dictionary[@"user_id"]]; self.userURL = [NSURL URLWithString:urlString]; } return self; }
Remove Business Logic
BILPost.m
SLIDE 34 @implementation BILPost
- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
self = [self init]; if (self) { self.title = dictionary[@"title"]; self.userURL = [NSURL URLWithString:dictionary[@"user"]]; } return self; }
Remove Business Logic
BILPost.m
SLIDE 35 { title: "Stop killing unicorns", user: “http://service.com/api/v2/users/1.json” }
Use hypermedia
/post.json
SLIDE 36 { title: "Stop killing unicorns", user: “https://api.v2.users.service.com/cbarrie” }
Update anytime
/post.json
SLIDE 37
Version your APIs
SLIDE 38 { title: "Stop killing unicorns", user: “https://api.v2.users.service.com/cbarrie” }
Version APIs
v2/post.json
SLIDE 39 { title: "Stop killing unicorns", user: { name: “Ron Burgundy”, image: “http://cdn.com/someimage.png”, href: “https://user.api.service.com/v3/1” } }
Version APIs
v3/post.json
SLIDE 40 { title: "Stop killing unicorns", user: “https://api.v2.users.service.com/cbarrie”, userSummary: { name: “Brick Tamland”, image: “http://cdn.com/someimage.png” } }
Version APIs
v2/post.json
SLIDE 41 Social
{ self.collectionViewDataSource.cellFactory = [WOWCollectionViewProductCellFactory class]; self.collectionViewDelegate.cellFactory = [WOWCollectionViewProductCellFactory class]; self.collectionViewLayoutArrangement = [WOWProductsViewController layoutArrangementForUserInterfaceIdiom: [UIDevice currentDevice].userInterfaceIdiom]; self.collectionViewLayoutDelegate.layoutSpacingClass = [WOWCollectionViewAutoSpacingLayoutDelegate sizeClassForUserInterfaceIdiom:[UIDevice currentDevice].userInterfaceIdiom]; self.collectionViewLayoutDelegate.cellSizeClass = [WOWProductsViewController sizeClassForLayoutArrangement:self.collectionViewLayoutArrangement] ; }
- (void)viewWillAppear:(BOOL)animated
{ [super viewWillAppear:animated]; self.barAnimatingScrollViewDelegate.animatableHeaderBar = (UIView <WOWAnimatableBar> *)self.navigationController.navigationBar; }
{ [self showLoadingIndicator]; [self.productsDataStore fetchDataWithRequestURL:nil errorHandler:^(NSError *error) { [self dataStore:self.productsDataStore didReceiveError:error]; }]; }
- (void)showLoadingIndicator
{ self.collectionView.hidden = YES; self.loadingIndicatorView.hidden = NO; } }
SLIDE 42
Defaults { self.collec tionViewDat aSource.cel lFactory = [WOWCollect ionViewProd uctCellFact
self.collec tionViewDel egate.cellF actory = [WOWCollect
pear: (BOOL)animated { [super viewWillAppear:a nimated]; self.barAnimatin gScrollViewDeleg ate.animatableHe aderBar = (UIView <WOWAnimatableBa r> *)self.navigatio nController.navi gationBar; }
{
Social
SLIDE 43
Be ready to ship
SLIDE 44
Feature flags
SLIDE 45 #ifndef SEARCH_INPUT self.searchInputView.hidden = YES; #endif
Feature flags
BILGlobalToolbar.m
SEARCH_FEATURES = SEARCH_TOOLBAR SEARCH_INPUT USER_TOGGLES = $(SEARCH_FEATURES)
User.xcconfig
SLIDE 46 #include "Shared.xcconfig" #include "Features.xcconfig" #include "User.xcconfig" DEBUG_TOGGLES = DEBUG=1 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) $(DEBUG_TOGGLES) $(FEATURE_TOGGLES) $(USER_TOGGLES)
Feature flags
Debug.xcconfig
SLIDE 47
Iterative development
SLIDE 48
Development pipeline
SLIDE 49
Continuous Integration
SLIDE 50
Continuous Delivery
SLIDE 51
Continuous Deployment
SLIDE 52
Robust processes
SLIDE 53
Speedy collaboration
SLIDE 54
Code previews
SLIDE 55
Pull requests
SLIDE 56
Social code reviews
SLIDE 57
Don’t stop the world
SLIDE 58
Maintain quality
SLIDE 59
Manage Tech Debt
SLIDE 60
Start thinking for the business
SLIDE 61
- Business
- Designers
- App Developers
- Backend Developers
- QAs
Product aligned teams
SLIDE 62
Start doing what you can do today
SLIDE 63
Walled Garden
SLIDE 64
Remove Business Logic
SLIDE 65
Be ready to ship
SLIDE 66
Enable collaboration
SLIDE 67
Product aligned teams
SLIDE 68
Keep moving forward
SLIDE 69
Keep building
SLIDE 70
Keep measuring
SLIDE 71
Keep learning
SLIDE 72
Keep shipping
SLIDE 73
Thanks
SLIDE 74 Cameron Barrie
Client Principal at Bilue cameron@bilue.com.au @whalec
SLIDE 75 Cameron Barrie
Client Principal at Bilue cameron@bilue.com.au @whalec