Dependency Injection, MVP, and the all reliable Android Studio IDE.

Aleksei Bingham
strava-engineering
Published in
17 min readNov 8, 2022

First and foremost, thanks for visiting my little slice of the internet! I hope the following is both insightful and comedic as I plan to @inject well constructed jokes throughout this post.

Introduction

Hello! My name is Aleksei and at the time of writing this I’m a rising Senior perusing a Bachelors of Science in Computer Science at the Rochester Institute of Technology. Specifically, I’m focusing on Programming Languages and Tools, a cluster of courses offered by the Golisano College of Computing and Information Sciences at RIT. Clusters are courses that supplement the core Computer Science curriculum allowing students to dive deeper into the topics that spark their interests the most. Below you will find the courses I have and will be taking from this cluster.

CSCI-742 Compiler Construction (Completed)

CSCI-541 Programming Skills in Haskell (Fall 2022)

CSCI-541 Systems Programming in Rust (Spring 2023)

As you can probably tell, I have a love for languages. But don’t be mistaken, computer programming languages are not the only type of languages I enjoy studying. While at RIT I also studied Russian, the language that was almost my native tongue. While my future schedule doesn’t support taking more Russian courses I have continued to study the language on my own time.

Outside of academics, I’m a passionate endurance athlete who trains to compete throughout three seasons: cross country, indoor, and outdoor track & field. I primarily race the 3K, 5K, and 8K and plan to continue my love for racing outside of college out on the roads running the 15K, half, and the marathon.

Enough about me, let’s switch over to talking about the heart of this blog post. The reason why I decided to spend a summer at Strava.

Why Strava?

It goes without saying, I love to run. I competed in cross-country, indoor, and outdoor track and field all of high-school and planned to do the same in college. Surprisingly, I didn’t use Strava throughout high-school. In fact, I don’t think I knew of Strava’s existence in the first place. It wasn’t until I arrived on campus that my new teammates would bug me to install Strava so I could join the team group. It was from that moment on that I was hooked.

There was something special about seeing my teammates’s activities on Strava. Whether I was finishing up a brutal workout or just jogging around the neighborhood I always had something to share on Strava and so did my teammates. Strava provided a feeling of camaraderie and I knew I wanted to be apart of it.

I vividly remember reading other intern blog posts from previous years. The team activities, company mission, and passion for the product showed through each and every medium post I stumbled across. It was for that reason that I had dreamed of landing an internship at Strava before graduation and ultimately would apply as an Android Engineer Intern during the Fall of 2021. In fact, I still have the activities leading up to my offer letter on Strava!

Growth Team

With a couple of weeks until the start of my internship I learned that I would be placed on the growth team. The growth team is all about experimentation in which product managers, designers, and engineers collaborate to drive user retention and subscription purchases. Specifically, I was going to be working on the subscription acquisition team inside growth where the primary goal is to encourage users to subscribe for all of Strava’s awesome features.

The First Week

Throughout my first week I spent the majority of my time learning Kotlin, Android Studio, and basic Android Development all the while meeting my new teammates. With the help of my remarkable mentor, Faraz, I was able to hack together the following path of execution:

Unfortunately, I only had a single thread to work with and many of these procedures were blocking operations

@Target(Dagger.Dependency, RxJava.Dependency and MVP.Design_Pattern)

so needless to say, learning all of this happened over the course of my internship.

While I don’t have the time (or energy) to write about everything I did throughout this internship. I will be highlighting the project that I worked on in significant detail.

My Summer Project

Shortly after my ramp-up period my manager disclosed my summer project with me. I had been assigned to bring student plans to Android. Previously, student plans was only accessible on our website. However, a significant portion of our athletes are also accessing Strava through our iOS and Android apps. Without including student plans on mobile we are potentially missing out on thousands if not millions of athletes.

At first glance, the project seemed trivial with just a few UI changes and a single backend API call. Keep in mind, my previous Android experience is practically nonexistent despite having published some (horrendous) apps that I am too embarrassed to share here. Needless to say I was feeling quite confident.

After a design review with our team’s phenomenal UI/UX designer (huge shoutout to Grace Kim. I will never understand how you come up with these designs) I was given a portion of the overall design and this is what it looked like:

Settings View

Prior to seeing the code firsthand, I had assumed the settings view would be a simple activity with a vertical linear layout using a reusable custom component responsible for rendering each setting option. However, The most interesting part of the settings view was learning how wrong my intuition really was. Instead, I learned all about Android’s Preference hierarchy, the preferred method for building setting layouts.

For example, the XML below using Preferences renders the settings view on the Android device that follows.

