preface

  • Original title: Koin vs Dagger, Say Hello to Koin
  • The original address: blog.usejournal.com/android-koi…
  • By Farshid ABZ

This article has received 2.4k+ likes so far and hit Medium hot. It’s a very good article. I also use Koin + Kotlin + Databinding to encapsulates the base library with inline and Reified powerful features. It contains DataBindingActivity, DataBindingFragment, DataBindingDialog, DataBindingListAdapter, and so on. New components are being added.

  • Article: https://juejin.im/post/6844904131803480071
  • GitHub:https://github.com/hi-dhl/JDataBinding

In this article you will learn the following, which will be given in the translator’s thinking section

  • Dagger Strengths and weaknesses versus Koin? Dagger or Koin?
  • Koin syntax features?
  • Why can Koin achieve no code generation, no reflection?
  • What does the Inline modifier do? How to use it correctly? Performance cost?
    • Why is the compiler giving us a warning just by using the Inline modifier?
    • Why does the compiler recommend that the inline modifier be used with a lambda expression?
    • When should I use the inline modifier?
  • What is Reified for? How to use it?
  • What are the performance costs of Koin?
  • Kotlin implements a quicksort algorithm in 5 lines of code, right?

This article involves a lot of important knowledge points, please read on patiently, I believe it should bring you a lot of different things.

The translation

While I was learning the Dagger over and over again, I met Koin. Koin not only saved me time, but also improved my efficiency, freeing me from the complex Dagger.

This article will show you what Koin is, what advantages it has compared to Dagger, and how to use Koin.

What is the Koin

Koin is a practical lightweight dependency injection framework for Kotlin developers, written in pure Kotlin language, using only functional resolution, no proxy, no code generation, no reflection.

Dagger vs Koin

In order to properly compare these two approaches, we used Dagger and Koin to implement a project with MVVM architecture and retrofit and LiveData, With an Activity, four fragments, five View Models, a Repository, and a Web Service interface, this should be the infrastructure for a small project

Let’s take a look at the structure under the DI package, with the Dagger on the left and Koin on the right

As you can see, it takes a lot of files to configure Dagger while Koin only needs 2 files. For example, injecting 1 View Model with Dagger requires 3 files (do you really need that many files?).

Compare the Dagger and Koin lines of code

I used Statistic tool to calculate the number of lines of code generated by the Dagger and Koin before and after compilation of the project. The result was quite surprising

As you can see, the Dagger generates twice as many lines of code as Koin

What about Dagger and Koin compile time

Every time I clean before I rebuild, I get this result

Koin:
BUILD SUCCESSFUL in 17s
88 actionable tasks: 83 executed, 5 up-to-date

Dagger:
BUILD SUCCESSFUL in 18s
88 actionable tasks: 83 executed, 5 up-to-date
Copy the code

I think the results prove that, in a larger, more realistic project, this is very expensive.

So how about using Dagger and Koin

You have to do this if you want to use Dagger in MVVM and Android Support Lib.

First add the Dagger dependency in Module Gradle.

kapt "com.google.dagger:dagger-compiler:$dagger_version"
kapt "com.google.dagger:dagger-android-processor:$dagger_version"
implementation "com.google.dagger:dagger:$dagger_version"
Copy the code

Then, after creating the Modules and Components files, you need to initialize the Dagger in Application (or otherwise).

Class MyApplication : Application(), HasActivityInjector { 
  @Inject
  lateinit var dispatchingAndroidInjector:    DispatchingAndroidInjector<Activity>
override fun activityInjector() = dispatchingAndroidInjector
fun initDagger() {
   DaggerAppComponent
      .builder()
      .application(this)
      .build()
      .inject(this)
  }
}
Copy the code

All the Activity of inheritance BaseActivity, we need to implement HasSupportFragmentInjector and inject DispatchingAndroidInjector.

For view Models, we need to inject The ViewModelFactory in the BaseFragment and implement Injectable.

But that’s not all. There is more to do.

