Engineering Manager Framework: How to Evaluate Mobile Multiplatform Solutions

Engineering Manager Framework: How to Evaluate Mobile Multiplatform Solutions

The following text is transcribed from our Kotlin Multiplatform webinar. There is a video at the bottom of the blog post if you would rather listen to the segment. If you find this segment interesting, you can register for the full webinar recording and slides here

 

Slide 1

This diagram. Here’s where we find out what Native really means, and, even simplified, there’s a lot going on here.

Definitions

This input category is how the app developer writes the app. A Native Language isa language officially supported by the platform. Swift and Objc on iOS, Kotlin and Java on Android, HTML/CSS/Javascript on Web. An Other Language is a language like JavaScript on mobile, or ClojureScript on the Web. It also includes WYSIWYG visual programming interfaces that some no-code multiplatform solutions use.

The Process category is what the build chain does to the input in order to create an output. I mentioned Cross-Compilation and Trans-Compilation earlier.

But to clarify both of these, cross-compilation here is when a high level language is compiled directly to the native code for each various platforms before outputting the final product. This is called Ahead of Time compilation or AOT for short.

Trans-compilation here is when a a high level language is compiled to a another high level language which can then be fed back as input to another process, usually to a language native to the platform that can then go through the native build-chain.

Runtime here includes things external to the platform that implements portions of an execution model needed to execute the app like virtual machines, interpreters, and necessary libraries.

The Outputs category determines the type of app generated. Native Code is the low level code executed natively by the platform. Native code generates Native apps. A Native Container bridges web technologies with platform specific technologies by running web code in a webview on the native platform. A native container plus web views generates Hybrid apps. As mentioned before, a Native Language needs to be processed again to become a mobile app. But, it could also create a Web app if the output language is JavaScript or Wasm.

Once I put this together, I searched through documentation looking for how different tools worked and fit them into this model. I’m going to talk specifically about Ionic because it is a Hybrid solution, but more importantly, I’ll talk about React Native, Xamarin, and Flutter because they are so popular and, of course, I’ll talk about Kotlin Multiplatform because that’s why we’re all here. One last thing before we move on. Notice how this starts to explain some of the negative feelings we have about cross-platform solutions. For any solution, If we need to code in a new language, it will slow developers down. And, If there is an interpreter or VM between code being asked to run and code being executed, it’s going to slow the app down. And for hybrid solutions, a webview is fairly heavyweight and will slow things down even more

 

Slide 2

Ionic is a popular Hybrid app development tool built on Apache Cordova. You write your app in standard web technologies; the runtime includes widget libraries and native platform interop libraries, then, everything is wrapped in a native container and you get a hybrid app. Now, the web and mobile are different. On mobile you need to handle things like the offline experience, the application lifecycle and back button expectations. Hybrid solutions promise to make things easy, but you actually need to work really hard to make a hybrid app meet native expectations. 

 

Slide 3

React Native takes something that worked really well on web (that is, react) and makes it work on mobile better than hybrid apps. You develop mainly in Javascript and the runtime provides native or native-like widgets along with tighter interop with the underlying platform. Native code is output at runtime by the JavaScriptCore interpreter which allows the app to be categorized as Native. The differences between web and mobile are still issues to consider, and it’s easier to meet native expectations with React native, but it’s still difficult. For example, one of the problems mentioned by both Airbnb and Udacity is the 3 platform issue: to make their React Native apps more native, they had to deal with the javascript bridge and code independently for Android and iOS. This was more of a pinpoint than expected and they needed to do it more often than expected

 

Slide 4

Xamarin was one of the first to focus on sharing business logic with native code across platforms. It was quite a bit later in Xamarin’s life that they introduced Xamarin.Forms to share UI as well. You write your code in C#, and it gets processed to include the .NET runtimes. On iOS, it is all compiled ahead of time because iOS doesn’t allow 3rd party execution engines on Android. The Mono VM is included in the distribution so it can just-in-time compile native code at runtime. AOT is generally faster than JIT and benchmarks do show Xamarin.Android is significantly slower than Xamarin.iOS

 

Slide 5

