قالب وردپرس درنا توس
Home / IOS Development / Dependency Injection With Koin | raywenderlich.com

Dependency Injection With Koin | raywenderlich.com



Dependency Injection (DI) is one of the "new" concepts that continue to appear in all blog posts on the Internet. In fact, the idea is not new, but a term that is revised from time to time. Mastering DI will allow you to handle large and complex applications in a more convenient way.

In this tutorial you will know Koin one of the most popular new frames for DI. You start by learning the basics of DI and how your Android projects can benefit from it. You will then use it for a test app that adopts Koin as a DI framework and illustrates the benefits.

DI: A "New" Old Friend

Believe it or not, many Android developers have used DI since their very first applications. However, most new developers have not used any DI framework, such as Dagger 2, on their first projects. This sounds ridiculous, but it is true. How?

In the above illustration, [AA459003] ClassA uses an object created, or created, by itself, while ClassB only uses an object instance, regardless of where it comes from. So it can be pointed out that DI means that a particular entity or class example can get any dependence it needs from the outer world . In other words, the class is not concerned with how it becomes an addiction ̵

1; just how to use it.

That's great, but how do you add an addiction to an object? There are two main options:

  • Pass dependencies through the object designer.
  • Use a DI framework.

Someone depends on the developer, who should base their decision according to the size and complexity of the application. For more information on DI, check out this helpful resource.

To DI or Not to DI?

So what are the pros and cons of using DI in your project? To help you with the answer, take a look at the SOLID principles of object-oriented programming, which are five principles that improve the reusability of code and reduce the need to refact any class. DI is directly related to 2 of these columns, in particular Simple responsibility Principle and Dependency Inversion . Explained Brief

  • Simple Responsibility Perspective states that each class or module of a program is responsible for only one single program of this function.
  • Dependency Inversion states that high level modules should not depend on low level modules; both should depend on abstractions.

DI supports these goals by decrypting the creation and use of an object. Thus, you can replace dependencies without changing the class that uses them, and also reduce the risk of changing a class because one of its dependencies changed.

This makes DI a good option when a program is expected to grow significantly in size and / or complexity.

Using Kotlin to Simplify DI

So why use Koin instead of one of the other DI frames? The answer: Koin is more consistent and fair than the others.

Take the popular Dagger 2 as an example. To use Dagger 2 you must first become familiar with terms such as module and component and notes such as @ Inject . While the steep learning curve of this framework ultimately pays off, to get the most out of it, you still need to learn some advanced concepts such as scope and subcomponents.

In contrast, Koin allows you to

Koin Basics

