Dependency injection is a design pattern of object-oriented programming designed to reduce program coupling, which is caused by dependencies between classes.
- Consistent with the single blame principle and the open closed principle
What is dependency injection
Classes often need to refer to other classes. Eg: Car may need People classes. These classes are called dependencies
In the past, dependency injection was not used very much in Android development. Until recently, I have been looking at several projects, including Java projects for several weeks, and noticed the status of injection in the existing technology. In Java Spring Boot, dependency injection was almost used to the extreme, reducing too much work
Advantage:
- Reuse code
- Easy to reconstruct
- Easy to test
1. Injection mode
- Injection through interface
public interface ClassBInterface {
void setClassB(ClassB classB);
}
class ClassB {
public void doSomething(a) {}}class ClassA implements ClassBInterface {
ClassB classB;
@Override
public void setClassB(ClassB classB) {
this.classB = classB; }}Copy the code
- Injection through the set method
- Injection through the constructor
- Injection via Java annotations
Dependency injection in Android
In general, Android mainly uses constructor injection or set method injection
Dagger2 is the best dependency injection framework available for Android. Google does the injection at static compile time. It has no impact on performance, is good for maintenance, and can reduce OOM problems caused by object references.
2.1 Dagger2
DI(dependency injection), divided into three parts:
- Dependent provider
- Dependent demand side
- Dependency Injection (Bridge)
Explain what is dependency
A class has two variables, and those two variables are its dependencies. There are two ways to initialize a dependency, self initialization, and external initialization is called dependency injection.
We can’t use a component without knowing what it provides. Second, what are the requirements that are relevant to our business? And finally, how do we use it
In the Dagger
- The @moudle annotation is generally used as a dependency provider
- @Component acts as a bridge between dependencies
- @inject annotates variables as demanders (can also be used for dependency providers)
2.1.1 to use
Add it to build.gradle of your Android project
apply plugin: 'kotlin-kapt'
// ...
implementation 'com. Google. Dagger: a dagger: 2.11'
kapt 'com. Google. Dagger: a dagger - compiler: 2.11'
annotationProcessor 'com. Google. Dagger: a dagger - compiler: 2.11'
/ / Java annotations
implementation 'org. Anyone: javax.mail. Annotation: 10.0 - b28'
Copy the code
@Inject
It is generally used to inject attributes, methods, and constructors. The injection constructors are used mostly in projects and have two functions
- As a provider of dependencies
// Annotate the constructor
class Navigator @Inject constructor() {
fun navigator(a) {
println("navigator method")}}Copy the code
- As the demand side of dependence
class MainActivity : AppCompatActivity() {
// Inject the Navigator into MainActivty so that MainActivity has a reference to the Navigator
@Inject lateinit var navigator: Navigator
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
navigator.navigator()
}
}
Copy the code
The two @inject annotations form a dependency,
what?? Isn’t it said that @inject since the supplier of dependency is also the demander of dependency? Am I using it wrong?
We’re not saying that the @Component in the dependency is a bridge between the demand side and the provider side.
@Component
Annotation interface
The Dagger2 container, as it were, acts as a bridge between injected dependencies and provided dependencies, injecting provided dependencies into the required dependencies
- Declare an interface and annotate it with @Component
@Component
interface ApplicationComponent {
// Provide a method for annotations
fun inject(application: Dagger2Application)
}
Copy the code
- Rebuild project, generates a file called DaggerApplicationComponent, and realize the ApplicationComponent, obviously it is the Dagger generated for us,
@Generated(
value = "dagger.internal.codegen.ComponentProcessor",
comments = "https://google.github.io/dagger"
)
public final class DaggerApplicationComponent implements ApplicationComponent {
private MembersInjector<MainActivity> mainActivityMembersInjector;
private DaggerApplicationComponent(Builder builder) {
/ /...
Copy the code
- Declare, initialize, and provide references to annotations in the ApplicationComponent to global books
class Dagger2Application: Application() {
val appComponent: ApplicationComponent by lazy(mode = LazyThreadSafetyMode.NONE) {
DaggerApplicationComponent
.builder()
.build()
}
override fun onCreate(a) {
super.onCreate()
appComponent.inject(this)}}Copy the code
- In the MainActivity
class MainActivity : AppCompatActivity() {
private val appComponent: ApplicationComponent by lazy(mode = LazyThreadSafetyMode.NONE) {
(application as Dagger2Application).appComponent
}
@Inject lateinit var navigator: Navigator
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// This is the real injection
appComponent.inject(this)
navigator.navigator()
}
}
Copy the code
Don’t forget to configure Application in AndroidMianfest.xml
Of course, with a framework in a project, we need to consider whether we can cover as many business scenarios as possible, so there is a problem right now, Dagger to provide annotations to the third party library
@ the Module and @ Provides
@Module and @Provides dependencies for Dagger2. This is a supplement to @inject (3). It is used in cases where @inject cannot be used to provide dependencies, such as classes provided by third-party libraries, basic data types, etc. @Provides can annotate only methods, and the class in which the method is provided has the @Module annotation. The annotated method indicates that Dagger2 can provide dependencies on the method instance object. By convention, the @provides method is prefixed with provide for easy reading and management.
eg:
- Create @ the Module
@Module
class ApplicationModule {}Copy the code
- Combine with @Provides to provide dependencies
// This simply provides a Retrofit (third-party library) dependency
@Module
class ApplicationModule {
@Provides
fun provideRetrofit(a): Retrofit {
return Retrofit.Builder().build()
}
}
Copy the code
After @ Inject lesson, we should first think of is and how to DaggerApplicationComponent this interface
- Associate the ApplicationModule with @Component
@Component(modules = [ApplicationModule::class])
interface ApplicationComponent {
fun inject(application: Dagger2Application)
fun inject(activity: MainActivity)
}
Copy the code
- Add a line of code to Application
val appComponent: ApplicationComponent by lazy(mode = LazyThreadSafetyMode.NONE) {
DaggerApplicationComponent
.builder()
/ / is this line
.applicationModule(ApplicationModule())
.build()
}
Copy the code
conclusion
The @Component annotation contains a Module class, indicating that Dagger2 can look for dependencies from the Module class. Dagger2 will automatically look for dependencies on the Module class with the @Provides annotation, and then complete the injection
This is the end of Dagger2’s simple annotations, which can also be used in the project. Of course, there are many Dagger2 annotations, you can refer to:
Android | finish don’t forget the series, “dagger
Read so much, my original intention is Dagger2 under a more simple, less code amount of network request framework, a few days ago summed up a set of framework and friends talk about it is relatively large, some bloated
3. Kotlin Coroutines + Jetpack + Retrofit + okHttp3 + Dagger Network request
The idea is to put the fixed parts in Dagger2 and then minimize the variable code
Obviously the initialization of Retrofit will have to be in the Dagger2 annotation
3.1 Declare a dependency on Retrofit in @Module
@Module
class ApplicationModule(private val application: Dagger2Application) {
@Provides
@Singleton fun provideApplicationContext(a): Context = application
@Provides
@Singleton
fun provideRetrofit(a): Retrofit {
return Retrofit.Builder()
.baseUrl("https://gank.io/api/v2/data/category/Girl/type/Girl/")
.client(createClient())
.addConverterFactory(GsonConverterFactory.create())
.build()
}
private fun createClient(a): OkHttpClient {
val builder = OkHttpClient.Builder()
if (BuildConfig.DEBUG) {
val loggingInterceptor =
HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC)
builder.addInterceptor(loggingInterceptor)
}
return builder.build()
}
}
Copy the code
There is an @singleton annotation, which is literally a Singleton, but based on the way Dagger2 is compiled, @singleton is just a reminder because each compilation checks to see if an annotation has been compiled, and if it has, it will not be compiled again. So it is impossible to have a reference that is not a unified object
3.1.1 Error Handling
The network request framework chosen for your application should be tailored to your needs, but with any framework we need to handle exceptions and other unexpected error situations
Sealed classes are similar to enumerations, but are more flexible than enumerations, can carry parameters, and so on
sealed class Failure {
object NetworkConnection : Failure()
object ServerError : Failure()
abstract class FeatureFailure : Failure()}Copy the code
3.1.2 Return Value Processing
There is also the handling of the return value, which is divided into two states, success and failure
sealed class Either<out L, out R> {
data class Error<out L>(val a: L) : Either<L, Nothing> ()data class Success<out R>(val b: R) : Either<Nothing, R>()
val isRight get() = this is Success<R>
val isLeft get() = this is Error<L>
fun <L> left(a: L) = Error(a)
fun <R> right(b: R) = Success(b)
fun fold(fnL: (L) - >Any, fnR: (R) - >Any): Any =
when(this) {
is Error -> fnL(a)
is Success -> fnR(b)
}
}
Copy the code
Anything into a system is not have a specific order, because open the refrigerator is put in the elephant has a variety of ways, but the objective is the elephant in the refrigerator, so we don’t need to worry about how to build up a framework, we just according to their own ideas to write out the things we want, and then improve it. So don’t think about what do I do after this step
3.2 Declaring apis and Services
interface ImageApi {
@GET("page/{page}/count/{size}")
fun images(@Path("page") page: Int.@Path("size") size: Int): Call<ImageEntry>
}
Copy the code
@Singleton
class ImageService @Inject constructor(retrofit: Retrofit) : ImageApi {
private val imageApi by lazy { retrofit.create(ImageApi::class.java) }
override fun images(page: Int, size: Int) = imageApi.images(page, size)
}
Copy the code
This links Retrofit to the API, so where do we use ImageService next? This is obviously the class for the network request service,
It is recommended that network requests or data sources be stored in a warehouse for centralized processing and data cache design
3.3 Establish a data warehouse
interface ImageRepository { fun images(page: Int, size: Int): Either<Failure, ImageEntry> class NetWork @Inject constructor( private val networkHandler: NetworkHandler, private val imageService: ImageService ) : ImageRepository { override fun images(page: Int, size: Int): Either<Failure, ImageEntry> { return when (networkHandler.isConnected) { true -> request( imageService.images(page, size), { it }, ImageEntry.empty() ) false, null -> Either.Error(Failure.NetworkConnection) } } private fun <T, R> request( call: Call<T>, transform: (T) -> R, default: T ): Either<Failure, R> { return try { val response = call.execute() when (response.isSuccessful) { true -> Either.Success(transform((response.body() ? : default))) false -> Either.Error(Failure.ServerError) } } catch (e: Throwable) { Either.Error(Failure.ServerError) } } } }Copy the code
After the above two operations, our answer framework is out. Now it is to write how to perform a network request and how to display the requested data to the page
3.4 Create a request use case
We summarize the request directly into a use case, which is the class dedicated to the request
abstract class UseCase<out Type, in Params> where Type : Any {
abstract suspend fun run(params: Params): Either<Failure, Type>
operator fun invoke(params: Params, onResult: (Either<Failure.Type- > >)Unit = {}) {
val job = GlobalScope.async(Dispatchers.IO) { run(params) }
GlobalScope.launch(Dispatchers.Main) { onResult(job.await()) }
}
class None
}
Copy the code
Here we use coroutines and expose a run(params) method that implements the specific request
The specific interface request method
class GetImage @Inject constructor(private val imageRepository: ImageRepository) :
UseCase<ImageEntry, GetImage.Params>() {
override suspend fun run(params: Params): Either<Failure, ImageEntry> = imageRepository.images(params.page, params.size)
data class Params(val page: Int.val size: Int)}Copy the code
3.5 create ViewModel
class ImageViewModel @Inject constructor(private val getImage: GetImage) : BaseViewModel() {
private val _image: MutableLiveData<List<Image>> = MutableLiveData()
val image: LiveData<List<Image>> = _image
fun loadImage(page: Int, size: Int) = getImage(GetImage.Params(page, size)) {
it.fold(::handleFailure, ::handleImageList)
}
private fun handleImageList(imageEntry: ImageEntry) {
_image.value = imageEntry.toImage()
}
}
Copy the code
The double colon operator in “::” kotlin means that one method is passed as an argument to another method for use
Reference project: Android-CleanArchitecture-Kotlin
Interpretation item: github.com/kongxiaoan/…
Download the Demo
Four, the purpose,
Some projects for foreigners are particularly excellent, but because their English is not easy to understand for people with weak English, I will see this project is split, and realized again, from which to obtain some knowledge, is very worthwhile