<PreferenceScreen
xmlns:app="http://schemas.android.com/apk/res-auto">

<SwitchPreferenceCompat
app:key="notifications"
app:title="Enable message notifications"/>

<Preference
app:key="feedback"
app:title="Send feedback"
app:summary="Report technical issues or suggest new features"/>

</PreferenceScreen>

Easy right? All we need to do is add a single <Preference ... /> tag, put in the correct text and off we go 🚀!

Not so fast… If you recall, the design document shows a “New” icon, text highlighted in Strava orange, and a dismiss button in the top right to remove it from the view permanently when tapped. These little customizations are not easy to do, if not impossible, with the built in Preference. Instead, we need to

  1. Design a standalone layout file for the specific preference containing the “New” notification, dismiss button, text, and additional styling.
  2. Write a custom class that extends the Android Preference class to inflate the layout file we created in part one.
  3. Get reference to the specific items in the layout file such as the “New” notification and write public methods to modify its visibility outside of the class.
  4. Get reference to the entire custom preference and set an
    onClickListener { … } to open a dialog containing the student plan information when it’s tapped!

But before all of that we still need to create another layout file for our new dialog fragment that will appear as a pop-up when the user clicks our new setting preference. The class that will be responsible for displaying the pop-up will be referred to as the StudentPlanDialog for future reference.

Implicit Intents

The Strava Android code-base is neatly organized across several unique modules. In fact, some of these modules can live on its own as a standalone application serving as a testament to how well organized the project is. However, this comes with its own issues as you will later read.

As I started to work on the StudentPlanDialog class it made the most sense to store its implementation inside the subscriptions module as student plans will make most of its appearance around the checkout section of the app. But of course, student plans also appears in settings so we will need to reference the StudentPlanDialog from the settings module. At this point I was unsure about how the importing would work across multiple modules so instead I did what any intern would do. I pretend everything is going to be fine and continued onward until the compiler inevitably rejects my code.

As I’m wiring up the StudentPlanDialog to appear when the user clicks our new settings option I get this lovely error message from inside Android Studio

It was here that I realized I cannot access the class’s public methods without adding the subscriptions module as a dependency to the settings module.

At the time this didn’t feel like a good solution but I went ahead and added the dependency regardless. Shortly after, I grabbed some Java with Gradle for a quick sync-up and before I knew it the JVM was interpreting my byte-code to perfection! In other-words, my feature was working! Although, I did notice a new change appeared in my git diff for the settings.gradle file…

Seeing a change to the Gradle file made my heart sink. After-all, making modifications to any Gradle configurations as an intern isn’t a wonderful feeling, it’s actually quite terrifying. After a few slack messages and reading some articles online I came to the conclusion that my solution was horrible and here’s why.

Prior to adding subscriptions as a dependency, this is what a (heavily) simplified version of Strava’s dependency tree could look like:

Here, in order to run the Strava Android App we first must build the Activity, Feed, Subscriptions, and Settings modules. But, in order to build the Activity module we first need to build the Activity Recording and Activity Upload modules and so fourth. However, After adding subscriptions as a dependency to the settings module our tree transforms into the following one:

Now, in order to run anything related to settings we first need to build and compile the entire subscriptions module. If you think about it, settings has practically nothing to do with purchasing a Strava subscription. In fact, we just slowed down the build time of the project by preventing the compiler from parallelizing these build tasks. To work around this there are two primary methods to pick from: Create a new student plan module or use implicit intents!

For student plans, it didn’t make much sense to create a brand new module as it would only contain a handful of views and a couple kotlin classes. Not to mention, modules are expensive to create and should be thought of as standalone portions of the app. Instead, I created an implicit intent which can be thought of as a broadcast message and setup an intent filter to catch this broadcast and use an activity inside the subscriptions module to handle it. This allowed me to open the pop-up dialog from inside the settings module even though it’s located inside the subscriptions module.

After proofreading the previous paragraph I realized it can be rather confusing to someone who doesn’t know anything about Android development. Hence, I decided to create a stunning and intuitive diagram to better illustrate implicit intents (take notes Grace). This also served as a wonderful time sink on a Friday afternoon.

Implicit intents can be thought of as a request often used for cross app communication. Explicit intents on the other-hand explicitly define who is handling the message by providing the specific Activity as a required argument. Hence, explicit intents do not need an intent filter to resolve who is handling what type of intent as it was already made explicit!

Without implicit intents, every app that wants to access the photo gallery would need to write the logic themselves. Of course this sounds ridiculous and instead we should somehow use the already built photo gallery app to do this. Implicit intents provide the path of communication between the two applications much like an API. Best of all, they can also live in a single application and communicate across the application’s modules. In my project, I used implicit intents to communicate across the settings and subscription modules without needing to modify their dependencies.

