$30 off During Our Annual Pro Sale. View Details »

Modularizing Android Applications

Gerard
December 06, 2018

Modularizing Android Applications

Depuis toujours, le développement d’applications Android consistait à développer des projets monolithiques. Mais dès que votre projet commence à grossir, il devient difficile à maintenir et l’ajout de nouvelles fonctionnalités peut devenir délicat. Qui n’a jamais développé une nouvelle fonctionnalité dans un coin d’une application Android monolithique et a constaté des répercussions dans un autre endroit de l’application alors qu’il n’y a visiblement aucun lien entre eux ?

Cette présentation est un retour d’expérience sur la transformation d’un projet Android monolithique vieux de plusieurs années en présentant l’architecture logicielle et Gradle pour concevoir ou migrer vos applications Android vers une approche modulaire en responsabilisant chaque module sur sa fonctionnalité.

Gerard

December 06, 2018
Tweet

More Decks by Gerard

Other Decks in Programming

Transcript

  1. MODULARIZING ANDROID APPLICATIONS
    Gérard Paligot Othman Barhdadi

    View Slide

  2. Othman Barhdadi
    Technical Leader Plus
    @Modis
    Gérard Paligot
    Technical Leader Android
    @Decathlon

    View Slide

  3. PRESENTATION
    Plus

    View Slide

  4. APPLICATION ANDROID
    MONOLITHIQUE

    View Slide

  5. APPLICATION ANDROID MONOLITHIQUE
    • Développement du deeplink
    • Maintenance difficile des
    applications inter-connectées
    • Durée de vie limitée des
    applications inter-connectées
    • Investissement à courte
    durée des marques
    Multiplication des applications

    View Slide

  6. APPLICATION ANDROID MONOLITHIQUE
    Application Plus

    View Slide

  7. APPLICATION ANDROID MONOLITHIQUE
    Application Plus avec les écrans de l’équipement du sportif

    View Slide

  8. APPLICATION ANDROID MODULAIRE

    View Slide

  9. DÉPENDANCES

    View Slide

  10. View Slide

  11. IMPLEMENTATION VS API
    implementation
    noun
    Garde une dépendance locale au
    module déclaré et ne l’expose pas
    dans son api à travers le graphe de
    dépendance.
    api
    noun
    Permet l’utilisation de la dépendance à
    travers tout le graphe de dépendance
    du moment qu’elle est propagée.

    View Slide

  12. OPTIMISATION ET OBFUSCATION

    View Slide

  13. apply plugin: 'com.android.application'
    apply plugin: 'kotlin-android'
    apply plugin: 'kotlin-android-extensions'
    apply plugin: 'kotlin-kapt'
    android {
    compileSdkVersion versions.compileSdk
    buildToolsVersion versions.buildTools
    defaultConfig { … }
    buildTypes {
    release {
    minifyEnabled true
    shrinkResources true
    proguardFiles file('proguard-rules.pro')
    }
    }
    // …
    }

    View Slide

  14. # No rules needed for now.

    View Slide

  15. apply plugin: 'com.android.library'
    apply plugin: 'kotlin-android'
    apply plugin: 'kotlin-android-extensions'
    apply plugin: 'kotlin-kapt'
    android {
    compileSdkVersion versions.compileSdk
    defaultConfig {
    // …
    consumerProguardFiles 'consumer-proguard-rules.pro'
    }
    }

    View Slide

  16. ## Okio
    -dontwarn org.codehaus.mojo.animal_sniffer.*
    ## OkHttp
    -dontwarn javax.annotation.**
    -keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
    -dontwarn okhttp3.internal.platform.ConscryptPlatform
    ## Retrofit
    -keepattributes Signature, InnerClasses, EnclosingMethod
    -keepclassmembers,allowshrinking,allowobfuscation interface * {
    @retrofit2.http.* ;
    }
    -dontwarn kotlin.Unit
    -dontwarn retrofit2.-KotlinExtensions
    ## Kotlin
    -keep class kotlin.Metadata { *; }
    -keepclassmembers class kotlin.Metadata {
    public ;
    }

    View Slide

  17. MODULE PAR L’EXEMPLE

    View Slide

  18. View Slide

  19. CRÉATION D’UN MODULE

    View Slide

  20. class SportEquipmentApplication

    View Slide

  21. class SportEquipmentApplication :
    HasSupportFragmentInjector

    View Slide

  22. class SportEquipmentApplication :
    HasSupportFragmentInjector {
    @Inject
    lateinit var injector: DispatchingAndroidInjector
    fun inject(fragment: Fragment) {
    supportFragmentInjector().inject(fragment)
    }
    override fun supportFragmentInjector(): AndroidInjector = injector
    }

    View Slide

  23. class SportEquipmentApplication :
    HasSupportFragmentInjector {
    // …
    fun onCreate(context: Context) {
    initApp(context).let {
    this.app = it
    app.inject(this)
    }
    }
    private fun initApp(context: Context): AppComponent =
    DaggerAppComponent.builder()
    .application(context.applicationContext as Application)
    .context(context)
    .build()
    // …
    }

    View Slide

  24. package extensions
    import android.content.Context
    import androidx.fragment.app.Fragment
    import feature.SportEquipmentApplication
    private val appSingleton = SportEquipmentApplication()
    val Context.moduleApplication: SportEquipmentApplication
    get() = appSingleton
    val Fragment.moduleApplication: SportEquipmentApplication
    get() = appSingleton

    View Slide

  25. class SportEquipmentMainFragment : Fragment()

    View Slide

  26. class SportEquipmentMainFragment : Fragment() {
    override fun onAttach(context: Context) {
    super.onAttach(context)
    moduleApplication.onCreate(context)
    }
    }

    View Slide

  27. class SportEquipmentMainFragment : Fragment() {
    override fun onAttach(context: Context) {
    super.onAttach(context)
    moduleApplication.onCreate(context)
    }
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    inject(context!!.moduleApplication)
    }
    override fun inject(panoplyApplication: SportEquipmentApplication) =
    panoplyApplication.inject(this)
    }

    View Slide

  28. NAVIGATION INTERNE D’UN MODULE

    View Slide

  29. interface ScreenSaverContracts

    View Slide

  30. interface ScreenSaverContracts {
    interface View {
    fun show()
    }
    interface Presenter {
    fun load()
    }
    interface NavigationListener {
    fun onScreenTouched()
    }
    }

    View Slide

  31. interface SportPageContracts {
    interface View {
    fun show()
    }
    interface Presenter {
    fun load()
    }
    interface NavigationListener {
    fun onProductSelected(id)
    }
    }

    View Slide

  32. class SportEquipmentMainFragment :
    Fragment(),
    SportPageContracts.NavigationListener,
    ScreenSaverContracts.NavigationListener {
    // …
    }

    View Slide

  33. class SportEquipmentMainFragment :
    Fragment(),
    SportPageContracts.NavigationListener,
    ScreenSaverContracts.NavigationListener {
    // …
    override fun onScreenTouched() {
    // navigation inside current module
    show(SportPageFragment(), addToBackStack = true)
    }
    }

    View Slide

  34. class SportEquipmentMainFragment :
    Fragment(),
    SportPageContracts.NavigationListener,
    ScreenSaverContracts.NavigationListener {
    // …
    override fun onScreenTouched() {
    // navigation inside current module
    show(SportPageFragment(), addToBackStack = true)
    }
    override fun onProductSelected(id) {
    // navigation outside current module
    }
    }

    View Slide

  35. NAVIGATION ENTRE MODULES

    View Slide

  36. interface MainComContracts

    View Slide

  37. interface MainComContracts {
    interface Module
    }

    View Slide

  38. interface MainComContracts {
    interface Module {
    fun onBackPressed(): Boolean = false
    fun onUserInteraction() {}
    fun onRefreshModule() {}
    }
    }

    View Slide

  39. class MainActivity : AppCompatActivity()

    View Slide

  40. class MainActivity : AppCompatActivity() {
    private lateinit var currentFragment:
    MainComContracts.Module
    }

    View Slide

  41. class MainActivity : AppCompatActivity() {
    private lateinit var currentFragment:
    MainComContracts.Module
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    if (savedInstanceState == null) {
    replaceFragment(SportEquipmentMainFragment())
    } else {
    currentFragment = supportFragmentManager
    .findFragmentById(R.id.container) as? MainComContracts.Module
    ?: throw IllegalStateException("No fragment found!")
    }
    }
    }

    View Slide

  42. class MainActivity : AppCompatActivity() {
    private lateinit var currentFragment:
    MainComContracts.Module
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    if (savedInstanceState == null) {
    replaceFragment(SportEquipmentMainFragment())
    } else {
    currentFragment = supportFragmentManager
    .findFragmentById(R.id.container) as? MainComContracts.Module
    ?: throw IllegalStateException("No fragment found!")
    }
    }
    private fun replaceFragment(fragment: T)
    where T : Fragment, T : MainComContracts.Module {
    supportFragmentManager.inTransaction {
    currentFragment = fragment
    replace(R.id.container, fragment)
    }
    }
    }

    View Slide

  43. class MainActivity : AppCompatActivity() {
    private lateinit var currentFragment:
    MainComContracts.Module
    }

    View Slide

  44. class MainActivity : AppCompatActivity() {
    private lateinit var currentFragment:
    MainComContracts.Module
    override fun onBackPressed() {
    if (!currentFragment.onBackPressed()) {
    super.onBackPressed()
    }
    }
    }

    View Slide

  45. class MainActivity : AppCompatActivity() {
    private lateinit var currentFragment:
    MainComContracts.Module
    override fun onBackPressed() {
    if (!currentFragment.onBackPressed()) {
    super.onBackPressed()
    }
    }
    override fun onUserInteraction() {
    super.onUserInteraction()
    currentFragment.onUserInteraction()
    }
    }

    View Slide

  46. class MainActivity : AppCompatActivity() {
    private lateinit var currentFragment:
    MainComContracts.Module
    override fun onBackPressed() {
    if (!currentFragment.onBackPressed()) {
    super.onBackPressed()
    }
    }
    override fun onUserInteraction() {
    super.onUserInteraction()
    currentFragment.onUserInteraction()
    }
    fun onTimeoutOccurred() {
    currentFragment.onRefreshModule()
    }
    }

    View Slide

  47. interface MainComContracts {
    interface Module { … }
    }

    View Slide

  48. interface MainComContracts {
    interface Data {
    fun getStoreId(): String
    fun getActualLanguageCode(): String
    fun getActualCountryCode(): String
    fun getDeviceId(): String
    fun getKioskModel(): String
    }
    interface Module { … }
    }

    View Slide

  49. interface MainComContracts {
    interface Toolbar
    interface Data {
    fun getStoreId(): String
    fun getActualLanguageCode(): String
    fun getActualCountryCode(): String
    fun getDeviceId(): String
    fun getKioskModel(): String
    }
    interface Module { … }
    }

    View Slide

  50. interface MainComContracts {
    interface Toolbar
    interface Navigation {
    fun onShowProduct(codeModel: String?)
    fun onAddProductToBasket(codeModel: String?)
    }
    interface Data {
    fun getStoreId(): String
    fun getActualLanguageCode(): String
    fun getActualCountryCode(): String
    fun getDeviceId(): String
    fun getKioskModel(): String
    }
    interface Module { … }
    }

    View Slide

  51. class SportEquipmentMainFragment :
    Fragment(),
    SportPageContracts.NavigationListener,
    ScreenSaverContracts.NavigationListener {
    // …
    override fun onScreenTouched() {
    // navigation inside current module
    show(SportPageFragment(), addToBackStack = true)
    }
    override fun onProductSelected(id) {
    // navigation outside current module
    }
    }

    View Slide

  52. class SportEquipmentMainFragment :
    Fragment(),
    SportPageContracts.NavigationListener,
    ScreenSaverContracts.NavigationListener {
    // …
    override fun onScreenTouched() {
    // navigation inside current module
    show(SportPageFragment(), addToBackStack = true)
    }
    override fun onProductSelected(id) {
    (fragment.activity as MainComContracts.Navigation).onShowProduct(id)
    }
    }

    View Slide

  53. View Slide

  54. NOS OBJECTIFS

    View Slide

  55. NOS OBJECTIFS
    • Team working

    View Slide

  56. NOS OBJECTIFS
    • Team working
    • Découplage de code

    View Slide

  57. NOS OBJECTIFS
    • Team working
    • Découplage de code
    • Contributions externes

    View Slide

  58. NOS OBJECTIFS
    • Team working
    • Découplage de code
    • Contributions externes
    • Unification des applications

    View Slide

  59. NOS OBJECTIFS
    • Team working
    • Découplage de code
    • Contributions externes
    • Unification des applications
    • Réutilisation de code

    View Slide

  60. NOS OBJECTIFS
    • Team working
    • Découplage de code
    • Contributions externes
    • Unification des applications
    • Réutilisation de code
    • Diversité des technologies

    View Slide

  61. DIFFICULTÉS

    View Slide

  62. DIFFICULTÉS
    • Diversité des technologies

    View Slide

  63. DIFFICULTÉS
    • Diversité des technologies
    • Redondance de code

    View Slide

  64. DIFFICULTÉS
    • Diversité des technologies
    • Redondance de code
    • Identification des dépendances

    View Slide

  65. DIFFICULTÉS
    • Diversité des technologies
    • Redondance de code
    • Identification des dépendances
    • Unification de la vision de l'approche modulaire

    View Slide

  66. LET’S THINK ABOUT THE FUTURE…

    View Slide

  67. THE NAVIGATION ARCHITECTURE COMPONENT

    View Slide

  68. APP BUNDLE

    View Slide

  69. MODULARIZING ANDROID APPLICATIONS
    Gérard Paligot Othman Barhdadi

    View Slide