The Flutter solution is yet more native because it AOT compiles on both Android and iOS. You write your app in the Dart language and, for dev builds, the Dart VM will JIT compile, but for production builds it will AOT compile all the necessary run-time libraries for widget rendering and business logic directly to native code. 

 

Slide 6

Kotlin Multiplatform is the most native solution yet. It is first-class for developing Android apps. Kotlin is already the best for this and fully supported by Google. Flutter is not first-class for developing Android apps until Google puts a lot more money into it and commits to it because they will need to sustain both the Material Widget library on Flutter and the standard Android widget library. Neither Xamarin nor ReactNative will ever be first-class for developing Android apps. It AOT compiles on iOS to a standard Objective-C framework which is already first-class on iOS and, even though Swift is becoming more popular, Objective-C frameworks are still prevelant. ReactNative is difficult to use in existing apps. Xamarin is impossible to use with natively developed apps. Flutter is working on it.

And that’s a huge difference: Kotlin Multiplatform is about optionally and easily sharing code. ReactNative, Xamarin, and Flutter are foreign ecosystem with a disconcerting amount of vendor lock-in. Kotlin Native also outputs to JavaScript or Wasm for sharing code with web. It’s more difficult than it should be to share React for web and ReactNative, but there is a way. Xamarin isn’t officially supporting web output yet, but people are trying. This model is quite good and this is how most people approach the definition of native, even if they haven’t thought about it as deeply as this. Often it is simplified to just the final App Types, with Web being on one end of a spectrum, Native being on the other, and hybrid filling out the in between. 

 

Recording: Webinar Segment

 

The video is from our Kotlin Multiplatform webinar. If you find this segment interesting, you can register for the full webinar recording and slides here

Making Kotlin Multiplatform a Native Experience for iOS Developers

Making Kotlin Multiplatform a Native Experience for iOS Developers

You need iOS developers to achieve the full potential of Kotlin Multiplatform; they’re half of the equation! At Touchlab, we’re committed to building the native experience for iOS developers using Kotlin Multiplatform.

We interviewed Partner Kevin Galligan on what we’re doing as an organization to ensure iOS developers are engaged when building with Kotlin Multiplatform.

Cross-Platform? We Don’t Say That Around Here Anymore.

Cross-Platform? We Don’t Say That Around Here Anymore.

In the application development world, you hear the term cross-platform all the time. The idea of “write once, run anywhere” is like an unreachable promised land for management stakeholders, and an unrealistic and frustrating reality for developers.

At Touchlab, we don’t use the term cross-platform. We prefer “multiplatform” – for a couple of important reasons.

 

Baggage

First, cross-platform has a lot of baggage because of existing development platforms. PhoneGap, Titanium, Xamarin, Ionic, RubyMotion… the list is long. Initially, these solutions went through a phase of exuberance, followed by disappointment. Developers were frustrated with not being able to express the UI how they wanted, unavailability of features on their wish list, and invariably, teams would end up managing three platforms – cross-platform, iOS and Android. For product owners — and ultimately, the end-users — the UX was rarely satisfying.

Note: If your organization doesn’t care about native, traditional cross-platforms solutions might be okay. Context matters.

 

Wide Net

Second, cross-platform really is a super-inaccurate term because the solutions that are lumped under it are wildly different. We believe you need much more nuanced terminology to describe coding for a variety of platforms. The way we see it, at one level, when a native developer sees anything that isn’t Java, Kotlin or Swift, they’ll lump all alternatives into cross-platform and immediately (without pause) classify them all as bad based on a singular bad experience with a solution. “If it’s not native, it’s bad. I would know, we tried PhoneGap once.”

And while the world has always been ready for shared code, it’s rarely been implemented well. The good news? There are now better options to make things really work.

 

Multiplatform

At Touchlab, we’re about shared logic that interops natively with the platform you’re working with, homogenized logic and libraries – all of which should occur below the UI and be performant.  Simply put, our multiplatform approach can’t and shouldn’t be compared to “cross-platform.” It’s not an apple-to-apple comparison.

We call it “post-platform thinking”. It doesn’t matter what platform you’re delivering on, because the ultimate goal is to have a well-tested and well-architected backend, middle communication layer, and front-end architectural layer that’s all homogeneous.

