Skip to main content

Android and iPhones and Web, oh my

It's regularly asked why J2ObjC purposely avoids translating UI code; after all, wouldn't it be wonderful if a tool existed where a developer can drop in Android source and out pops an iOS app?

Our usual response is that world-class apps need user interfaces that are tightly integrated with each platform, and that common-denominator attempts to span platforms degrade user experiences. As I found when working on Swing many years ago, customers notice the smallest deviations from a platform's UI standards and generally find them off-putting. But non-compromising UIs are just one of the reasons we focus on translating shared logic.

The pressure for platform-independent apps is due to the cost and effort to create separate versions for each platform. This is especially true for the non-UI parts of the app, where the logic is identical but must be rewritten in different languages for Android, iOS, and browsers. Software development is expensive, and this sort of redundancy is wasteful, risky, and perhaps most important to developers, mind-numbingly boring. The Don’t Repeat Yourself principle is very applicable when building apps for multiple platforms.

Additionally, there are hidden costs due to differences between an app on different platforms: missing functionality, unique bugs, etc. Your marketing people will (should) argue that this weakens the app's brand; in other words, its reputation among its current and potential customers. When an app behaves differently on different platforms, it’s often perceived as being lower-quality than its competitors.

J2ObjC was created to help address the problems associated with independent releases, without sacrificing the requirement for world-class, platform-specific user experiences. It's goal is to provide a GWT-like translator and runtime, so iOS apps can be designed to share as much Java code as possible without compromising best-of-breed user interface designs. The key word in the previous sentence is designed, because as software engineers know, reusability needs to be an important design goal and not just an afterthought.

We’ve found that test-driven development really helps support code reuse, because there is a huge overlap between testable and portable code. If code is hard to test, it's also likely to be difficult to use on other platforms; UI and networking code in particular are difficult to unit test easily. By writing tests as the design is being implemented, this difficulty encourages hard-to-test code to be isolated from more easily tested code. Often, the design can be improved to further isolate hard-to-test code and thereby increase sharing, such as using dependency injection or mock objects in tests. Software development is challenging enough without sticking to designs that create unnecessary work.

We strongly encourage using continuous integration for apps on all platforms they target. When an app is written to share Java code using Android, GWT, and J2ObjC, any changes that are committed to that shared code cause it to be rebuilt for Android, translated to JavaScript by GWT for browsers, and translated to Objective-C by J2ObjC for iOS, and all unit tests executed. Any changes that break any of these builds can then be quickly fixed or rolled back. This infrastructure allows engineers to work together, regardless of their Android, web, or iOS experience; effectively it leverages their work, cutting in third the effort that three separate ports of an app would otherwise require. It also allows the team to assess the three platform builds as parts of a single product, with (for the app's core functionality) a single set of features and outstanding issues.

Hopefully this clarifies J2ObjC's objectives. It's why we consider it more of a compiler than a source translator, since the output that matters most is what winds up in each app build, not how pretty the intermediate Objective-C looks (though it's very useful to inspect generated code). It's why the JUnit and Mockito libraries were added before many JRE classes. And it's why we open-sourced J2ObjC; the software engineering community developed all these best practices we rely on, and as community members it's important we help improve them further.

Comments

  1. Why not just build the non UI parts in C since both platforms support C natively?

    ReplyDelete
  2. This is absolutely fascinating to me. I wrote an article on multi platform UI design back in 2012, when Google's apps didn't look great on Android or iOS. I remember feeling this need for apps that function the same way on any platform, yet have a UI that feels right at home on the given platform. I'm so pleased to see the solution Google has demised. Working at a software company myself, I would want any of our mobile apps to be built the same way. Thought I'm just a user advocate on the dev team, so time for some convincing. Thanks Tom.

    ReplyDelete
  3. @kdt because android C support is limited and requires JNI which is much more complex.

    ReplyDelete

Post a Comment

Popular posts from this blog

Mapping tests from TestNG to JUnit

In the summer of 2020, J2ObjC's JRE emulation library (a fork of Android's libcore library ) was updated from Android Nougat to Android 10. The update consisted of adding new APIs, and updating the existing code. Apart from general functionality changes, one of the main goals of the update was to port new test cases to better verify that the JRE emulation library works correctly. While updating the java.time package, I came across a tck.java.time package containing approximately 15,000 new test cases written in TestNG. The problem? J2ObjC only supports JUnit. Instead of supporting both TestNG and JUnit, we decided to build testng2junit , a tool that converts TestNG tests to JUnit. How Can These Frame works be Mapped? To convert from TestNG to JUnit, we needed to determine where the two testing frameworks differed. Attempting to run the TestNG tests through the compiler gave us plenty of compile-time errors to sift through. These syntax errors, coupled with documentation from

Catching Java exceptions in Swift via j2objc

TL;DR : it’s possible to handle Java-originating exceptions in Swift for j2objc -based projects. Scroll to the end for example code. It’s getting more common to call j2objc -generated Objective-C code from Swift as iOS development shifts to this modern language. At a high level, we can imagine this means calling Java code from Swift. But Objective-C is an important link in this chain and it shapes the way Swift interacts with the code that started its life as Java. j2objc  does a great job of supporting Swift’s features when called with the --swift-friendly  flag. This feature is particularly useful when the Java code is annotated with @Nonnull , @Nullable , and @ParametersAreNonnullByDefault  to enforce Swift’s optionality at compile time and as you type in Xcode. But there’s one important Java feature that gets lost in translation on the way to Swift: exceptions. You might expect to catch Java exceptions from Swift like: do {     // original Java code throw