As usual, release takes longer than planned. We’ve been doing some chatting and client work with Doppl/J2objc (interesting stuff). If you’d like to read more about what this is, see here. Pretty soon we’ll be pushing out a public release. In the meantime, I’ve built a simple sample app to demonstrate the what and why in a more concrete manner.
Well, the basic goal is fairly obvious. Sharing code should reduce development effort, bugs, and feature drift between platforms. Why Doppl is somewhat more interesting.
The dimensions you have to consider are varied and go way beyond the scope of this post. In summary, using Doppl:
Q: How much code can you share?
A: Mostly everything up to the UI.
Q: How much will this platform affect your risk?
A: Minimal runtime risk (zero on Android). Minimal ecosystem risk.
Q: How much time are you saving?
A: All the time you would’ve spent re-coding on iOS. Since there’s no 3rd platform, your shared code dev time shouldn’t be affected.
Q: How will the platform reduce quality?
A: It won’t.
To review, J2objc makes Objective-C from Java. It works well but does not provide a build system familiar to Android developers, does not implement very much of the Android platform, and does not provide a structured method of distributing dependencies (outside of the fixed set). Doppl is: a Gradle plugin, a bunch of iOS implementations of “Android” stuff, and a dependency standard. It’s a toolset for J2objc for sharing your Android code in iOS apps.
Under the hood, iOS and Android are pretty similar beasts. Lots of the same things with different names. Their innards, and the things their apps do, are far more similar in practice than, say, what web apps for the same “product” generally do*. Given the right kinds of apps, you can share lots more code, lots more quickly, with lots less risk.
Recently I’ve seen some talks about how iOS and Android are pretty similar, and how teams working on the same product can explore using similar best-practice architectures for their logic. MVP/MVVM, testing, etc. I’ve had discussions with folks, and I point out “yeah, use this. You can use the exact same code, using best practices”. The public example was our Droidcon app, but it’s a tech stew where we’ve been testing all kinds of weird shit. Everything except any actual unit tests. Not the best example. So, enter Party Clicker.
This is not to say that Party Clicker is elegant or useful, but it’s dead simple to understand.
You’ve seen one of these.
It does that, but it’s an app.
There’s a list of “parties”, and a edit box and button to create a new one. On the “party” page, you can add or remove people.
The data is kept in sqlite, retrieved in a background thread. The threading and logic is managed in presenters, in a rough approximation of MVP. There’s a set of unit tests that individually test the database logic, and presenter coordination.
On Android there are 2 Activity classes that point at two Presenters. On iOS, there are 2 equivalent UIViewController classes that talk to the same presenter classes.
So, MVP architecture with unit tests, running on Android and iOS.
In the com.kgalligan.partyclicker.data package, there’s a sqlite open helper (DatabaseHelper) which manages basic data interaction, an interface for those methods (DataProvider), and data objects used by the orm. There’s also a Runnable for some background processing. That could be rolled into RX, but I wanted to make things more difficult.
In the com.kgalligan.partyclicker.presenter package you’ll find the 2 presenters, but also some related Dagger code, and a CrashReporter interface.
Looking at PartyListPresenter, you’ll see methods that interact with the data model, and an interface, UiInterface, which is implemented by PartyListActivity on Android, and PartyListViewController on iOS. The presenter code can interact with the view by way of UiInterface.
So, for example, to load the list of parties, the activity will call callRefreshPartyList(), which loads the data in a different thread, then calls refreshPartyList() on the UiInterface instance (which is the Activity or ViewController).
The unit tests for the presenters mock the UiInterface and verify that methods you expect to be called actually are. All of the tests happen in one thread. We inject an Observable.Transformer with dagger. In the app, the transformer handles subscribeOn and observeOn. In testing, nothing happens, so all calls to rx are on a single thread.
You’ll also notice we’re injecting DataProvider, which is implemented by sqlite in the live app, but an in-memory hand rolled implementation is available during testing. Unit testing!
Also notice DatabaseHelperTest. We’re testing the actual sqlite db, so we need access to the Android context. We have a special test runner, DopplRobolectricTestRunner, that will delegate to Robolectric when you’re testing in Java, and create an instance of co.touchlab.doppl.testing.TestingContext when running in iOS. Just a note on Robolectric here. We’re only using it for the Android context. None of the other functionality is implemented on the iOS side. Future implementations will either create this context without Robolectric, or possibly implement more of the Robolectric stack. See how that goes…
The important concept to take away is you can code your logic using the best practice methodologies of your choosing, assuming you keep the UI out of the logic, and write a complete set of tests to be run in both Android studios and Xcode.
For parts of your app that need to be exposed to shared code but cannot themselves be shared, the simple pattern is to create an interface in the shared code, and implement it on each platform. This is one of the core concepts of Doppl. Weaving shared code with platform specific code is natural and intuitive.
The obvious example here is the UiInterface provided in each presenter. To allow the shared code to manipulate the UI code, an interface is created in the presenter, which is then implemented by the platform specific UI, and passed into your shared code.
You can do something similar with things such as location listeners, bluetooth, etc.
First of all, we’re hiring. I think it’s very important I get that out there.
Second, we’re looking at an early stage release of “everything” shortly after Google I/O. That includes gradle plugin, libraries, etc. We’ve grown the Doppl team and are rapidly sorting out docs and code.
Third, we’re setting up a Slack group to start chatting. Until we create a signup page, email firstname.lastname@example.org
After a little tweaking, there are a few extra branches to try some stuff. The master branch uses “Squeaky”, which is a rewrite of ormlite I did a couple years ago, but never really promoted.
There’s another branch that uses SQLDelight for the database. There’s a third branch that uses greendao. Those were pretty simple. Then I decided to ruin my weekend and port SQLCipher to Doppl. The 4th branch is ‘greendaoencrypted’, which uses greenDAO encrypted db with SQLChiper. This is both experimental and somewhat more difficult to set up, as we can’t distribute SQLCipher directly, but you can do it. I believe in you.
Also, J2objc just released version 2! Our lightly modified J2objc runtime is up to date with master, so we’ll be at v2 as well.
*There are ways to share some code with JS which we’re looking to leverage in a similar way, but the practical extent of what’s feasible is much smaller, so maybe a little later…