So, if as developer can build the logic from the back-end to the front-end, and model the logic for the UI in a microservices model, the developer’s job becomes one of service aggregator rather than implementer. The iOS developer can focus on UI and UX, not on implementing the services themselves.

 

Kotlin Multiplatform

And that’s what Kotlin Multiplatform does: natively shared, performant, tested architecture with a fantastic developer experience and ecosystem. It enables you to do runtimes efficiently, and have the ability to test – all of which makes shared logic a lot more possible. The architectural stuff is well tested, it’s well engineered, it’s economical and efficient from a development standpoint, so it’s an easy platform to move to another UI.

So why is Kotlin the way to go?  Here’s the thing: anything that requires you to make large decisions and potentially large rewrites, perform large retrainings and rehirings, or anything that has to share UI or doesn’t work well with a native platform is, well, very risky. The fact is, Kotlin Multiplatform is not risky in that same way, and that’s why we’re working with it at Touchlab.

Kotlin Multiplatform is a modern language with enthusiastic community support that allows native optional interoperability with the platform on which you’re working, with straightforward logic and architecture. Android, iOS, desktop platforms, JavaScript, and WebAssembly. For me, the idea behind Kotlin Multiplatform is that you can invest in this way of building logic, and you don’t have to guess which way the industry is going to progress in the next 5 or 10 years. If everything moves to the Web, you can support that that. If it all goes mobile, you can support that too. And that’s true multiplatform.

The Future of Cross-Platform is Native

The Future of Cross-Platform is Native

Cross-Platform Baggage

Cross-platform development is not held in high regard these days, largely because the apps that purport to provide cross-platform support have never really done the job effectively. But I believe the time has come for us to reconsider.

The arguments in favor of cross-platform development are the same as they’ve always been (D.R.Y., Risk Mitigation and Feature Parity)

 

Don’t. Repeat. Yourself (D.R.Y.)

There’s the argument for gaining efficiencies and cost savings through streamlining the development process. Program once to create cohesive code that can be deployed simultaneously to iOS and Android.

 

Risk Mitigation

There’s also the argument for minimizing risk regarding how UI will be developed. The biggest risk is that the UI won’t meet user expectations on either platform. This is a major reason why development teams opt for programming apps independently from one another. At the same time, business logic and backend development are at higher exposure because both determine how all features within an app will work. Ideally, in a cross-platform development scenario, an organization could take the time to focus on the nuances of backend and logic development, while putting less strain on UI development.

 

Feature Parity

The other argument is for feature parity and inclusivity, that is, fewer differences in functionality, whether iOS or Android. The benefit – you treat all users equally because they are using essentially the same program, whichever platform they choose.

 

Native Multiplatform Development

However, what was missing from “cross-platform” is native multiplatform development: Native CPU, Native UX, and Native developer experience and tools for iOS and Android. Cross-platform programming has the potential to thrive if the focus shifts to native coding, which is a more direct approach to produce the same functionality across platforms and devices. To better understand this, here’s a quick look at the most popular cross-platforms solutions and their native limitations.

 

Xamarin

Xamarin was one of the first to focus on a native approach to programming across iOS, Android, and Windows, starting with shared business logic and working its way toward shared UI with Xamarin Forms. However, its native elements are limited because it lives within its own ecosystem and uses C# (a language not native to iOS or Android development) and Microsoft Visual Studio instead of Android Studio or Xcode.

Xamarin Mobile Architecture

 

React Native

React Native (RN) represented a leap forward in terms of how developers thought about cross-platform because it empowered them to apply their web development knowledge to build native iOS and Android apps. But, like Xamarin, it too, lives within its own ecosystem, using Javascript and non-standard editors . And like Xamarin, it needs to wrap native controls and view hierarchy from its own interop, making it necessary to construct the UI with its own language.

React Native Mobile Architecture

 

Flutter

Flutter, a newer addition to cross-platform programming, uses its language Dart to create iOS and Android apps. Flutter also makes use of rich widgets to provide remarkable native experiences on Android and iOS platform – but the widgets are not native. Flutter also employs a shared UI platform that only works on mobile with a language (Dart) that isn’t widely used.

Flutter Mobile Architecture

These are just a few examples of why cross-platform programming has traditionally been a serious challenge to manage. It’s one of the reasons I believe we should move away from the term “cross-platform.”  A more apt term is “multiplatform”, because the goal is that any code you share maximizes what each platform offers.

 

