Twitch for Android: From Meme to Dream
Meet PogDroid, our beloved Android app, to watch the latest memes unfold live while on the go. PogDroid started with very humble beginnings, and has seen incredible growth in the past couple of years. We quadrupled the team, redesigned the entire app, and rewrote most of it in only a few months using the latest and greatest Android has to offer.
Buckle up: this is PogDroid’s epic adventure.
Level 1: Meme app with big dreams
Back in March 2017, PogDroid was maintained by a very small team of engineers, and covered the most basic functionality of Twitch: browse and watch a stream with Chat.
Mobile, and Android in particular, was getting more and more users, and the company decided it was time for a serious investment in PogDroid. The plan was to give the app a fresh new look, and build up some important features that were missing. The team grew only a little bit, but were tasked to take on this big project.
As with most apps maintained by a very small team, the code structure was a mess. It became clear that such a big redesign compounded with adding new features could not be built cleanly on top of the current foundation. There was no common pattern to build screens, not a single unit test, and a lot of core classes were more than 3,000 lines of code long, with very complex state management, handling everything from network requests to UI rendering.
We all agreed that in order to make PogDroid what we were dreaming of, we first needed to rethink its foundations.
Step 1: Agree on a common architecture pattern
The first thing we did is come up with a common design pattern to build features and screens. We wanted this design pattern to be:
easy to understand
hard to get wrong
flexible enough to be applied to any feature or screen
easy to unit test
We quickly settled on a straightforward MVP pattern that looks like this:
With these layers in mind, it became easy to categorize the type of classes we needed for every feature:
Typical classes living in the Data Layer:
Typical classes living in the Presentation Layer:
Typical classes living in the View layer:
AdapterBinder (wrapper that manages our RecyclerView adapter)
ViewDelegate (wrapper that inflates and holds our actual Android views)
With this design pattern and injected dependencies (manually, with static create methods for now), writing unit tests for the data and presentation layer became extremely easy, removing any excuse for not testing your code.
The team quickly adopted the pattern and started rewriting all our main screens in this fashion, adding unit tests in the process! It was also easy to onboard new team members to write consistent code with the rest of codebase.
Having a common pattern that everybody agreed on really accelerated the pace of development of new features and screens, and the team got really good at it. In only three months, we had completed the redesign of the app along with big new features, with around 70 percent of our main screens rewritten from scratch, and about 15 percent unit test code coverage. By this point, the team had tripled, but was still quite small.
Level 2: Stronger, but can do better
With PogDroid’s foundations much stronger and consistent and the big redesign project shipped, we could start focusing on modernizing the codebase.
The first big change that we introduced was adopting Kotlin. The day after it got officially supported by Google in June 2017, we committed our first Kotlin class, and we’ve never looked back since then. The whole team adopted Kotlin in a flash, and today, our whole Android repository is more than 70 percent Kotlin code.
We learned a lot along the way, coming up with our own style guide, some useful extension functions, and of course, getting a few Java interop Null Pointer Exceptions in production. The whole team loves Kotlin and agrees that we all write cleaner, more concise code than we did with Java.
The next big shift in our codebase was the introduction to RxJava. Adoption of RxJava took a bit longer compared to Kotlin, as it requires a slight change of mindset. But with internal team presentations, code examples, helper classes, Kotlin extensions, and a healthy amount of mentoring, we got everyone on the team comfortable with leveraging RxJava.
But just like with Kotlin, the team has now fully adopted it and would never look back. We use it for everything from networking to state management to UI events. Callback hells are a thing of the past and so are memory leaks, thanks to convenient automatic disposing using Kotlin extensions. Unit testing state changes involving background tasks also became easier.
Quickly following the Kotlin introduction to the codebase, one of our team members started to push for dependency injection. We chose Dagger2 as our DI framework of choice, and started working on a separate branch to integrate it. Once we had a clean working example and basic dependencies setup, we had a formal presentation on the library benefits, adoption strategy, and demo. After hearing the benefits, the team voted for a broad adoption of it and we distributed tasks among every team member to “daggerize” their features and screens, and prioritized them by having a full “Dagger Day,” where everybody paused their feature work to get familiar with Dagger and integrate it in parts of the app they were familiar with. This process was very successful, and we now have most of our dependencies managed by Dagger which further accelerated our pace of development.
While we were pretty happy with Retrofit handling our network stack, Twitch services were rapidly migrating to GraphQL in an effort to consolidate all our APIs. At the time, the only available GraphQL library for Android was Apollo, which back then was in a pre-alpha state. We had to make the choice of either writing our own GraphQL client, or take the risk of adopting a very early-stage library for something as critical as our network stack.
After consideration, we chose Apollo despite being so early stage, because it’s open source and the team behind it was easily accessible and responsive through Slack. A few experiments and bug reports later, we went all in on GraphQL and successfully migrated about 90 percent of our API calls. We love how flexible we can be with our queries without needing support from our backend teams to expose mobile-specific APIs.
Another area we wanted to improve on is our automated testing. We have a manual QA team, and they were getting more and more overwhelmed with the amount of new features to test, as well as the usual regression testing. We wanted to make their life easier by automating as much regression testing as possible and incorporate it closer to our development process.
In order to effectively write those integration tests, we leveraged Espresso and Kotlin, writing our own components so that they can be used with a simple and readable DSL.
This greatly helped the team to write their own integration tests for their features, and similarly to the “Dagger Day,” we did a few “UI Test days,” where everybody took the day to focus just on writing UI tests. It was a great opportunity to ramp up those who didn’t have much experience and get the ball rolling on writing more UI tests. We now cover most of the regression test cases with a nightly job that helps us catch issues much earlier than before.
Level 3: We made it here, now what?
Fast forward to 2019, PogDroid is in a good place. We know how to write features cleanly in a consistent way, our tech stack is modern and pleasant to work with, and we have enough tests in place that we feel confident taking on big refactors and code changes. But we’re not stopping there. We still have much to do!
Here’s some of the platform projects we’re currently working on:
App Bundles: we just changed our build scripts and release process to deploy App Bundles to Google Play instead of APKs, reducing our final APK size by 44 percent!
Modularization: as we grow the team even more, we want the code boundaries to be clearer than they are now. That means breaking down our app into core and feature modules. This will also help our build times and prepare us to adopt new trends like Dynamic Delivery and Instant Apps.
A more reactive MVP pattern: now that the team is familiar with RxJava and MVP, we’re taking our pattern one step further by using a state/event propagation approach using Rx. This makes unit testing more resilient to code changes and further reduces the coupling between view delegate and presenter.
We also have some other big mobile platform projects in the pipeline:
Video and Chat performance monitoring tools and reports
Automated analytics framework
Revamped experimentation framework
Server-driven UI framework
And more! Stay tuned for more blog posts from the mobile team about those topics.