For each ViewModel, Fragment, and Activity we need to tell DI how to inject them, as you can see we have an ActivityModule, FragmentModule, and ViewModelModule.

Let’s look at the following code

@Module
abstract class ActivityModule {
    @ContributesAndroidInjector(modules = [FragmentModule::class])
    abstract fun contributeMainActivity(): MainActivity
   
    //Add your other activities here
}
Copy the code

Fragments are shown below:

@Module
abstract class FragmentModule {
    @ContributesAndroidInjector
    abstract fun contributeLoginFragment(): LoginFragment

    @ContributesAndroidInjector
    abstract fun contributeRegisterFragment(): RegisterFragment

    @ContributesAndroidInjector
    abstract fun contributeStartPageFragment(): StartPageFragment
}
Copy the code

ViewModels are as follows:

@Module
abstract class ViewModelModule {

    @Binds
    abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory

    @Binds
    @IntoMap
    @ViewModelKey(loginViewModel::class)
    abstract fun bindLoginFragmentViewModel(loginViewModel: loginViewModel): ViewModel

    @Binds
    @IntoMap
    @ViewModelKey(StartPageViewModel::class)
    abstract fun bindStartPageViewModel(startPageViewModel:  StartPageViewModel): ViewModel
    ......
}
Copy the code

So you have to add these Fragments, Activities, and ViewModels to DI Modules.

So what do you do in Koin

You need to add Koin dependencies to The Module Gradle

implementation "org.koin:koin-android-viewmodel:$koin_version"
Copy the code

Then we need to create the Module file, and I’ll show you how to do that in a moment, but actually we don’t need the Dagger file.

Dagger has other questions

Learning Dagger is expensive. If someone joins your project or team, he or she will have to spend a lot of time learning Dagger. I’ve been using Dagger for two years and still don’t know much about it. I had to search and learn how to implement new techniques with the Dagger.

Take a look at the Koin code

First we need to add Koin dependencies, as follows:

implementation "org.koin:koin-android-viewmodel:$koin_version"
Copy the code

We used the Koin-Android-ViewModel library because we wanted to use it in MVVM, along with other dependent libraries.

After adding the dependencies, let’s implement the first Module file, like the Dagger, which can implement each module in a separate file, but due to the simplicity of the code I decided to implement all modules in one file, which you can also separate.

First we need to look at the koin syntax features

  • Get () : Parse the instance in the Koin module. Call get() to parse the instance required by the requested component. This get() function is usually used in the constructor, and the constructor value is injected
  • Factory: Declares that this is a factory component and provides you with a new instance on each request
  • Single: Adopt the singleton design pattern
  • Name: Used for naming definitions, needed when you want multiple instances of the same class of different types

Instead of creating many files with multiple annotations and multiple components, we provide a simple, readable file for each class that DI injects.

After understanding the koin syntax features, let’s explain what the following code means

private val retrofit: Retrofit = createNetworkClient()
Copy the code

The createNetworkClient method creates a Retrofit instance, sets baseUrl, and adds ConverterFactory and Interceptor

private val generalApi: GeneralApi =  retrofit.create(GeneralApi::class.java)
private val authApi: AuthApi = retrofit.create(AuthApi::class.java)
Copy the code

AuthApi and GeneralApi are the Retrofit interfaces

val viewModelModule = module {
    viewModel { LoginFragmentViewModel(get()) }
    viewModel { StartPageViewModel() }    
}
Copy the code

Declared as viewModel in the Module file, Koin will supply the viewModel to the ViewModelFactory, binding it to the current component.

As you can see, the LoginFragmentViewModel constructor calls the get() method. Get () parses a LoginFragmentViewModel’s required arguments and passes them to the LoginFragmentViewModel, This parameter is AuthRepo.

Finally, add the following code to the Application onCreate method

startKoin(this, listOf(repositoryModule, networkModule, viewModelModule))
Copy the code

You just call the startKoin method, passing in a context and a list of modules you want to use to initialize Koin.