Kotlin Multiplatform

That brings us to Kotlin Multiplatform. It is the rising star in the multiplatform space, and is, in fact, more native than Xamarin, RN or Flutter. Currently the dominant Android language, Kotlin has a strong, enthusiastic base of developers worldwide, and is praised by the community for providing a superior developer experience. Kotlin Multiplatform enables developers to write once, and test once, then use the same code across iOS, Android, and Web apps.

While not the first multiplatform tool to split business logic and UI (for instance, Xamarin for logic/libraries and Xamarin Forms for UI), it’s more native than Xamarin, RN, and Flutter because it uses shared logic and libraries below the UI layer, which developers can interact with in the native developer environments — Xcode, Swift, and Objective-C for iOS; Android Studio and Kotlin for Android; JavaScript for the web — and it outputs native code for each platforms.

As a language, Kotlin enables developers to produce applications more cohesively. It’s a modern language that dovetails with native platforms on Android, iOS, Java, and the web, allowing development teams to build on what’s already been coded. And because it’s essentially an extension of Java, it’s relatively easy for Java developers to get started. Kotlin isn’t too much of a departure from Swift either and at Touchlab we currently have iOS developers coding in Kotlin.

Kotlin Multiplatform Mobile Architecture

Equally important, Kotlin is a way to future-proof applications developed today. It’s a sound technology investment because the code works on all platforms without vendor lock-in like Xamarin or React Native. So, whether the dominant platform in the future is web or mobile doesn’t really matter, because the code ports to either environment.

At the same time, development teams no longer have to be siloed, and instead can work together as a cohesive whole. There is no longer a need to have dedicated iOS and Android teams.  This unified mobile approach simply makes more sense operationally, technically, culturally, and financially.

Is Kotlin the safest bet? Everything indicates that it, in fact, is. In a world where we’re always looking for greater efficiencies in development time and costs, Kotlin has proven itself to be a very viable player – one that has already shown itself to be a truly multiplatform programming option.

Stately, a Kotlin Multiplatform Library

Stately, a Kotlin Multiplatform Library

This started as a monster single post, now split in 2. Part 1, Saner Concurrency, is about what Kotlin is doing with concurrency.

During my talk at KotlinConf, I promised a part 2 of Stranger Threads to better explain threading and state in Kotlin/Native. I built a library instead.

Update!!! Video and Slides from my Droidcon UK talk!

What is Stately

Stately is a collection of structures and utilities designed to facilitate Kotlin/Native and multiplatform concurrency. As of today, it is a set of expect/actual definitions that most apps will wind up needing, and a set of frozen, sharable collection classes that allow you to maintain mutable collections across threads.

Why does it exist?

Kotlin/Native, and hopefully soon, all of Kotlin, will be implementing Saner Concurrency. Native, today, has runtime rules, and notably a lack of “standard” concurrency primitives, that help ensure runtime concurrency sanity. The 2 basic rules are:

  1. All mutable state is in one thread. It can be transferred, but is “owned” by one thread at a time
  2. All shared state is immutable (frozen)

These are good rules, because they will make concurrent code safer.

However, there are times where being able to access shared, mutable state is pretty useful. That may change as we adapt architectural thinking, but at least for today, there have been a few tricky situations to arise without that available. Global service objects and caches, for example.

Kotlin/Native has a set of Atomic classes that allow you to mutate state inside of an immutable (frozen) object. The Stately collections are constructed with atomics.

For the most part, I’d expect Stately to be used sparingly, if at all, but it’s one of those things that you’ll really miss if you need it and don’t have it.

What you shouldn’t use it for

Kotlin/Native has rules that help encourage safer concurrency. Changing frozen state is, on some level, enabling manually managed concurrency. There are practical reasons to have these collections available, but if you’re using them a lot, it might be better to try for some architectural changes.

If you’re new to Native and running into mutability exceptions, you might be tempted to make everything atomic. This is equivalent to marking everything synchronized in Java.

It’s important to understand how Native’s threading and state work, and why you need a concurrent collection. But, you know, no judgements.

Basic Principles

