Considerando la modularizzazione del progetto Android

Pubblicato: 2019-08-21

Quando un progetto raggiunge una certa scala, l'ulteriore lavoro con esso in un singolo modulo diventa meno efficace. Modularizzarlo diventa allora una soluzione efficace.

4 vantaggi della modularizzazione

Prima di decidere sulla modularizzazione, è bene essere chiari esattamente in cosa consiste. I vantaggi di una struttura di progetto Android modulare includono:

Migliore isolamento del codice

Ogni modulo può esporre le sue interfacce API pubbliche e nascondere i dettagli di implementazione. Con un singolo modulo, non puoi essere completamente sicuro che la sua implementazione sia ben nascosta (specialmente in Kotlin, dove non è disponibile un modificatore di visibilità del pacchetto).

Valutazione delle nuove tecnologie più semplice

Quando crei un modulo, puoi controllare il nuovo modello di architettura o la nuova libreria senza influire sugli altri moduli.

Tempi di costruzione del progetto ridotti

La modifica di un modulo richiede la ricostruzione di quel modulo e di altri che dipendono da esso. Guarda esattamente come funziona leggendo questa documentazione: sviluppatori Android. Configurazioni delle dipendenze

Lavoro più conveniente

Diventa più facile analizzare/debug/refactoring pezzi di codice più piccoli e isolati. Inoltre, l'onboarding di nuovi sviluppatori sarà più veloce.

Questi vantaggi sembrano sufficienti per convincerti ad avviare il processo di modularizzazione, ma come si inizia?

#1: Identifica i tuoi moduli e le loro relazioni

Ci sono due approcci per riconoscere i tuoi moduli: per caratteristica e per livello.

Sotto i moduli delle funzionalità, puoi comprendere alcune aree dell'app disponibili per gli utenti (ad es. login, dashboard, profilo ecc.). Queste aree possono essere costituite da un unico schermo o da un flusso di schermi che coprono alcuni processi. I moduli di questo tipo non possono dipendere da moduli dello stesso tipo.

Dopo aver identificato le funzionalità, dovrai sicuramente estrarre le funzionalità comuni richieste da alcuni o addirittura tutti i moduli. Tali moduli possono essere responsabili di livelli di architettura separati (come archiviazione persistente, rete, navigazione, componenti dell'interfaccia utente...) o della logica aziendale dell'elaborazione di alcuni dati utilizzati da un insieme di funzionalità. Questi tipi di moduli sono generalmente chiamati librerie . I moduli della libreria possono creare alberi delle dipendenze.

Oltre ai moduli funzionalità e libreria c'è anche la necessità di un modulo per gestire le connessioni orizzontali tra altri moduli (ulteriori informazioni su questo nel prossimo punto). Questo modulo conterrà una classe dell'applicazione personalizzata e una configurazione dell'inserimento delle dipendenze. Nessun altro modulo può dipendere da questo modulo, ma questo modulo dipende da tutti gli altri nel progetto.

Moduli e loro relazioni nelle app

Tenendo conto delle definizioni di cui sopra, la gerarchia dei moduli può assomigliare a questa:

#2: Configurazione dell'iniezione di dipendenza

Nonostante le dipendenze tra i moduli del progetto, dovresti anche impostare le dipendenze del pugnale. Dagger offre due modi per dichiarare la dipendenza: sottocomponenti e dipendenza dal componente .

La dipendenza del sottocomponente Dagger richiede ai genitori di dichiarare tutti i figli a carico. Tra i moduli del progetto, questo tipo di relazione non funzionerebbe, perché inverte la direzione della dipendenza del modulo del progetto. Ma può essere utilizzato all'interno di moduli di progetto separati.

La dipendenza del componente Dagger è più flessibile perché un figlio può dichiarare di dipendere dal genitore. Ciò rende possibile utilizzare questo tipo di dipendenza tra moduli di progetto separati.

Ad un certo punto potresti scoprire che un modulo necessita di conoscenze limitate su un altro modulo. Un ottimo esempio di ciò può essere la navigazione tra i moduli di funzionalità. Fornire questo tipo di relazione è spesso chiamato dipendenza orizzontale . Per creare questo canale di comunicazione tra moduli separati sono necessari moduli aggiuntivi con interfacce che descrivono questa comunicazione e un modulo che legherà un'implementazione alle interfacce dichiarate.

La configurazione della dipendenza del modulo di progetto per gestire la dipendenza orizzontale è presentata nell'immagine seguente:

Configurazione delle dipendenze del modulo di progetto

Il codice di esempio per tali relazioni è fornito in un progetto alla fine dell'articolo.

# 3: configurazione del grado

Ogni modulo di progetto ha il suo gradle.build, che è praticamente lo stesso tranne per l'applicazione di dipendenze e plug-in aggiunti. Quindi è bello estrarre la configurazione ripetitiva in un file gradle nella directory principale del progetto. Tale file può anche registrare attività gradle comuni per eseguire analisi del codice statico o eseguire unit test.

Il frammento di codice di tale configurazione comune si trova qui:

 afterEvaluate { project -> def isAndroid = project.plugins.hasPlugin('com.android.library') || project.plugins.hasPlugin('com.android.application') setupModule(isAndroid) setupCommonTestDependencies(isAndroid) setupCommonTasks(isAndroid) } def setupModule(isAndroid) { if (isAndroid) { android { compileSdkVersion projectCompileSdk defaultConfig { minSdkVersion projectMinSdk targetSdkVersion projectTargetSdk } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } lintOptions { abortOnError true checkReleaseBuilds false checkAllWarnings true warningsAsErrors true def lintBaseline = file("quality/lint-baseline.xml") if (lintBaseline.exists()) baseline lintBaseline } } } else { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } } def setupCommonTestDependencies(isAndroid) { dependencies { testImplementation "junit:junit:${junitVersion}" testImplementation "org.assertj:assertj-core:${assertJVersion}" testImplementation "org.mockito:mockito-core:${mockitoVersion}" testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:${mockitoKotlinVersion}" if (isAndroid) { androidTestImplementation "androidx.test.ext:junit:${axTestJUnitVersion}" androidTestImplementation "androidx.test.espresso:espresso-core:${axEspressoLibVersion}" } } } def setupCommonTasks(isAndroid) { if (isAndroid) { tasks.register("unitTest") { task -> task.dependsOn(testDebugUnitTest) } } else { tasks.register("unitTest") { task -> task.dependsOn(test) } } }

Conclusione

Questo articolo non è esaustivo né una guida completa per la modularizzazione di un progetto Android. Ma penso che affronti aspetti che dovresti considerare quando inizi la modularizzazione del progetto.

Un pezzo di codice per mostrare una dipendenza orizzontale si trova al link.

Vuoi creare un'applicazione nativa per Android? Scegli Miquido!