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

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. 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
  2. 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.
  3. 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') } } // … }
  4. 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' } }
  5. ## 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>; }
  6. class SportEquipmentApplication : HasSupportFragmentInjector { @Inject lateinit var injector: DispatchingAndroidInjector<Fragment>

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

    super.onAttach(context) moduleApplication.onCreate(context) } }
  10. 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) }
  11. interface ScreenSaverContracts { interface View { fun show() } interface

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

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

    fun onScreenTouched() { // navigation inside current module show(SportPageFragment(), addToBackStack = true) } }
  14. 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 } }
  15. interface MainComContracts { interface Module { fun onBackPressed(): Boolean =

    false fun onUserInteraction() {} fun onRefreshModule() {} } }
  16. 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!") } } }
  17. 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) } } }
  18. class MainActivity : AppCompatActivity() { private lateinit var currentFragment: MainComContracts.Module

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

    override fun onBackPressed() { if (!currentFragment.onBackPressed()) { super.onBackPressed() } } override fun onUserInteraction() { super.onUserInteraction() currentFragment.onUserInteraction() } }
  20. 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() } }
  21. interface MainComContracts { interface Data { fun getStoreId(): String fun

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

    String fun getActualLanguageCode(): String fun getActualCountryCode(): String fun getDeviceId(): String fun getKioskModel(): String } interface Module { … } }
  23. 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 { … } }
  24. 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 } }
  25. 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) } }
  26. NOS OBJECTIFS • Team working • Découplage de code •

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

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

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

    Identification des dépendances • Unification de la vision de l'approche modulaire