Blog
Pour les passionnés…

Hilt

Chez v-Labs les plus récents projets ont été créer en utilisant le principe d’inversion de contrôle.
Divers outils ont été utilisé à cette fin : Dagger, Kodein et plus récemment Koin.
L’inversion de contrôle pour faire court est la création de dépendance d’une classe en dehors de cette dernière, les dépendances lui sont passé via le constructeur au moment de son instanciation.
Ce faisant, les classes créer sont alors plus modulaire et plus facile à tester.

Kodein et Koin utilisent le pattern de Service Locator pour lequel une classe est chargé de de transmettre les dépendances là où elles sont nécessaires
Dagger de son côté utilise l’injection de dépendance. Les dépendances sont alors résolues au build time.
Forcement cela rallonge le build, ce qui est souvent reproché à Dagger, mais cela permet néanmoins de se rendre compte des erreurs en amonts, contrairement aux précédentes librairies pour lesquels la résolution est au runtime et les erreurs de même.

Malgré cette sécurité proposé par Dagger, ce dernier est souvent mis de cote et cela a été notre cas du fait de sa complexité, de sa courbe d’apprentissage et de la quantité de boiler plate nécessaire à son utilisation. Il est fastidieux de maitriser ce Framework mais aussi de comprendre les erreurs de build.

Récemment Google à annoncé une nouvelle librairie pour Jetpack basé sur Dagger et ayant pour but de palier à ces problèmes.
Hilt est actuellement en Alpha mais on peut déjà voir une nette amélioration par rapport à Dagger.

Avec Hilt il est toujours question d’utiliser les annotations afin de générer notre code et de nouvelles annotations font forcement leur apparitions avec parmi les plus importantes:

@HiltAndroidApp qui permet l’initialisation du framework et qui se placera forcement sur notre Classe Application et évitera d’initialiser Dagger a la main.
@AndroidEntryPoint qui permet d’injecter dans nos classes (Activity, Fragment, View, Services, BroadcastReceiver)
@EntryPoint pour toutes les autres classes non supporte par la précédente annotation.
@ViewModelInject Comme pour Koin, Hilt se pare de son mot clé pour les ViewModel

On retrouve toujours @Inject  qui permet l’injection de constructeur et de champs et également @Provides dans nos modules pour les types ne pouvant pas être injecté via le constructeur (Généralement toutes les dépendances externes au projet)

La plus grosse différence avec Dagger est la présence de Component prédéfinis. Tous ces components auront pour but d’injecter les dépendances définies dans nos modules dans les classes annoté avec @AndroidEntryPoint/@EntryPoint.

 

Hilt Components et scopes
Hilt Components

On comprend aisément quel component utilise avec quel type de classe sauf peut-être pour@ActivityRetainedComponent dont le but principal est de garder les instances malgré les changements de configuration

Comme pour Dagger, on retrouve toujours nos Module a la différence près que l’on utilisera @InstallIn pour spécifier les components dans lequel installer le module

Au sein de nos modules il sera alors possible de préciser le scope, à savoir si l’on souhaite ou non conserver l’instance (Scoped) ou bien la recréer à chaque fois.

En clair, une instance note @FragmentScoped sera toujours la même dans le fragment demandé, cependant elle sera bien évidement différente dans un autre fragment. Afin de la rendre disponible dans tous les fragments il sera nécessaire de changer le scope.

Voila pour un tour succins de Hilt, mais rien ne vaut la mise en pratique avec la migration d’un de nos projets de Koin a Hilt pour se rendre compte des éventuels problèmes que l’on peut rencontrer.

La mise en place est plutôt simple mais dés lors que l’on souhaite passer des interfaces cela se complique, il est alors nécessaire d’utiliser @Binds (comme auparavant avec Dagger) au lieu de @Provider et notre module doit alors être abstract alors qu’auparavant il s’agissait d’un Object
Pour bien différencier chaque implémentation de notre interface, il est nécessaire d’utiliser un @Qualifier, une annotation custom. Cette nouvelle annotation devra alors être utilise au moment de l’injection dans notre classe pour spécifier l’implémentation attendue.

Extrait du codelab :

@Qualifier
annotation class InMemoryLogger

@Qualifier
annotation class DatabaseLogger

@InstallIn(ApplicationComponent::class)
@Module
abstract class LoggingDatabaseModule {

    @DatabaseLogger
    @Singleton
    @Binds
    abstract fun bindDatabaseLogger(impl: LoggerLocalDataSource): LoggerDataSource
}

@InstallIn(ActivityComponent::class)
@Module
abstract class LoggingInMemoryModule {

    @InMemoryLogger
    @ActivityScoped
    @Binds
    abstract fun bindInMemoryLogger(impl: LoggerInMemoryDataSource): LoggerDataSource
}

class LoggerLocalDataSource @Inject constructor(private val logDao: LogDao){…}

@AndroidEntryPoint
class ButtonsFragment : Fragment() {

    @InMemoryLogger
    @Inject lateinit var logger: LoggerDataSource
    …
}

Bon jusque-là tout va bien premier problème résolut facilement grâce au codelabs que je vous invite fortement à suivre pour vous familiariser avec Hilt.

Finalement ma migration s’est trouve stoppé, suite a la nécessité d’injecter un paramètre dynamique dans mon constructeur de viewModel. Pour le moment, à moins d’avoir raté cette information Hilt contrairement a Dagger ne permet pas de le faire.

Forcement en faisant cette migration et du au manque de maitrise de la librairie, j’ai pu faire quelques erreurs mais contrairement à mes souvenir de Dagger, la stacktrace me semble nettement plus compréhensible et il est plus simple de debugger l’application.

Hilt est encore en Alpha mais il clair que pour le moment les promesses sont tenues et l’utilisation de Dagger est nettement facilité, de plus du fait de son intégration à jetpack, son évolution par rapport aux autres lib de jetpack pourrait apporter plus de cohésion avec ses dernières, on attend avec impatiences la release officiel pour en savoir plus sur ce que pourrait apporter Hilt dans notre processus de développement