The three problems discussed are:
1. "Disconnected" networking apps - how do you handle bad connections.
2. Multiple UIs - how do you adapt to different UI idioms.
3. After release - marketing, customer support, etc.
2. META - Frank Krueger
• fak@praeclarum.org
• @praeclarum
• http://github.com/praeclarum
• http://praeclarum.org
3. META - Why me?
• Written about 12 iOS apps
• One success, one near success, both MonoTouch
• Beta tester and avid Mono and MonoTouch user
• Open Source contributor
• Latest Professional Bias - iOS, Mac, .NET, MonoTouch
• Professional
Embedded control systems developer (auto and naval)
Masters in EE, 1 solid year at Microsoft (I stink at XAML)
Freelance web developer, mobile app developer since Dec '08 (2.5 years)
8. Mobile App Architecture
App UI
Local
Storage
Sometimes the cloud is defined
for you, sometimes you code it
Takes data from the cloud and
local storage, displays it and
makes it editable
User data, UI settings, cloud
account info, cloud cache, ...
10. Mobile means: Design for failure
• The network is unreliable
• Your app can crash any time
Out of memory
User force kills you
You might just crash yourself
11. Does this matter?
• User Data (iCircuit, Pages, “Desktop Productivity App”)
• Cloud Data (Quicklytics, Instagram, Instapaper, ...)
• Pub Sub Data (NYT, Flipboard, Pulse, ...)
• Real Time User Data (Facebook, Twitter, SMS, ...)
• Temporal Data (Sensor Data: GPS, Compass, Gyro, ...)
What kind of app are you?
Where does your data come from?
12. Does this matter?
• User Data (iCircuit, Pages, “Desktop Productivity App”)
• Cloud Data (Quicklytics, Instagram, Instapaper, ...)
• Pub Sub Data (NYT, Flipboard, Pulse, ...)
• Real Time User Data (Facebook, Twitter, SMS, ...)
• Temporal Data (Sensor Data: GPS, Compass, Gyro, ...)
What kind of app are you?
Where does your data come from?
13. User Data Expectations
1. Should be able to get data out of the app
• Email
• iTunes sync
• iCloud
• App Specific Gallery
• Dropbox, Google Docs, MS Skydrive, ...
2. Should be able to get data into the app
• Same List Above
• OS Registration
14. Cloud Data Expectations
1. The App should let the user know that the data
they're seeing is old
2. The App should be able to show whatever data
has already been seen, or looked at recently
3. The App should be trying to update the
displayed data in the background
4. The User can tell the App to try harder
17. Implications on App
Architecture
• Need to cache cloud data
• HTTP level
• Entity level
• Need to be able to do updates in the background -
presenting old data first then refreshing with new data
• Need to prioritize (schedule) network requests or
make them cancelable if the user can jump around
quickly in the UI
18. Just to be clear, HTTP Caching
Great when scraping HTML, Atom, RSS, or any other REST API
Procedure:
1. Store GET requests and their responses locally and with a time
stamp
2. When you try to load data using HTTP, first check this cache
3. If it is fresh enough parse the data as you would a network
response
4. If it's not fresh enough, try the network
5. GOTO 1
19. HTTP Caching Benefits
• Unified way to cache media (images, docs,
videos) along with the data
• Familiar way to code apps: one UI screen is
akin to the browser reading one page of a
website
• Allows you to code your app assuming that
network calls always succeed or respond in a
timely manner
20. HTTP Data Display
public class HttpSyncViewController : UITableViewController
{
public void Initialize ()
{
try {
var resp = new Http ().Get (
"http://api.stackoverflow.com/1.0/questions?tagged=monotouch");
Data = Model.ParseQuestions (resp.Content, resp.ContentType);
TableView.ReloadData ();
}
catch (Exception error)
{
DisplayError (error);
}
}
void UserRefreshRequested ()
{
Initialize ();
}
}
Problems: Will lock up the UI thread (20 s).
21. HTTP Async Data Display
public class HttpViewController : UITableViewController
{
public void Initialize ()
{
new Http ().Get (
"http://api.stackoverflow.com/1.0/questions?tagged=monotouch",
(resp, error) => {
if (error == null) {
Data = Model.ParseQuestions (resp.Content, resp.ContentType);
TableView.ReloadData ();
}
else {
DisplayError (error);
}
});
}
void UserRefreshRequested ()
{
Initialize ();
}
}
Just run the Init code when
the user wants a refresh
Fetch the data in the background
Parse it, display it in foreground
Problems: No initial data (20 s), No auto refresh,
No user notification, Bad update experience, No
network scheduling
22. .NET doesn't do HTTP caching for you
• Need to wrap
HttpWebRequest or
WebClient requests
public interface IHttpCache
{
bool ContainsFreshData (string url);
HttpResponse Get (string url);
void Add (string url, HttpResponse resp);
}
public class HttpResponse
{
public string Content;
public string ContentType;
public DateTime ExpirationDate;
}
If you're using a 3rd party networking library, you need
to enable their cache or move on to entity caching
23. HTTP Cached Data Display
public class HttpCacheViewController : UITableViewController
{
public IHttpCache HttpCache { get; set; }
public void Initialize ()
{
new Http (HttpCache).Get (
"http://api.stackoverflow.com/1.0/questions?tagged=monotouch",
(resp, error) => {
if (error == null) {
Data = Model.ParseQuestions (resp.Content, resp.ContentType);
TableView.ReloadData ();
}
else {
DisplayError (error);
}
});
}
void UserRefreshRequested ()
{
Initialize ();
}
}
When you control the HTTP
stack, global caching is easy
Problems: No auto refresh, No user notification,
Bad update experience, No network scheduling
24. HTTP Decent Data Display
Problems: No network scheduling
public class HttpDecentViewController : UITableViewController
{
public IHttpCache HttpCache { get; set; }
public void Initialize ()
{
ScheduleRefresh (TimeSpan.FromMilliseconds (10));
}
void ScheduleRefresh (TimeSpan howLong, bool useCache = true)
{
NSTimer.CreateScheduledTimer (
howLong,
delegate {
DisplayRefreshing ();
new Http (useCache ? HttpCache : null).Get (
"http://api.stackoverflow.com/1.0/questions?tagged=monotouch",
(resp, error) => {
DisplayFinishedRefreshing ();
if (error == null) {
var newData = Model.ParseQuestions (resp.Content, resp.ContentType);
IntegrateNewData (newData);
if ((DateTime.UtcNow - resp.GetDate).TotalMinutes > 3) {
ScheduleRefresh (TimeSpan.FromMilliseconds (10), false);
}
else {
ScheduleRefresh (TimeSpan.FromMinutes (1));
}
}
else {
DisplayError (error);
ScheduleRefresh (TimeSpan.FromSeconds (15));
}
} );
} );
}
void UserRefreshRequested ()
{
ScheduleRefresh (TimeSpan.FromMilliseconds (10));
}
}
Inform the user that we’re
refreshing
Integrate new data, don’t just
replace the old
If recent data received,
auto refresh in 1 min, otherwise
force cache invalidation
Auto refresh after errors
25. "3rd Party"
• If you have a library that does its own HTTP work,
then you have to hope they implemented caching
or that they support some IoC
• I make this mistake myself
• Wrote a library to access Google+ that calls
WebRequest.Create itself, and doesn't provide a
caching layer
• So here we have a very elegant and powerful
technique, that I'm not smart enough to even use
26. Entity caching
• If HTTP caching is not a good fit, then you
need to store your in-memory data structures
• This is complex because you have to store
objects that have no serialization information
• That means you have to write serialization
code or find a lib
27. Entity Data Display
public class EntityViewController : UITableViewController
{
public void Initialize ()
{
Task.Factory.StartNew (() => {
return new StackOverflowApi ().GetQuestionsTagged (
"monotouch");
})
.ContinueWith (t => {
Data = t.Result;
TableView.ReloadData ();
}, UIThreadScheduler);
}
void UserRefreshRequested ()
{
Initialize ();
}
}
Problems: No caching
After it’s done, reload the table
APIs rarely concern themselves
with concurrency
Make sure to sync with UI thread
http://gist.github.com/1100597
28. Entity Cached Data Display
public class EntityCacheViewController : UITableViewController
{
...
public void BeginRefresh (bool useCache)
{
Task.Factory.StartNew (() => {
var store = new LocalStorage ();
var item = useCache ? store.GetQuestionsTagged (
"monotouch") : default(QuestionsItem);
var data = item.Data;
if (item == null) {
data = new StackOverflowApi ().GetQuestionsTagged (
"monotouch");
store.SaveQuestionsTagged ("monotouch", data);
}
else if (item.ExpirationDate > DateTime.Now) {
BeginRefresh (false);
}
return item.Data;
})
.ContinueWith (t => {
IntegrateData (t.Result);
}, UIThreadScheduler);
}
...
}
Most APIs don’t do concurrency,
so we have to do it for them
Problems: No caching
Have to deserialize domain
objects from local storage
Now we have to serialize domain
objects
Still need to check if the data is
old
32. Managing Multiple UIs
• Cross-platform introduces the problem of how to
rewrite your UI while preserving as much else of your
app as possible.
• Preserving as much of your app as possible:
• Features in the shared code are added all platforms
• Bug fixes are added to all platforms
• Huge savings in time - mind boggling - wouldn’t do
mutli-platform if couldn’t reuse significant chunks of
code
33. Anecdote: iCircuit Code Reuse
66%
34%
iCircuit for
iOS
87%
13%
iCircuit for
Mac
wc -l `find . -name *.cs`
39. Not Shared
• Document Management Code
iOS uses Sqlite DB with revision control + cloud services
Mac uses text files with Cocoa Document Architecture
• Native UI + Interaction Orchestration
• Some Export Code (PNG, PDF)
40. Steps to Cross-Platform
1. Build a good, highly transportable Model
Every dependency counts!
2. Use a variety of view abstractions to factor UI
code out of the Native UI
41. Circuit Editor, Scope, and Meter
Code Reuse
• View abstraction with Drawing and Touch handling
• Given direct access to the model!
• Just have to build a Native UI host, and these views
come for free
public class Scope : IView {
public void Draw (IGraphics g);
public void TouchesBegan (ITouch[] touches);
...
}
Problems: Creates an identical UI experience
across platforms
http://github.com/praeclarum/
CrossGraphics
42. Inspector Code Reuse
Edit Cell abstraction implements scaffolding-like generation
• Cell maps to values in the model
• Defines interaction and validation info for those values
• Native UI decides the best way to present the cell and
notifies the cell of value changes
Problems: Great for CRUD, bad for everything else
public class CircuitData : IModel {
public IEnumerable<EditInfo> GetEditInfos ();
public void SetEditValue (EditInfo info);
}
43. Model Enumerates its Edit Cells
e = new EditInfo ("Sim Bandwidth (Hz)", 1.0 / SimTimeStep);
e.ValueIndex = 5;
e.Section = "Sim";
e.SetIntegral ();
yield return e;
e = new EditInfo ("Voltage Color", ShowVoltage);
e.Section = "Appearance";
e.ValueIndex = 0;
yield return e;
e = new EditInfo ("Current Dots");
e.Section = "Appearance";
e.ValueIndex = 7;
e.Choice = new Choice ("Off", "Conventional Flow", "Electron Flow");
e.Choice.SelectedIndex = !ShowDots ? 0 : (ShowElectronFlow ? 2 : 1);
yield return e;
...
44. Model Receives Edit Values Back
if (val.ValueIndex == 0) {
ShowVoltage = val.Checkbox.Checked;
}
else if (val.ValueIndex == 1) {
ThemeName = val.Choice.SelectedItem;
}
else if (val.ValueIndex == 5) {
SetBandwidth (val.Value);
}
else if (val.ValueIndex == 6) {
KeepInRealTime = val.Checkbox.Checked;
}
else if (val.ValueIndex == 7) {
ShowDots = val.Choice.SelectedIndex != 0;
ShowElectronFlow = val.Choice.SelectedIndex == 2;
}
45. Library Code Reuse
Library contains potential element to add, and
manages the drag and drop operation onto the
Circuit Editor
• Data + Native-dependent Interaction
• Perfect job for View Models!
Problems: You’ve got a lot of work ahead of you...
public class LibraryViewModel {
public List<CircuitElm> Elements;
}
46. Guidelines
• Build a good Model using either a UI as a
driver or TDD
• Define your high-level shared GUI objects
using View abstractions - and make them easy
to use
• Think about the interactions you want to
present to the user and pick the right
abstraction
47. Don’t Over-engineer!
• My first GUI development tools were Visual
Basic 6 and Delphi 2
• I haven’t been as productive since...
• If you don’t need abstraction, don’t use it
• Writing code in a known toolkit is 707% easier
than writing code for many toolkits
48. Toolkits for C# Devs
• iOS: MonoTouch
• Mac: MonoMac
• Android: Mono for Android
• Silverlight + Windows Phone + Windows 8: Silverlight
• Windows: WPF
• Web Client: JavaScript Add-in Language for Reflector
• Web Server: ASP.NET MVC
51. Other Cross Platform Solutions
• MonoCross (C#) for an MVC abstraction.
• Unity (C#) for realtime graphics intensive apps (games)
• PhoneGap (JavaScript) if you love JavaScript and the
web but want to be native-y
• Java gets you Android + Applets and I hear the
Blackberry Tab can run Android apps
• Web App (JavaScript) runs everywhere using a
language that makes VB6 look like a low level
assembly
• C/C++ runs everywhere but the web
... Emscripten (https://github.com/kripken/emscripten)
52.
53. How to become rich and
famous using MonoTouch
Jeff Atwood
54. Before You Release ...
• Developing V1.0 is happy land
• No bugs
• No backwards compatibility
• No expectations
55. After You Release ...
• Infinite bugs (every user has some opinion)
• Backwards compatibility of data is critical
• Backwards compatibility of UI is important too
• Expectations
• Marketing
• Sales
• Support
• Updates
56. Marketing
• Own website - Lots of traffic flows through here
• Facebook / Twitter / LinkedIn / Adwords / Reddit ads - Predictable but low
• Forum posts - Not worth it
• Press releases - Never tried
• Print ads (banners, flyers, ...) - Too much work
• Physical Networking - Silly
• Podcasts - Meh, but I like
• App review sites - Decent
• Blogs - The Best!
• Apple Love (demo app, featured app, profiled app) - Better than the best
59. Support
• I love FogBugz for customer support
Email submission so works perfectly with iTunes
Users can track info with public pages
I can see a user’s history whenever they contact me
Ties into bug tracker and version control
• There are other good ones:
Github Issues
Uservoice
Bugzilla
http://www.google.com/search?q=user+support+software
60. Bugs
• Bugs are anything that makes the user
experience worse than it should be
• Prioritize between crashing bugs and others
61. Collecting Bugs
• Crash Reports
• Support Emails
• App Reviews
• User Forum
• “Doctor Watson”
• Twitter, Flames on forums and blogs
62. “Doctor Watson”
• Unhandled exceptions in your UI will crash the app
• Need to catch them, so let’s log them too
public override void TouchesMoved (NSSet touches, UIEvent evt)
{
try {
var touch = (UITouch)touches.AnyObject;
var loc = touch.LocationInView (this);
...
} catch (Exception error) {
Log.Error (error);
}
}
63. Write Tools!!
• We’re developers
• It’s OK to take a little time to write tools that
improve “the big app”
• Examples: View layout and Memory
allocations
64. View Layout - SpyTouch
Presents a wireframe and tree of the UIView hierarchy
http://code.google.com/p/spytouch/