匕首线切割图纸下载_我们从匕首到科恩的旅程

匕首线切割图纸下载

When you think about Dependency Injection (DI) on Android, the first library you probably think of is Dagger. But, with Kotlin becoming a first-class citizen as an Android language, there were a couple of new developments in the DI area.

当您想到Android上的依赖注入(DI)时,您可能想到的第一个库是Dagger 。 但是,随着Kotlin成为使用Android语言的一流公民,DI领域出现了一些新的发展。

Like probably 90% of the Android apps out there, we at COYO also relied on Dagger as our dependency injection framework. We had a couple of global modules and many Activity- and Fragment-scoped modules as well. The Engage app consists of roughly 25 screens.

就像大约90%的Android应用程序一样, COYO的开发人员也依赖Dagger作为我们的依赖注入框架。 我们有几个全局模块,以及许多“ Activity和“ Fragment作用域的模块。 参与应用大约由25个屏幕组成。

But at some point, especially the scoped modules, caused us headaches. Adding a new screen (thus new Activity and sometimes a Fragment, too) forced the author to write a lot of boilerplate code. It was also very hard to get it right in the first attempt. Most Dagger users probably know what I’m talking about. Who of you never forgot to add your new module in ActivityBindingModule? Sometimes this became very frustrating.

但是在某些时候,特别是作用域内的模块使我们头疼。 添加一个新屏幕(因此也添加了新的Activity和一个Fragment ),迫使作者编写了许多样板代码。 第一次尝试也很难做到正确。 大多数Dagger用户可能知道我在说什么。 你们中谁从未忘记在ActivityBindingModule添加新模块? 有时,这变得非常令人沮丧。

Our COYO Engage app already has a decent age, since it started at the beginning of 2017. Back then, it was written in Java and migrated to Kotlin during the last years. Today, the amount of Kotlin code is almost 100%. Of course, the app started with Dagger, but with the migration to Kotlin, the new Kotlin based DI frameworks (Koin, Kodein, andmany more) became interesting.

自从2017年初开始,我们的COYO Engage应用程序就已经有了不错的时代。那时,它是用Java编写的,并在过去几年中迁移到Kotlin。 如今,Kotlin代码的数量已接近100%。 当然,该应用程序从Dagger开始,但是随着向Kotlin的迁移,基于Kotlin的新DI框架( KoinKodein以及更多)变得很有趣。

我们如何使用匕首 (How we used Dagger)

With Dagger, we had a couple of global modules and the already mentioned ActivityBindingModule and also a FragmentBindingModule to provide scoped dependencies.

使用Dagger,我们有几个全局模块,已经提到的ActivityBindingModuleFragmentBindingModule以提供范围内的依赖项。

Our ActivityBindingModule looked like this (of course, in reality it had way more activities):

我们的ActivityBindingModule看起来像这样(当然,实际上它具有更多的活动):

@Module(subcomponents = {
        HomeActivityComponent.class,
        MainActivityComponent.class
})
@SuppressWarnings("unused") public abstract class ActivityBindingModule {


    @Binds @IntoMap @ActivityKey(MainActivity.class)
    public abstract ActivityComponentBuilder<?, ?> componentBuilderMainActivity(
            MainActivityComponent.Builder impl);


    @Binds @IntoMap @ActivityKey(HomeActivity.class)
    public abstract ActivityComponentBuilder<?, ?> componentBuilderChannelsAndContactsActivity(
            HomeActivityComponent.Builder impl);


}

An activity module looked like this:

活动模块如下所示:

@ActivityScope
@Subcomponent(modules = MainActivityComponent.MainActivityModule.class)
public interface MainActivityComponent extends ActivityComponent<MainActivity> {


  @Subcomponent.Builder interface Builder extends ActivityComponentBuilder<MainActivity, MainActivityComponent> {
  }


  @Module final class MainActivityModule extends ActivityModule<MainActivity> {
    @Provides
    @ActivityScope
    MainPresenter provideMainPresenter(UserDao userDao, MainApiInterface apiInterface) {
      return MainPresenter(userDao, apiInterface);
    }
  }
}

We also migrated from MVP to MVVM during 2019, which meant having some more needed methods in every Activity or Fragment module. Some people use a ViewModelModule for that, which works similarly as the ActivityBindingModule. We were using a different approach with a dedicated ViewModelFactory for each activity/fragment. For every ViewModel we had to write 3 additional functions in the Activity- or Fragment-related module.

