Ceci est une version article du matériel DevFest. Je vais expliquer chaque étape avec la différence de l'exemple d'application. Nous allons commencer par expliquer pourquoi vous avez besoin d'injection de dépendances, expliquer Dagger Hilt et vous entraîner!
DI a une image un peu difficile, mais pourquoi est-ce nécessaire en premier lieu? Disons que vous créez une application qui lit des vidéos et que vous avez une classe appelée VideoPlayer. La base de données, le codec, etc. sont ** codés en dur dans la classe VideoPlayer. ** **
Code: https://github.com/takahirom/hilt-sample-app/commit/8c36602aaa4e27d8f10c81e2808f0ff452f1c8a4#diff-bbc9d28d8bcbd080a704cacc92b8cf37R19
class VideoPlayer {
//Une liste de vidéos est stockée dans la base de données(J'utilise une bibliothèque appelée Room)
private val database = Room
.databaseBuilder(
App.instance,
VideoDatabase::class.java,
"database"
)
.createFromAsset("videos.db")
.build()
//Liste des codecs utilisables
private val codecs = listOf(FMP4, WebM, MPEG_TS, AV1)
private var isPlaying = false
fun play() {
...
}
}
Cela seul semble assez simple à utiliser.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val videoPlayer = VideoPlayer()
videoPlayer.play()
}
}
Cependant, avec cela seul, les problèmes suivants se produiront pendant le processus de développement.
** Essayez une simple DI pour contourner facilement ce problème! ** **
Voici l'injection de dépendance (DI). En japonais, il s'agit d'une injection de dépendances. Dans un premier temps, je vais vous montrer brièvement comment injecter des dépendances en utilisant un constructeur appelé ** Constructor Injection **. Passez simplement la dépendance au ** constructeur ** de VideoPlayer. Une autre méthode d'injection avec un setter est appelée injection setter.
Exemple d'injection de constructeur Différence: https://github.com/takahirom/hilt-sample-app/commit/a1fdef28515d158577313b90f7c2590bd5905366
** VideoPlayer est simple avec des dépendances interchangeables! ** **
class VideoPlayer(
private val database: VideoDatabase,
private val codecs: List<Codec>
) {
private var isPlaying = false
fun play() {
...
}
}
Si vous utilisez l'injection de constructeur, vous devez d'abord créer une dépendance pour cette classe de votre côté pour l'instancier. ** **
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val videoDatabase = Room
.databaseBuilder(
this,
VideoDatabase::class.java,
"database"
)
.createFromAsset("videos.db")
.build()
val codecs = listOf(FMP4, WebM, MPEG_TS, AV1)
val videoPlayer = VideoPlayer(videoDatabase, codecs)
videoPlayer.play()
}
}
Cela peut être une ** plaque chauffante **, car vous devrez écrire un code comme celui-ci chaque fois que vous créez un VideoPlayer sur différents écrans. Si vous regardez attentivement ce processus, vous pouvez voir qu'il existe ** "processus de création de VideoPlayer" et "processus d'appel de VideoPlayer # play". ** **
"Traitement pour créer VideoPlayer" est juste la logique de création d'autres types et classes, qui s'appelle "** logique de construction **".
val videoDatabase = Room
.databaseBuilder(
this,
VideoDatabase::class.java,
"database"
)
.createFromAsset("videos.db")
.build()
val codecs = listOf(FMP4, WebM, MPEG_TS, AV1)
val videoPlayer = VideoPlayer(videoDatabase, codecs)
Le "processus qui appelle VideoPlayer # play" devient la logique de création de la valeur de l'application et s'appelle "** logique métier **". Appelons ici autre chose que la logique de construction.
videoPlayer.play()
La combinaison de la logique de construction et de la logique métier rend difficile le suivi et la lecture du code. De plus, cela n'a souvent pas beaucoup de sens pour le lecteur de la classe. ** Les bibliothèques d'injection de dépendances (DI) peuvent séparer cette logique de construction de la logique métier. ** **
Le framework crée des instances telles que Activity. ** Par exemple, startActivity créera une instance **. (Vous ne pouvez pas jouer avec le constructeur.)
//L'activité est instanciée par le framework Android
startActivity(Intent(context, MainActivity::class))
Des améliorations ont été apportées, telles que la possibilité de créer des activités avec Factory à partir d'Android API niveau 28, mais ce n'est pas réaliste pour le moment car cela ne fonctionnera pas à moins qu'il s'agisse d'Android 9 ou supérieur.
** 74% des 10 000 meilleures applications utilisent Dagger **, qui est actuellement la principale solution. Cependant, selon l'enquête, ** 49% des utilisateurs avaient besoin d'une meilleure solution DI **.
De quelle solution DI aviez-vous besoin?
Cela semble être la principale raison pour laquelle Dagger Hilt est né.
Dagger Hilt Dagger Hilt est une bibliothèque construite sur Dagger. Vous pouvez utiliser les bons points de Dagger. Il est co-créé par les équipes Android X et Dagger de Google.
Je dois apprendre à Dagger Hilt comment créer un VideoPlayer, comment puis-je l'enseigner?
La façon la plus simple d'apprendre à créer une instance dans ** Dagger Hilt est d'ajouter @ Inject
au constructeur **.
Actuellement, VideoPlayer n'a aucune dépendance.
** Dagger Hilt peut être créé car il instancie simplement cette classe. ** **
class VideoPlayer @Inject constructor() {
private var isPlaying = false
fun play() {
...
}
}
Ensuite, nous devons dire à Hilt que cette application fonctionne avec Hilt.
L'utilisation de @ HiltAndroidApp
dans la classe Application Android vous indiquera que cette application fonctionne avec Hilt. De plus, si vous utilisez ** @ HiltAndroidApp
, Component
sera créé en interne **. Ce ** composant
est la partie qui contient la logique de construction et l'instance créée **. (Je reparlerai des composants plus tard.)
@HiltAndroidApp
class VideoApp : Application()
Il a été dit que le constructeur Activty ne pouvait pas être falsifié, mais en réponse, ajoutez @ AndroidEntryPoint
à Activity.
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
En ajoutant @ AndroidEntryPoint
, nous enseignons trois choses à Hilt.
La variable est annotée avec «@ Inject». Cela signifie qu'il sera injecté à partir de Hilt. ** C'est un formulaire qui enseigne à Hilt "Inject VideoPlayer when Actiivty is created" **. Et vous pouvez faire tout ce que vous voulez avec la méthode onCreate, comme appeler VideoPlayer.
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
//↓ Injecté par onCreate par Dagger Hilt
@Inject lateinit var videoPlayer: VideoPlayer
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
videoPlayer.play()
}
}
Différence: https://github.com/takahirom/hilt-sample-app/commit/6a8a3711808e806e5953712adeb19b11cb73c3a9#diff-bbc9d28d8bcbd080a704cacc92b8cf37R24
Je pense que c'est plus facile à comprendre si vous connaissez un peu le mécanisme du contenu, alors je vais vous l'expliquer.
Les activités avec @ AndroidEntryPoint
sont converties par Hilt.
Entre MainActivity et AppCompatActivity après conversion par Hilt
Contient le ** généré Hilt_MainActivity **.
** Injecté dans le champ dans onCreate de Hilt_MainActivity. ** **
Code converti par Hilt
@AndroidEntryPoint
class MainActivity : Hilt_MainActivity() { // Hilt_C'est l'activité principale
@Inject lateinit var videoPlayer: VideoPlayer
override fun onCreate(savedInstanceState: Bundle?) {
//Injecté sur le terrain dans ce
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
videoPlayer.play()
}
}
Vous pouvez maintenant utiliser le lecteur vidéo dans l'activité principale. Cependant, cela ne permet pas au lecteur vidéo d'accéder à la base de données, etc. Que devrais-je faire?
//↓ Je n'ai pas de base de données, etc.
class VideoPlayer @Inject constructor() {
private var isPlaying = false
fun play() {
...
}
}
Si vous pouvez changer le constructeur de ** VideoDatabase, vous pouvez également ajouter @ Inject
à VideoDatabase et Hilt vous apprendra comment créer une instance. Dagger créera une instance de dépendance, VideoDatabase, puis instanciera VideoPlayer. ** **
class VideoPlayer @Inject constructor(
private val database: VideoDatabase
) {
private var isPlaying = false
fun play() {
...
}
}
class VideoDatabase @Inject constructor() {
...
}
Cependant, cette fois, ce sera une instance créée par Room, vous ne pouvez donc pas ajouter @ Inject
en jouant avec le constructeur. Je ne me dis pas et ne me dis pas comment créer la base de données vidéo suivante sur Dagger Hilt pour cela.
val videoDatabase = Room
.databaseBuilder(
this,
VideoDatabase::class.java,
"database"
)
.createFromAsset("videos.db")
.build()
Nous utilisons donc Module. ** Vous pouvez apprendre à Hilt à créer une instance à l'aide de Module. ** **
C'est juste une classe avec les annotations @ Module
et @ InstallIn
.
Ajoutez une méthode à ce module.
** Il est facile de considérer les méthodes du module comme des recettes de cuisine. C'est une recette pour fabriquer des moules pour VideoDatabase, et j'enseigne cette recette à Hilt. ** **
** Placez cette recette dans SingletonComponent
avec @ InstallIn
. SingletonComponent
signifie ajouter au composant de l'application. ** **
Si vous regardez attentivement la méthode, vous pouvez voir qu'elle dit «@ Provides». Il enseigne à Hilt une méthode qui enseigne comment créer une base de données vidéo.
Lorsque Hilt doit créer une VideoDatabase, il exécute cette méthode et renvoie une instance.
À propos, une instance de la classe de contexte est fournie par @ ApplicationContext
, mais il existe également des classes prédéfinies par Hilt, et Hilt fournit des instances.
Différence: https://github.com/takahirom/hilt-sample-app/commit/c85a6f668a0bf447c0a4b119f4f6d8cc8c2cff80
@Module
@InstallIn(SingletonComponent::class)
object DataModule {
@Provides
fun provideVideoDB(@ApplicationContext context: Context): VideoDatabase {
return Room
.databaseBuilder(
context,
VideoDatabase::class.java,
"database"
)
.createFromAsset("videos.db")
.build()
}
}
Maintenant que nous avons ce SingletonComponent
, parlons un peu plus de Component.
Component
Le composant peut:
A une logique sur la façon de construire un objet. La base de données vidéo est créée comme ceci. .. Tel VideDatabase : Room.databaseBuilder() … .build()
A une logique pour l'ordre d'instanciation. "Générer VideoPlayer après VideoDatabase." Etc.
Réutilisez les instances en fonction de la portée. (Voir ci-dessous)
Hilt a dit qu'il était Opnionné, mais Dagger Hilt a un ** composant standard, vous n'avez donc pas à vous soucier de la structure du composant. ** ** Cette figure montre la hiérarchie des composants et a une structure avec des composants tels que SingletonComponent de l'application entière, ActivityRetainedCompoent qui survit à la rotation de l'écran et ActivityComponent lié à Activity. Les annotations ci-dessus sont des annotations de portée. J'en parlerai plus tard Par exemple, il est livré avec ActivityRetainedComponent, ActivityComponent et FragmentComponent qui survivent à SingletonComponent et à la modification de configuration.
Sur https://dagger.dev/hilt/components
Dans cet exemple, Singleton Component explique comment créer VideoPlayer et VideoDatabase, ainsi que l'ordre de création.
Par exemple, actuellement, la base de données vidéo est instanciée chaque fois qu'elle est utilisée.
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var videoPlayer: VideoPlayer
@Inject
lateinit var videoPlayer2: VideoPlayer
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
println(videoPlayer.database)
// VideoDatabase_Impl@764b474 ← Le code de hachage est différent
println(videoPlayer2.database)
// VideoDatabase_Impl@a945d9d ← Le code de hachage est différent
}
}
class VideoPlayer @Inject constructor(
val database: VideoDatabase
)
@Module
@InstallIn(SingletonComponent::class)
object DataModule {
@Provides
fun provideVideoDB(@ApplicationContext context: Context): VideoDatabase {
return ...
}
}
Vous souhaiterez peut-être recycler les instances pour diverses raisons, telles que la réutilisation des connexions. De plus, bien que cela ne soit pas lié à cet exemple, OkHttp, qui est couramment utilisé dans la communication Android, améliorera les performances si l'instance est utilisée dans toute l'application. (De https://square.github.io/okhttp/4.x/okhttp/okhttp3/-ok-http-client/)
Si vous souhaitez le partager dans Activity, il sera conservé dans ActivityComponent en utilisant ** @ ActivityScoped
**, donc
Vous pouvez réutiliser la même instance dans l'activité. Ce @ ActivityScoped
s'appelle l'annotation de portée.
Vous pouvez maintenant partager l'instance au sein de l'activité. Différence: https://github.com/takahirom/hilt-sample-app/commit/f895dfac123a0317b9e0e247af3a48b57388ad5d
@Module
@InstallIn(ActivityComponent::class)
object DataModule {
@ActivityScoped
@Provides
fun provideVideoDB(@ApplicationContext context: Context): VideoDatabase {
...
}
}
Dans @ ActivityScoped
, si l'activité est différente, ce sera une instance différente. Si vous souhaitez l'utiliser pour l'ensemble de l'application, vous pouvez l'utiliser pour l'ensemble de l'application, car il est conservé par Singleton Component en utilisant ** @ Singleton
Scope Annotation **.
Différence: https://github.com/takahirom/hilt-sample-app/commit/64cc1b50388cf9c79fba26a774ae86efb1f093bc
@Module
@InstallIn(SingletonComponent::class)
object DataModule {
@Singleton
@Provides
fun provideVideoDB(@ApplicationContext context: Context): VideoDatabase {
...
}
}
VideoDtabase contient désormais également des instances dans SingletonComponent.
Les dépendances peuvent être obtenues auprès de Hilt dans les classes MainActivity et VideoPlayer gérées par Hilt, mais il peut être difficile de les obtenir dans des classes non gérées par Hilt. Par exemple, la classe ContentProvider, les classes générées par d'autres bibliothèques, les classes existantes lors de la migration vers Dagger Hilt, etc. Ici, vous pouvez utiliser un mécanisme appelé EntryPoint. ** En utilisant EntryPoint, vous pouvez accéder aux dépendances du composant Hilt. ** **
Voici un exemple de Hilt utilisant VideoPlayer avec une logique de construction dans une activité qui ne peut pas être gérée par Hilt. Différence: https://github.com/takahirom/hilt-sample-app/commit/d66fb46b395b0c9b6a98ff91bd55f3c4f12c99c9
class NonHiltActivity : AppCompatActivity() {
@EntryPoint // @Ajouter un point d'entrée.
@InstallIn(SingletonComponent::class)
interface NonHiltActivityEntryPoint {
fun videoPlayer(): VideoPlayer
}
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
val entryPoint = EntryPointAccessors.fromApplication(
applicationContext,
NonHiltActivityEntryPoint::class.java
)
val videoPlayer = entryPoint.videoPlayer()
videoPlayer.play()
}
}
Vous pouvez écrire le test en créant vous-même l'objet cible nouveau de la même manière que pour écrire un test général. Dans ce cas, vous devez d'abord créer la dépendance vous-même. Différence: https://github.com/takahirom/hilt-sample-app/commit/068082bf7bcb20ecbb1258ac6a3027988d624303
@Test
fun normalTest() {
//Créer un DB en mémoire
val database = Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(),
VideoDatabase::class.java
).build()
val videoPlayer = VideoPlayer(database)
videoPlayer.play()
assertThat(videoPlayer.isPlaying, `is`(true))
}
Hilt vous permet de laisser Hilt créer une instance comme celle-ci ** sans avoir à créer vos propres dépendances. ** ** Mais cette fois, je veux l'utiliser avec la base de données en mémoire au lieu d'utiliser la base de données réelle. Comment feriez-vous cela?
@HiltAndroidTest
class VideoPlayerTest {
@get:Rule
var hiltAndroidRule = HiltAndroidRule(this)
@Inject
lateinit var videoPlayer: VideoPlayer
@Test
fun play() {
hiltAndroidRule.inject()
videoPlayer.play()
assertThat(videoPlayer.isPlaying, `is`(true))
}
En ** @UninstallModules (DataModule :: class)
ici, vous pouvez faire en sorte que Dagger Hilt oublie comment créer une base de données vidéo que DataModule a **.
Ensuite, dans le test **, vous pouvez fournir la base de données en définissant un nouveau module. ** Si vous déclarez Module en dehors du test, il sera installé tout au long du test.
Différence: https://github.com/takahirom/hilt-sample-app/commit/4c862ee62e8dfc133ea6e7e3ff0735c0497cfb6a
@HiltAndroidTest
@UninstallModules(DataModule::class)
@RunWith(RobolectricTestRunner::class)
class VideoPlayerTest {
@InstallIn(SingletonComponent::class)
@Module
class TestDataModule {
@Provides
fun provideVideoDatabase(): VideoDatabase {
return Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(),
VideoDatabase::class.java
).build()
}
}
C'est une histoire pour les gens qui connaissent Dagger, donc si vous ne comprenez pas Dagger, regardez-la.
Je vais aborder étape par étape comment migrer de Dagger à Dagger Hilt.
→ Migrez simplement normalement.
Il existe différents plug-ins pour les outils qui produisent des diagrammes de composants Dagger. Dagger SPI (interface du fournisseur de services) à l'intérieur de Dagger Il existe quelque chose comme l'API de Dagger qui peut récupérer des informations, il est donc recommandé de vérifier avec l'outil qui l'utilise.
https://github.com/arunkumar9t2/scabbard https://github.com/Snapchat/dagger-browser Tel
Et dans une certaine mesure, voyons quels composants sont susceptibles d'être associés à quels composants.
Exemple de diagramme utilisant un fourreau
L'exemple de situation est qu'il existe un composant AppComponent au niveau de l'application, et qu'il existe de nombreux composants pour chaque activité en dessous. (Étant donné que la forme du composant n'est pas normalisée dans Dagger, la forme du composant sera différente en premier lieu.)
Dagger Hilt ne prend pas en charge la création de ce module ** en le transmettant, vous devez donc vous arrêter. ** C'est possible en supprimant l'argument de Module et en le référençant. Il semble que ce n'est pas bon dans le document de Dagger. (Il est dit de ne pas faire cela https://dagger.dev/dev-guide/testing)
NG
DaggerAppComponent
.builder()
.networkModule(networkModule)
.build()
OK
DaggerAppComponent.factory()
.create(application)
Présentez la bibliothèque Dagger Hilt. Consultez la documentation de base pour cela. https://dagger.dev/hilt/migration-guide
Si vous souhaitez migrer petit à petit, vous devez installer disableModulesHaveInstallInCheck
.
Par défaut avec Dagger Hilt, une erreur se produira lorsqu'il y a un module existant qui ne contient pas @ InstallIn
. ** Si vous incluez cette option, vous pouvez simplement inclure la bibliothèque sans obtenir l'erreur. ** **
javaCompileOptions {
annotationProcessorOptions {
// ↓ **Voici+=Notez que s'il n'est pas défini sur, le plug-in Dagger Hilt ajoutera un argument, vous en serez donc accro.**
arguments += [
"dagger.hilt.disableModulesHaveInstallInCheck": "true"
]
}
}
Le ** EntryPoint de Dagger Hilt récupère non seulement les dépendances des composants, mais peut également créer des sous-composants, utilisez donc cette fonctionnalité pour les remplacer **. (Je vais sauter l'explication ici.) Différence: https://github.com/takahirom/hilt-sample-app/commit/8e542f191bb50ce50db30cb2a72a569f7d17b178
@Subcomponent
interface JustDaggerComponent {
@Subcomponent.Factory
interface Factory {
fun create(): JustDaggerComponent
}
fun inject(justDaggerActivity: JustDaggerActivity)
}
@InstallIn(SingletonComponent::class)
@EntryPoint
interface JustDaggerEntryPoint {
fun activityComponentFactory(): JustDaggerComponent.Factory
}
class JustDaggerActivity : AppCompatActivity() {
@Inject lateinit var videoPlayer: VideoPlayer
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
// old: appComponent.justDaggerComponent().inject(this)
val entryPoint = EntryPointAccessors.fromApplication(
applicationContext,
JustDaggerEntryPoint::class.java
)
entryPoint.activityComponentFactory().create().inject(this)
videoPlayer.play()
}
}
En gros, ajoutez @ AndroidEntryPoint
et supprimez le traitement Dagger existant.
Changez la forme de «JustDaggerActivity» en forme de «MainActivity».
Il y a beaucoup d'autres choses à propos de la migration, mais si vous pensez à codelab, veuillez essayer Codelab.
Les bibliothèques qui fonctionnent avec les composants Jetpack tels que ViewModel et WorkManager, qui sont souvent utilisés dans le développement, sont fournies et peuvent être utilisées. Différence: https://github.com/takahirom/hilt-sample-app/commit/1bec3370fec0fd5b4233db1884e8427bcf91a540 ViewModel est souvent utilisé dans le développement d'applications Android. Regardons d'abord ViewModel. Dans ViewModel, des modifications sont apportées au constructeur, mais c'est généralement difficile car il est créé via Provider, Factory, etc., mais ** Dagger Hilt masque bien cette partie et facilite ViewModel. ** **
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val videoPlayerViewModel: VideoPlayerViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
videoPlayerViewModel.play()
}
}
class VideoPlayerViewModel @ViewModelInject constructor(
private val videoPlayer: VideoPlayer
) : ViewModel() {
fun play() {
videoPlayer.play()
}
}
Si vous vous perdez, jetez un œil aux exemples d'applications.
Architecture Samples https://github.com/android/architecture-samples/tree/dev-hilt Application Google I / O https://github.com/google/iosched Sunflower https://github.com/android/sunflower chrisbanes/tivi https://github.com/chrisbanes/tivi
En raison de la forme standardisée de Component dans Dagger Hilt, de nombreux composants tels que SingletonComponent auront des liaisons telles que ce type. En général, Dagger prend plus de temps à instancier à mesure que le nombre de cette liaison augmente. Lorsque Dagger Hilt est inséré, le mode fastInit est activé au lieu du mode normal, ce qui permet de gagner du temps. Cependant, il semble y avoir un compromis dans ce processus, alors vérifions-le avec Firebase Performance, Android Vitals, etc. après la sortie **. ** **
val PROCESSOR_OPTIONS = listOf(
"dagger.fastInit" to "enabled",
https://github.com/google/dagger/blob/d3c1d2025a87201497aacb0a294f41b322767a09/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/HiltGradlePlugin.kt#L108
public final class DaggerApp_HiltComponents_SingletonC extends App_HiltComponents.SingletonC {
private Provider<Context> provideContextProvider;
private Provider<VideoDatabase> provideVideoDBProvider;
private DaggerApp_HiltComponents_SingletonC(
ApplicationContextModule applicationContextModuleParam) {
initialize(applicationContextModuleParam);
}
...
@SuppressWarnings("unchecked")
private void initialize(final ApplicationContextModule applicationContextModuleParam) {
this.provideContextProvider = ApplicationContextModule_ProvideContextFactory.create(applicationContextModuleParam);
this.provideVideoDBProvider = DoubleCheck.provider(DataModule_ProvideVideoDBFactory.create(provideContextProvider));
}
@Override
public VideoPlayer videoPlayer() {
return new VideoPlayer(provideVideoDBProvider.get());
}
fastInit activé Le composant contient maintenant la valeur au lieu du fournisseur qui détient la valeur.
public final class DaggerApp_HiltComponents_SingletonC extends App_HiltComponents.SingletonC {
private final ApplicationContextModule applicationContextModule;
private volatile Object videoDatabase = new MemoizedSentinel();
private DaggerApp_HiltComponents_SingletonC(
ApplicationContextModule applicationContextModuleParam) {
this.applicationContextModule = applicationContextModuleParam;
}
private VideoDatabase videoDatabase() {
Object local = videoDatabase;
if (local instanceof MemoizedSentinel) {
synchronized (local) {
local = videoDatabase;
if (local instanceof MemoizedSentinel) {
local = DataModule_ProvideVideoDBFactory.provideVideoDB(ApplicationContextModule_ProvideContextFactory.provideContext(applicationContextModule));
videoDatabase = DoubleCheck.reentrantCheck(videoDatabase, local);
}
}
}
return (VideoDatabase) local;
}
@Override
public VideoPlayer videoPlayer() {
return new VideoPlayer(videoDatabase());
}
Étant donné que la structure de Component est normalisée dans Dagger Hilt, il est difficile de créer EpisodeDetailComponent et d'y distribuer l'ID de détail de l'écran. Il existe de nombreuses façons de procéder, mais l'exemple de méthode Google semble en être un. ** Il s'agit d'une méthode de passage direct sans distribution à l'aide de Dagger. ** ** Sur la page officielle de création de composants Hilt, il y a une histoire que le contexte est un peu différent quand on parle de tâches d'arrière-plan, mais "en général, il est plus simple et suffisant de passer par vous-même". Pour Assisted Inject, qui est le plus susceptible d'être transmis, vous pouvez en concevoir un peu lors de la transmission à ViewModel.
https://dagger.dev/hilt/custom-components
for most background tasks, a component really isn’t necessary and only adds complexity where simply passing a couple objects on the call stack is simpler and sufficient.
Passer des arguments dans la pile d'appels est plus simple et suffisant, et Component ne fait qu'ajouter de la complexité. Je dis ça.
Architecture Samples https://github.com/android/architecture-samples/blob/dev-hilt/app/src/main/java/com/example/android/architecture/blueprints/todoapp/taskdetail/TaskDetailFragment.kt#L90 Iosched https://github.com/google/iosched/blob/b428d2be4bb96bd423e47cb709c906ce5d02150f/mobile/src/main/java/com/google/samples/apps/iosched/ui/speaker/SpeakerViewModel.kt#L101 Sunflower https://github.com/android/sunflower/blob/2bbe628f3eb697091567c3be8f756cfb7eb7258a/app/src/main/java/com/google/samples/apps/sunflower/PlantDetailFragment.kt#L55 chrisbanes/tivi https://github.com/chrisbanes/tivi/blob/27348c6e4705c707ceaa1edc1a3080efa06109ae/ui-showdetails/src/main/java/app/tivi/showdetails/details/ShowDetailsFragment.kt#L60
Il a été dit que "ID etc. sera passé directement", mais si vous ne pouvez pas le passer avec le constructeur de ViewModel, il sera rendu lateinit ou nullable et ce sera une forme peu dangereuse, non? Une bibliothèque appelée AssistedInject peut être implémentée pour transmettre des arguments de constructeur. (Il semble qu'il sera incorporé dans Dagger dans le futur)
Différence: https://github.com/takahirom/hilt-sample-app/commit/6584808f8fe13cc92317df50d413f828d1dfdf00
Dagger essaie de s'adapter à ce qu'on appelle une injection assistée. Cela signifie que lors de l'injection, le programme peut passer une valeur comme argument. https://github.com/google/dagger/issues/1825
Il existe une bibliothèque qui peut utiliser cela en premier. https://github.com/square/AssistedInject
Plus précisément, vous pouvez transmettre une valeur à ViewModel dans le constructeur à l'aide du contenu essentiel suivant de Googler. https://gist.github.com/manuelvicnt/437668cda3a891d347e134b1de29aee1
Il est difficile de comprendre en substance, donc si vous êtes intéressé par son fonctionnement, veuillez lire ce qui suit. https://qiita.com/takahirom/items/f28ceb7a6d4e69e4dafe
EntryPoint ne semble fondamentalement pas être utilisé dans l'exemple de Google. Je vais l'introduire car il semble être utilisé pour la migration de grandes applications. EntryPoint peut être écrit n'importe où, mais où dois-je l'écrire? Si vous utilisez EntryPoint pour obtenir la dépendance, vous pouvez réduire le nombre d'objets dépendants en obtenant uniquement la dépendance nécessaire, donc en gros, définissons-la à l'endroit pour l'obtenir et l'utiliser. **.
class NonHiltActivity : AppCompatActivity() {
@EntryPoint
@InstallIn(SingletonComponent::class)
interface NonHiltActivityEntryPoint {
fun videoPlayer(): VideoPlayer
}
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
val entryPoint = EntryPointAccessors.fromApplication(
applicationContext,
NonHiltActivityEntryPoint::class.java
)
val videoPlayer = entryPoint.videoPlayer()
videoPlayer.play()
}
}
Je ne pense pas qu'il y ait encore de bonne pratique, mais pensons à Dagger Hilt et aux multi-modules. Supposons que vous ayez la configuration de module suivante.
Le module Gradle qui compile la classe Application Tous les modules et constructeurs Hilt Injecter des classes pour l'injection ** Doit être inclus dans les dépendances transitoires **.
Depuis https://developer.android.com/training/dependency-injection/hilt-multi-module?hl=ja
Vous avez donc besoin d'une référence du module racine au module Gradle avec le module Dagger comme suit **.
En ce qui concerne cette partie, ** je pense que ça va parce que la forme au milieu se déplace dans divers modèles sans dépendances croissantes inutiles **, mais il n'y a pas encore de meilleure pratique.
** Cependant, lorsque vous créez un module, incluez-le simplement dans le chemin de la classe et le module @ InstallIn
sera installé et utilisé dans le composant, il est donc très facile et vraiment facile à utiliser. ** **
référence
Référence du module d'application de l'application chrisbanes / tivi pour Google
Vous pouvez voir que chaque module est référencé à partir du module Gradle de la racine.
Pouvez-vous construire avec Hilt? Ne peux pas? N'est-il pas gênant de créer un environnement pour des expériences comme celle-ci? Si vous souhaitez expérimenter avec une application sur laquelle Hilt est installé, nous avons préparé cet exemple de projet, alors veuillez expérimenter. https://github.com/takahirom/dagger-transitive-playground/tree/hilt
Il existe une page appelée Philosophie de test Hilt, qui décrit les pratiques de test avec Dagger Hilt. C'est assez sûr et intéressant, alors lisez-le https://dagger.dev/hilt/testing-philosophy Voici mon mémo. https://qiita.com/takahirom/items/a3e406b067ad645605da
D'après cela, il y a deux choses que je veux dire.
Si vous essayez de tester normalement et d'utiliser les dépendances réelles, vous obtiendrez une ** plaque chauffante ** pour créer les dépendances. Si vous écrivez que vous avez besoin d'une ViewModel Factory pour créer un PlayerFragment, vous avez besoin d'un VideoPlayer pour créer un ViewModel, vous avez besoin d'une VideoDatabase pour créer un VideoPlayer, et ainsi de suite. .. ** Ça va être difficile. ** **
launchFragment {
//Beaucoup!
PlayerFragment().apply {
videoPlayerViewModelAssistedFactory =
object : VideoPlayerViewModel.AssistedFactory {
override fun create(videoId: String): VideoPlayerViewModel {
return VideoPlayerViewModel(
videoPlayer = VideoPlayer(
database = Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(),
VideoDatabase::class.java
).build()
),
videoId = "video_id"
)
}
}
}
}
onView(withText("playing")).check(matches(isDisplayed()))
Comme présenté, vous pouvez également tester avec Dagger Hilt en faisant ** Injecter avec les dépendances réelles. ** **
@RunWith(AndroidJUnit4::class)
@HiltAndroidTest
@UninstallModules(DataModule::class)
class AndroidPlayerFragmentTest {
@InstallIn(SingletonComponent::class)
@Module
class TestDataModule {
@Provides
fun provideVideoDatabase(): VideoDatabase {
return Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(),
VideoDatabase::class.java
).build()
}
}
@get:Rule
var hiltAndroidRule = HiltAndroidRule(this)
@Test
fun play() {
hiltAndroidRule.inject()
launchFragmentInHiltContainer<PlayerFragment> {
}
onView(withText("playing")).check(matches(isDisplayed()))
}
Pour le moment, vous pouvez tester en utilisant une application personnalisée avec @ CustomTestApplication
.
Si vous créez une classe TestApp à des fins de test, combinez-la avec Hilt
Il existe plusieurs inconvénients. De plus, il semble préférable de l'arrêter en général.
Actuellement, l'exécution du code converti dans Android Studio n'est pas prise en charge, vous devez donc exécuter le test dans ** Gradle. ** (Vous pouvez également définir Gradle dans Android Studio et l'exécuter dans Android Studio.)
Pour plus d'informations: https://dagger.dev/hilt/gradle-setup.html#running-with-android-studio
** Depuis Android Studio 4.1, les configurations d'exécution / débogage peuvent être enregistrées, vous pouvez donc en profiter. ** **
Différence: https://github.com/takahirom/hilt-sample-app/commit/2274ff3b5712e6b266cf022ff91f4581532bf45b
Créez des règles pour faciliter les tests
Si vous préparez HiltAndroidAutoInjectRule etc., cela fonctionnera sans appeler vous-même Inject.
@get:Rule val hiltAndroidAutoInjectRule = HiltAndroidAutoInjectRule(this)
class HiltAndroidAutoInjectRule(testInstance: Any) : TestRule {
private val hiltAndroidRule = HiltAndroidRule(testInstance)
private val delegate = RuleChain
.outerRule(hiltAndroidRule)
.around(HiltInjectRule(hiltAndroidRule))
override fun apply(base: Statement?, description: Description?): Statement {
return delegate.apply(base, description)
}
}
class HiltInjectRule(val rule: HiltAndroidRule) : TestWatcher() {
override fun starting(description: Description?) {
super.starting(description)
rule.inject()
}
}
J'ai parlé de:
Vous pouvez vous attendre à divers effets en utilisant Dagger Hilt, alors créons une application en utilisant Dagger Hilt!
Page Web officielle https://dagger.dev/hilt/ https://developer.android.com/training/dependency-injection/hilt-android?hl=ja
Android Dependency Injection https://www.youtube.com/watch?v=B56oV3IHMxg Dagger Hilt Deep Dive https://www.youtube.com/watch?v=4di2TTqeCrE
Architecture Samples https://github.com/android/architecture-samples/tree/dev-hilt Application Google I / O https://github.com/google/iosched Sunflower https://github.com/android/sunflower chrisbanes/tivi https://github.com/chrisbanes/tivi