Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Offline is Not a Special Case

Offline is Not a Special Case

Presented at 360iDev 2014.

More than ever, apps exist beyond the bounds of the device. Apps have server-side infrastructure, social components, and other features requiring the device to have an Internet connection. Unfortunately, our reality is that although our devices are always with us, the Internet is not. Even in areas where we have a connection, the network may be so burdened that any sort of meaningful communication is impossible.

Understanding this reality, how should we approach developing applications that require some sort of server communication? The trick is rethinking how our apps should work offline—offline is, in fact, not a special case. To help contextualize this, we’ll go over strategies for caching responses from network requests in core data or flat files, queuing network requests until an Internet connection is available, and even tips for designing API endpoints for the most efficient interactions with mobile devices.

Daniel Pfeiffer

August 25, 2014
Tweet

More Decks by Daniel Pfeiffer

Other Decks in Programming

Transcript

  1. Offline is Not a Special Case Daniel Pfeiffer Lead Developer

    / Float Mobile Learning ! @mediabounds dpfeiffer@floatlearning.com
  2. 360|iDev 2014 / Offline is Not a Special Case When

    Communication Fails • No connection • Connection without Internet • “Crowded” connection • Bad/slow connection
  3. 360|iDev 2014 / Offline is Not a Special Case always

    * Offline is Not a Special Case
  4. 360|iDev 2014 / Offline is Not a Special Case When

    is offline "special"? • When an action must be immediately 
 verified by an “authority" • When stale data could impact a user’s behavior • When the timeliness of the data is important • When the user is attempting to control 
 another device in real time
  5. 360|iDev 2014 / Offline is Not a Special Case When

    is offline not "special"? • When the user is working within their own data • When timestamping transactions is sufficient • When the data doesn’t frequently change
  6. 360|iDev 2014 / Offline is Not a Special Case Principle

    Network performance is out of your control— don’t let it impact your user experience;
 network requests belong in the background.
  7. 360|iDev 2014 / Offline is Not a Special Case Roadmap

    1. Retrieving data from the server 2. Sending data to the server 3. Tips for designing API endpoints
  8. 360|iDev 2014 / Offline is Not a Special Case View

    Controller User opens view Show loading indicator Update View Show offline message Request Data Show error message Online Offline Success Error
  9. 360|iDev 2014 / Offline is Not a Special Case Problems

    • Business logic is too close to view controllers—view controllers shouldn't be managing data freshness • Error messages may get in the way • Inefficient in managing data—requests too frequently or not at the right time • Allows factors outside of our control to directly impact user experience—leaves too much up to luck
  10. 360|iDev 2014 / Offline is Not a Special Case User

    opens view Show loading indicator Update View Show offline message Request Data Show error message Online Offline Success Error Retrieve cached data Save data 
 to cache Retrieve cached data Check for cached data Network Change Just Checked Exists No Data Refresh
  11. 360|iDev 2014 / Offline is Not a Special Case A

    Better Approach • Is not wasteful in communicating with a server • Builds views using local data • Doesn't display error messages for something 
 that's not the user's fault • Retrieves data in a timely manner
  12. 360|iDev 2014 / Offline is Not a Special Case Store

    Server Responses • Server data first goes to your local database/cache • The user may have just paid to retrieve that data— don’t throw it away • Handle data parsing in a background queue
  13. 360|iDev 2014 / Offline is Not a Special Case Benefits

    of Keeping Data Local • Application views can be built very quickly • Your logic flow doesn't change based on the connected state of the device • Allows you to make pinpointed decisions about when the application should retrieve data
  14. 360|iDev 2014 / Offline is Not a Special Case When

    should you get fresh data? • At a predefined interval • When the application comes to the foreground • In response to a push notification • When the user directly/implicitly requests new data
  15. 360|iDev 2014 / Offline is Not a Special Case Keeping

    Views Fresh • Use key-value observing (KVO) to respond to changes in data (or ReactiveCocoa, NSNotifications, etc.) • Don’t interrupt the user when updating a view - Remember the user’s position in a scroll view - Avoid moving interactive objects
  16. 360|iDev 2014 / Offline is Not a Special Case Show

    error Failed & No Data Main User opens view Fetch data from local DB Update view Data update notification Background Request data from server Parse data Determine error Save data Callback/ Notification Success Failure ! "
  17. 360|iDev 2014 / Offline is Not a Special Case Handling

    User Input • User input goes directly into the local cache • Synchronize the data when you can • Syncing is hard—avoid the temptation to 
 over-engineer—stick with what you need
  18. 360|iDev 2014 / Offline is Not a Special Case Strategies

    • Send entire data set to server and let it sort it out (only for small data sets) • Add a "sync" flag to your data model that indicates whenever an object has been changed • Create a queue of changes to send to the server
  19. 360|iDev 2014 / Offline is Not a Special Case Using

    a sync flag Task identifier summary description dueDate isCompleted createdDate (more) syncStatus enum SyncStatus: Int { case NeedsSynced case OK case NeedsRemoved case Unknown }
  20. 360|iDev 2014 / Offline is Not a Special Case class

    TasksViewController: UITableViewController { var task = Task() ! func onCheckedBox() { self.task.markAsCompleted() } } Using a sync flag: changing/inserting data class Task: NSManagedObject { @NSManaged var syncStatus: Int @NSManaged var isCompleted: NSNumber func markAsCompleted() { self.isCompleted = true self.syncStatus = .NeedsSynced.toRaw() } }
  21. 360|iDev 2014 / Offline is Not a Special Case class

    Task: NSManagedObject { @NSManaged var syncStatus: Int func remove() { self.syncStatus = .NeedsRemoved.toRaw() } } Using a sync flag: deleting data • Remember to keep the model for syncing • All other views should filter by syncStatus, 
 skipping any tasks with SyncStatus.NeedsRemoved
  22. 360|iDev 2014 / Offline is Not a Special Case Using

    a sync flag: catching up with the server • Find all the models that need synced • Find all the models that need deleted • Change sync flag or delete model only
 after successful response from server
  23. 360|iDev 2014 / Offline is Not a Special Case Creating

    a Queue Task completed Queue New task created Due date changed Task deleted Task name changed
  24. 360|iDev 2014 / Offline is Not a Special Case Creating

    a Queue • Operations represent a single user action • Operations are stored FIFO (first in, first out) • Entire queue is persisted to disk so it can be rebuilt • Operations are removed after they succeed Task completed Queue New task created Due date changed Task deleted Task name changed
  25. 360|iDev 2014 / Offline is Not a Special Case class

    TaskUpdateOperation: NSOperation, NSCoding { var request: NSURLRequest? var taskIdentifier: String? ! required init(coder aDecoder: NSCoder!) { self.request = aDecoder.decodeObjectForKey("request") as? NSURLRequest self.taskIdentifier = aDecoder.decodeObjectForKey("id") as? String } func encodeWithCoder(aCoder: NSCoder!) { aCoder.encodeObject(self.request, forKey: "request") aCoder.encodeObject(self.taskIdentifier, forKey: "id") } } Creating a Queue: NSURLRequest NSKeyedArchiver.archiveRootObject([…operations], toFile: file)
  26. 360|iDev 2014 / Offline is Not a Special Case Background

    Modes in iOS 7 • Background Fetch —Have your application periodically launch in the background to 
 catch up with the server • Remote Notification — The server can notify 
 the application of new content that can be 
 fetched in the background
  27. 360|iDev 2014 / Offline is Not a Special Case Many

    small requests or one large request? Consider: • Each network request comes with overhead • A large response could be progressively 
 parsed as it arrives • Easier to handle failure of one response 
 than one out of many
  28. 360|iDev 2014 / Offline is Not a Special Case Managing

    content • Use UUIDs so you can generate identifiers 
 on the device • Design endpoints to be able to update 
 multiple objects with one request
  29. 360|iDev 2014 / Offline is Not a Special Case Entity

    tags are better than timestamps • The user's clock is not absolutely trustworthy • It's better to determine if data has changed using an entity tag or content hash • Could simply be an MD5 hash of the response • Provides a way to manage data concurrency • Use HTTP/1.1 ETag and If-None-Match headers when comparing content
  30. 360|iDev 2014 / Offline is Not a Special Case When

    Things Go Wrong • Keep what data you can—if a large download fails, keep the data you did receive so you can resume • Error handling is part of your application architecture • Always handle network errors immediately; 
 don't TODO them for later • Ugly (but informative) error messages are better than vague, catch-all error messages • Report error occurrences to your analytics service
  31. 360|iDev 2014 / Offline is Not a Special Case Let's

    Review ✓ Being offline is not the only scenario where your app will be unable to communicate with your server ✓ Views should only be built using local data ✓ User input should be immediately stored in a 
 local database and synced later ✓ Take advantage of background modes in iOS 7 & up ✓ Use entity tags to identify data states ✓ Error handling is part of your application architecture ✓ Offline is not the special case