Portable Logic/Native UI

Portable Logic/Native UI

(Originally Presented at AUC /dev/world/2013, October 1, Melbourne, this version updated for DroidCon India, November 28, Bangalore)

The conventional wisdom on producing applications that run on both iOS and Android says that to make the best possible experience for both platforms, you’ll need to write a completely separate application for each platform.

The conventional wisdom, of course, thinks that the only important task in mobile applications is to make a pretty UI. This ignores all the hard work that goes into writing application logic. With a bit of up-front design work, it’s possible to get your important application logic running on multiple operating systems.

This talk looks at separating application logic and UI from two separate angles. First, we look at collective knowledge about structuring modular code, with a strong focus on separation of concerns. Then we’ll look at approaches to building portable core code that runs on multiple systems, and ways to design your apps to take advantage of this — we’ll look at approaches using compiled code, in this case, using C++.

E4e9b67c5e7c596193395c5dedc4d7ee?s=128

Christopher Neugebauer

November 28, 2013
Tweet

Transcript

  1. 2.

    PORTABLE LOGIC N AT I V E U I Christopher

    Neugebauer chrisjrn@gmail.com chris.neugebauer.id.au @chrisjrn
  2. 3.

    Hi! I’m very excited to be here at DroidCon India

    -- it’s only my second time to the country. Thanks to HasGeek for both inviting me to speak, and for bringing me out to Bangalore.
  3. 4.

    About me Australian And before I get started properly --

    I’m Australian. Apologies if I use any strange words. I live in Hobart, in Tasmania.
  4. 5.

    I’ve been working as an Android developer for the best

    part of three years now. One of the great things about working in mobile is that the field is changing so much. Every job I’ve worked in the field, I’ve come away with a different and interesting story to tell. This one’s about the job I work for currently.
  5. 6.

    Today: Building Apps for Multiple Platforms. Today’s talk is on

    the challenge of building mobile apps for multiple platforms. Most of the projects I’ve been involved with have involved the development of an Android application as well as an iOS application. The app I currently work on -- AsdeqDocs -- is an app that basically runs on both iOS and Android. Today, I want to show you how that’s done.
  6. 7.

    Now, when you think about cross-platform apps, the most important

    players are toolkits like PhoneGap or Titanium. These make 100% cross-platform apps, right down to the UI. I’m of a view that making apps 100% cross-platform doesn’t lead to the best results on any platform you target. You need to make separate design decisions for each platform, and that usually involves using that platform’s native UI kit.
  7. 8.

    Phone Gap Now, when you think about cross-platform apps, the

    most important players are toolkits like PhoneGap or Titanium. These make 100% cross-platform apps, right down to the UI. I’m of a view that making apps 100% cross-platform doesn’t lead to the best results on any platform you target. You need to make separate design decisions for each platform, and that usually involves using that platform’s native UI kit.
  8. 9.

    But you already know that. You’ve read the title of

    the talk. You’ve read the abstract. So you know what this talk is all about.
  9. 10.

    PLAY WHEREIN TWO PEOPLE FALL IN LOVE + EVENTUALLY KILL

    THEMSELVES. But you already know that. You’ve read the title of the talk. You’ve read the abstract. So you know what this talk is all about.
  10. 11.

    Cross-Platform Apps with Native UIs We’re talking about making apps

    that run on multiple platforms: By having our core logic be portable; but by having UI implemented in the native toolkit of the platform you’re using.
  11. 13.

    Portable Logic / Native UI Actually Possible. So, let’s conclude

    that part of the talk right now. Making an app like that is completely possible, and it’s actually a pretty good idea. We’re shipping an app on both iOS and Android, and the bulk of its code is the same. So this talk is going to focus on something completely different...
  12. 14.

    How do we get there? We want to know how

    we get there. This talk is about things I wish we’d known when we started on our efforts. Lessons that we’ve learned. Design principles that we’ve adopted since we started. What do you need to do, in order to design an app that CAN run on multiple platforms?
  13. 15.

    Engineering So this talk, rather than looking at code, is

    going to be more about engineering concepts.
  14. 16.

    Modularity The most important thing that you need to take

    away from this talk is an understanding of modularity: what it is, how to spot it, and what it lets you do. The payoff for this talk is understanding the engineering tools in order to make cross-platform code.
  15. 17.

    Agenda • How to code portably • How portable code

    actually works • How to make portable code easy So, we’re going to cover three things in this talk: the first is to cover some engineering theory -- how to think about app design to make your code portable. Then I’ll show you a possible pathway to making portable code that runs on both iOS and Android -- once you have usable portable code, you can put a UI on top of it. Finally, we’ll look at some design patterns that make designing portable code much easier. So, let’s begin.
  16. 18.

    0. Designing for Modularity The first part of this talk

    deals with how to think about producing a modular piece of software. We’ll cover some ideas that you’ve seen before in isolation, but hopefully this does a good job of linking these ideas together.
  17. 19.

    So, you’re here at a Mobile developers conference. So I

    guess you all know about Mobile development. What’s astounding to me is that Mobile as a development field is only really a very new thing...
  18. 20.

    2008 Mobile development didn’t really happen until 2008 -- that

    was when the first iPhone SDK came out; Android 1 came out in the same year. That’s a long time ago in computer years...
  19. 21.
  20. 26.

    1974 and Edsger Dijkstra wrote a paper called “On Scientific

    Thought”. He was asked how best to analyse a computer program. He noted that for any program you can consider any number of things as worthy of analysis; but that rather than considering them at the same time, <CLICK> it was best to analyse things as a sum of their parts. Considering each aspect separately allows you to think about how each part of your program works in isolation, and only to think about two aspects together when they interact with each other. This is called the separation of concerns.
  21. 27.

    1974 … nothing is gained by tackling these various aspects

    simultaneously. It is what I sometimes have called "the separation of concerns" … and Edsger Dijkstra wrote a paper called “On Scientific Thought”. He was asked how best to analyse a computer program. He noted that for any program you can consider any number of things as worthy of analysis; but that rather than considering them at the same time, <CLICK> it was best to analyse things as a sum of their parts. Considering each aspect separately allows you to think about how each part of your program works in isolation, and only to think about two aspects together when they interact with each other. This is called the separation of concerns.
  22. 28.

    Separation of Concerns Now, Dijkstra was talking about performance analysis

    of code, but these days, we talk about separation of concerns as dealing with different subsystems of a greater application. So what is separation of concerns, and why do we care about it?
  23. 29.

    Concerns Sets of information that have an effect on the

    code of a computer program. So, a concern, according to wikipedia is anything that has effect upon the system as a whole. It could be something that interacts with a database; it could be something that displays things to a user. It’s basically anything that can affect any observable part of a system. Vague, huh?
  24. 30.

    Separation of Concerns Dividing your system so that specific details

    of each concern is hidden from other concerns. Separation of concerns, then, is the act of dividing your system -- so that each observable part of your system is dealt with by a different piece of code.
  25. 31.

    Design separate aspects of an app in isolation. In particular,

    separation of concerns means that you can design each aspect of an application separately from another, and only have these aspects interact where absolutely necessary.
  26. 32.

    EVERYTHING FEATURE A FEATURE B So here’s an awful diagram:

    An app where concerns are not separated looks something like this. You have one logical unit of code, and it does everything the app needs to do within that unit. Think of this like a PHP script that queries a database whilst HTML is being output -- the concerns of fetching information and displaying it aren’t separate.
  27. 33.

    EVERYTHING FEATURE A FEATURE B An app where concerns are

    separated has several logical units of code that are only joined at a very small point. <CLICK> The point where two separated concerns meet is called the ‘interface’. In this case, it’s also an ‘Application Programming Interface’ or ‘API’.
  28. 34.

    EVERYTHING FEATURE A FEATURE B Interface An app where concerns

    are separated has several logical units of code that are only joined at a very small point. <CLICK> The point where two separated concerns meet is called the ‘interface’. In this case, it’s also an ‘Application Programming Interface’ or ‘API’.
  29. 35.

    Separating Concerns begets Modularity The key observation at this point

    is that code that exhibits separation of concerns is modular.
  30. 36.

    Why Do We Care?™ We now know what separation of

    concerns is. What’s more important is why you’d consider it as worthy of consideration at all.
  31. 37.

    World of Warcraft figures at NYC ComicCon CC-BY-NC; Rob Blatt

    Say you have a massively multiplayer game of some sort. I . Sending stuff is a pretty important feature of apps like this. If you need to send stuff, you’ll need some sort of network to send things over.
  32. 38.

    “Piggy bank” by 401(k) 2013, CC-BY Say you’ve also got

    a banking app. Internet banking apps also need to send stuff over some sort of network. Whilst these apps appear like they’ve got not much in common, <CLICK> they’ve got a very core feature in common. That’s that they want to send stuff somewhere...
  33. 39.

    Apps that a user puts stuff into and that stuff

    gets sent somewhere. “Piggy bank” by 401(k) 2013, CC-BY Say you’ve also got a banking app. Internet banking apps also need to send stuff over some sort of network. Whilst these apps appear like they’ve got not much in common, <CLICK> they’ve got a very core feature in common. That’s that they want to send stuff somewhere...
  34. 40.

    Sending some stuff to a place You can think of

    these apps as something that sends stuff to a place, but you can think about it differently...
  35. 41.

    What you want to send How you send it Interface

    Instead, you can think about what you want to send, and where you want to send it. These are two separated concerns, separated by an interface. That interface is your standard TCP/IP sockets library...
  36. 42.

    because to an ethernet cable... <CLICK> there’s basically no difference

    between a banking app and a game. This is a pretty powerful idea...
  37. 43.

    = because to an ethernet cable... <CLICK> there’s basically no

    difference between a banking app and a game. This is a pretty powerful idea...
  38. 44.

    Because if you think about it, until very recently, telephone

    lines and telephones were basically indistinguishable. These were networks built entirely with a single application in mind. Separating concerns is what gives us the internet.
  39. 45.

    Other examples of separating concerns is splitting CSS from your

    HTML pages... <CLICK> ... which is just a concrete example of separating information from presentation.
  40. 46.

    Information vs Presentation Other examples of separating concerns is splitting

    CSS from your HTML pages... <CLICK> ... which is just a concrete example of separating information from presentation.
  41. 47.

    So to conclude our discussion of modularity, there’s one more

    thing I need to explain... A consequence of making modular code is the need for a thing called “coupling”.
  42. 48.

    FEATURE A FEATURE B Interface When an interface -- where

    some code in one module needs to interact with another module -- is implemented, this generates a point called a “coupling”. Couplings are points where two modules become unavoidably attached.
  43. 49.

    More Coupling = Less Separation The more coupling required between

    two modules, the less separation there is.
  44. 50.
  45. 51.

    Breadth Number of interfaces to join Breadth refers to the

    range of places that need to be joined in order to fulfil an interface.
  46. 52.

    Feature A Feature B If you have two features, then

    broad coupling means that you have lots of points that need to be joined in order to communicate between two aspects of your code. This tends to look like lots of methods on an object, each communicating a very small amount of information. If your coupling ends up being very broad, it means that there’s effectively no difference between each module; so there’s no point in separating it.
  47. 53.

    Depth Information transferred at each interface Depth refers to the

    amount of stuff that you need to communicate at a single interface in order to get information between two parts of a system.
  48. 54.

    Feature A Feature B Lots of information to transfer between

    each part oh gosh Deep coupling is code that minimises the number of coupling points, but in doing so, has to provide large amounts of information to transfer at the interface. These look like methods with lots of parameters, or even parameter objects that contain the information that needs to be transferred. If your coupling ends up being very deep, it means that you’re basically transferring all of the state from one module to another and back again. Basically no modularity here.
  49. 55.

    Coupling impedes Modularity The take-away message here: the stronger the

    coupling between two potential modules, the less modular they are -- the less you can consider them as separate parts.
  50. 56.

    Stuff transferred at interface Number of interfaces Here’s a graph

    <CLICK> Deep coupling maximises the amount of information transferred at the interface at the expense of ways to transfer it. <CLICK> Broad coupling maximises the number of points where information can be transferred at the expense of complexity of the interface. <CLICK> the ideal is to minimise both the amount of information that needs to be transferred, as well as the number of points where it can be transferred.
  51. 57.

    Stuff transferred at interface Number of interfaces Deep Coupling Here’s

    a graph <CLICK> Deep coupling maximises the amount of information transferred at the interface at the expense of ways to transfer it. <CLICK> Broad coupling maximises the number of points where information can be transferred at the expense of complexity of the interface. <CLICK> the ideal is to minimise both the amount of information that needs to be transferred, as well as the number of points where it can be transferred.
  52. 58.

    Stuff transferred at interface Number of interfaces Deep Coupling Broad

    Coupling Here’s a graph <CLICK> Deep coupling maximises the amount of information transferred at the interface at the expense of ways to transfer it. <CLICK> Broad coupling maximises the number of points where information can be transferred at the expense of complexity of the interface. <CLICK> the ideal is to minimise both the amount of information that needs to be transferred, as well as the number of points where it can be transferred.
  53. 59.

    Stuff transferred at interface Number of interfaces Deep Coupling Broad

    Coupling Ideal Here’s a graph <CLICK> Deep coupling maximises the amount of information transferred at the interface at the expense of ways to transfer it. <CLICK> Broad coupling maximises the number of points where information can be transferred at the expense of complexity of the interface. <CLICK> the ideal is to minimise both the amount of information that needs to be transferred, as well as the number of points where it can be transferred.
  54. 60.

    Summary • Separate Your Concerns. It’s good. • Separated concerns

    begets modularity • Avoiding coupling increases modularity Summary: - Flexible code separates concerns - Separated concerns allows you to view your code with much more modularity - Coupling is the result of needing to communicate between modules. You should reduce coupling as much as possible.
  55. 61.

    1. Designing for Portability So, we now know that a

    modular program is one that features strong separation of concerns. Now we’re going to see why this way of thinking is so important to producing maintainable cross-platform apps.
  56. 62.

    Multiple Platforms And if your project needs to maintain clients

    on multiple platforms, you then need to figure out how to make the same logic for each platform... The tradeoff here is usually between being completely cross-platform (and losing consistency with the rest of the system); or writing the entire app for each platform (and doubling your codebase/test burden/etc).
  57. 63.

    the same code on Multiple Platforms The alternative is to

    make it so that as much of the same code as possible can run on multiple platforms. So let’s do a quick look over how you can do this!
  58. 64.

    Choosing a Language The first step is to choose a

    language that facilitates deploying code onto multiple sorts of devices. This is probably a bit counter-intuitive to you...
  59. 65.

    Because if you’re an Android developer, you’re probably perfectly happy

    with coding Java. Having a huge Java codebase doesn’t help you with iOS -- <CLICK> iPhone developers will be writing all of their code in Objective-C. So if you begin in the native language of these two platforms, you’ll end up with a codebase that doesn’t run on the other.
  60. 66.

    Because if you’re an Android developer, you’re probably perfectly happy

    with coding Java. Having a huge Java codebase doesn’t help you with iOS -- <CLICK> iPhone developers will be writing all of their code in Objective-C. So if you begin in the native language of these two platforms, you’ll end up with a codebase that doesn’t run on the other.
  61. 67.

    • Object-Oriented • “Garbage Collected” • “Static Linked” Objective-C &

    Java So to figure out what language might be appropriate, consider what features we have in both languages. Both are object-oriented. Both have automatic memory management -- Java has garbage collection, Objective- C has reference counting. Both have reasonably static linking.
  62. 68.

    C++ For AsdeqDocs, we chose C++ as our codebase language.

    I personally am not a fan of C++ as a language – but as a common codebase language, it makes a lot of sense. It has most of the features that I’ve just described.
  63. 69.

    C++ Boost Smart Pointers C++ mostly has manual memory management,

    so out of the box, it’s not quite usable as a common language, especially for Java users. So in our case, we use Boost Smart Pointers -- shared pointers give us automatic memory management, and the semantics are close enough to Java’s that you don’t notice the difference.
  64. 71.

    Java Android Framework C++ Common Code Android Logic UI If

    we’re going to make an app with logic written in C++, what we end up with is an Android app that uses the standard Android UI toolkit -- you write the UI in Java, with Common Logic written in C++. <CLICK> The iOS app then looks pretty similar -- but instead of writing the UI code in Java, you write it in Objective-C, using the iOS framework.
  65. 72.

    Java Android Framework Objective-C UIKit C++ Common Code C++ Common

    Code Android iOS Logic UI If we’re going to make an app with logic written in C++, what we end up with is an Android app that uses the standard Android UI toolkit -- you write the UI in Java, with Common Logic written in C++. <CLICK> The iOS app then looks pretty similar -- but instead of writing the UI code in Java, you write it in Objective-C, using the iOS framework.
  66. 73.

    iOS Objective-C++ iOS is surprisingly easy. Objective-C is really just

    a thin wrapper over the top of C. It’s possible to embed Objective-C calls in basically any language -- so what about C +++?
  67. 74.

    iOS Objective-C++ Well, you get a nice language called Objective-C++!

    You can just embed your Objective-C calls inside C++ code. The code compiles just as you expect it. So this means it’s unbelievably easy to take advantage of C++ in Objective-C. It basically just works.
  68. 75.

    Android? The situation in Android is considerably more complicated than

    it is for iOS. So if you want to make this process easy to manage, you need to spend a bit more time planning how to make things work. Let’s take a look why.
  69. 76.

    Write in Java Compile to JVM Bytecode Execute on Dalvik

    VM Convert to Dalvik Bytecode Android works in a managed environment that kinda-sorta looks like a Java VM. You write your code in Java, and you execute on this magic non-JVM thing called the Dalvik Virtual Machine.
  70. 77.

    Java Native Interface Despite not using the Java Virtual Machine,

    it still provides you with a thing called the Java Native Interface. And this lets us take advantage of code that’s compiled down to machine code from within the safe, managed constraints of Java.
  71. 78.

    Java Class JNI C Functions Basically, it works like this:

    you declare a method as native within your Java Code, and the JNI figures out what function you need to call within a C library, and then your code magically gets executed.
  72. 79.

    extern "C" JNIEXPORT void JNICALL Java_ClassName_MethodName (JNIEnv *env, jobject obj,

    jstring javaString) { //Get the native string from javaString const char *nativeString = env->GetStringUTFChars(javaString, 0); std::cout << nativeString << std::endl; //DON'T FORGET THIS LINE!!! env->ReleaseStringUTFChars(javaString, nativeString); } (Wikipedia) The problem is that the JNI is quite complicated to write -- this is a simple echo function that I pinched from Wikipedia. And for just a simple function like this, there’s already three really complicated things you have to do: <CLICK> You have to get this method name exactly right depending on what class and method you’re implementing. <CLICK> Getting variables out of Java-land has a really complex API <CLICK> And you have to manually manage your Java objects within C. For a codebase of any degree of complexity, it’s basically impossible to get this right and maintain it. For example, if you do any refactoring of Java code, your C method names might be wrong.
  73. 80.

    extern "C" JNIEXPORT void JNICALL Java_ClassName_MethodName (JNIEnv *env, jobject obj,

    jstring javaString) { //Get the native string from javaString const char *nativeString = env->GetStringUTFChars(javaString, 0); std::cout << nativeString << std::endl; //DON'T FORGET THIS LINE!!! env->ReleaseStringUTFChars(javaString, nativeString); } (Wikipedia) The problem is that the JNI is quite complicated to write -- this is a simple echo function that I pinched from Wikipedia. And for just a simple function like this, there’s already three really complicated things you have to do: <CLICK> You have to get this method name exactly right depending on what class and method you’re implementing. <CLICK> Getting variables out of Java-land has a really complex API <CLICK> And you have to manually manage your Java objects within C. For a codebase of any degree of complexity, it’s basically impossible to get this right and maintain it. For example, if you do any refactoring of Java code, your C method names might be wrong.
  74. 81.

    extern "C" JNIEXPORT void JNICALL Java_ClassName_MethodName (JNIEnv *env, jobject obj,

    jstring javaString) { //Get the native string from javaString const char *nativeString = env->GetStringUTFChars(javaString, 0); std::cout << nativeString << std::endl; //DON'T FORGET THIS LINE!!! env->ReleaseStringUTFChars(javaString, nativeString); } (Wikipedia) The problem is that the JNI is quite complicated to write -- this is a simple echo function that I pinched from Wikipedia. And for just a simple function like this, there’s already three really complicated things you have to do: <CLICK> You have to get this method name exactly right depending on what class and method you’re implementing. <CLICK> Getting variables out of Java-land has a really complex API <CLICK> And you have to manually manage your Java objects within C. For a codebase of any degree of complexity, it’s basically impossible to get this right and maintain it. For example, if you do any refactoring of Java code, your C method names might be wrong.
  75. 82.

    extern "C" JNIEXPORT void JNICALL Java_ClassName_MethodName (JNIEnv *env, jobject obj,

    jstring javaString) { //Get the native string from javaString const char *nativeString = env->GetStringUTFChars(javaString, 0); std::cout << nativeString << std::endl; //DON'T FORGET THIS LINE!!! env->ReleaseStringUTFChars(javaString, nativeString); } (Wikipedia) The problem is that the JNI is quite complicated to write -- this is a simple echo function that I pinched from Wikipedia. And for just a simple function like this, there’s already three really complicated things you have to do: <CLICK> You have to get this method name exactly right depending on what class and method you’re implementing. <CLICK> Getting variables out of Java-land has a really complex API <CLICK> And you have to manually manage your Java objects within C. For a codebase of any degree of complexity, it’s basically impossible to get this right and maintain it. For example, if you do any refactoring of Java code, your C method names might be wrong.
  76. 83.

    JavaCPP So, instead, it makes much more sense to automatically

    generate code for bridging the gap. If you write your “native” -- cross-platform -- code in C++, you can use a tool called JavaCPP to generate C++ code that matches the Java Native Interface for you, so you can make Java Classes that matches your C++ interface.
  77. 84.

    Wrappers So what JavaCPP lets you do is construct wrappers.

    <CLICK> WRAPPERS Wrappers are classes that you implement in Java, that contain instructions for JavaCPP to generate C++ JNI code for you. Typically, you write one Java wrapper class for every C++ class that exposes an interface that you need to call.
  78. 85.

    Wrappers So what JavaCPP lets you do is construct wrappers.

    <CLICK> WRAPPERS Wrappers are classes that you implement in Java, that contain instructions for JavaCPP to generate C++ JNI code for you. Typically, you write one Java wrapper class for every C++ class that exposes an interface that you need to call.
  79. 86.

    class Frobber { public: void setFoo(int foo); int getFoo(); }

    And to give you an idea of how simple writing JavaCPP wrappers is, here’s an example: This is a really simple C++ class with a couple of public interface methods.
  80. 87.

    @Platform(include = "Frobber.hpp") @Name( "Frobber") public class Frobber { public

    native void setFoo(int foo); public native int getFoo(); } And this is a JavaCPP wrapper for that C++ class. All you need to do is - declare each method that you want to generate code for - tell JavaCPP what the name of the C++ class is - tell JavaCPP what C++ headers to include -- both of these use Java annotations And it spits out a C++ source file that you can build.
  81. 88.

    @Platform(include = "Frobber.hpp") @Name("boost::shared_ptr<Frobber>") public class Frobber { @Name("get()->setFoo") public

    native void setFoo(int foo); @Name("get()->getFoo") public native int getFoo(); } And if you’re using Smart Pointers, so you get sensible memory management, all you need to do is add this extra annotation, which substitutes in a different method call name to make on the C++ side. So, as long as you’re happy to write a wrapper for every C++ class you need to implement, things are pretty easy!
  82. 89.

    C++ C++ C++ Java Java Java JNI Bindings (C++) Native

    Stubs (Java) So what you end up with is something that looks like this -- a bunch of Java classes, a bunch of C++ classes, and an interface between them <CLICK> It should be clear to you that the interface here is the point where you do the wrapping between C++ and Java. This means that whenever you change some C++ code that touches the interface you have to update the matching Java code. If Java needs to get some data out of C++ land to display things, you need to wrap a new class or method...
  83. 90.

    C++ C++ C++ Java Java Java JNI Bindings (C++) Native

    Stubs (Java) Interface So what you end up with is something that looks like this -- a bunch of Java classes, a bunch of C++ classes, and an interface between them <CLICK> It should be clear to you that the interface here is the point where you do the wrapping between C++ and Java. This means that whenever you change some C++ code that touches the interface you have to update the matching Java code. If Java needs to get some data out of C++ land to display things, you need to wrap a new class or method...
  84. 91.

    Wrappers beget Coupling What I’m saying is that every point

    where you need to relate between C++ and Java code, you introduce an unavoidable point of coupling. The more classes and methods you need to wrap, the more broadly coupled the two aspects of your code -- the portable core, and the native UI become. So, if you don’t think about structuring your interface well, you’ll have a difficult to maintain, tightly-coupled mess.
  85. 92.

    Summary • UI vs Logic is a Separation of Concerns

    issue. • Android needs Code Wrapping for native code • Wrapping Causes Coupling
  86. 93.

    2. Patterns & Lessons We know now that if you

    decide to split your logic and UI into a portable section and a native section, it’s easy to end up with a difficult-to-write, tightly coupled set of classes in two languages. So, to finish up this talk, let’s look at some design patterns that can help you think about separating your code into native and portable sections.
  87. 94.

    Separation of Concerns! And to remind you -- the main

    reason why we even have design patterns is to make it obvious how to separate your concerns...
  88. 95.

    Separation of UI & Logic And in our case, what

    we care about is the direct separation of UI and logic.
  89. 96.

    M / V / C View Controller Model So, the

    first one, which you’re probably familiar with is the concept of is MVC, or Model/View/Controller. This is what iOS normally uses as its way of structuring an app.
  90. 97.

    m c v Model View Controller Basically, this involves separating

    each part of your code into the Model: which deals exclusively with the storage of data View: which deals exclusively with the display of data, and taking inputs from the user Controller: which intermediates between the two.
  91. 98.

    v Model View Controller m c ... So in reality,

    the code is actually linked up more like this -- Model-Controller- View. If you’re trying to separate between portable code and native UI code, <CLICK> the interface between the two should sit between the controller and the view. The problem with MVC is that it does not define a way to link your controller to your view. This means that the coupling between the view and controller can be very strong. So if you need to write a wrapper between the controller and a view, it can be difficult unless you’ve planned for wrapping.
  92. 99.

    v Model View Controller m c Cross-platform Native ... So

    in reality, the code is actually linked up more like this -- Model-Controller- View. If you’re trying to separate between portable code and native UI code, <CLICK> the interface between the two should sit between the controller and the view. The problem with MVC is that it does not define a way to link your controller to your view. This means that the coupling between the view and controller can be very strong. So if you need to write a wrapper between the controller and a view, it can be difficult unless you’ve planned for wrapping.
  93. 100.

    M / V / VM Model View View Model This

    problem is solved by a newer approach, known as Model-View-View Model. The point of MVVM is to make sure that there’s a standardised interface between low-level code, and display code.
  94. 101.

    m vm v Model View View Model So as with

    MVC, we describe the components of MVVM in the wrong order, it actually looks more like this:
  95. 102.

    m vm v Model View View Model So, the basic

    idea of MVVM is that you have a Model (<CLICK> which may also contain a controller), and a View. The difference is that the View code and the Low- Level code is joined by a new class called a View Model. The role of the view model is to give the view the set of information it needs to display stuff, and the view can set properties on the model to update the underlying code. <CLICK> This means that the interface between cross-platform and native code is between the View Model and the View code... but the difference is <CLICK> that it’s really obvious which bits of the native code to wrap. The View Model contains everything you need.
  96. 103.

    c m vm v Model View View Model So, the

    basic idea of MVVM is that you have a Model (<CLICK> which may also contain a controller), and a View. The difference is that the View code and the Low- Level code is joined by a new class called a View Model. The role of the view model is to give the view the set of information it needs to display stuff, and the view can set properties on the model to update the underlying code. <CLICK> This means that the interface between cross-platform and native code is between the View Model and the View code... but the difference is <CLICK> that it’s really obvious which bits of the native code to wrap. The View Model contains everything you need.
  97. 104.

    c m vm v Model View View Model Cross-platform Native

    So, the basic idea of MVVM is that you have a Model (<CLICK> which may also contain a controller), and a View. The difference is that the View code and the Low- Level code is joined by a new class called a View Model. The role of the view model is to give the view the set of information it needs to display stuff, and the view can set properties on the model to update the underlying code. <CLICK> This means that the interface between cross-platform and native code is between the View Model and the View code... but the difference is <CLICK> that it’s really obvious which bits of the native code to wrap. The View Model contains everything you need.
  98. 105.

    c m vm v Model View View Model Cross-platform Native

    So, the basic idea of MVVM is that you have a Model (<CLICK> which may also contain a controller), and a View. The difference is that the View code and the Low- Level code is joined by a new class called a View Model. The role of the view model is to give the view the set of information it needs to display stuff, and the view can set properties on the model to update the underlying code. <CLICK> This means that the interface between cross-platform and native code is between the View Model and the View code... but the difference is <CLICK> that it’s really obvious which bits of the native code to wrap. The View Model contains everything you need.
  99. 106.

    (Django?; Web Frameworks) If you’ve used the Django Web Framework,

    for instance, you may be familiar with something like this -- you have a model and a Django View, which is basically a controller -- you export data to the web through a View Model that is used to render your template.
  100. 107.
  101. 108.

    Aggregator And the way that Publish/Subscribe works in our case

    is that we have an aggregator; <CLICK> Bits of our UI tell the aggregator that it’s interest in certain types of events <CLICK> When an event occurs somewhere in the app, we publish that event to the aggregator. <CLICK> And the aggregator tells all of the UI events about the event. In practice, <CLICK> the events get raised on the portable side, and the events get listened to on the UI side. And this means that the aggregator itself is the interface. This means the only things that need to be wrapped is the aggregator itself, and the events that get passed from the portable side to the UI side. This reduces a LOT of coupling.
  102. 109.

    Aggregator UI UI UI Subscribe And the way that Publish/Subscribe

    works in our case is that we have an aggregator; <CLICK> Bits of our UI tell the aggregator that it’s interest in certain types of events <CLICK> When an event occurs somewhere in the app, we publish that event to the aggregator. <CLICK> And the aggregator tells all of the UI events about the event. In practice, <CLICK> the events get raised on the portable side, and the events get listened to on the UI side. And this means that the aggregator itself is the interface. This means the only things that need to be wrapped is the aggregator itself, and the events that get passed from the portable side to the UI side. This reduces a LOT of coupling.
  103. 110.

    Aggregator UI UI UI Event Publish And the way that

    Publish/Subscribe works in our case is that we have an aggregator; <CLICK> Bits of our UI tell the aggregator that it’s interest in certain types of events <CLICK> When an event occurs somewhere in the app, we publish that event to the aggregator. <CLICK> And the aggregator tells all of the UI events about the event. In practice, <CLICK> the events get raised on the portable side, and the events get listened to on the UI side. And this means that the aggregator itself is the interface. This means the only things that need to be wrapped is the aggregator itself, and the events that get passed from the portable side to the UI side. This reduces a LOT of coupling.
  104. 111.

    Aggregator UI UI UI Event Publish And the way that

    Publish/Subscribe works in our case is that we have an aggregator; <CLICK> Bits of our UI tell the aggregator that it’s interest in certain types of events <CLICK> When an event occurs somewhere in the app, we publish that event to the aggregator. <CLICK> And the aggregator tells all of the UI events about the event. In practice, <CLICK> the events get raised on the portable side, and the events get listened to on the UI side. And this means that the aggregator itself is the interface. This means the only things that need to be wrapped is the aggregator itself, and the events that get passed from the portable side to the UI side. This reduces a LOT of coupling.
  105. 112.

    Portable Native Aggregator UI UI UI Event Publish And the

    way that Publish/Subscribe works in our case is that we have an aggregator; <CLICK> Bits of our UI tell the aggregator that it’s interest in certain types of events <CLICK> When an event occurs somewhere in the app, we publish that event to the aggregator. <CLICK> And the aggregator tells all of the UI events about the event. In practice, <CLICK> the events get raised on the portable side, and the events get listened to on the UI side. And this means that the aggregator itself is the interface. This means the only things that need to be wrapped is the aggregator itself, and the events that get passed from the portable side to the UI side. This reduces a LOT of coupling.
  106. 113.

    AsdeqDocs So this is the point where I mention the

    product I work on for my Day Job -- AsdeqDocs is a file management tool for business users. Pretty early on in the product’s life the decision was made to structure our code so that we had a portable core and a Native UI...
  107. 114.

    • File synchronisation • File management • On-device crypto •

    Device management & security policy • More than 200 classes of Portable Logic This is because we do a lot of things, relating to correctness and security. Bugs are normally shared across multiple platforms, and we can make the same security guarantees about every platform we target. We’re also a very small team. We couldn’t target multiple platforms if we didn’t have substantial code re-use.
  108. 115.

    We also pride ourselves on making an app that actually

    looks good and works well on both major platforms. We have a tool that looks at home on iOS (where it was originally designed). <CLICK> but we now also have a version that great on Android. For that, we’ve needed our UI to be native and idiomatic on both platforms. We have clients released on both of these platforms, and there are more coming -- the approach works surprisingly well for us.
  109. 116.

    We also pride ourselves on making an app that actually

    looks good and works well on both major platforms. We have a tool that looks at home on iOS (where it was originally designed). <CLICK> but we now also have a version that great on Android. For that, we’ve needed our UI to be native and idiomatic on both platforms. We have clients released on both of these platforms, and there are more coming -- the approach works surprisingly well for us.
  110. 117.

    Lessons So that gives us an opportunity to explain how

    this works for us -- what we’ve learned; where things can improve.
  111. 118.

    Design for Portability We made the active decision to make

    our app’s logic work on multiple platforms. This means that we actively made decisions about what code belongs on a single device and what code belongs on all devices. Writing our logic in C++ establishes an upper bound on what’s cross-platform. Where another client finds functionality that isn’t cross-platform, we make the effort to make it portable, rather than re-writing for another platform. Re-writing functionality doesn’t scale.
  112. 119.

    Plan for multiple languages. C++ has a lot of constructs

    that don’t transfer between different languages very well. For instance, passing around function pointers; construction semantics; etc. Designing for convenience in C++ doesn’t necessarily translate to convenience in Java. Java doesn’t have functions, let alone function pointers; copy construction; etc. Writing code to wrap weird C++ features is difficult and non-idiomatic. Work to replicate non-compatible features increases coupling - so make sure you only use constructs that have analogs in each target language.
  113. 120.

    Plan for wrapping. Figuring out how to wrap a C++

    class effectively is a quite large sink of developer time. It turns out that designing for iOS first is a bad idea here -- there’s no effort to call C++ code from Obj-C++. (MVC also hurts here) Thinking about how C++ classes will look in another language is a great way to consider which weird features to drop.
  114. 121.

    Design with Opinion. C++ is a surprisingly un-opinionated language. It

    will allow you to do anything within reason, within the language. Decide what features of the language you want to use. Figure out what your design goals are, and choose a subset that will let you accomplish that easily with every language that will be calling your cross-platform code.
  115. 122.

    Clearly separate UI from logic Make it very clear what

    code provides logic; make it clear how a UI can attach itself to that logic. The most important separation of concerns you can make here is between your UI and logic. Choose design patterns for your interface that make the separation between UI and Logic clear. M/V/VM is emerging as a clear winner for us -- view models make an obvious target point for wrapping.
  116. 123.

    Summary • Structure code to avoid coupling • UI determines

    coupling • Aim for simplicity in cross-platform interface
  117. 124.

    Epil. Where to From Here? So -- a quick summary

    of where we can take this approach from here.
  118. 125.

    Conclusion So that brings us to the end of the

    talk. Yay! We now know that you can write apps for each platform with a native, consistent UI for each platform, but with a substantial common codebase. How did we get here?
  119. 126.

    Separation of Concerns We started off by looking at the

    concept of separation of concerns. Separation of concerns is a key idea in software engineering. It’s the basis of making modular, manageable software.
  120. 127.

    Modularity Complexity Management Portability Separating concerns gives us modularity. It

    gives us a chance to manage the complexity of a codebase. Separating concerns well results in easier interfaces to understand and code for. It gives us portability. By separating concerns, we can make part of our code run on one specific platform, but keep the rest of our code cross-platform.