The collections mostly act like their mutable counterparts. You designate generic types, and get/set/remove data entries. One key thing to note.

Anything you put into the collection will get frozen.

That is very important to understand. The collection itself is “mutable” in the sense that you can add and remove values, but the collection and values it holds are all frozen.

For data objects, this is generally OK, but for callbacks, this may be somewhat mind bending to understand. That’s not a Stately problem so much as a Kotlin/Native problem.

I’ve talked to several people who struggle with this reality. All I can say is it seems weird at first, but is not as big of a deal as you think. You just really need to be aware of what that means.

For example, in the Droidcon app, we’re using LiveData to build a reactive architecture. To avoid freezing everything in the Presenter/UI layer, we simply keep all of our callbacks thread local to the UI thread, which means they don’t need to be frozen. The lower level callbacks all exist in Sqldelight, and are frozen, but they exist just to push data back to the main thread and don’t capture UI-layer state.

Summary, it’s different, but not that bad. The details would turn this blog post back into a monster, so I’ll just push out my promised “Stranger Threads” deadline out by a couple weeks.

A lot of how we think about architecture will change as coroutines mature on Native, and as the community has some time to think about the implications. I suspect there will be less use for shared collections as that happens, but for today, they’re pretty useful.

Available Collections

CopyOnWriteList

Similar to Java’s CopyOnWriteArrayList. In fact, the JVM implementation is Java’s CopyOnWriteArrayList. Useful in cases with infrequent changes and critical read stability. Registering callback listeners, for example. If you’re changing the list often, or it’s large, each edit requires a full copy. Something to keep in mind.

SharedLinkedList

This is a mutable list that will have reasonable performance. All edits lock, just fyi, but individual edits themselves are pretty small, so locks are quick. You can hold onto a node reference that will allow you to swap or remove values without traversing the list, which is important in some cases.

There are 2 basic flavors. One has unstable but performant iterations, the other I’d call CopyOnIterateList. It’s similar to CopyOnWriteList, except the copy happens when you call iterate. This may prove more generally useful than COWAL, as it should handle frequent changes better, but if you’re editing often and iterating often, remember you’re dealing with an aggressively locking data structure.

The other flavor will let you iterate without copying or locks, but edits to the list will be reflected while you’re iterating. The edits are atomic, so you won’t get errors, but you’ll also wind up with potentially fuzzy iterations. If that isn’t an issue, this should be a better choice.

I am aware that there are lockless implementations of linked list, but I didn’t attempt one. Going for simple.

SharedHashMap

Implements a pretty basic version of our favorite structure HashMap. Performance should have similar characteristics to what we’re used to from Java🌶, although obviously locking will produce different absolute numbers. In short, same big O.

I probably don’t need to go into where a hash map would be useful, but in the service object context, it’s probably caches, which leads to…

SharedLruCache

Shared version of a least recently used cache. If you’re not familiar, there’s a cap on the number of values, and the “oldest” get bounced first. “Oldest” being defined as the least recently accessed.

Status

This is pretty new. As Kotlin 1.3 matures, we should have a more stable deployment, and may add some other features. I’ll try to add some issues for “help-wanted” in case anybody wants to contribute.

Also, as of today (Friday 10/26) the JS code has an implementation but tests need to be wired in. That means don’t use the JS until that happens.

Notes
The only supported Native implementation is for mac and iOS. Other Native targets should work, except we’ll need to find a lock implementation. Pthread_mutex is fine, except you need to destroy it explicitly, and K/N has no destructor. That means a ‘close’ method on the collection, which I’d rather avoid. Right now there’s a spin lock, but not sure if that’s a great idea.

The collection implementations could be described as minimal. My main goal was to start replacing some of the custom C++ code we’d put into earlier K/N implementations, which generally existed because of a need for shared state. If there’s a real need for something else, open an issue to discuss.

🌶 Similar to Java 7 and below. That is, if your bucket size doesn’t increase, as your entry size increases, or you have a bad hash, you start to approach N, because the bucket list means a lot of scanning. In Java 8, after the list gets to size 8(ish), it’ll store those values as a tree, so worst case gets capped. Our hash map resizes like Java’s, so you should only find yourself in “worst case” if your hash is bad, or if you have horrible settings on your map. You probably don’t need to know this, but Java 8’s optimization is kind of cool, and I didn’t bother trying to implement it 🙂

