How we migrated to new architecture yougov bulldogjob


It’s been almost a year since the first public release of YouGov app for Android. During this time, we’ve received a lot of feedback and gathered valuable analytics data. With this new insight, we have decided to completely redesign the whole product. While the design team is connecting all the dots and crafting a brand new user experience, we’re taking the opportunity to build the new app from scratch.


Kotlin, do you speak it?!

Even though Kotlin has been around for a while, we remained faithful to Java, as we wanted to stay on a safe, well-documented and stable side of the Android ecosystem. However, this year’s announcement at Google I/O has tipped the scales and timed perfectly with our need to rebuild the app. Long story short: Kotlin is now the second official Android language and we’re jumping on the hype train by committing to a 100% Kotlin codebase.


The switch to MVVM

Our current architecture relies heavily on Model-View-Presenter design pattern. In light of never ending discussions about the superiority of one pattern over another, we were quite happy with MVP, as it allowed us to isolate the business logic from UI to a large extent. The presenter-view interface communication has proved to be an important benefit considering a fast development cycle and frequently updated functional requirements. However, faced with a complete UI redesign, we started looking for other possibilities that would suit our new needs even better.


Another announcement of I/O ‘17 introduced Architecture Components library - and again, it seemed like a well-timed opportunity to leverage on platform’s support by integrating parts of the AC toolbox into the codebase. One of them, the ViewModel class, promised to significantly reduce effort needed to manage lifecycle-related challenges and finally convinced us to make the switch to MVVM.


The foundations

Having abandoned the tab-based navigation, the new app is going to concentrate around the home screen. It is an entry point for most of the user’s journeys and consists mainly of widgets. We think of a widget as an atomic, self-aware and reusable component which provides a particular set of features.



To meet these requirements, each widget needs its own presentation, domain and data layers. Its actual brain is in ViewModel implementation; it knows who to ask for the data, when to ask for it and what to do with it before it is published to a subscribing view. The view’s responsibility is therefore limited to listening for incoming data, presenting it and reacting to user’s actions. Raw data processing, including API access, mapping and storage is split between atomic components and remains transparent for the view model:




By delegating most of the jobs to single widgets, the HomeViewModel’s role generally comes down to requesting widgets from WidgetProvider and feeding the view.



Remote control

A dynamic structure of the home screen creates a natural need to implement the remote config solution. The plan is to move significant portion of logic to the backend, so we can remotely adjust the content for a particular group of users, time of day or any other sensible context by toggling widgets, modifying widget parameters, etc. Consequently, it also opens a possibility to conduct more rewarding A/B tests in the future.


Persistance

We have given up on Realm and chose Room Persistance Library for storage instead. It seamlessly integrates with RxJava 2 and makes the SSOT practice easy to implement. It basically allows us to narrow down the data flow to one rule: when data is fetched from the network, store it in the database, which will then push it as a Flowable to all its subscribers. This is also true for the existing data being updated. The other important benefit of SSOT is support for caching mechanism and, in consequence, offline mode capability.


RxJava 2 + LiveData

To limit the boilerplate related to RxJava’s subscriptions management, we are able to transform the stream into a LiveData wrapper. Since LiveData was not built in compliance with ReactiveStreams specification, this has to be done using the LiveDataReactiveStreams API, for example:

interface SurveyRepository {
 fun getSurvey(): Flowable<Resource<Survey>>
}

class SurveyViewModel(repository: SurveyRepository) : ViewModel() {
 val survey: LiveData<Resource<Survey>> = LiveDataReactiveStreams.fromPublisher(repository.getSurvey())​


Dependency injection

Dagger 2 helps us tie everything up - assign responsibilities to separate modules and provide all necessary dependencies. We’ve encountered one minor obstacle though when trying to inject view models into the Dagger’s graph, so they could be later used in activities and fragments. In order to do this, a custom view model factory had to be implemented.

@Singleton

class ViewModelFactory @Inject constructor(
  private val creators: Map<Class<out ViewModel>, Provider<ViewModel>>) : ViewModelProvider.Factory {

  override fun <T : ViewModel> create(modelClass: Class<T>): T {
    var creator: Provider<ViewModel>? = creators[modelClass]
    if (creator == null) {
      for ((key, value) in creators) {
        if (modelClass.isAssignableFrom(key)) {
          creator = value
          break
        }
      }
    }

    (…)

   return creator.get() as T
  }
}

The factory requires that each class extending ViewModel is bound into a map (multibinding):

@Module abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(HomeViewModel::class) abstract fun bindHomeViewModel(viewModel: HomeViewModel): ViewModel

@Binds
@IntoMap
@ViewModelKey(SurveyWidgetViewModel::class) abstract fun bindSurveyWidgetViewModel(
    viewModel: SurveyWidgetViewModel): ViewModel

(...)

@Binds abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
}​


Finally, a view model instance can be then obtained from ViewModelProviders by using an instance of the custom factory:

val viewModel = ViewModelProviders.of(this, viewModelFactory).get(HomeViewModel::class.java)


This wraps up the current iteration of our ongoing work to deliver the all-new YouGov app for Android.



Borys Wojciechowski
Android Developer