Most apps are either thin-clients for server APIs (Whatsapp) or fat, stateful, rich clients (Games) that sync state with the server. You can probably break any app down into the following sections:
- Network Layer — server API calls over HTTP, TCP, UDP black magic
- Object mapper to get domain models from JSON
- May need to support caching
- Retries with exponential backoffs
- Detect network connectivity
- Does device say we have internet? If not, don't even try.
- Do we really, really have internet? Can we talk to our server?
- Data Access Layer (SQLite, SharedPrefs, Core Data, or Realm)
- ORM support mapping to/from domain models
- Thread-safe access
- Platform Layer — API calls to use device features (network state, sensors, BLE, camera)
- all your silly "ThisThatOtherManager" classes
- Manage notifications (GCM, APNS)
- Background services that record or publish device/sensor state
- Here be dragons
- encapsulates all unholy complexity
- XML layouts with i18n strings, conditional views and strings
- the goddamn View-Controllers
- code trying to make sense of it all (what is the purpose of life?)
- silly variables tracking user or view or task state, isClickingButtonAfterFeedingCat
- Lots of if-s, else-s, switch-es
- Scope for state-machines to track state, but nah
- over 90% of bugs, debug time, and changes go here
App development really boils down to management of view-state. Everything else falls into place super easily. This is also true for single-page webapps written in [extravagent-framework-here]. IMHO, Angular v1 did an impressive job with this.
There are many opinions on how to structure the 4th layer. The one I find most useful is Dagger2 (DI framework). Dagger2 introduces compile-time DI which addresses many of the performance criticisms of Dagger. DI is an interesting pattern since you can easily swap mocked implementations or dev envs. See this code sample from Google. Layer 4 is also where react-native operates so you may find that route useful if your app has the same look across platforms.
After giving react-native a shot, I'm not convinced it can handle acute UI design changes as effectively as plain old XML layouts. UI performance is perfectly fine, but this may be problematic if your team has been doing native UIs so far (from a code maintenance and release-cycle standpoint). Much like how we'd hit walls in Phonegap apps when certain native bridges and features weren't available yet, with react-native you may end up writing more native code for each platform for those pixel-perfect designs than you can justify. But in the near future, I think react-native has immense potential to be the de-facto way of building mobile apps.
Cross Platform Business Logic with Go (gomobile)
Fair warning before you hate me for this, Arrays (slices in Go) don't work yet.
Check out Nic Jackson's talk and come back to see why it may be useful.
The components that can be productively reused across platforms deal with business logic. Users interact with views and trigger certain states that require an action at the server-side. You have API calls in all your iOS, Android, Desktop apps. All API responses map to some domain models. Those domain models are processed by some business logic and the views show the results. What changes across platforms the most is the View-Presenter.
Here's where Go comes in. Go compiles your business logic to native code and generates bindings for iOS and Android. This is exactly like C/C++ code that you call via JNI in Android or use directly in iOS, but with Go you get a bunch of neat features that includes a simpler concurrency paradigm.
Some ideas that may make this useful for you —
- Move all REST API calls, response parsing, and models to Go. JNI/bridge maps models in Go to data models in Android/iOS
- Persistence layer — replace CoreData or ContentProvider/SQLite with an embedded database in Go (such as Bolt)
- Data is somewhat protected compared to trivial SQLite access (but you could use Realm)
- You can offload your async computational tasks (RxSwift/GCD, AsyncTask/Bolts) to Go and have cross platform async tasks
- Performance "isn't terrible"; haven't benchmarked
- Compared to writing Java code + proguard, this may give you better code obfuscation
- Perhaps most importantly, you can independently develop and test business logic for all platforms
However, all this is not without caveats —
- Only simple data types are supported. [See doc]
- Large API responses that need to cross language boundaries may be slightly slower to load (large ListViews)
- Integration tests become a lot more important (which you should be writing anyway)
- Generated library file can be of significant size. For android/arm, .aar file is about 6MB
Do not adopt this approach for any performance benefits. In fact, there may be performance penalties during interop across Java-Go boundaries. Many benchmarks still show Java to be faster than Go because JVM is undeniably state-of-the-art. Dalvik and ART probably also have good design decisions backing them. This approach also disregards many of the scheduling and thread-priority features that higher envs provide (GCD, java.lang.Thread).
The implementation of this that got me excited uses the following stack —
Client-side uses a realtime HTTP/2 transport that is generated from a protobuf service definition
Tiny client wraps transport details and exposes interface for external calls
See Makefile for build steps.
btw, Google API open-source clients now use autogenerated code created from service definition files. I think that is very neat.
HTML5 webview-based apps still aren't good enough. Despite its promise, Appcelerator still sucks. Xamarin is great but nobody cares (except maybe enterprises). Design language continues to vary across platforms and except for hugely popular apps, everyone is sticking to what the platform prescribes (unless costs or time don't permit).
So our discussion went into how dirty it feels to implement the exact same business logic in Android and copy/pasta it into iOS. Having developed the (as yet unreleased) iOS and Android app for BrowserStack (with a kick-ass team), the discussion went into familiar territory. We did take a few decisions in our app such as using RxAndroid and RxSwift to reduce learning [read: threading surprises] across platforms, but duplication of business-logic at the time seemed unavoidable.
State of Android
Devs working on complex apps are starting to pick up DI (with Dagger2) and RxAndroid or Bolts for async tasks and dataflow. Retrofit is the de-facto REST API client. Robolectric has championed unit-testing, devs use it in conjunction with CIs. Fabric is the preferred crash-reporting platform (over the likes of Crittercism). Leak Canary is a blessing included in all projects from the very beginning.
Java 8 features (lambdas, etc.) still aren't deemed important enough to refactor or include Retrolambda according to my friend. On the product side, there's more demand for supporting tablets and android TV is now also expected to work. Fragments are totally in. Google continues to help out by pushing new view helpers and material design components in the support libraries.