Make your app modular!

Jarosław Michalik

City App

Generic app for concerts, conferences, city events...

Offers, partners, localization, events

  • News
  • City transport
  • Notifications
  • Partners & offers
  • Gamification
  • Social media integration

One project - many cities

Multiple deployments

handled with gradle flavors

                    
productFlavors {
    cityOne {
        applicationId "com.company.smartcity.cityOne"
        resValue("string", "app_name", "cityOne")
        versionCode 33
        versionName "$googlePlayVersion"
    }
    cityTwo {
        applicationId "com.company.smartcity.cityTwo"
        resValue("string", "app_name", "cityTwo")
        versionCode 20
        versionName "$googlePlayVersion"
    }
    cityThree {
        applicationId "com.company.smartcity.cityThree"
        resValue("string", "app_name", "cityThree")
        versionCode 8
        versionName "$googlePlayVersion"
    }
}
                    
                

Each city has different

  • server urls
  • transport options
  • languages available
  • layouts

published separately to Google Play

But...

What if one client wants some extra feature?

Or doesn't want some at all

Check at runtime

                    
    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            if (BuildConfigHelper.isCityOneFlavor()) {
                 addNewsToDrawer()
            }
        }
    }
                    
                

Unused code in production!

New task - add city game

Gamification - that's what we do!

Write new code within existing app?

Create new app?

Instead - create module

Gamification module has it's own

  • business logic
  • views
  • build.gradle
  • networking
  • database

Resources (strings, drawables, styles) can be overridden in main app

Shared dependencies:

  • standard and support libraries
  • RxJava
  • Retrofit
  • Dagger

Module can be easily integrated into other app

do not enforce architecture

App can work flawless without module

create some layers of abstraction

Each module can become separate app

create complete business logic

config should be simple as possible

How does it looks like?

                
    Gamification.Builder()
        .with(app) //com.android.Application
        .httpClient(client) //endpoint and interceptors configured
        .database(dbName) //realm file name
        .notificationHandler(fcmHandler)
        .specialOffersHandler(offersHandler)
        .build()
                
            

So what is module?

something, that provides business logic

something, that can be used many times in different projects

almost "microservice"

Creating module in Android Studio

settings.gradle

                    
    include ':app', ':gamification'
                    
                

app-level build.gradle

                    
    dependencies {
        (....)
        compile project(':gamification')
    }
                    
                

One package per functionality

"ui" package does not contain separate business logic

Almost good...

Perfect?

Not perfect, just a good start

Compile only things you need

                    

//compile for all flavors and build types
compile supportDependencies.values()


//only test builds
testCompile jUnitDependencies.values()


//compile in city one flavor
cityOneCompile gameDependencies.values()
cityOneCompile newsDependencies.values()


//compile only in city two flavor
cityTwoCompile newsDependencies.values()

                    
                

Compile different configurations

                    
dependencies {
    //compile debug version of news module only to city one debug build
    cityOneDebugCompile project(path: ':news', configuration: 'debug')

    //compile release version of news for all city two build types
    cityTwoCompile project(path: ':news', configuration: 'release')
}
                    
                

Gradle tips - use dependency only during compilation

                    
provided codeGenerationTools.values()
                    
                

Manage dependencies the good way

                
ext {

    def RxJava2Version = '2.0.6'
    def RxAndroid2Version = '2.0.1'
    def Retrofit2Version = '2.3.0'
    def OkHttp3Version = '3.8.0'

    rxJava2Dependencies = [
            rxJava   : "io.reactivex.rxjava2:rxjava:${RxJava2Version}",
            rxAndroid: "io.reactivex.rxjava2:rxandroid:${RxAndroid2Version}"
    ]

    retrofit2Dependencies = [
            retrofit             : "com.squareup.retrofit2:retrofit:${Retrofit2Version}",
            retrofitRxJavaAdapter: "com.squareup.retrofit2:adapter-rxjava2:${Retrofit2Version}",
            okhttp               : "com.squareup.okhttp3:okhttp:${OkHttp3Version}",
            okHttpLogging        : "com.squareup.okhttp3:logging-interceptor:${OkHttp3Version}",
            gsonConverter        : "com.squareup.retrofit2:converter-gson:${Retrofit2Version}"
    ]
}


                
            

top-level build.gradle

                    
apply from: 'dependencies.gradle'
                    
                

module-level build.gradle

                    
compile rxJava2Dependencies.values()
                    
                

Ultimate goal - share modules across projects

Share module between various apps!

Separate app for module

e.x app displaying only news

Separate git repo for module

Teams can work independent

Prepare tests

mock backend for module

Keep API models for your module

RAML (Rest Api Modelling Language)

https://github.com/isaacloud/local-api Simple mock backend - ready to use with module demo app

Module sharing

git clone from module repo...

or...

Use maven artifactory!

                    
dependencies{
    compile "com.yourcompany.modules:gamification:1.0.2"
    compile "com.yourcompany.modules:news:1.2.4"
}
                    
                
https://www.jfrog.com/open-source/

Don't want something to be public?

Android needs Activity as public class

                    
@RestrictTo(RestrictTo.Scope.LIBRARY)
public class InternalActivity extends AppCompatActivity{...}
                    
                

IDE will notify you that you shouldn't use it outside of module

Share module between technologies

                
public class ToastModule extends ReactContextBaseJavaModule {
    private static final String DURATION_SHORT_KEY = "SHORT";
    private static final String DURATION_LONG_KEY = "LONG";

    public ToastModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }
}
                
            

Prepare flavor of your module with ReactNative API

https://facebook.github.io/react-native/docs/native-modules-android.html
                    
Intent intent = new Intent(mContext, UnityPlayerActivity.class);
startActivity(intent);
                    
                

Embed unity game in your app

https://medium.com/@davidbeloosesky/embedded-unity-within-android-app-7061f4f473a

Next level modularization

Facebook...

LinkedIn...

New app for major functionality

Only if you have loyal users

Why you should try modules?

  • Your app is too large
  • You provide similar features to different customers
  • You build app across the teams
  • You want to go open source someday
  • You want to try some new technologies, patterns or architecture

Don't create module if...

you are 100% sure this is one-time project

you don't have to maintain app

you have to meet the deadline

you simply can't afford to refactor your code

How to split application into modules?

  • determine separate business logic and use cases
  • analyze code with https://codescene.io
  • check your backend architecture
  • ask product owner

Summary

  • Don't Repeat Yourself (extreme version)
  • use clean architecture patterns
  • build apps with and bigger components
  • decrease build times

Thank you!

Jarosław Michalik

https://rozkminiacz.github.io/

michalik@protonmail.ch

Q&A