Mots clés:
koin fournit une interface API facile à utiliser pour Android , vous permettant d'accéder facilement au framework koin.
[configuration progressive de koin dans Android]
1. startKoin dans la classe Application
Depuis votre classe, vous pouvez utiliser la fonction et injecter le contexte Android comme ceci :
Application startKoin androidContext class MainApplication : Application()
override fun onCreate() super.onCreate() startKoin // Log Koin into Android logger androidLogger() // Reference Android context androidContext(this@MainApplication) // Load modules modules(myAppModules)
Si vous avez besoin de démarrer Koin à partir d'une autre classe Android, vous pouvez utiliser cette fonction pour fournir votre instance Android comme suit :startKoin
Context
startKoin
//inject Android context
androidContext(/* your android context */)
// ...
2. Configuration supplémentaire
Depuis votre configuration Koin (en code bloc), vous pouvez également configurer différentes parties de Koin.startKoin
2.1 Koin Logging pour Android
koin fournit une implémentation Android de log.
startKoin
// use Android logger - Level.INFO by default
androidLogger()
// ...
2.2 Charger les propriétés
Vous pouvez utiliser les propriétés Koin pour stocker les clés/valeurs dans le fichier : assets/koin.properties
startKoin
// ...
// use properties from assets/koin.properties
androidFileProperties()
3. Injecter une instance d'objet dans Android
3.1 Préparation pour le cours Android
koin fournit KoinComponents
des extensions, qui sont disponibles pour les composants Android, y comprisActivity
Fragment Service ComponentCallbacks
Vous pouvez accéder aux extensions Kotlin comme suit :
by inject()
- Instance de calcul paresseux du conteneur Koin
get()
- Obtenir une instance du conteneur Koin
Nous pouvons déclarer qu'une propriété est injectée paresseusement :
module
// definition of Presenter
factory Presenter()
classe DetailActivity : AppCompatActivity()
// Lazy inject Presenter
override val presenter : Presenter by inject()
override fun onCreate(savedInstanceState: Bundle?)
//...
Ou nous pouvons obtenir une instance directement :
override fun onCreate(savedInstanceState: Bundle?) super.onCreate(savedInstanceState)
// Retrieve a Presenter instance val presenter : Presenter = get()
Remarque : Si votre classe n'est pas étendue, ajoutez simplement l'interface KoinComponent si vous avez besoin d'une instance d'une autre classe. injecter() obtenir()
3.2 Utilisation du contexte Android
class MainApplication : Application()
override fun onCreate() super.onCreate() startKoin //inject Android context androidContext(this@MainApplication) // ...
Dans votre définition, la fonction suivante vous permet d'obtenir des instances dans le module Koin pour vous aider à écrire facilement des expressions qui nécessitent des instances.androidContext() androidApplication() Context Application
val appModule = module
// create a Presenter instance with injection of R.string.mystring resources from Android factory MyPresenter(androidContext().resources.getString(R.string.mystring))
4. Constructeurs DSL pour Android
4.1 Constructeurs DSL
Koin fournit désormais un nouveau mot-clé DSL qui vous permet de cibler directement les constructeurs de classe et d'éviter de taper vos définitions dans des expressions lambda.
Pour Android, cela signifie les nouveaux mots-clés DSL constructeur suivants :
viewModelOf()
- équivalent àviewModel
fragmentOf()
- équivalent àfragment
workerOf()
- équivalent àworker
Remarque : Assurez-vous d'utiliser avant le nom de la classe pour localiser le constructeur de la classe : :
4.2 Exemple de fonction Android DSL
Soit une application Android avec les composants suivants :
// A simple service
class SimpleServiceImpl() : SimpleService
// un présentateur, utilisant SimpleService et peut recevoir la classe de paramètres injectée "id"
FactoryPresenter(val id : String, val service : SimpleService)
// un ViewModel qui peut recevoir le paramètre injecté "id", utiliser SimpleService et obtenir
la classe SavedStateHandle SimpleViewModel(val id : String, val service : SimpleService, val handle : SavedStateHandle) : ViewModel()
// une session étendue, qui peut recevoir un lien vers la
session de classe MyActivity (depuis l'étendue) (activité val : MyActivity)
// un Worker, utilisant SimpleService et obtenant
la classe Context & WorkerParameters SimpleWorker(
private val simpleService: SimpleService,
appContext: Context,
private val params: WorkerParameters
) : CoroutineWorker(appContext, params)
On peut les déclarer ainsi :
module singleOf(::SimpleServiceImpl) bind<SimpleService>()
factoryOf(::FactoryPresenter) viewModelOf(::SimpleViewModel) scope<MyActivity>() scopedOf(::Session) workerOf(::SimpleWorker)
5. Utilisation de plusieurs modules koin dans Android
En utilisant Koin, vous pouvez décrire des définitions dans des modules. Dans cette section, nous verrons comment déclarer, organiser et lier des modules.
Multi-module 5.1 koin
Les composants ne doivent pas nécessairement se trouver dans le même module. Les modules sont des espaces logiques qui vous aident à organiser les définitions et peuvent dépendre d'autres modules de définition. Les définitions sont paresseuses et résolues uniquement lorsqu'un composant le demande.
Prenons un exemple où les composants liés sont dans des modules séparés :
// ComponentB <- ComponentA
class ComponentA()
class ComponentB(val componentA : ComponentA)
val moduleA = module
// Singleton ComposantA
single ComposantA()
val moduleB = module
// Singleton ComponentB avec instance liée ComponentA
single ComponentB(get())
Il suffit de déclarer la liste des modules utilisés au démarrage du conteneur Koin :
class MainApplication : Application()
override fun onCreate() super.onCreate() startKoin // ... // Load modules modules(moduleA, moduleB)
5.2 Contenu du module
Une nouvelle fonction est disponible dans la classe qui permet de combiner des modules en incluant d'autres modules de manière organisée et structuréeincludes() Module
Le nouveau module a 2 caractéristiques exceptionnelles :
Divisez les grands modules en modules plus petits et plus ciblés.
Dans les projets modulaires, il permet un contrôle plus fin de la visibilité des modules (voir exemple ci-dessous).
Comment ça marche? Prenons quelques modules, nous incluons les modules dans : parentModule
// `:feature` module
val childModule1 = module
/* Other definitions here. */
val childModule2 = module
/* Autres définitions ici. */
val parentModule = le module
inclut (childModule1, childModule2)
// :app
module
startKoin modules(parentModule)
Notez que nous n'avons pas besoin de définir explicitement tous les modules : en incluant, tous les modules déclarés seront chargés automatiquement.
parentModule includes childModule1 childModule2 parentModule childModule1 childModule2
INFO : Le chargement des modules est désormais optimisé pour aplatir tous les graphiques de modules et éviter les définitions de modules en double.
Enfin, vous pouvez inclure plusieurs modules imbriqués ou répétés et Koin aplatira tous les modules inclus, supprimant les doublons :
// :feature module
val dataModule = module
/* Other definitions here. */
val domainModule = module
/* Autres définitions ici. */
val featureModule1 = le module
inclut(domainModule, dataModule)
val featureModule2 = le module
inclut(domainModule, dataModule)
// :
classe de module d'application MainApplication : Application()
override fun onCreate()
super.onCreate()
startKoin
// ...
// Load modules
modules(featureModule1, featureModule2)
Notez que tous les modules ne seront inclus qu'une seule fois :dataModule domainModule featureModule1 featureModule2
5.3 Android ViewModel 和 Navigation
Le module Gradle introduit un nouveau mot-clé DSL qui est ajouté pour aider à déclarer et à lier les composants ViewModel au cycle de vie des composants Android. Le mot-clé est également disponible vous permettant de déclarer un ViewModel à l'aide de son constructeur.koin-android viewModel singlefactory viewModelOf
val appModule = module
// ViewModel for Detail View viewModel DetailViewModel(get(), get()) // or directly with constructor viewModelOf(::DetailViewModel)
Le composant déclaré doit étendre au moins la classe. Vous pouvez spécifier comment injecter le constructeur d'une classe et utiliser cette fonction pour injecter des dépendances.android.arch.lifecycle.ViewModel get()
Remarque : le mot-clé aide à déclarer l'instance d'usine de ViewModel. Cette instance sera gérée par la ViewModelFactory interne et rattachera l'instance ViewModel si nécessaire. Il permettra également d'injecter des paramètres.viewModel viewModelOf
5.4 Injecter ViewModel
Utilisez viewModel dans le composant Android,Activity Fragment Service
by viewModel()
- Propriétés déléguées paresseuses pour injecter des modèles de vue dans les propriétés
getViewModel()
- Obtenez l'instance du modèle de vue directement
class DetailActivity : AppCompatActivity()
// Lazy inject ViewModel val detailViewModel: DetailViewModel by viewModel()
5.5 Modèle de vue partagé d'activité
Une instance ViewModel peut être partagée entre un fragment et son activité principale.
Pour injecter le modèle de vue partagé en utilisant : Fragment
by activityViewModel()
- Propriétés déléguées paresseuses pour injecter des instances viewModel partagées dans les propriétés
get ActivityViewModel()
- Obtenez directement l'instance viewModel partagée
Déclarez simplement le modèle de vue une fois :
val weatherAppModule = module
// WeatherViewModel declaration for Weather View components viewModel WeatherViewModel(get(), get())
Remarque : les qualificatifs de viewModel seront traités comme des balises de viewModel
et réutilisez-le dans Activity et Fragment :
class WeatherActivity : AppCompatActivity()
/* * Declare WeatherViewModel with Koin and allow constructor dependency injection */ private val weatherViewModel by viewModel<WeatherViewModel>()
classe WeatherHeaderFragment : Fragment()
/*
* Declare shared WeatherViewModel with WeatherActivity
*/
private val weatherViewModel by activityViewModel<WeatherViewModel>()
classe WeatherListFragment : Fragment()
/*
* Declare shared WeatherViewModel with WeatherActivity
*/
private val weatherViewModel by activityViewModel<WeatherViewModel>()
5.6 Passer des arguments aux constructeurs
Transmettez les paramètres à viewModel, l'exemple de code est le suivant :
dans le module
val appModule = module
// ViewModel for Detail View with id as parameter injection viewModel parameters -> DetailViewModel(id = parameters.get(), get(), get()) // ViewModel for Detail View with id as parameter injection, resolved from graph viewModel DetailViewModel(get(), get(), get()) // or Constructor DSL viewModelOf(::DetailViewModel)
Paramètres entrants du point d'injection de dépendance
class DetailActivity : AppCompatActivity()
val id : String // id of the view // Lazy inject ViewModel with id parameter val detailViewModel: DetailViewModel by viewModel parametersOf(id)
5.7 Injection SavedStateHandle
Ajoutez de nouvelles propriétés saisies dans le constructeur pour gérer l'état de ViewModel :SavedStateHandle
class MyStateVM(val handle: SavedStateHandle, val myService : MyService) : ViewModel()
Dans le module Koin, analysez-le simplement avec le paramètre ou : get()
viewModel MyStateVM(get(), get())
ou utilisez le constructeur DSL :
viewModelOf(::MyStateVM)
Dans le fragment d'activité
by viewModel()
- Propriétés déléguées paresseuses pour injecter des instances de modèle de vue d'état dans les propriétés
getViewModel()
- Obtenez directement l'instance du modèle de vue d'état
class DetailActivity : AppCompatActivity()
// MyStateVM viewModel injected with SavedStateHandle val myStateVM: MyStateVM by viewModel()
5.8 ViewModel dans le diagramme de navigation de navigation
Vous pouvez étendre les instances ViewModel au graphique de navigation. Transmettez simplement l'identifiant àby koinNavGraphViewModel()
class NavFragment : Fragment()
val mainViewModel: NavViewModel by koinNavGraphViewModel(R.id.my_graph)
5.9 API commune viewModel
Koin fournit des API "de bas niveau" pour ajuster directement votre instance ViewModel.viewModelForClass ComponentActivity Fragment
ComponentActivity.viewModelForClass(
clazz: KClass<T>,
qualifier: Qualifier? = null,
owner: ViewModelStoreOwner = this,
state: BundleDefinition? = null,
key: String? = null,
parameters: ParametersDefinition? = null,
): Lazy<T>
Une fonction de niveau supérieur est également fournie :
fun <T : ViewModel> getLazyViewModelForClass(
clazz: KClass<T>,
owner: ViewModelStoreOwner,
scope: Scope = GlobalContext.get().scopeRegistry.rootScope,
qualifier: Qualifier? = null,
state: BundleDefinition? = null,
key: String? = null,
parameters: ParametersDefinition? = null,
): Lazy<T>
5.10 API ViewModel - Compatibilité Java
La compatibilité Java doit être ajoutée aux dépendances :
// Java Compatibility
implementation "io.insert-koin:koin-android-compat:$koin_version"
您可以使用以下函数或静态函数将 ViewModel 实例注入到 Java 代码库中:viewModel() getViewModel() ViewModelCompat
@JvmOverloads
@JvmStatic
@MainThread
fun <T : ViewModel> getViewModel(
propriétaire : ViewModelStoreOwner,
clazz : Class<T>,
qualificateur : Qualifier ? = null,
parameters : ParametersDefinition ? = null
)
6. Injecter dans Jetpack Compose
Veuillez d'abord en savoir plus sur Jetpack Compose :
developer.android.com/jetpack/com…
6.1 Injection de @Composable
Lors de l'écriture de fonctions composables, vous pouvez accéder aux API Koin suivantes :
get()
- Obtenir une instance du conteneur Koin
getKoin()
- Obtenir l'instance Koin actuelle
Pour un module déclarant le composant "MyService" :
val androidModule = module
single MyService()
Nous pouvons obtenir votre instance comme ceci :
@Composable
fun App()
val myService = get<MyService>()
Remarque : Pour des raisons de cohérence dans les fonctionnalités de Jetpack Compose, il est préférable d'injecter l'instance directement dans l'attribut de fonction. Cette approche permet d'utiliser Koin pour l'implémentation par défaut, mais la laisse ouverte pour injecter des instances si nécessaire.
@Composable
fun App(myService: MyService = get())
6.2 viewModel @Composable
De la même manière que vous accédez à une instance singleton/factory classique, vous pouvez accéder aux API Koin ViewModel suivantes :
getViewModel()
ou - obtenir une instancekoinViewModel()
Pour le module déclarant le composant "MyViewModel" :
module
viewModel MyViewModel()
// or constructor DSL
viewModelOf(::MyViewModel)
Nous pouvons obtenir votre instance comme ceci :
@Composable
fun App()
val vm = koinViewModel<MyViewModel>()
Nous pouvons obtenir votre instance dans le paramètre de fonction :
@Composable
fun App(vm : MyViewModel = koinViewModel())
7. Gérer les portées Android
Les composants Android, par exemple Activity、Fragment、Service
, ont un cycle de vie, ces composants sont instanciés par System et il existe des rappels de cycle de vie correspondants dans les composants.
Étant donné que les composants Android ont des attributs de cycle de vie, les instances de composants ne peuvent pas être transmises à koin. Selon la durée du cycle de vie, les composants peuvent être divisés en trois catégories :
- • Composant à longue durée de vie (
Service、database
) - utilisé par plusieurs écrans, jamais jeté - • Composants de période moyenne (
User session
) - utilisés par plusieurs écrans et doivent être supprimés après un certain temps - • Composants de courte durée (
ViewModel)
--utilisés par un seul écran et doivent être supprimés à la fin de l'écran
Pour les composants à long terme, nous utilisons généralement single pour créer une seule instance globalement dans l'application
En mode architecture MVP, Presenter est un composant à cycle court
La façon de le créer dans Activity est la suivante
class DetailActivity : AppCompatActivity()
// injected Presenter override val presenter : Presenter by inject()
Nous pouvons également créer dans le module
Nous utilisons la portée de l'usine pour créer l'instance Presenter
val androidModule = module
// Factory instance of Presenter factory Presenter()
Générer une portée d'instance liée à la portée
val androidModule = module
scope<DetailActivity> scoped Presenter()
La plupart des fuites de mémoire Android proviennent du référencement de composants UI/Android à partir de composants non Android. Le système conserve des références à celui-ci et ne peut pas le récupérer entièrement via la récupération de place.
7.1 Déclarer la portée Android
Pour qualifier une dépendance sur un composant Android, il faut déclarer une portée à l'aide d'un bloc comme celui-ci : scope
class MyPresenter()
class MyAdapter(val presenter : MyPresenter)
module
// Déclare la portée de MyActivity
scope<MyActivity>
// obtient l'instance de MyPresenter à partir de la portée actuelle
scoped MyAdapter(get())
scoped MyPresenter()
7.2 Classe de portée Android
Koin fournit des classes Scope liées aux composants du cycle de vie AndroidScopeActivity Retained ScopeActivity ScopeFragment
class MyActivity : ScopeActivity()
// MyPresenter is resolved from MyActivity's scope val presenter : MyPresenter by inject()
Android Scope doit être utilisé avec des interfaces pour implémenter des champs comme celui-ci :AndroidScopeComponent scope
abstract class ScopeActivity( @LayoutRes contentLayoutId: Int = 0, ) : AppCompatActivity(contentLayoutId), AndroidScopeComponent
override val scope: Scope by activityScope() override fun onCreate(savedInstanceState: Bundle?) super.onCreate(savedInstanceState) checkNotNull(scope)
Nous devons utiliser des interfaces et implémenter des propriétés. Cela définira la portée par défaut utilisée par la classe.AndroidScopeComponent scope
7.3 Interface de portée Android
Pour créer un scope Koin lié à un composant Android, il suffit d'utiliser la fonction suivante :
createActivityScope()
- Créer un Scope pour l'Activité en cours (la partie Scope doit être déclarée)
createActivityRetainedScope()
- Créer un RetainedScope (supporté par ViewModel Lifecycle) pour l'activité en cours (la partie Scope doit être déclarée)
createFragmentScope()
- Créer un Scope pour le Fragment actuel et le lier au Scope d'Activité parent Ces fonctions peuvent être utilisées comme délégués pour implémenter différents types de scopes :
activityScope()
- Créer un Scope pour l'Activité en cours (la partie Scope doit être déclarée)
activityRetainedScope()
- Créer un RetainedScope (supporté par ViewModel Lifecycle) pour l'activité en cours (la partie Scope doit être déclarée)
fragmentScope()
- Créer un champ d'application pour le fragment actuel et le lier au champ d'activité parent
class MyActivity() : AppCompatActivity(contentLayoutId), AndroidScopeComponent
override val scope: Scope by activityScope()
Nous pouvons également définir la portée de rétention (alimentée par le cycle de vie ViewModel) en utilisant ce qui suit :
class MyActivity() : AppCompatActivity(contentLayoutId), AndroidScopeComponent
override val scope: Scope by activityRetainedScope()
Si vous ne souhaitez pas utiliser la classe Android Scope, vous pouvez utiliser votre propre classe et créer une API avec ScopeAndroidScopeComponent
7.4 Lien portée
Les liens d'étendue permettent de partager des instances entre des composants avec des étendues personnalisées. Dans une utilisation plus large, vous pouvez utiliser des instances sur plusieurs composants. Par exemple, si nous devons partager une instance.Scope UserSession
Déclarez d'abord une définition de portée :
module
// Shared user session data
scope(named("session"))
scoped UserSession()
Lorsqu'il est temps de commencer à utiliser une instance, créez-lui un champ d'application :UserSession
val ourSession = getKoin().createScope("ourSession",named("session"))
// lier la portée ourSession à current scope
, de ScopeActivity ou ScopeFragment
scope.linkTo(ourSession)
Utilisez-le ensuite partout où vous en avez besoin :
class MyActivity1 : ScopeActivity()
fun reuseSession() val ourSession = getKoin().createScope("ourSession",named("session")) // link ourSession scope to current `scope`, from ScopeActivity or ScopeFragment scope.linkTo(ourSession) // will look at MyActivity1's Scope + ourSession scope to resolve val userSession = get<UserSession>()
classe MyActivity2 : ScopeActivity()
fun reuseSession()
val ourSession = getKoin().createScope("ourSession",named("session"))
// link ourSession scope to current `scope`, from ScopeActivity or ScopeFragment
scope.linkTo(ourSession)
// will look at MyActivity2's Scope + ourSession scope to resolve
val userSession = get<UserSession>()
8.Usine de fragments
Depuis qu'AndroidX a publié une série de packages pour étendre les fonctionnalités d'Androidandroidx.fragment Fragment
developer.android.com/jetpack/et…
8.1 Usine de fragments
Depuis la sortie, une classe dédiée à la création d'instances de classe a été introduite : 2.1.0-alpha-3 FragmentFactory
Fragment
developer.android.com/reference/k…
Koin fournit également une classe d'usine KoinFragmentFactory
Fragment pour créer Fragment
8.2 Configuration de l'usine de fragments
Tout d'abord, dans KoinApplication
la déclaration, définissez l'instance par défaut à l'aide du mot-clé : fragmentFactory()
KoinFragmentFactory
startKoin // setup a KoinFragmentFactory instance fragmentFactory()
modules(...)
8.3 Déclarer et injecter le Fragment
Déclarer un Fragment et l'injecter dans le module
class MyFragment(val myService: MyService) : Fragment()
val appModule = module
single MyService()
fragment MyFragment(get())
8.4 Obtenir un fragment
utiliser setupKoinFragmentFactory()
les paramètresFragmentFactory
Pour interroger votre fragment, utilisezsupportFragmentManager
supportFragmentManager.beginTransaction()
.replace<MyFragment>(R.id.mvvm_frame)
.commit()
Ajouter des paramètres facultatifs
supportFragmentManager.beginTransaction()
.replace<MyFragment>(
containerViewId = R.id.mvvm_frame,
args = MyBundle(),
tag = MyString()
)
8.5 Usine de fragments et portées Koin
Si vous souhaitez utiliser Koin Activity Scope, vous devez déclarer votre Fragment en tant que définition dans votre Scope :scoped
val appModule = module
scope<MyActivity>
fragment MyFragment(get())
Et configurez votre Koin Fragment Factory avec votre Scope :setupKoinFragmentFactory(lifecycleScope)
class MyActivity : AppCompatActivity()
override fun onCreate(savedInstanceState: Bundle?) // Koin Fragment Factory setupKoinFragmentFactory(lifecycleScope) super.onCreate(savedInstanceState) //...
9. Injection Koin de WorkManager
koin fournit un package de composants séparé koin-androidx-workmanager pour WorkManager
Tout d'abord, dans la déclaration KoinApplication, utilisez le mot clé pour définir une instance WorkManager personnalisée :workManagerFactory()
class MainApplication : Application(), KoinComponent
override fun onCreate() super.onCreate() startKoin // setup a WorkManager instance workManagerFactory() modules(...) setupWorkManagerFactory()
Modification d'AndroidManifest.xml pour éviter d'utiliser la valeur par défaut
<application . . .>
. . .
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="$applicationId.workmanager-init"
tools:node="remove" />
</application>
9.1 Déclarer ListenableWorker
val appModule = module
single MyService()
worker MyListenableWorker(get())
9.2 Créer une WorkManagerFactory supplémentaire
class MainApplication : Application(), KoinComponent
override fun onCreate() super.onCreate() startKoin workManagerFactory(workFactory1, workFactory2) . . . setupWorkManagerFactory()
Si Koin et workFactory1 fournis WorkManagerFactory
peuvent être instanciés ListenableWorker
, la fabrique fournie par Koin sera celle utilisée.
9.3 Modifier le manifeste de koin lib lui-même
Si koin-androidx-workmanager
l'usine par défaut dans est désactivée et que le développeur de l'application n'initialise pas l'infrastructure du gestionnaire de travail de koin, il se retrouvera sans usine de gestionnaire de travail disponible.
Compte tenu de la situation ci-dessus, nous apportons les améliorations DSL suivantes :
val workerFactoryModule = module
factory<WorkFactory> WorkFactory1()
factory<WorkFactory> WorkFactory2()
Ensuite, demandez à koin de faire quelque chose en interne comme
fun Application.setupWorkManagerFactory(
// no vararg for WorkerFactory
)
. . .
getKoin().getAll<WorkerFactory>()
.forEach
delegatingWorkerFactory.addFactory(it)
lien de référence
lecture recommandée
- Koin du framework d'injection de dépendances kotlin (1)
- Koin du framework d'injection de dépendances kotlin (2)
- Cadre d'injection de dépendance Koin of kotlin (3)
Auteur : Calvin873
Lien : https://juejin.cn/post/7189917106580750395