Now that it’s easier to use viewModels than pure ViewModels, add the following code to the Fragment and Activity views

private val startPageViewModel: StartPageViewModel by viewModel()
Copy the code

With this code, Koin creates a StartPageViewModel object for you, and you can now use the ViewModel in your fragments and activities

The translator think

The author compares Dagger and Kotlin in the following four aspects:

  • Number of files: Based on the MVVM architecture, Dagger and Koltin are used as dependency injection frameworks, respectively. It takes at least 9 files to initialize Dagger, while Koltin only needs 2 files. The number of Dagger files far outnumbers Koltin
  • Number of lines of code: The author used Statistic tool to repeatedly compare the number of lines of code generated by Dagger and Koin before and after compilation of the project, as shown in the figure below

  • The compilation time of THE Dagger and Koin was repeatedly compared, and the results showed that Koin was faster than Dagger
Koin:
BUILD SUCCESSFUL in 17s
88 actionable tasks: 83 executed, 5 up-to-date

Dagger:
BUILD SUCCESSFUL in 18s
88 actionable tasks: 83 executed, 5 up-to-date
Copy the code
  • The cost of learning Dagger is huge. If YOU use Dagger, you should feel the same as the author. The cost of learning Dagger is very high

Note: The author calls the startKoin method in the Application to initialize the Koin module list, which is the way Koin 1X. The Koin team has made a lot of changes in the 2x module column (described below). The code is as follows:

startKoin {
    // Use Koin Android Logger
    androidLogger()
    // declare Android context
    androidContext(this@MainApplication)
    // declare modules to use
    modules(module1, module2 ...)
}
Copy the code

Why can Koin achieve no code generation, no reflection

As a lightweight dependency injection framework, why can Koin achieve no code generation, no reflection? Because of Kotlin’s powerful syntactic sugar (such as Inline, Reified, and so on) and functional programming, let’s take a look at some code first. koin-projects/koin-core/src/main/kotlin/org/koin/dsl/Module.kt

Case a

// TypeAlias is used to redefine the name of an existing type. ModuleDeclaration = Module.() -> Unit Fun Module (createdAtStart: Boolean =false, override: Boolean = false, moduleDeclaration: ModuleDeclaration): Module {// Create Module val Module = Module(createdAtStart, override) // Execute the extension function moduleDeclaration(Module)returnVal mModule: module = module {single {... } factory { ... }}Copy the code

Module is a lambda expression, so you can freely define single and Factory in “{}” until you need them.

Case 2

inline fun <reified T : ViewModel> Module.viewModel(
    qualifier: Qualifier? = null,
    override: Boolean = false,
    noinline definition: Definition<T>
): BeanDefinition<T> {
    val beanDefinition = factory(qualifier, override, definition)
    beanDefinition.setIsViewModel()
    return beanDefinition
}
Copy the code

Inline functions support reified type parameters, which can be accessed within the function by using the reified modifier. Since the function is inline, no reflection is required. The above two examples show why Koin can achieve no code generation and no reflection. I suggest you all go to look at the Koin source code, can learn a lot of skills, I will spend several articles on Koin source code analysis.

Performance penalty from the Inline modifier

What Inline does: To improve performance, calling a function that is Inline will put the code inside it where I call it.

If you’ve read the Koin source code, you’ll notice that inline is used with both the lambda expression and reified modifier. What if we just used the inline modifier to mark functions?

In this article, Consider inline modifier for higher order functions also analyzes why using inline modifier only causes performance problems. And Android Studio comes with a big caveat.

The compiler recommends that we use Inline in functions that take lambda expressions as parameters. If the Inline modifier improves performance, why does the compiler give us a warning? Why does the compiler recommend that the inline modifier be used with a lambda expression?

1. If the Inline modifier improves performance, why is the compiler warning us?

We said earlier that calling a function that is inline puts the code inside the function where I called it. Take a look at this code.

inline fun twoPrintTwo() {
    print(2)
    print(2)
}

