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

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++.

Christopher Neugebauer

November 28, 2013
Tweet

More Decks by Christopher Neugebauer

Other Decks in Programming

Transcript

  1. The C
    Programming
    Language
    vs
    NYC’s ‘C’ Train

    View Slide

  2. PORTABLE LOGIC
    N AT I V E U I
    Christopher Neugebauer
    [email protected]
    chris.neugebauer.id.au
    @chrisjrn

    View Slide

  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.

    View Slide

  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.

    View Slide

  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.

    View Slide

  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.

    View Slide

  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.

    View Slide

  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.

    View Slide

  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.

    View Slide

  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.

    View Slide

  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.

    View Slide

  12. iOS + ANDROID
    And the platforms we’re looking at are iOS and Android.

    View Slide

  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...

    View Slide

  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?

    View Slide

  15. Engineering
    So this talk, rather than looking at code, is going to be more about engineering
    concepts.

    View Slide

  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.

    View Slide

  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.

    View Slide

  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.

    View Slide

  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...

    View Slide

  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...

    View Slide

  21. 2008
    but we want to go back further than 2008. We want to go back to...

    View Slide

  22. 1974
    1974!

    View Slide

  23. 1974
    BLACK
    AND
    WHITE
    In 1974, TV was still black & white;

    View Slide

  24. 1974
    James Bond was a comedian in flared trousers;

    View Slide

  25. 1974
    Richard Nixon was president of the USA;

    View Slide

  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, 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.

    View Slide

  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, 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.

    View Slide

  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?

    View Slide

  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?

    View Slide

  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.

    View Slide

  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.

    View Slide

  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.

    View Slide

  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.
    The point where two separated concerns meet is called the ‘interface’. In
    this case, it’s also an ‘Application Programming Interface’ or ‘API’.

    View Slide

  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.
    The point where two separated concerns meet is called the ‘interface’. In
    this case, it’s also an ‘Application Programming Interface’ or ‘API’.

    View Slide

  35. Separating Concerns
    begets
    Modularity
    The key observation at this point is that code that exhibits separation of concerns is
    modular.

    View Slide

  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.

    View Slide

  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.

    View Slide

  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, they’ve
    got a very core feature in common. That’s that they want to send stuff
    somewhere...

    View Slide

  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, they’ve
    got a very core feature in common. That’s that they want to send stuff
    somewhere...

    View Slide

  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...

    View Slide

  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...

    View Slide

  42. because to an ethernet cable...
    there’s basically no difference between a banking app and a game.
    This is a pretty powerful idea...

    View Slide

  43. =
    because to an ethernet cable...
    there’s basically no difference between a banking app and a game.
    This is a pretty powerful idea...

    View Slide

  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.

    View Slide

  45. Other examples of separating concerns is splitting CSS from your HTML pages...
    ... which is just a concrete example of separating information from
    presentation.

    View Slide

  46. Information
    vs
    Presentation
    Other examples of separating concerns is splitting CSS from your HTML pages...
    ... which is just a concrete example of separating information from
    presentation.

    View Slide

  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”.

    View Slide

  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.

    View Slide

  49. More Coupling
    =
    Less Separation
    The more coupling required between two modules, the less separation there is.

    View Slide

  50. Aspects of Coupling
    Coupling comes in many shapes and sizes. I’m going to describe two of them.

    View Slide

  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.

    View Slide

  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.

    View Slide

  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.

    View Slide

  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.

    View Slide

  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.

    View Slide

  56. Stuff transferred at
    interface
    Number of
    interfaces
    Here’s a graph
    Deep coupling maximises the amount of information transferred at the
    interface at the expense of ways to transfer it.
    Broad coupling maximises the number of points where information can be
    transferred at the expense of complexity of the interface.
    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.

    View Slide

  57. Stuff transferred at
    interface
    Number of
    interfaces
    Deep
    Coupling
    Here’s a graph
    Deep coupling maximises the amount of information transferred at the
    interface at the expense of ways to transfer it.
    Broad coupling maximises the number of points where information can be
    transferred at the expense of complexity of the interface.
    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.

    View Slide

  58. Stuff transferred at
    interface
    Number of
    interfaces
    Deep
    Coupling
    Broad
    Coupling
    Here’s a graph
    Deep coupling maximises the amount of information transferred at the
    interface at the expense of ways to transfer it.
    Broad coupling maximises the number of points where information can be
    transferred at the expense of complexity of the interface.
    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.

    View Slide

  59. Stuff transferred at
    interface
    Number of
    interfaces
    Deep
    Coupling
    Broad
    Coupling
    Ideal
    Here’s a graph
    Deep coupling maximises the amount of information transferred at the
    interface at the expense of ways to transfer it.
    Broad coupling maximises the number of points where information can be
    transferred at the expense of complexity of the interface.
    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.

    View Slide

  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.

    View Slide

  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.

    View Slide

  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).

    View Slide

  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!

    View Slide

  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...

    View Slide

  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 -- 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.

    View Slide

  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 -- 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.

    View Slide

  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.

    View Slide

  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.

    View Slide

  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.

    View Slide

  70. Using C++ on
    mobile platforms

    View Slide

  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++.
    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.

    View Slide

  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++.
    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.

    View Slide

  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
    +++?

    View Slide

  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.

    View Slide

  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.

    View Slide

  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.

    View Slide

  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.

    View Slide

  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.

    View Slide

  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:
    You have to get this method name exactly right depending on what class
    and method you’re implementing.
    Getting variables out of Java-land has a really complex API
    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.

    View Slide

  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:
    You have to get this method name exactly right depending on what class
    and method you’re implementing.
    Getting variables out of Java-land has a really complex API
    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.

    View Slide

  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:
    You have to get this method name exactly right depending on what class
    and method you’re implementing.
    Getting variables out of Java-land has a really complex API
    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.

    View Slide

  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:
    You have to get this method name exactly right depending on what class
    and method you’re implementing.
    Getting variables out of Java-land has a really complex API
    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.

    View Slide

  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.

    View Slide

  84. Wrappers
    So what JavaCPP lets you do is construct wrappers. 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.

    View Slide

  85. Wrappers
    So what JavaCPP lets you do is construct wrappers. 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.

    View Slide

  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.

    View Slide

  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.

    View Slide

  88. @Platform(include = "Frobber.hpp")
    @Name("boost::shared_ptr")
    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!

    View Slide

  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
    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...

    View Slide

  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
    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...

    View Slide

  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.

    View Slide

  92. Summary
    • UI vs Logic is a Separation of Concerns
    issue.
    • Android needs Code Wrapping for native
    code
    • Wrapping Causes Coupling

    View Slide

  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.

    View Slide

  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...

    View Slide

  95. Separation
    of UI & Logic
    And in our case, what we care about is the direct separation of UI and logic.

    View Slide

  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.

    View Slide

  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.

    View Slide

  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,
    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.

    View Slide

  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,
    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.

    View Slide

  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.

    View Slide

  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:

    View Slide

  102. m vm v
    Model View
    View Model
    So, the basic idea of MVVM is that you have a Model ( 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.
    This means that the interface between cross-platform and native code is
    between the View Model and the View code... but the difference is that it’s
    really obvious which bits of the native code to wrap. The View Model contains
    everything you need.

    View Slide

  103. c
    m vm v
    Model View
    View Model
    So, the basic idea of MVVM is that you have a Model ( 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.
    This means that the interface between cross-platform and native code is
    between the View Model and the View code... but the difference is that it’s
    really obvious which bits of the native code to wrap. The View Model contains
    everything you need.

    View Slide

  104. c
    m vm v
    Model View
    View Model
    Cross-platform Native
    So, the basic idea of MVVM is that you have a Model ( 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.
    This means that the interface between cross-platform and native code is
    between the View Model and the View code... but the difference is that it’s
    really obvious which bits of the native code to wrap. The View Model contains
    everything you need.

    View Slide

  105. c
    m vm v
    Model View
    View Model
    Cross-platform Native
    So, the basic idea of MVVM is that you have a Model ( 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.
    This means that the interface between cross-platform and native code is
    between the View Model and the View code... but the difference is that it’s
    really obvious which bits of the native code to wrap. The View Model contains
    everything you need.

    View Slide

  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.

    View Slide

  107. Publish/Subscribe
    Another pattern that we’ve adopted is to use a publish-subscribe event passing
    model.

    View Slide

  108. Aggregator
    And the way that Publish/Subscribe works in our case is that we have an aggregator;
    Bits of our UI tell the aggregator that it’s interest in certain types of events
    When an event occurs somewhere in the app, we publish that event to the
    aggregator.
    And the aggregator tells all of the UI events about the event.
    In practice, 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.

    View Slide

  109. Aggregator
    UI
    UI
    UI
    Subscribe
    And the way that Publish/Subscribe works in our case is that we have an aggregator;
    Bits of our UI tell the aggregator that it’s interest in certain types of events
    When an event occurs somewhere in the app, we publish that event to the
    aggregator.
    And the aggregator tells all of the UI events about the event.
    In practice, 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.

    View Slide

  110. Aggregator
    UI
    UI
    UI
    Event
    Publish
    And the way that Publish/Subscribe works in our case is that we have an aggregator;
    Bits of our UI tell the aggregator that it’s interest in certain types of events
    When an event occurs somewhere in the app, we publish that event to the
    aggregator.
    And the aggregator tells all of the UI events about the event.
    In practice, 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.

    View Slide

  111. Aggregator
    UI
    UI
    UI
    Event
    Publish
    And the way that Publish/Subscribe works in our case is that we have an aggregator;
    Bits of our UI tell the aggregator that it’s interest in certain types of events
    When an event occurs somewhere in the app, we publish that event to the
    aggregator.
    And the aggregator tells all of the UI events about the event.
    In practice, 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.

    View Slide

  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;
    Bits of our UI tell the aggregator that it’s interest in certain types of events
    When an event occurs somewhere in the app, we publish that event to the
    aggregator.
    And the aggregator tells all of the UI events about the event.
    In practice, 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.

    View Slide

  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...

    View Slide

  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.

    View Slide

  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).
    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.

    View Slide

  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).
    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.

    View Slide

  117. Lessons
    So that gives us an opportunity to explain how this works for us -- what we’ve
    learned; where things can improve.

    View Slide

  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.

    View Slide

  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.

    View Slide

  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.

    View Slide

  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.

    View Slide

  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.

    View Slide

  123. Summary
    • Structure code to avoid coupling
    • UI determines coupling
    • Aim for simplicity in cross-platform
    interface

    View Slide

  124. Epil. Where to
    From Here?
    So -- a quick summary of where we can take this approach from here.

    View Slide

  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?

    View Slide

  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.

    View Slide

  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.

    View Slide