According to the official documentation, you can start using Koin in three simple steps: [[19659022] Declare a module : Defines the units to be injected into a time in the app.

selection applicationModule = module {
single {AppRepository}
} 
  • Start Koin : A single line, startKoin (this listOf (applicationModule)) allows you to start the DI process and specify which modules are available when needed, in this case, only applicationModule .

    class BaseApplication: Application () {
    override fun on Create () {
    super.onCreate ()
    startKoin (this, listOf (applicationModule))
    }
    } 
  • Perform an Injection :
    In tune with the Kotlin functions, Koin allows to perform lazy injections in a very convenient manner.

    class FeatureActivity: AppCompatActivity () {
    private selection appRepository: AppRepository by injection ()
    ...
    } 
  • A limitation when using Koin is that you can only inject addictions into Activity classes out of the box. In order to inject addictions into other class types, you must do so through the associated designers. To solve this problem, Koin allows classes to adhere to the KoinComponent interface, so that injections are possible on non-[Class] activity classes. You will see an example of this later.

    Getting Started

    Now that the theory is clear, it's time to get to the action! From this section on, you will create a program named Note Me! . You will see how a program can include DI while following a proper architecture that advocates separation of concerns. You will use the popular model viewer (MVP) as an architectural pattern in the presentation layer.

    Start by downloading the startup project using the Download materials button at the top or bottom of the tutorial. The start-up project includes basic skeleton and some assets.

    The code is organized by functional modules, especially splash main and [19659000] and . Furthermore, the application includes help packages such as di for DI, repository including database function, model and utils .

    . ] Build and run the startup project.

    As you can see, the program looks like it. But before it can work, you still have to add all the logic. If you click either Attendance or Grading buttons, the program will report a crash due to failure to implement certain features.

    Remember that by default Android Studio contains the following line of methods that have been exceeded from interface:

      TODO ("not implemented") // To change the body of created functions, use File | Settings | Film Alver. 

    This will cause a crash until this chip is replaced with a proper method implementation.

    Building Mark Me!

    In this section you will include Koin as the DI framework for the application skeleton in the start project.

    Notice me! is an app designed for teachers. It allows a teacher to register registration and grading for a class. The Start File AndroidManifest.xml file shows three elements :

    • SplashActivity includes the MAIN intent filter .
    • MainActivity allows the user to navigate to two functions, Presence and Grading .
    • FeatureActivity actually implements said features. The app skeleton includes the class Student defined in the file Data.kt in the model package. The project also includes a couple of adapters in the package . These implementations are not directly related to the subject of this tutorial, but please look at them and analyze their behavior.

      Take the time to inspect the rest of the startup project and all the features included outside the box, such as the strings.xml, dimension.xml and styles.xml resource files.

      Add Koin to the project

      First, add Koin to the app dependencies. Open the project build.gradle and add the following line in the ext block of the object buildscript :

        koin_version = & # 39; 1.0.2 & # 39 ; 

      ] Then see build.gradle of the module app and include the next addiction in the corresponding section:

        // Koin for Android
      implementation "org.koin: koin-android: $ koin_version" 

      Now synchronize your project and you will be ready to start using Koin .

      Defining Addictions

      Once you have added Koin to your project, you can start defining the dependencies to be injected into your code when needed.

      If you are considering the project, you see it to finish Notice me! You must specify whether the information is to be stored in a database or user preferences. Create a package di and a new file Modules.kt to define the units to be specified.

      Then add the following excerpts, taking care to import what IDE suggests in each case.

       selection applicationModule = module (override = true) {// 1
      factory {(see: SplashContract.View) -> SplashPresenter (view)} // 2
      factory {(see: MainContract.View) -> MainPresenter (view)}
      factory  {(see: FeatureContract.View) -> FeaturePresenter (view)}
      single  {AppRepository} // 3
      single  {androidContext (). getSharedPreferences ("SharedPreferences", context.MODE_PRIVATE)}
      single {
      Room.databaseBuilder (androidContext (),
      AppDatabase :: class.java, "app database"). Build ()
      }
      } 

      As you can see, the code above creates a new Koin module that includes several key entities. Remember:

      1. The module is marked as override which means that the content will override any other definition in the application.
      2. A factory ] is a definition that gives you a new occurrence every time you request this object type. In other words, it represents a normal object. Any presenter involved in the application will be injected in the example example in this manner. depicts a singleton component, for example an example unique across the application. : Koin single and factory object declarations allow you to include a type in angle brackets and a lambda expression that defines the way the object is to be built. Due to the SOLID principles, the type specified is usually an interface that the object for injection must implement. This makes this object easily exchanged in the future. For example, SplashPresenter must initially implement SplashContract.Presenter and will use an SplashContract.View object as argument constructor.

        Start Koin

        Since the addiction module is already defined, you only need to declare the availability. Open BaseApplication.kt and include the following excerpts, and make sure the imports are also there:

         class BaseApplication: Application () {
        override fun on Create () {
        super.onCreate ()
        startKoin (this, listOf (applicationModule))
        }
        } 

        As you can see, applicationModule now contains the list of add-on modules for Koin. Then, this class makes visible in the manifesto as application class:

         ...
        <application
        android: name = "BaseApplication"
        ...
        

        Injection Objects

        As previously mentioned, DI helps to make the code easier to reuse and test. Given that you are using an MVP architecture implementation, the goal is to make any presenter in the app as resolved as possible. In other words, presenting will receive but not instantiate other classes.

        Begin by changing how SplashActivity instances the presenter .

        Basically, you can see that it is created in a lazy way on this line:

          private selection splashPresenter: SplashContract.Present of lat {SplashPresenter (this)} 

        But when using DI, the line will look like this:

          private selection splashPresents: SplashContract.Presents by injection {parametersOf (this)} "Now,  splashPresenter  is easily injected when needed. The term  parametersOf ()  is part of the Koin library and allows you to enter entry arguments for the object designer. If you remember in the  Modules.kt  file, you defined a factory to return a new  SplashContract.Present  when a  SplashContract.View  is given. In this case,  SplashActivity    SplashContract.View  is submitted to the factory through  parametersOf () . 

        Like the previous module must also be updated are how MainActivity gets his presenter . The new form should be:

          private selections mainPresenter: MainContract.Presents by injection {parametersOf (this)} 

        Now, feature package content has some updating. In FeatureActivity the presenter's call changes by replacing:

          private selection featurePresent: FeatureContract.Present of lat {FeaturePresenter (this)} 

        With:

          private selection functionPresent: FeatureContract.Presents of inject {parametersOf (this)} 

        Now complete the method onResume by adding:

          // Load persistent data if any
        featurePresenter.loadPersistedData (data = classList, featureType = function) 

        This tells the presenter to load persistent data available. If you happen to be running the project right now, you will get a crash on this exact line since loadPersistedData does not yet have implementation. Don't worry, you need to fix this.

        Slightly further in the code, replace a pair of TODO s as follows:

         override the fun showToastMessage (msg: String) {
        toast (msg) // Anko tool for Toast messages
        }
        
        override fun on PersistedDataLoaded (data: List ) {
        (rvItems? .adapter like? RwAdapter )?. updateData (data)
        } 

        When the user stores data, a message is displayed thanks to showToastMessage . You use onPersistedDataLoaded to publish the retrieved data in a list. Clearly, you still need to define updateData .

        Open the RwAdapter interface and the abstract method there:

          funny updateData (data: List) 

        You should see that both FeatureGradingAdapter and FeatureAtendanceAdapter ] requires an implementation for this method. For FeatureGradingAdapter add the following:

         override funny update data (data: list) {
        data.forEachIndexed {index, student ->
        data list? .first {student.name == it.name}?. grade = student.grade
        notifyItemChanged (index)
        }
        } 

        FeatureAttendanceAdapter proposal is:

         override funny update data (data: list ) {
        data.forEach {student ->
        data list? .first {student.name == it.name}?. attendance = student.attendance
        }
        notifyDataSetChanged ()
        } 

        As you can see, the implementations are quite similar, but not exactly the same. In the first implementation, the changes to the [attendance] list are notified individually to the adapter, while the second rating list in the second implementation changes as a group at the end of the loop. The only reason for this difference is to show two possible approaches.

        Finally, FeaturePresenter modifies how repository instances so it looks like:

          private selection repository: FeatureContract.Model  by injection () 

        Don't forget to turn the class into a KoinComponent .

          class FeaturePresenter (private display: FeatureContract.View ]?)
        : FeatureContract.Presenter, KoinComponent {

        Remember that Koin cannot inject Activity items out of the box. In this case, since FeaturePresenter is not an instance of Activity you must add the KoinComponent interface to the class.

        Now is the time to give a proper definition for loadPersistedData which finally looks like this:

          overrides funny loadPersistedData (data: List  featureType: ClassSection) {
        when (featureType) {
        ClassSection.ATTENDANCE -> repository.fetchFromPrefs (data)
        ClassSection.GRADING -> repository.fetchFromDb (data = data,
        callback = {loadedData ->
        show? .onPersistedDataLoaded (loadedData)
        })
        } 

        Again, don't be upset if you run your code now and you get a crash since you haven't given any definition for fetchFromDb yet.

        Finishing!

        Last, you want to work on the depot module - especially the singleton class AppRepository . When you open it, you see a few TODO s to get rid of. Time to deal with them!

        Begin by adding these two constants at the beginning of the file, before class definition, so that they are available when needed:

          private const selection MSG_DATA_SAVED_TO_DB = "Data saved to DB"
        private const selection MSG_DATA_SAVED_TO_PREFS = "Data saved to prefs" 

        Then proceed by replacing the first method in AppRepository turning it into this:

         override funny add2Db (data: List, callback: ( String) -> Unit) {
        doAsync {
        database.userDao (). insertStudentList (data)
        uiThread {
        callback (MSG_DATA_SAVED_TO_DB)
        }
        }
        } 

        Once you have added the above code, you will receive an undissolved reference error because database was never injected into AppRepository . This device refers to the implementation of Room in the project. This device was also defined within the appendices.

        Add the next line right after the class header and make sure it implements the interface KoinComponent .

          private selection database: AppDatabase by injection () 

        The method asynchronously insert a list into the database and notify via a message in a lambda callback.

        The next method for filling in allows you to store data in the App SharedPreferences . Again, using Anko to perform the task in the background using doAsync and notifying it in the main using uiThread :

         ]
        override fun add2Prefer (data: List  callback: (String) -> Device) {
        doAsync {
        data.forEach {
        with (sharedPreferences.edit ()) {
        select jsonString = Gson (). toJson (den)
        putString (it.name, jsonString) .commit ()
        }
        }
        uiThread {
        callback (MSG_DATA_SAVED_TO_PREFS)
        }
        }
        } 

        The last two methods implement data retrieval from the database and preferences, respectively.

         override fun fetchFromDb (data: List  callback: (List ) -> Unit) {
        doAsync {
        choice list = database.userDao (). loadAllStudents ()
        uiThread {
        callback (list)
        }
        }
        }
        
        override fun fetchFromPrefs (data: List ): List  {
        data.forEach {
        choice element: Student? = Gson (). FromJson (sharedPreferences.getString (it.name, ""), Student :: class.java)
        element? .let {persitem ->
        it.attendance = persItem.attendance
        it.grade = persItem.grade
        }
        }
        
        return data
        } 

        The only difference between the two methods above is that the database questions are performed in a work thread, not the most important.

        And with that, you're done implementing Notice me! Congratulations!

        App Performance Analysis

        When done, get the app running as in the following Notice me! demo video:

        When started, the screen appears for just a few seconds and then jumps directly to MainActivity . From this screen, the user can navigate to one of the available features: Presence or Grading. Both provide a list showing some information about a class of students.

        You can gracefully scroll along these lists and check / remove the mark in one or more student registers. Pressing the corresponding buttons stores the selections. The navigation is handled by Toolbar and Back Arrow events.

        Testing: Insert Koin

        In order to assess a DI framework, you need to know how well it behaves when it comes to a unit test. Good news! Koin perfectly addresses this need.

        First, add some dependencies to the app module build.gradle :

          dependencies {
        ...
        // Koin testing tool
        test implementation "org.koin: koin-test: $ koin_version"
        test implementation & com; nhaarman: mockito-kotlin: 1.5.0 & # 39;
        } 

        The first library brings Koin DSL, version 1.0.2 in this case, to testing, while others add additional features to Mockito so that it can work with Kotlin.

        Note ]: If you've never heard of Mockito, it's a mocking framework for device testing in Java. If you are interested in adding Mockito to your project, read about Android Unit Testing with Mockito on our site.

        Generally speaking, device tests make sense on non-Android layers since you want to check the logic, but not the frame itself. Since this project uses MVP as an architecture pattern, presenters should be the target.

        In this example, you will only check that a method is called when a particular state occurs, which will show the compatibility and convenience of using Koin for testing.

        Checking the Approach

        According to the above, create a file FeaturePresenterTest.kt in the folder test .

        Then add the following excerpts:

         class FeaturePresenterTest: KoinTest {// 1
        private selection display: FeatureContract.View = mock () // 2
        private selection depot: FeatureContract.Model by injection ()
        private selection presenter: FeatureContract.Presents by injection {parametersOf (view)}
        
        @For
        fun before () {
        startKoin (listOf (applicationModule)) // 3
        declareMock  () // 4
        }
        
        @After
        funny after () {
        stopKin () // 9
        }
        
        @Test
        fun` check that onSave2DbClick invokes a storage number «() {// 5
        select student list = listOf (
        Student (0, "Pablo", true, 8),
        Student (1, "Irene", false, 10)
        )
        choice dummyCallback = argumentCaptor <(String) -> Unit> () // 6
        
        presentator.onSave2DbClick (student list) // 7
        Mockito.verify (repository) .add2Db (eq (studentList), dummyCallback.capture ()) // 8
        }
        } 

        While the code is quite simple, there are some things to keep in mind:

        1. The class must implement the KoinTest interface.
        2. All the elements used must be instantiated or injected, even if you do not use them directly, like the view . Since the Android OS does not allow you to invoke activity, the view must be directly mocked .
        3. Koin must start normally so that the injections take place, including all the addiction modules.
        4. All non-tested objects must be highlighted. In Koin you can use declarer for the injected objects.
        5. It is good practice to use descriptive names for your methods. Remember that you can use `` to enter a name with white spaces.
        6. argumentCaptor allows you to take a value or variable and use it later with some Mockito expressions. This is required when using Koin .
        7. Since you check the presenter, call one of their methods with the right arguments.
        8. This line is where the evaluation actually takes place. In this case, verify that the method add2Db from the repository is called. If you remember the FeaturePresenter class, this should be the case when invoking onSave2DbClick . The method eq is also mandatory because of limitations between Kotlin and Mockito .
        9. Always remember to stop Koin .

        Run the test and make sure everything passes!

        Where to go from here?

        You can download the finished sample project with Download Materials button at the top or bottom of the tutorial.

        While there are several good references about DI, and Koin in particular, the best source may be the official documentation, which is quite concise and self-explanatory.

        Articles like this make you start working with Koin much easier. This article is a more practical example that can help you with your first attempts.

        If you want to continue working on the sample account you just finished, try replacing the presentation layer architecture with MVVM. This pattern is becoming very popular in the field, especially after the Google I / O 2017 conference, where it was incorporated into Android Architecture Components (AAC). Koin contains a special DSL classes to handle this new ViewModel thing.

        I hope you liked this tutorial on Koin . If you have any questions or comments, please join the forum discussion below!


    Source link