Il boilerplate contiene un app base Android che implementa l'architettura MVVM usando Dagger2, RxJava e Android Databinding.
Il boilerplate contiene il file ruby boilerplatemvvm.rb
che permette di creare un nuovo progetto a partire dal boilerplate fornendo le principali informazioni per l'inizializzazione. Di seguito le istruzioni:
- Entra nella cartella di questo repo
- Rendi lo script
boilerplatemvvm.rb
eseguibilechmod +x boilerplatemvvm.rb
- Quindi eseguilo
./boilerplatemvvm.rb
- Puoi dargli direttamente i parametri seguenti:
a.
-n
/--name
Nome del progetto (es: ProjectName) b.-p
/--package
Package name (es: com.tiknil.projectname) - Oppure:
-h
/--help
Per visualizzare l'help
I principali attori del pattern MVVM sono:
- La View he informa il ViewModel sulle azioni dell'utente
- Il ViewModel - espone flussi di dati rilevanti per la view
- Il Model - astrae la fonte dei dati. Il ViewModel lavora con il DataModel per ottenere e salvare i dati.
A prima vista, il pattern MVVM sembra molto simile al modello Model-View-Presenter, perché entrambi svolgono un ottimo lavoro nell'astrazione dello stato e del comportamento della vista. Il Presentation Model astrae una View indipendente da una specifica piattaforma di interfaccia utente, mentre il pattern MVVM è stato creato per semplificare la programmazione event driven delle interfacce utente.
Se nel pattern MVP il Presenter indica direttamente alla View cosa visualizzare, nel MVVM il ViewModel mostra i flussi di eventi a cui le viste possono legarsi. In questo modo, il ViewModel non ha più bisogno di tenere un riferimento alla vista, come il Presenter fa. Ciò significa anche che tutte le interfacce che il pattern MVP richiede sono state eliminate.
Le View comunicano anche al ViewModel le diverse azioni. Pertanto, il pattern MVVM supporta l'associazione dati bidirezionale tra View e ViewModel e vi è una relazione many-to-one tra View e ViewModel. La View ha un riferimento al ViewModel ma il ViewModel non ha informazioni sulla View. Il consumatore dovrebbe conoscere il produttore, ma il produttore - il ViewModel - non sa, e non gli importa conoscere il consumatore.
La parte event driven richiesta da MVVM viene eseguita utilizzando gli Observable
di RxJava e gli ObservableFields<>
di android-databinding:
- Un riferimento all'utilizzo gli
Observable
di RxJava con il pattern MVVM è possibile trovarlo a questo link: https://medium.com/upday-devs/android-architecture-patterns-part-3-model-view-viewmodel-e7eeee76b73b - Un riferimento sulla configurazione e l'utilizzo del databinding di Android è possibile trovarlo a questo link: https://nullpointer.wtf/android/mvvm-architecture-data-binding-library/
L'Inversion of Control (IoC) è un software design pattern secondo il quale ogni componente dell'applicazione deve ricevere il controllo da un componente appartenente ad una libreria riusabile.
L'obiettivo è quello di rendere ogni componente il più indipendente possibile dagli altri in modo che ognuno sia modificabile singolarmente con conseguente maggior riusabilità e manutenibilità.
La Dependency Injection (DI) è una forma di IoC dove l'implementazione del pattern avviene stabilendo le dipendenze tra un componente e l'altro tramite delle interfacce (chiamate interface contracts).
A tali interfacce viene associata un'implementazione in fase di istanziazione del componente (nel costruttore) oppure in un secondo momento tramite setter.
In ogni caso è generalmente presente un oggetto container che si occupa di creare le istanze di ogni interfaccia; la configurazione di tale container può così influenzare le dipendenze tra i vari componenti.
L'utilizzo della DI è molto utile per la realizzazione di test automatici, infatti modificando il container è possibile mockare le dipendenze che non si desidera testare.
References:
Definiamo in questo capitolo le best practices di Tiknil per l'impostazione del boilerplate TiknilBoilerplateMVVM.
Nel boilerplate corrente è presente l'implementazione di un esempio di implementazione che segue il pattern Model-View-ViewModel: la splashscreen. Nello specifico:
viewmodel.SplashScreenViewModel.java
che rappresenta il ViewModel, estende ilBaseViewModel
dal quale eredita il costruttore. Tutti i viewmodel devono estendere ilBaseViewModel
.view.activities.SplashScreenActivity.java
che rappresente la View, essa contiene il riferimento al viewmodel injettato tramite l'annotation@Inject
. Tutte le activity devono estendere laAbstractBaseActivity
; in egual modo tutti i fragment dovranno estendere l'AbstractBaseFragment
.model.BaseModel
è la classe dedicata all'implementazioni della parte Model del pattern, in base ai modelli richiesti del contesto essa verrà implementata di conseguenza. Tutti i model devono estendere ilBaseModel
.
L'utilizzo dell'android-databinding all'interno del viewmodel di un activity (o di un fragment) è vincolato alla specifica, all'interno del file layout
relativo, del tag layout
e data
come definito di seguito:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".view.activities.SplashScreenActivity">
<data>
<variable
name="viewModel"
type="com.tiknil.boilerplatemvvm.viewmodel.SplashScreenViewModel" />
</data>
<LinearLayout>
... contenuto dell'activity/fragment ...
</LinearLayout>
</layout>
Dove variable.name
è il nome assegnato al viewModel di tipo variable.type
, in questo modo ciascuna proprietà bindabile di un elemento della view (es: EditText
) può essere bindata come specificato di seguito:
<EditText
android:id="@+id/name_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={viewModel.nameText}"
android:visibility="@{viewModel.isHidden ? View.VISIBLE : View.GONE}"
android:textSize="15dp" />
Per ulteriori dettagli in merito all'android-databinding rimandiamo alla documentazione ufficiale: Data Binding Library.
Tra gli observable data object disponibili, quelli utilizzati nel nostro pattern sono gliObservableFields
e i suoi simili ObservableBoolean
, ObservableInt
,... che permettono al binding di collegare un listener all'oggetto associato per ascoltare le modifiche delle proprietà su quell'oggetto.
A questo link si può trovare la documentazione: Data Object
La dichiarazione degli Observable all'interno del viewmodel è la seguente:
// Dichiarazione di un ObservableFields
public ObservableField<String> nameText = new ObservableField<>("Mario Rossi");
// Dichiarazione di un ObservableBoolean
public ObservableBoolean isHidden = new ObservableBoolean(true);
Implementato tramite android-databing utilizzando gli oggetti di tipo Observable
. Essi infatti hanno il metodo set
che consente di modificare il contenuto di una proprietà e apportare il cambiamento sulla UI.
// Setting di un ObservableFields: testo dell'EditText settato a "Luigi Verdi"
nameText.set("Luigi Verdi");
// Setting di un ObservableBoolean: l'EditText viene nascosta
isHidden.set(false);
- Android-databinding mette a disposizione per alcune classe di oggetti UI l'operatore
=
:
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={viewModel.nameText}" />
L'aggiunta di =
alla classe generata dal binding di chiamare il metodo setter di nameText
ogni volta che il testo cambia. Ciò significa che l'ObservableField
relativo al name
contenuto nel viewmodel conterrà sempre il testo presente nell'EditText
.
- Nel caso in cui si richiedesse di eseguire un binding UI => Dato in l'operazione da eseguire da parte del viewmodel fosse più complessa, a seguito di un cambiamento o un evento proveniente della UI, si ricorre all'uso degli oggetti
Observable
diRxJava
all'interno della view. Nell'esempio seguente e nell'implementazione di questo pattern, i listener sugli eventi UI sono stati implementati conRxBinding
:
// Esecuzione del metodo foo() del viewmodel quando viene lanciato l'evento clicks sul `button`
RxView.clicks(button)
.throttleFirst(1000, TimeUnit.MILLISECONDS) // observable attivo in modalità throttle
.compose(bindToLifecycle()) // observable attivo solamente durante il ciclo di vita della view
.observeOn(AndroidSchedulers.mainThread()) // observable attivo sul mainThread
.subscribe(l -> {
getViewModel().foo(); // esecuzione del metodo del viewmodel
});
L'injection viene realizzata tramite le librerie Dagger2 e dagger-android. La documentazione è possibile trovarla a questo link. I componenti principali per la sua implementazione si trovano nel package com.tiknil.boilerplatemvvm.di
:
AppComponent.java
classe definita tramite l'annotation@Component
che tramite la specifica dei moduli utilizzati permette di eseguire l'injection. In questo tipo di implementazione viene definita una sola classeComponent
per l'intera applicazione.AppModule.java
classe i cui metodi forniscono le dependencies. Seguendo le style guide, ciascun provider deve essere definito all'interno del packagecom.tiknil.boilerplatemvvm.services
(es.:ApiService.java
con la relativa interfacciaIApiService.java
).BuildersModule.java
classe astratta che, utilizzando l'AndroidInjection
fornita dalla libreriadagger-android
permette di injettare i viewmodel all'interno delle activity e nei fragment.ViewModelModule.java
classe che implementa il submodule utilizzato inBuildersModule
per la specifica dei viewmodel "providers".
I providers, qui definiti all'interno del package com.tiknil.boilerplatemvvm.services
, hanno un costruttore che, se necessario, viene definito con l'annotation @Inject
in modo tale da esplicitare a dagger la necessità di injettare gli oggetti passati.
Chiamiamo Service una classe dedicata all'esecuzione di business logic legata a una stesso ambito iniettabile come dipendenza ove necessario, tramite corrispettivo ServiceProtocol.
Esempi dei più utilizzati:
- ApiService: dedicato alle chiamate network alle API del server.
- CacheService: dedicato alla storicizzazione di dati (database, portachiavi, file).
- LocationService: dedicato alla gestione del geoposizionamento dell'utente.
- BluetoothService: dedicato alla gestione della comunicazione bluetooth.
- WebSocketService: dedicato alla comunicazione via WebSocket.
- ecc...
La cartella contenente il codice sorgente dell'app avrà la seguente struttura:
|--java
|--com.tiknil.boilerplatemvvm
|-- di # Classi per l'implementazione della dependency injection con Dragger2
|-- model # Tutti gli oggetti model
|-- services # Oggetti che forniscono servizi (providers) come networking, persistenza dei dati, ecc...
|-- utils # Classi di generico aiuto per tutto l'app
|-- view # Le classi che implementano la ui
|-- activities # Tutte le activity eventualmente inseriti in sottocartelle di sezione
|-- fragments # Tutte i fragment eventualmente inseriti in sottocartelle di sezione
|-- viewmodel # Tutti i viewmodel eventualmente inseriti in sottocartelle di sezione
|-- assets
|-- fonts # Contiene i file dei fonts
|-- res # Cartella di resources: color, drawable, layout,...