Divide and conquer

Marius Merkevičius
Treatwell Engineering
6 min readJul 4, 2018

--

So we have this app which has quite a few moving parts here and there. We also have some complex views. Here is a real life example for you. We have a calendar (which in itself is quite complex and has a bit of calculations). We wanted to introduce *scaling* feature to it. But tinkering it around and always trying to recompile the widget is a bit pricy. Just changing anything and trying to recompile-package-upload-install-run-navigate takes a bit of time. Not to mention, when when we want to provide *fake* data to it to create it in various forms just to see if its rendering properly. And here is how we approach such environment here at `Treatwell Connect`.

In a nutshell its divide and conquer — we split the app into different modules, thus resulting in separation in its component testing.

Why

When your app is small, it does not have many constraints and it does not make too much sense in splitting app. But when you have quite a bit of components inside of app, most of the time you would probably prefer to have more moving parts that could be tested in isolation. Thats why we split the UI, business logic and all other parts. Here are the most visible benefits for this

  • Compile modules even without the main project working
  • Run unit/integration tests on only the separated module
  • Inject mock data — which might be hard to do in normal circumstances as you may need special case/set up in the main app

After the obvious thingies, there are also more hidden benefits, that for me personally, are even a bit more important.

  • Speed — whenever app is split into multiple modules (depending on dependency tree) you can gain compilation speed as more things can be compiled in parallel. Also whenever you’re rebuilding the project, the already built modules will not be touched, and the also adds to the compilation speed
  • Separation of concern — the view/widget you separate, it naturally does not allow specific business logic to be injected in itself, because you cannot do that easily (you have to make a dependency on the business models/logic). This results in more generic widgets. That brings me to other point…
  • Ability to open source — as the widget is generic and not attached to specific business logic, it can be shared among the community, bringing something back. Also, with a bit of luck, you may even find people that will be using and even fixing your bugs.

How

So the idea is quite simple how to do this. You just need to split your app into multiple modules.

Fictional app

Lets examine how we would do this in some fictional app. Separating more complex app parts (in our case for ex.: Calendar, Some creation form, smart search components) into different modules. Our fictional app might look like this:

Fictional app structure
  • UI component modules
    ui components are your daily UI components that you develop and inject into the view
    calendar, forms are example UI components to be big enough to be separated into different module
    common ui components are components that might be used by both ui components, calendar (kind of middle ground)
  • Other modules that *might* be in the app structure
    business logic some logic that come with the app
    entity models that are so common, that are used by everyone
  • UI test module `app-test` will always have the same dependencies as `app`, but will build separately

Example app

Ok, enough of the fictional app. Lets get our hands dirty. Here you can find a simplified version of our split app. It looks like this:

Example app structure

And here is where you can find the example app: https://github.com/marius-m/complex-views

Here we have our main modules
- `app` module — a fully blown app that we will be shipping to our client
- `app-test` module — a test environment where we test out our UI components
- `app-ui` module — that holds all our complex views, needed by the `app`

How-to

To set up test UI environment, you’ll have to

  • Create `app-test` module. Its just an empty android project, nothing fancy.
  • Split the dependencies into different gradle file, for ex.: dependencies.gradle. You’ll have build.gradle and dependencies.gradle in the `app` module now.
  • Move all the dependencies from `app` module to build.gradle to dependencies.gradle
dependencies {

}
  • Add reference to `app-test` build.gradle with `app` dependency file. As the dependencies.gradle file is in the `app` module, we need a back-reference to the different module.
apply from: ‘../app/dependencies.gradle’
  • Create an activity that holds a view you want to test
class TestActivityTreatwellProgress : AppCompatActivity() {

lateinit var ui: MyActivityUI

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ui = MyActivityUI()
ui.setContentView(this)
}

class MyActivityUI : AnkoComponent<TestActivityTreatwellProgress> {
override fun createView(ui: AnkoContext<TestActivityTreatwellProgress>) = with(ui) {
frameLayout {
customView<TreatwellProgress> {
setShouldAnimate(true)
}.lparams(width = dip(100), height = dip(100), gravity = Gravity.CENTER)
}
}
}
}
  • yeah, on some views we do use Kotlin Anko, but thats another post
  • Add a reference to the activity in the AndroidManifest.xml
<application android:name=".TestApplication">
<activity
android:name=".test.TestActivity"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize"
android:theme="@style/Theme.AppCompat.DayNight"
>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>

<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity
android:name=".test.TestActivityTreatwellProgress"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize"
android:theme="@style/Theme.AppCompat.DayNight"
>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
</activity>
</application>
  • Launch the activity
  • Running different app components on a UI test environment
    — Progress indicator

- Calendar component

Approach

At first it might not be obvious what should be separated. Also on already established project you might not make the separation easy (as the line where the generic and specific business logic is, is always flaky). Also doing this in one swoop might get complicated quite a lot. I would suggest taking this approach:

  • Identify components that could be separated
  • Identify what needs to be moved in order to separate the component
  • Identify when and how complex the change might be
  • Move the parts bit by bit

Don’t be discouraged with working not fully moved component. As a matter a fact, only in time you’ll identify which parts belong where, as this understanding will come later.

I believe this strategy can be applied not only on widgets/views but also on different business components also. Here at `Treatwell` we separate business concerns also, just for the sake of running unit tests without the need to compile the app.

Take away

While this is might seem hard to figure how this kind of approach might be applied for you project, in the longer run, bigger project and more people — this kind of separation gets more rewarding and easier to maintain (and easier on your computer fans).

--

--