onCreate()-ing Kickstarter’s Android app

Once upon a time, Kickstarter existed on the web and iOS. Creators and backers rejoiced! But as our creative community blossomed, Android users were left in the dust. “Why is there still no Android app?” they said, “Kickstarter should run a Kickstarter to build an Android app!” they said. Finally on January 21, 2016, six years […]

Once upon a time, Kickstarter existed on the web and iOS. Creators and backers rejoiced! But as our creative community blossomed, Android users were left in the dust. “Why is there still no Android app?” they said, “Kickstarter should run a Kickstarter to build an Android app!” they said.

Finally on January 21, 2016, six years after the launch of Kickstarter.com and three years after the iOS release, Kickstarter for Android 1.0 entered the Play Store. We’d like to share the the story now, nearly two years later, the story of how the native team built the Android app, why we made the architectural decisions we did, and what we learned.

This will be from the team’s perspective, but much kudos to Kickstarter alum Christopher Wright and Brandon Williams for masterminding our base FRP architecture and pushing our team to open source both native apps in their entirety.

Contributions to master, 2015–2016.

Putting the fun in functional reactive programming

RxJava has been a core design dependency since the very beginning of the project. Our team liked how functional reactive programming (FRP) was rooted in mathematical principals, primarily the framework of thinking in functions: modular, composable relations between inputs and outputs with no side effects or mutability.

Why is an Android or iOS app a good candidate for FRP? Unlike web development, native app development includes implementing the lifecycle of the native device’s operating system. In short, every app is a jungle of lifecycle callbacks, memory management, and asynchronous tasks which are a breeding ground for mutable variables and state. Mutability often makes code hard to read and impossible to test, but by designing our app in a Model View ViewModel (MVVM) pattern using RxJava, we were able to test our app’s business logic easily — more on that soon.

MVVM MVVM good!

We designed the app in an MVVM pattern with the hope of keeping all business logic in ViewModels. The theory was to keep our ViewModel classes agnostic of their View classes, and vice-versa: each BaseActivity had a ViewModel instance and was responsible for pinging the ViewModel’s input functions with the data as defined and using the output values as described. We named our outputs very explicitly, e.g. startProjectActivity, so both the View and the developer would know exactly what the output should do. If you’d like to see more specifics on our ViewModel style, read this.

This input-output ViewModel structure allowed seamless data flow between a View (an Activity, ViewHolder, or Fragment) and its ViewModel class via explicitly defined pipelines. In the ViewModel class, the resulting business logic code is often quite readable:

https://medium.com/media/c88c1e90669ed2e42da024115094bf69/href

RxJava’s library of Observable types were the key to bridging the world between the imperative View and the functional ViewModel. We fed our input PublishSubjects with the appropriate Java types and subscribed to output Observables or BehaviorSubjects, which allowed for us to observe the history of their values emitted and poll the latest values to live-update our Views.

Extracting our logic from the View by separating inputs from outputs not only allowed for us to break down and remove if/else chains and async networking tasks, used too fondly in Android development, from our Views, but also allowed us to write lightweight unit tests for each output using Robolectric.

Test driven development 😱

The moment we wrote our first ViewModel test solidified the good that we had stumbled upon with RxJava and MVVM design. Our theoretical beliefs and proof-of-concept white boarding sessions paid off right then: the reactive architecture we felt we had hammered into our Android app not only worked, but also made testing both feasible and fun. We used trusty ol’ Robolectric and RxJava’s TestSubscribers to test our outputs, with mock data including Model factories and a MockApiClient.

What’s so fun about this? Well, aside from the sheer excitement of having thorough tests in an Android app, tests that read clearly like scripts for each screen’s interactions, they were fast to write, fast to run, and helped us develop in a TDD manner even under deadline pressures. During our 1.0 development we seldom merged a new ViewModel pull request without accompanying tests; the ViewModel looked lonely, and investing the small cost of a test file upfront has accounted for our <0.01% crash rate, which to this date our small Android team is very grateful for.