To further emphasize implicit intents, here’s a code snippet that initiates a phone call from inside any Android application:

val callIntent: Intent = Uri.parse("tel:5551234").let { number ->
Intent(Intent.ACTION_DIAL, number)
}

Here callIntent can be started using startActivity(...) and will initiate a phone call to the given number 555-1234 using the default dialer app. If you’re lucky enough Big Bird might just pick up.

However, implicit intents alone will only allow us to open the pop-up dialog. If we wish to also access business logic across the two modules then we will need to use Dagger.

Feature Switches and Dependency Injection

@GET(“strava/feature-switches”)

Feature switches are an essential Strava server side service that both web and mobile clients rely on. Imagine you wish to release a brand new feature but are hesitant to release it to all of your users. Maybe you only want 30% of your users to have access to it or maybe you would like a particular age group to see it. Feature switches control what features are available to a particular client. Every user who logs into Strava whether it be on web or mobile receives a list of available features specific to them.

With that being said, student plans needed to be placed behind a feature switch for experimentation purposes. To accomplish this, all I needed to do was

  1. Add student plans as a new feature switch on the backend
  2. Create a StudentPlanHelper class that would encapsulate the business logic needed to check if the feature flag was enabled or not (and some additional strava magic)
  3. Set all student plan user interface elements to be invisible by default
  4. Reference the StudentPlanHelper during setup to conditionally render the student plan UI

As easy as it sounds don’t be fooled, there was yet another hidden road-bump patiently waiting ahead. Recall that the majority of the student plan work resides in the subscription module including the StudentPlanHelper logic. It would be bad practice to copy and paste the StudentPlanHelper logic into both modules separately but we know from experience that if we try to access it inside the settings module we will get the following error

and this would defeat the entire implicit intent workaround, what a shame! Luckily, the brilliant Strava engineers have already solved this problem by using a well known design pattern known as dependency injection.

Dependency Injection

Dependency injection is a design pattern often used without knowing it. Simply put, a class’s dependencies are any other classes that it relies on. Dependency injection is when we construct all of these dependencies ahead-of time and pass it into another classes constructor. Let’s look at a simple example without using dependency injection.

Notice how the Strava class constructs its own dependencies at the time of its own creation. Not only is object instantiation expensive but it leaves Strava tightly coupled with WebApp, iOSApp, and AndroidApp. If Strava wanted to add another dependency such as WatchOS or modify an existing one such as iOSApp. We would have to directly modify Strava’s implementation to support those changes. This also makes unit testing a disaster because we cannot mock the dependencies Strava is using as we don’t get to pass them in during runtime.

Now, let’s look at an example using dependency injection:

In order to build the Strava class it requires three dependencies: WebApp, iOSApp, and AndroidApp. Inside the main function we create each dependency manually and pass them in all at once to the Strava class to construct it. However, this is very verbose and with large and complex class structures it can become hard to follow and maintain.

Introducing Dagger

Dagger is a dependency injection framework that can generate code to remove the verbosity from the example above and much, much more. Dagger manages dependency graphs much like Gradle except these are not Android Studio modules rather classes. The following is a simple example using Dagger and Dagger Annotations to clean up the example from above:

When compiling the project Dagger uses the annotations @Component and @Inject to generate code that resembles a dependency graph. Dagger also generates class files for us to use such as DaggerStravaComponent that uses the generated code to implement the StravaComponent interface! Using Dagger, Dagger’s @Binds annotation, and an interface representing the business logic of the StudentPlanHelper. I was able to provide its implementation across different modules all without adding any dependencies 😎.

But even after all that, there was still one more design pattern that I needed to become comfortable with in order to fully implement student plans.

Model View Presenter

The Model View Presenter (MVP) is a design pattern commonly used in Android Development. When developing Android apps for the first time it’s tempting to write both the business logic and view logic inside a single Android activity. However, this makes unit testing more of a nightmare than it already is. After countless videos and articles about MVP I just couldn’t grasp how Strava was implementing it until I spoke with a different Android engineer. Within a matter of minutes I finally understood the design pattern and was able to build my views using it (thank you Maria Botross 🙌). In fact, I felt confident enough to explain it here!

The generic MVP is made-up of three (hopefully obvious) components: The model, view, and presenter. The Android environment changes things slightly and instead we have an Activity, Model, View Delegate, and Presenter.

Activity: Handles initial setup such as creating a view delegate and binding itself to a presenter as well as routing to other activities. For the sake of simplicity the following example will not showcase activity routing.

