A long time ago, in a galaxy far, far away we released Tropos for iOS : a simple weather conditions app. Today, from this galaxy, I am thrilled to announce that we have released Tropos for Android.
Backstory & Design
Tropos was designed with a single question in mind:
“What does it feel like outside?
Tropos answers this by relating the current conditions to the conditions at the same time yesterday. It turns the weather into information you can act on.
Code
A few months after we released Tropos for iOS we open sourced the code for the app. And since anything iOS developers can do, Android developers can do better(πͺ), we are making the source code available on GitHub today!
For those of you who didn’t immediately click on the link to the code and are still reading this, hello! π Thanks for sticking around! As a consolation prize, here are some of the technical specs of the app that might make you want to go back and click that link!
Language
We decided to write the entire app (including all the tests) in Kotlin. While have used Kotlin in a few of our client apps, we were excited to try it out with such a design and custom view heavy app. Custom views turned out to be a fantastic learning tool and an interesting way to test out the merits and interoperability of a new language. Unlike model objects, or generic logic classes where the developer has total freedom when it comes to how to write and design their code, custom view classes come with stricter rules enforced by the API and design of the framework.
Architecture
This app is relatively small in terms of backend logic (there are only two network requests) and number of screens (there’s only one). So we decided to have a little fun with our architecture and not strictly adhere to any single pattern. We knew we wanted optimize for testability, so we decided to take the best of both MVP and MVVM and mix them in with some of Kotlin’s fancy language features like, Delegated Properties.
For example, the main activity of the app, aptly named MainActivity
, passes
all of it’s business logic off to a presenter class, MainPresenter
. As with
any classic MVP app, the presenter tells the view (in Android’s case, the
Activity
), what to do via a view interface. But rather than have several
methods for each individual view update (i.e. setTitle()
, setSubtitle()
) we
have only a single variable in our MainView
interface - viewState
.
viewState
is of the type ViewState
which is a sealed class that has
subclasses for each of the different states our main screen could be in -
loading, showing the weather, and an error state. Sealed classes are Java enums
on steroids π, but if steroids compiled on the JVM. They allow each case to
have their own associated values which can be passed through at run time - very
similar to how Swift enums work. So for us, that meant that each ViewState
could pass along their own ToolbarViewModel
which we could build at runtime.
For example, the Weather
case is instantiated with a WeatherToolbarViewModel
which uses the users current location as the title.
This approach also made our unit tests more flexible. The MainPresenterTest
now only has to check that our view is in the expected state:
verify(view).viewState = isA<ViewState.Weather>()
While the WeatherToolbarViewModelTest
handles all the testing for what the
expected values should be:
@Test
fun testSubtitle() {
val viewModel = WeatherToolbarViewModel(context, mockCondition)
val expected = "Updated at 4:16 PM"
val actual = viewModel.subtitle()
assertEquals(expected, actual)
}
This means that down the line if we added more views to a particular state, i.e. we added a fancy icon to the toolbar, the presenter test remains the same and only the view model test has to be updated.
Dependencies
In case all these patterns and approaches weren’t enough, when it came time to design our networking layer, we decided to add one more paradigm - Rx (reactive programming). Because the entire app stems from a single piece of information - the weather - the reactive paradigm felt like a good fit. To that end, we used RxJava2 in conjunction with Retrofit as our networking client.
Custom Views and Layouts
As aforementioned, we really wanted to use this app as a way to explore all the
features of Kotlin and test it against as much of the Android framework as
possible, so when it came time to the views and interactions in the app we
wanted to make as much of it from scratch as possible. The hardest challenge was
the custom
PullToRefreshLayout
. It has several (literal) moving pieces - from the content on the screen that
actually shifts down, to the animating striped background to the progress wheel
that animates based on how much the user is dragging their finger.
But there were also a few smaller components that we also wanted to get just
right. At the bottom of the screen we show the current wind conditions along
with a quick overview of the day’s high, low, and current temperatures. While
the standard Android TextView
does allow you to set an image within the
TextView
widget it doesn’t allow for exact placement or alignment. While we
could have used a simple LinearLayout
to display the icons along side the
text, we wanted to keep the layout hierarchy as flat as possible,
because #Perfmatters ππ so we made DrawableTextLabel
to allow us control
over the vertical alignment and size of the icon.
Download
Download or check out Tropos today!
Visit our Open Source page to learn more about our team’s contributions.