In fact we love testing so much that we’ve given several talks on how we do it, check them out!

Dependency injection

Although RxJava has been the backbone of both our Android app and this blog post, we did make many non-FRP design decisions that have served us well. We used Dagger from the start for dependency injections, and have since created a global Environment parcelable class to holds our global values including the current user, api client, and currency formatter. The Environment is provided to base ViewModels to reduce scattered dependency injections, but Views still use Dagger to unwrap its required dependencies.

thanks for the meme idea, jules

One fragment

Can a generalist Android post be published without mentioning fragments? Well we have one for our discovery’s sorting ViewPager, we love her, and it was a joy including Fragments into our MVVM design. We simply added a BaseFragment and a base FragmentViewModel modeled after our BaseActivity and ActivityViewModel, with a few more lifecycle callbacks and arguments() instead of an intent(). ☺︎

Takeaways

A long long time ago, back in 2014, building an Android app from scratch with a deadline using RxJava was a challenge in itself. The minimal online resources and having to relearn how to debug RxJava operators in Android Studio resulted in many ViewModel iterations to feel solid with our architecture— in the beginning we named our logic classes Presenters but later switched to ViewModel and MVVM from our not-quite MVP design. We realized only after releasing 1.0 that by binding ViewHolders to the lifecycle, they could also instantiate their own ViewModel classes. We’re still working on modernizing many of our classes with these iterations, with the help of our open source community!

body[data-twttr-rendered=”true”] {background-color: transparent;}.twitter-tweet {margin: auto !important;}

function notifyResize(height) {height = height ? height : document.documentElement.offsetHeight; var resized = false; if (window.donkey && donkey.resize) {donkey.resize(height);resized = true;}if (parent && parent._resizeIframe) {var obj = {iframe: window.frameElement, height: height}; parent._resizeIframe(obj); resized = true;}if (window.location && window.location.hash === “#amp=1” && window.parent && window.parent.postMessage) {window.parent.postMessage({sentinel: “amp”, type: “embed-size”, height: height}, “*”);}if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.resize) {window.webkit.messageHandlers.resize.postMessage(height); resized = true;}return resized;}twttr.events.bind(‘rendered’, function (event) {notifyResize();}); twttr.events.bind(‘resize’, function (event) {notifyResize();});if (parent && parent._resizeIframe) {var maxWidth = parseInt(window.frameElement.getAttribute(“width”)); if ( 500 < maxWidth) {window.frameElement.setAttribute("width", "500");}}

Luckily our team of two iOS and two Android engineers agreed from the start that functional reactive programming was well worth the daunting learning curve. From a non-mathematical background, I went in with the attitude of “anything is better than the current Android development standard, I’m in!” Despite our late nights dissecting extraneous API flatMap calls only to learn that hot and cold observables were a thing, and moments of “let’s hope our fancy functional code will be…functional,” writing tests, finding patterns, and drawing debugging diagrams solidified our team’s understanding of and passion for FRP. Our architecture became a solid, organized codebase that enabled our team to ship the app in about ten months with localization, test coverage, and accessibility.

After shipping Android, we realized that we liked the project’s MVVM architecture, ease of development, and testing so much that we were inspired to rewrite our iOS app in Swift with the same MVVM, Rx, and TDD design. Check out our ios-oss and android-oss repos and see how similar our ViewModels are — write your business logic once, reuse everywhere: why limit that to one platform??

The Future™

Today the Android team continues to develop core features and drive design. We hope to continue improving and modernizing our MVVM and RxJava infrastructure, make the app feel alive with animation, add screenshot testing, write more Kotlin code — whatever it takes to deliver a fantastic native Android experience to backers and creators alike!

If any of this sounds cool to you, we’re hiring.


onCreate()-ing Kickstarter’s Android app was originally published in Kickstarter Engineering on Medium, where people are continuing the conversation by highlighting and responding to this story.

Source: Kickstarter