Model: Contains objects that represent the possible view events and view states during the lifetime of the host activity. These objects are passed between the view delegate and presenter to initiate business logic such as an API call or make UI changes. These objects can also contain data that can be later extracted and used (thanks Kotlin).

View Delegate: Initializes the view and establishes view events such as click listeners. When an event occurs such as a button click, the view delegate will send a view event to the presenter to do some business logic. Then, the view delegate will receive a state events from the presenter that tells it how to update the view.

Presenter: Responds to view events from the view delegate and handles them appropriately. After handling the view event, the presenter will send back a state event for the view delegate to handle.

Combining these four components makes for highly testable applications and often leads to very easy to read code. At Strava, MVP is used practically everywhere. However, this forced me to get out of my comfort zone and ultimately made me a better Android engineer. After using MVP for an extended period of time it’s hard to imagine writing an app without it.

Ok I lied …

I said I wasn’t going to talk about anything unrelated to my project but I just had to talk about Strava Jams. For those who (somehow) don’t know, Strava Jams is a kick-ass week long event were everyone groups up into teams across the company and hacks together whatever they want! What makes the event so exciting is that your project can be totally unrelated to the app and can be completed in solo fashion 😎 or with a team 🏃‍♂️ 🚴 🏃‍♀️ 🚴 🏃🚴 🏃‍♀️ . Whether you wanna learn Jetpack Compose or build an internal tool the week is yours to decide! Best of all, Strava Jams ends in style with each individual and team presenting their work (if they so choose of course), earning awesome awards, and potentially advancing their feature into production for millions to enjoy. With that being said, let’s dive into what I worked on during Jams week.

#Jams-Breathe

Both the Jams project and Jams team was assembled by a talented product analytics intern, Malhar Khandare! Our team consisted of other interns and full-time members on iOS, Web, Server, and myself on Android paired with a previously mentioned full-time Android engineer, Maria. Malhar’s proposal was to bring mediation to the app as a brand new activity that lives within the recording screen. Without going into the details this was a very daunting task. To put it simply, the recording screen is one of the most complicated portions of the code-base and I had no clue where to begin.

On day one of Jams week I reached out to Faraz for advice and he recommended that I reach out to the Strava engineer who has extensively worked on the recording module, Dave Rozsnyai. Without hesitation, Dave was more than happy to schedule a quick 1:1 over zoom. After the meeting I had a general idea of where my code changes needed to live and, more importantly, what portions of the code I should stay away from. It’s short meetings like this that prove time and time again to be invaluable to us interns.

However, even after receiving advice I continued to find myself struggling throughout all of day two while trying to implement a subset of the feature. I decided to switch gears and implement the feature into a standalone app to help break the problem down into more manageable pieces. This helped me understand all of the moving parts and gave me the confidence that I could bring it into Strava.

For the remaining portion of Jams week I paired up Maria. After several days of rewarding yet frustrating bugs and complications the feature was fully implemented. I even had enough time to write my own animation manager to elegantly render rich animations such as the levitating person and countdown timer (that you will see shortly). These animations were not in the initial design created by Malhar so needless to say it was a fun surprise to share with my team hours before our presentation.

As I’m writing these last couple of paragraphs I realize today is my last day and I only have a couple of hours until I need to submit this post. So, here’s a bunch of gifs and images to showcase our amazing Jams feature!

Navigating to the New Meditate Activity

Starting and Ending the Meditate Activity

Starting, Pausing, and Resuming the Meditate Activity

Still Image of Meditate Activity

As you can imagine, Jams week was an absolute blast for both my teammates and myself. I cannot thank my entire team enough for making this feature come to life during our live demo. Maybe one day meditation will arrive on Strava.

Closing Thoughts

The Strava internship exceeded my expectations, especially for a remote internship. Outside of my project I had the opportunity to meet some amazing members from Strava’s management team including Michael Horvath — CEO & Co-Founder and Claude Jones — VP of Engineering. I also spent a significant amount of time chatting with other interns and the talent team. At times it felt surreal to be surrounded with other individuals who are also striving to be their best in whatever they choose. In fact, and it started to grow on me. I ended up dusting off my road bike, purchasing a cycling jersey, and hit the roads nearly everyday for a ride. I don’t think I could’ve picked a better way to spend my last summer as a college student. It has been a pleasure working with everyone this summer and it wouldn’t be possible without my amazing manager, Amy Sheinhait.

Thank you for a wonderful internship experience Strava, kudos!

--

--

Aleksei Bingham
strava-engineering

Passionate endurance athlete and computer science student