Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Modularizing Android Applications

B6bf611a9e4f70a8455cb175339e0587?s=47 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é.

B6bf611a9e4f70a8455cb175339e0587?s=128

Gerard

December 06, 2018
Tweet

Transcript

  1. MODULARIZING ANDROID APPLICATIONS Gérard Paligot Othman Barhdadi

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

    Android @Decathlon
  3. PRESENTATION Plus

  4. APPLICATION ANDROID MONOLITHIQUE

  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
  6. APPLICATION ANDROID MONOLITHIQUE Application Plus

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

    du sportif
  8. APPLICATION ANDROID MODULAIRE

  9. DÉPENDANCES

  10. None
  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.
  12. OPTIMISATION ET OBFUSCATION

  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') } } // … }
  14. # No rules needed for now.

  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' } }
  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.* <methods>; } -dontwarn kotlin.Unit -dontwarn retrofit2.-KotlinExtensions ## Kotlin -keep class kotlin.Metadata { *; } -keepclassmembers class kotlin.Metadata { public <methods>; }
  17. MODULE PAR L’EXEMPLE

  18. None
  19. CRÉATION D’UN MODULE

  20. class SportEquipmentApplication

  21. class SportEquipmentApplication : HasSupportFragmentInjector

  22. class SportEquipmentApplication : HasSupportFragmentInjector { @Inject lateinit var injector: DispatchingAndroidInjector<Fragment>

    fun inject(fragment: Fragment) { supportFragmentInjector().inject(fragment) } override fun supportFragmentInjector(): AndroidInjector<Fragment> = injector }
  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() // … }
  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
  25. class SportEquipmentMainFragment : Fragment()

  26. class SportEquipmentMainFragment : Fragment() { override fun onAttach(context: Context) {

    super.onAttach(context) moduleApplication.onCreate(context) } }
  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) }
  28. NAVIGATION INTERNE D’UN MODULE

  29. interface ScreenSaverContracts

  30. interface ScreenSaverContracts { interface View { fun show() } interface

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

    Presenter { fun load() } interface NavigationListener { fun onProductSelected(id) } }
  32. class SportEquipmentMainFragment : Fragment(), SportPageContracts.NavigationListener, ScreenSaverContracts.NavigationListener { // … }

  33. class SportEquipmentMainFragment : Fragment(), SportPageContracts.NavigationListener, ScreenSaverContracts.NavigationListener { // … override

    fun onScreenTouched() { // navigation inside current module show(SportPageFragment(), addToBackStack = true) } }
  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 } }
  35. NAVIGATION ENTRE MODULES

  36. interface MainComContracts

  37. interface MainComContracts { interface Module }

  38. interface MainComContracts { interface Module { fun onBackPressed(): Boolean =

    false fun onUserInteraction() {} fun onRefreshModule() {} } }
  39. class MainActivity : AppCompatActivity()

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

    }
  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!") } } }
  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 <T> replaceFragment(fragment: T) where T : Fragment, T : MainComContracts.Module { supportFragmentManager.inTransaction { currentFragment = fragment replace(R.id.container, fragment) } } }
  43. class MainActivity : AppCompatActivity() { private lateinit var currentFragment: MainComContracts.Module

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

    override fun onBackPressed() { if (!currentFragment.onBackPressed()) { super.onBackPressed() } } }
  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() } }
  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() } }
  47. interface MainComContracts { interface Module { … } }

  48. interface MainComContracts { interface Data { fun getStoreId(): String fun

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

    String fun getActualLanguageCode(): String fun getActualCountryCode(): String fun getDeviceId(): String fun getKioskModel(): String } interface Module { … } }
  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 { … } }
  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 } }
  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) } }
  53. None
  54. NOS OBJECTIFS

  55. NOS OBJECTIFS • Team working

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

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

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

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

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

    Contributions externes • Unification des applications • Réutilisation de code • Diversité des technologies
  61. DIFFICULTÉS

  62. DIFFICULTÉS • Diversité des technologies

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

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

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

    Identification des dépendances • Unification de la vision de l'approche modulaire
  66. LET’S THINK ABOUT THE FUTURE…

  67. THE NAVIGATION ARCHITECTURE COMPONENT

  68. APP BUNDLE

  69. MODULARIZING ANDROID APPLICATIONS Gérard Paligot Othman Barhdadi