According to SOLID principles, a class should be focused only on its tasks and should not be involved in sampling other classes to perform its responsibilities.
In a simple project, sampling classes can be done manually, but in some cases, it is necessary to do this sampling automatically using, for example, a dependency injection framework. To better understand this issue, it is better first to review the concept of dependency in programming and then solve the problem.
The concept of dependency in programming
To better understand the concept of dependency, we will explain it with an example. Suppose we have two classes in our project called ClassA and ClassB, which are defined as follows:
class ClassA {
var classB: ClassB
}
class ClassB {
As you can see, ClassB uses ClassB. So we can say that ClassA is dependent on ClassB. In other words, Class B is a dependency on Class A because ClassA must use ClassB to do its job.
On the other hand, we must use class B whenever we need class A. Therefore, replacing ClassB with another class is virtually impossible. On the other hand, with any change in the constructor inputs of Class B. We need to make changes in Class A. For example, if we add a parameter to a Class B constructor, we must go to all dependent classes, including Class A, and update the instances made of Class B. This in itself is a violation of the Single Responsibility rule, which is one of the most important principles of SOLID.
What is dependency injection?
Dependency injection is a way to manage dependencies somewhere outside of dependent classes so that the dependent class no longer has to worry about managing its dependencies.
Also, the dependency injection, also known as DI for short, is an idea or method of implementing project code and not a technology, framework, library, or anything like that.
Dependency injection is a technology that, inspired by the fifth SOLID principle, makes a class independent of its dependencies.
Advantages of Dependency Injection
The interdependence between the project’s different components is weakened, and the project code becomes more coherent.
Allows you to test each part of the project separately.
It makes it much easier to change the constructor inputs of classes and generally to change or expand components.
What is the koin framework?
Dependency injection can also be done without a specific library or framework, but several frameworks for its implementation have been offered to simplify this process.
Koin is one of those frameworks defined for programming in Kathleen. It is a very powerful but, at the same time, simple and compact framework that helps programmers implement dependency injection in their Kathleen project as easily as possible.
What are the steps to implement koin?
To implement a project with Quinn in an Android project, you need to go through five steps as follows:
Step 1: Add the required dependencies to the Gradle file
To use the Quinn framework, you must first include its dependencies in your project. You must add the following code to “build. Gradle” at the level of your module:
dependencies {
def koin_version = "2.1.5"
// Koin AndroidX Scope feature
implementation "org.koin: koin-androidx-scope: $ koin_version "
// Koin AndroidX ViewModel feature
implementation "org.koin: koin-androidx-viewmodel: $ koin_version "
// Koin AndroidX Fragment Factory (unstable version)
implementation "org.koin: koin-androidx-fragment: $ koin_version "
}
Step 2: Define the Data class and Repository
What is the second step in implementing koin? First, we create a data class that holds a user’s ID, username, and username. To do this, we create a data class called SevenLearnUser as follows:
data class SevenLearnUser (val id : Int ?, val name : String ?, val username : String )
After creating the data class, it is time for the repository class. At this point, we will create a class that returns a list of users. This information is typically received from the local database or server database by web services, but in this example, we will suffice to create test information as follows:
UserRepository interface {
fun fetchUsers (): List <SevenLearnUser>
}
class UserRepositoryImpl: UserRepository {
override fun fetchUsers (): List <SevenLearnUser> {
return listOf (
SevenLearnUser ( 1 , name = "Alireza" , username = "@ Alireza1010" ),
SevenLearnUser ( 2 , name = "Mohammad" , username = "@ Mohammad01" ),
SevenLearnUser ( 3 , name = "Zeynab" , username = "@ Zeynab1210" ),
SevenLearnUser ( 4 , name = "Vahid" , username = "@paradise" ),
SevenLearnUser (5 , name = "Elham" , username = "@ Ely2000" )
)
}
}
class SevenLearnUserPresenter (repo: UserRepository) {
fun getSevenLearnUsers (): List <SevenLearnUser> = repo. fetchUsers ()
}
Next, by calling the SevenLearnUserPresenter class, we can get the list of created users.
Step 3: Define the modules to provide the dependencies
In the third step, we have to build Quinn modules for our project. Each module in the project is a container for a set of services that are to be called at runtime. We put all the services a module should provide in the module block.
val appModule = module {
// Single instance of UserRepository - Singleton
single < UserRepository > { UserRepositoryImpl ()}
// Simple Presenter Factory - new instances created each time
factory { SevenLearnUserPresenter ( get ())}
}
Each module must define several Factors and singles to control and describe the dependencies.
The difference between Factory and Single is that when a class in the module is defined as Single, Quinn samples it once and holds that sample. And it returns that instance wherever it is needed in the project, but when a class is defined as a Factory, Quinn builds a new instance whenever it needs it.
Step 4: Define Kevin in the project
After creating the modules, now it’s time to define Quinn at the project level and call the modules in it. To do this, we first create a class called MyApp that inherits from the Application class. And in it, we call the following function called to start on.
class MyApp: Application () {
override fun onCreate () {
super. onCreate ()
// Start Koin
startKoin {
androidLogger ()
androidContext (this @ MyApp)
modules (appModule)
}
}
}
This section, by calling the start Koin function, loads the dependencies that we have defined in the modules. In this section, if you have more modules in your project. You must enter them all in start Koin.
Next, we must define the MyApp class in the project manifest file as follows:
< application
android: name = ".MyApp"
.
.
.
Step 5: Inject dependencies
In the last step, we want to display the list of users in an activity. We need the SevenLearnUserPresenter class to return the list of users to us and use it to display in the design. So we need to inject the SevenLearnUserPresenter class that we defined in the third step as factory into our MainActivity.
There are two ways to call this class: the injector and the get () function. The inject function allows us to do sampling at runtime, and the get function directly returns an instance of the class in question.
class MainActivity: AppCompatActivity () {
// Lazy injected GithubUserPresenter
private val presenter: SevenLearnUserPresenter by inject ()
override fun onCreate (savedInstanceState: Bundle?) {
super. onCreate (savedInstanceState)
setContentView (R.layout.activity_main)
for (student in presenter. getSevenLearnUsers ()) {
Log. d ( "" , student. toString ())
}
}
}
In this part of the job, we have created a variable called presenter of the genre SevenLearnUserPresenter. Instead of direct sampling from the SevenLearnUserPresenter class, we called it an inject.
private val presenter: SevenLearnUserPresenter by inject ()
Note that the SevenLearnUserPresenter function requires the UserRepository class to be called; normally, without dependency injection, a sample of the UserRepository must be given to its input wherever it is sampled. But we saw no need to define and call UserRepository by using dependency injection to sample SevenLearnUserPresenter in Activity. So if we need to change the inputs of the SevenLearnUserPresenter class, we do not need to apply those changes to all the classes that use it (in this example, MainActivity).
The second advantage of this method is that the UserRepository can be easily replaced by another class in this class, allowing us to test each class separately.
Conclusion:
The biggest task of a software engineer is not development but the survival and development of the project. The more your code is based on good architecture, the easier it will be to test the different parts. The easier it will be to maintain it, so using Appropriate patterns such as injecting dependency into a project is important. Have you experienced using this framework?
Suppose you are interested in learning more about Android by participating in an Android specialist training course in less than a year. In that case, you will become a versatile Android developer ready to hire, receive a project and even implement your application.