我们还在2019年从MVP迁移到MVVM,这意味着每个Activity或Fragment模块中都需要更多一些方法。 有人为此使用ViewModelModule ,它与ActivityBindingModule相似。 对于每个活动/片段,我们使用了专用的ViewModelFactory的不同方法。 对于每个ViewModel,我们都必须在与Activity或Fragment相关的模块中编写3个其他函数。

In the end, we ended up writing so much boilerplate code when adding a new activity and we were really unhappy with this situation. Especially since DI is supposed to do the heavy lifting of generating as much boilerplate code as possible.

最后,当添加一个新活动时,我们最终编写了很多样板代码,对此我们真的感到不满意。 特别是因为DI应该承担繁重的工作,以生成尽可能多的样板代码。

Dagger is a replacement for these FactoryFactory classes that implements the dependency injection design pattern without the burden of writing the boilerplate. It allows you to focus on the interesting classes. Declare dependencies, specify how to satisfy them, and ship your app.

Dagger替代了这些FactoryFactory类, FactoryFactory实现了依赖注入设计模式,而无需编写样板。 它使您可以专注于有趣的课程。 声明依赖项,指定如何使其满足要求,然后交付您的应用程序。

This is a quote from the dagger website. For us, it felt like an unfulfilled promise.

这是匕首网站的报价。 对我们来说,这是一个未兑现的承诺。

寻找替代品 (Seeking for alternatives)

From time to time we researched if there are alternatives and spiked them. At COYO we have free coding Fridays every 2 weeks and we can use them for learning or improving the products on our own to some certain degree of freedom.

我们不时研究是否有替代方案,并加标。 在COYO,我们每两周的星期五免费的编码 ,我们可以使用它们来在一定程度上自由地学习或改进我们的产品。

Image for post
Photo by Amanda Dalbjörn on Unsplash
AmandaDalbjörnUnsplash拍摄的照片

In general, I like working on architectural topics and trying new libraries. So, I briefly searched the web, spiked Koin and Kodein, and quickly decided to go further with Koin (Honestly, I can’t remember why I ditched Kodein).

总的来说,我喜欢研究建筑主题并尝试新的库。 因此,我短暂地搜索了网络,掺入了Koin和Kodein,并Swift决定与Koin进一步合作(老实说,我不记得为什么放弃了Kodein)。

So after 2 short spikes, and also great developments on the Koin side with dedicated support for ViewModels, we decided to make the shift to Koin. To sum it up I think it took roughly 2.5 to 3 days. I like those pull requests reducing the lines of code count, and this was clearly one of them:

因此,在短暂的两次突飞猛进以及Koin方面的巨大发展以及对ViewModels的专门支持之后,我们决定转向Koin。 总结一下,我认为大约花了2.5到3天。 我喜欢那些减少代码行数的拉取请求,这显然是其中之一:

Image for post

所以.. Koin怎么样? (So.. How is Koin doing?)

Let me first explain our usage of Koin. In our Application class, we have to set up all the modules we want to use in Koin. These are basically all modules we have.

首先让我解释一下我们对Koin的用法。 在Application类中,我们必须设置要在Koin中使用的所有模块。 这些基本上是我们拥有的所有模块。

Our Koin setup looks similar to this:

我们的Koin设置类似于以下内容:

open class YourApplication: Application() {
  private val tracker: Tracker by inject()
  private val crashReporter: CrashReporter by inject()


  override fun onCreate() {
    super.onCreate()


    startKoin {
      androidLogger()
      androidContext(this@YourApplication)
      modules(myModules)
    }
    
    crashReporter.init()
    tracker.track("Application started")
  }
}

The Koin journey obviously starts with startKoin { ... } . We are telling Koin to use our application object as context. Also, we configure a list of modules: myModules which is a list of all modules of our app. When using by inject() to inject a dependency, this is happening lazily. The immediate way, it’d look like this: private val tracker: Tracker = get() . Needless to say, it’s generally better to use lazy loading to reduce loading times when spinning up a new activity.

Koin的旅程显然始于startKoin { ... } 。 我们告诉Koin使用我们的应用程序对象作为上下文。 另外,我们配置模块列表: myModules ,它是我们应用程序所有模块的列表。 当使用by inject()注入依赖项时,这是很懒惰的。 直接的方法如下: private val tracker: Tracker = get() 。 不用说,通常最好使用惰性加载来减少启动新活动时的加载时间。

Those modules in myModules , which is a List can be defined anywhere, like this:

可以在任何位置定义myModules那些模块(即一个List ,如下所示:

val trackingModule = module {
  single {
    DebugCrashProxy()
  } bind CrashProxy::class


  single<Timber.Tree> {
    Timber.DebugTree()
  }
  single {
    // get() is resolved to what single<Timber.Tree> produces
    TimberLogging(get())
  } bind Logging::class
}

Here can you see a module with 3 declaration, following the Singleton pattern. So it means the object is only created once, afterwards every call to this returns the already created object. The first created object in the example is (additionally) bound to its interface type CrashProxy . This way it can be injected by base class or the actual class:

在此处可以看到遵循Singleton模式的带有3个声明的模块。 因此,这意味着该对象仅创建一次,此后每次调用都会返回已创建的对象。 在示例中,第一个创建的对象(附加)绑定到其接口类型CrashProxy 。 这样,它可以由基类或实际类注入:

val crashProxy: CrashProxy by inject()         // This works!
val crashProxy: DebugCrashProxy by inject() // Also works! 🎉

Because the classes had a Debug prefix, you might have guessed, that we have the very same module also built for the release buildType, because we don’t want to DebugCrashProxy (which does nothing) to be in production releases.

因为这些类具有Debug前缀,您可能已经猜到,我们也为release buildType构建了完全相同的模块,因为我们不希望DebugCrashProxy (不执行任何操作)出现在生产版本中。

So we used the power of the Gradle build system and use almost the same module under src/release , while the other module resides in src/debug:

因此,我们使用了Gradle构建系统的强大功能,并在src/release下使用了几乎相同的模块,而另一个模块位于src/debug

val trackingModule = module {
  single {
    CrashlyticsProxy(FirebaseCrashlytics.getInstance())
  } bind CrashProxy::class


  single<Timber.Tree> {
    ReleaseTree(get())
  }
  single {
    TimberLogging(get())
  } bind Logging::class
}

By declaringTimberLogging(get()) , the dependency of TimberLogging is resolved to the declaration with the matching type, which we defined beforehand (single<Timber.Tree> { … }).

通过声明TimberLogging(get()) ,将TimberLogging的依赖TimberLogging解析为具有我们预先定义的匹配类型的声明( single<Timber.Tree> { … } )。

但是活动范围的模块呢? (But what about activity scoped modules?)

Good question! We have an example here:

好问题! 这里有一个例子:

val contactDetailsModule = module {
  scope<ContactDetailsActivity> {
    viewModel {
      ContactDetailsViewModel(contactId = getOrNull(named("contactId")) ?: "", contactRepository = get(),
        contactUtils = get(), sharedPrefsStorage = get(), featureManager = get())
    }


    scoped { AndroidToastFactory(get<ContactDetailsActivity>()) }
    scoped<String?>(named("contactId")) { get<ContactDetailsActivity>().intent.getStringExtra(CONTACT_ID) }
  }
}

All objects created here are inside the scope of ContactDetailsActivity . The viewModel node is self-explaining and the scoped keyword is like single , just inside a scope.

此处创建的所有对象都在ContactDetailsActivity范围内。 viewModel节点是不言自明的,并且scoped关键字就像single一样,仅在作用域内部。

We are also using the kotlin feature of named parameters. This is not necessary, but we decided to do that for better readability. Otherwise you’d have declarations like SomeObject(get(), get(), get(), get()) which is not very readable.

我们还使用命名参数的kotlin功能。 这不是必需的,但是我们决定这样做以提高可读性。 否则,您将拥有SomeObject(get(), get(), get(), get())声明,如SomeObject(get(), get(), get(), get())

You can also see another feature of named declarations in line 9. This is mandatory if you define multiple objects of the same type. In Dagger we usually used annotations to achieve that.

您还可以在第9行看到named声明的另一个功能。如果您定义了多个相同类型的对象,则这是必需的。 在Dagger中,我们通常使用注释来实现。

The last thing I have to mention about this snippet is this: get<ContactDetailsActivity>() . How does that work? We didn’t define how to create that activity and as you know, as an Android developer you don’t create Activities, but the system does instead. This is unfortunately not offered by Koin directly.

关于此代码段,我不得不提的最后一件事是: get<ContactDetailsActivity>() 。 这是如何运作的? 我们没有定义如何创建活动,并且您知道,作为Android开发人员,您不会创建活动,但是系统会创建活动。 不幸的是,这不是 Koin直接提供的。

But we often have dependencies where you want to use your current activity or fragment as a constructor parameter. After a bit of research, I found this way to be our way to go: https://github.com/InsertKoinIO/koin/issues/428#issuecomment-595950513

但是,在您要将当前活动或片段用作构造函数参数的地方,我们经常会有依赖关系。 经过一番研究,我发现这种方式是我们要走的路: https : //github.com/InsertKoinIO/koin/issues/428#issuecomment-595950513

inline val <reified T: BaseActivity> T.activityScope: Scope
  get() = lifecycleScope
    .addInstance(this)
    .addInstance(this as Activity)
    .addInstance(this as AppCompatActivity)


inline val <reified T: BaseFragment> T.fragmentScope: Scope
  get() = lifecycleScope
    .addInstance(this)
    .addInstance(this as Fragment)


inline fun <reified T> Scope.addInstance(instance: T): Scope {
  val definition = BeanDefinition(
    scopeDefinition = this._scopeDefinition,
    primaryType = T::class,
    kind = Kind.Single,
    definition = { instance }
  )
  val factory = SingleInstanceFactory(this._koin, definition)
  (this._instanceRegistry.instances as HashMap<IndexKey, InstanceFactory<*>>)[indexKey(
    T::class, null
  )] = factory
  return this
}

This code uses one of my favorite Kotlin features, namely extension functions. It is adding a function and fields to existing classes without inheritance. This piece of code is adding the activity or fragment instance to the Koin declarations (“BeanDefinition”) and makes it accessible. When taking a short look at the usage of our scoped module we see the whole picture:

这段代码使用了我最喜欢的Kotlin功能之一,即扩展功能 。 它在不继承的情况下向现有类添加了函数和字段。 这段代码将活动或片段实例添加到Koin声明(“ BeanDefinition”)中,并使其可访问。 简要了解我们的作用域模块的用法时,我们可以看到整个图片:

class ContactDetailsActivity: BaseActivity() {


  private val viewModel: ContactDetailsViewModel by activityScope.viewModel(this)


  private val toastFactory: AndroidToastFactory by activityScope.inject()
  
  private val contactId: String? by activityScope.inject(named("contactId"))




  // ...
}

Here we are not calling by inject() , but instead by activityScope.inject() . By this, we are adding the activity instance to our declarations and can use it then 🎉

在这里,我们不是by inject()调用,而是by activityScope.inject()调用。 这样,我们将活动实例添加到声明中,然后可以使用它it

Other dependencies from global modules can either be injected by activityScope.inject() or just inject().

来自全局模块的其他依赖关系可以由activityScope.inject()注入,也可以仅由inject()

结论 (Conclusion)

Migrating to Koin turned out to be a good idea. We have more fun and less stress adding new features because we have to write less boilerplate code. Also, the build times decreased a bit, since less code is generated.

迁移到Koin真是个好主意。 我们拥有更多的乐趣,而增加新功能的压力则更少,因为我们必须编写更少的样板代码。 而且,由于生成的代码更少,因此构建时间有所减少。

A couple of days ago, Google announced Hilt, which is a wrapper around Dagger, to make it easier to use on Android. This came a bit too late for us since the migration already happened in March 2020 mostly.

几天前,Google宣布推出Hilt ,它是Dagger的包装,以使其更易于在Android上使用。 这对于我们来说为时已晚,因为迁移主要发生在2020年3月。

One last thing to mention: If you consider migration to Koin, keep in mind, that it does not have compile-time checks! So when you lack a declaration of some dependency, you will have a crash at runtime (the dreaded NoBeanDefFoundException). This made the initial migration quite tedious (and made me doubting of course..), but after some initial pain, this turned out to be not a big problem for us, because when adding one new feature there are not so many new dependencies to fulfill. After all, this was the only weak spot of Koin I found.

最后要提到的一件事:如果考虑迁移到Koin,请记住,它没有编译时检查 ! 因此,当您缺少某些依赖项的声明时,运行时将崩溃(可怕的NoBeanDefFoundException )。 这使最初的迁移变得非常乏味(当然让我怀疑..),但是在经历了最初的痛苦之后,这对我们来说并不是什么大问题,因为在添加一个新功能时,并没有太多新的依赖项。履行。 毕竟,这是我发现的Koin的唯一弱点。

翻译自: https://medium.com/coyo-tech/our-journey-from-dagger-to-koin-a41348a09086

匕首线切割图纸下载

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值