inline fun twoTwoPrintTwo() {
    twoPrintTwo()
    twoPrintTwo()
}

inline fun twoTwoTwoPrintTwo() {
    twoTwoPrintTwo()
    twoTwoPrintTwo()
}

fun twoTwoTwoTwoPrintTwo() {
    twoTwoTwoPrintTwo()
    twoTwoTwoPrintTwo()
}
Copy the code

After executing the last method twoTwoTwoTwoPrintTwo, the decompilation results are quite surprising, as follows:

public static final void twoTwoTwoTwoPrintTwo() {
   byte var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print();
}
Copy the code

This shows the main problem with Inline modifiers, as the code grows rapidly when we overuse them. That’s why IntelliJ warns us when we use it.

2. Why does the compiler recommend that the inline modifier be used with a lambda expression?

Because the JVM does not support lambda expressions, lambda expressions in non-inline functions are compiled as anonymous classes, which has a significant performance overhead, and they are slow to create and use. When we use the inline modifier, we do not need to create any other classes at all.

fun main(args: Array<String>) {var a = 0 // With inline Lambda expression repeat(100_000_000) {a += 1} var b = 0 // without inline Lambda expression noinlineRepeat(100_000_000) { b += 1 } }Copy the code

The compilation results are as follows:

Public static final void main(@notnull String[] args) {int a = 0; // An inline Lambda expression will place the code in the int where I called ittimes$iv = 100000000;
   int var3 = 0;

   for(int var4 = times$iv; var3 < var4; ++var3) { ++a; } // Final IntRef b = new IntRef(); b.element = 0; noinlineRepeat(100000000, (Function1)(newFunction1() {
      public Object invoke(Object var1) {
         ++b.element;
         returnUnit.INSTANCE; }})); }Copy the code

So when should we use the inline modifier?

The most common scenario with inline modifiers is when a function takes an argument to another function (a higher-order function), such as Filter, Map, joinToString, or some independent function repeat.

If you do not have a function type as an argument, and if you do not have reified as an instantiated type parameter, you should not use the inline modifier.

Looking at the Koin source code, inline should be used in conjunction with a lambda expression or reified modifier, and Android Studio is getting smarter with a big warning if used incorrectly.

The Reified type parameter is the Reified modifier

Reified type parameter: Use the reified modifier to qualify a type parameter, which in combination with the inline modifier can be accessed directly within the function.

I want to share two very common examples of using Reified modifiers. Reified -type-parameters are not possible using Java.

Case 1:

Inline Fun <reified T> gson.fromJSON (json: String) = fromJson(json, T::class.java) User = Gson().fromJson(json) val user = Gson().fromJson<User>(json)Copy the code

Case 2:

inline fun <reified T: Activity> Context.startActivity(vararg params: Pair<String, Any? >) = AnkoInternals.internalStartActivity(this, T::class.java, params)Copy the code

Koin brings performance losses

We’ve been thinking a long time about whether we need to write this section because it was fixed in the Koin 2X version. Here’s an official link from the Trenches — What’s next for Koin? “, later thought or write, as their own a study note.

This is because someone started an Issue(Bad Performance in some Android devices) that has now been closed, and he pointed out that as Dependency numbers increase, Koin performance deteriorates. And I made a pair like the one below:

If you have used Koin 1X before, you will feel that the cold start time of Koin 1X is too long, and when there is a lot of dependence, the search time will be a little longer. Later, the Koin team also found that this problem exists, what is the problem?

They maintain a list of BeanDefinitions in the BeanRegistry class, and then use Kotlin’s filter function to find the corresponding Beandefinitions, So find a Definition whose time complexity is O(n). If there are M layers of Dependency on average, then the time complexity will become O(M *n).

The solution of the Koin team is to use HashMap and exchange space for time. The time complexity of finding a Definition becomes O(1). After optimization, the results are as follows:

Koin 2X not only provides significant improvements in performance optimization, but also includes many new features, such as the ability for FragmentFactory dependencies to be injected into Fragments just like ViewModels, and automatic unboxing, which will be examined in more detail in a later article.

Kotlin implements the quicksort algorithm in five lines of code

I want to share a quicksort algorithm, which is a cool examples of functional programming. When I saw this code, I was shocked that it could be written like this.

fun <T : Comparable<T>> List<T>.quickSort(): List<T> = 
    if(size < 2) this
    else{ val pivot = first() val (smaller, Partition {it <= pivot} smaller. QuickSort () + pivot + greater.quicksort ()} ListOf,5,1 (2). QuickSort () / /,2,5 [1]Copy the code

Finally, share a translator’s own navigation website

Based on the response framework of Material Design, the translation of a “Design for the Internet people domestic and foreign famous website navigation”, collected domestic and foreign popular websites, including news, sports, life, entertainment, Design, product, operation, front-end development, Android development and so on, if you have any good suggestions, You can also leave a message, click to browse if it helps you, please help me a like, thank you

Ps: If there is any address in the website that the original author does not want to show, please leave a message and tell me, I will delete it immediately

International information website daquan

Android webmaster

reference

  • My favorite examples of functional programming in Kotlin
  • Koin website: https://insert-koin.io/
  • Effective Kotlin: Consider inline modifier for higher-order functions

conclusion

Committed to sharing a series of Android system source code, reverse analysis, algorithm, translation related articles, is currently translating a series of European and American selected articles, not only translation, more important is behind the translation of each article thinking, if you like this article, please help me a like, thank you, looking forward to growing with you.

We are planning to build a complete and up-to-date project of AndroidX Jetpack related components and related component theory analysis articles. So far, we have already included App Startup, Paging3, Hilt, etc. We are gradually adding other new Jetpack members, and the warehouse is continuously updated. Check it out: AndroidX-Jetpack-Practice, if this warehouse is helpful to you, please give me a like, I will finish more Jetpack new members project Practice.

algorithm

Due to the huge question bank of LeetCode, hundreds of questions can be screened for each category. Due to the limited energy of everyone, it is impossible to complete all the questions. Therefore, I classified the questions according to the classical types and sorted the questions according to their difficulty

  • Data structures: arrays, stacks, queues, strings, lists, trees…
  • Algorithms: search algorithms, search algorithms, bit operations, sorting, mathematics,…

Each problem is implemented in Java and Kotlin, and each problem has a solution idea. If you like algorithms and LeetCode as I do, you can follow my LeetCode problem on GitHub: Leetcode-solutions-with-java-and-kotlin, let’s learn And look forward to growing with you

Android 10 source code series

I am writing a series of Android 10 source code analysis articles. Understanding the system source code is not only helpful for analyzing problems, but also very helpful for us in the interview process. If you like studying Android source code like I do, Keep an eye on my GitHub android 10-Source-Analysis repository, where articles will be synced

  • Android 10 source code analysis: How is APK generated
  • 0xA02 Android 10 Source code Analysis: APK installation process
  • 0xA03 Android 10 Source code Analysis: APK load process resource loading
  • 0xA04 Android 10 source code Analysis: APK Load Process resource Load (2)
  • 0xA05 Android 10 source code analysis: Dialog load drawing process and use in Kotlin, DataBinding
  • 0xA06 Android 10 Source code Analysis: WindowManager View binding and architecture
  • More and more

Android Apps

  • How to obtain video screenshots efficiently
  • How to encapsulate Kotlin + Android Databinding in a project
  • Google engineers have just released a new feature for Fragments, “A new way to transfer data between Fragments” and source analysis

Tool series

  • The few people who know about AndroidStudio shortcuts (1)
  • The few people who know about AndroidStudio shortcuts (part 2)
  • What you need to know about the ADB command
  • 10 minutes to get started with Shell scripting

The reverse series

  • Android Studio dynamically debugs the APP based on Smali files
  • The Android Device Monitor tool cannot be found in Android Studio 3.2