Saner Concurrency (and the cost of change)

Saner Concurrency (and the cost of change)

I’ve been talking about Kotlin/Native for a little while now, and one of the big things I tell people is that threading will be very different than what they’re used to. It’s just something that you need to learn and understand.

Kotlinconf was a couple weeks ago. Progress on Multiplatform was definitely a big topic. A more general discussion surfaced around how Jetbrains and the Kotlin team are thinking about concurrency. Andrey said in his Keynote that future Kotlin will focus on Saner Concurrency. I think we should unpack this a bit.

It is generally accepted that managing concurrency by using mutexes, synchronized blocks, etc (AKA. “manually” or “status quo”), is difficult and error prone. Concurrency in Java, with Java’s concurrency primitives, is difficult.

Some languages attempt to improve upon this situation using various approaches. Another point from the keynote I thought was great is that being unique is not important. Assimilating the best ideas of other languages is important (2:40).

Saner Concurrency

Kotlin/Native has baked into it some ideas about concurrency that make managing it safer. Andrey alluded to the idea that Kotlin, across variants, will start attempting to tackle “Saner Concurrency”. This is good to watch (37:00) to get a sense of where Kotlin is going, which makes me hopeful that not only will the various incarnations of Kotlin share the same concurrency concepts, but that those concepts will start to emerge as first class parts of the language at compile time rather than strictly runtime (Rust-like?).

By “various incarnations” I mean Kotlin-JVM will hopefully start getting some of the “new stuff” Kotlin/Native is getting🚧.

One of the consequences of change is that things will be different. We’ll need to learn new ideas, build different libraries, and adopt different ways of coding. That inevitably means resistance and time.

I wound up on the closing panel of the conference (thanks Hadi!), and one of the questions I gave an answer to addresses this issue. It wasn’t actually a question so much as expressing concern that the different threading model will slow down adoption (at 18:00)

I jumped in because, frankly, I had the same concerns earlier on. A few months ago I drafted up a blog post talking about how such a departure will impact adoption and may ultimately derail the effort. Thankfully, like most of my blog posts, I never finished it, and I don’t think that way anymore.

Considering it was on stage, at the end of a conference, I think I expressed my thoughts OK, but let me expand a bit here.

At the beginning of my talk, I told everybody about how I got there. I’d spent a couple years working on an architectural framework based on J2objc called Doppl. In short, compatible Android structures, lots of familiar libraries, and a gradle tool. Replicating testable mobile architecture across Android and iOS, in a familiar and practical manner.

How practical? The day after Google announced room, I got Room working on iOS, from the java source jars, in about 90 minutes.

Here’s the thing, though. Nobody cared. I bet more people read this post in the first week than read that post after a year and a half.

Obviously these aren’t direct parallels. The Android community wants Kotlin and iOS wants Swift, so pitching a Java/Objc thing isn’t fun (trust me). Community interest is critical, no matter how practical your pitch may be.

However, I think it’s also important to have a vision. Kotlin wants to adopt better, safer concurrency practices. They’re making the language available on many platforms, including Webassembly, and making “tough love” choices for a better future platform.

I think building something practical but somewhat compromised will make it easier to learn, and definitely easier to develop the library ecosystem necessary to support what currently exists, but it’s also uninspiring. It does not encourage change.

In short, yes, I think “giving up” on change would make adoption quicker for an individual iOS developer, but I don’t think it would be appealing to the iOS developer community. Change and new ideas, although they will take more work, are appealing and inspiring, and will ultimately be the best bet at gaining adoption.

When somebody says, “Kotlin is just Swift for Android”, this is meaningful pushback. It’s not simply “another language”.

In any case, it doesn’t matter. They’re doing it ✈️

This post started as one post, but turned into a monster. Part 2 will be released after my Droidcon UK talk Friday where we announce a new Kotlin Multiplatform state management library…

🚧 I didn’t want to derail up top, but one major issue for right now is that JVM and Native do some runtime things fairly differently. That is likely to create confusion early on, especially with teams that have engineers fully on one platform or the other. It would be great if we could run JVM with the same runtime checking as Native, even if just in a debug configuration.