Lightweight, minimalistic dependency injection library for Kotlin & Android

Overview

Build Status Kotlin Android Arsenal

‼️ This project is in maintenance mode and not actively developed anymore. For more information read this statement. ‼️

Katana

Katana is a lightweight, minimalistic dependency injection library (similar to the service locator pattern) for Kotlin on the JVM, designed especially with Android in mind.

  • Extremely lightweight footprint (only ~15kB in classes and ~130 methods after ProGuard), plain Kotlin, no third-party dependencies
  • It's fast (also see this comparison)
  • "Less is more", therefore:
    • No global singleton state. Likelihood of memory leaks is greatly reduced unless YOU are doing something wrong ;P
    • No reflection (see this regarding type erasure)
    • No code generation (unless inline functions count)
    • No dependency overrides possible (see Overrides)

Getting Started

Want to know more? Please read Getting Started.

Artifacts

Katana consists of a core library and several additional libraries that extend Katana's functionality. All artifact are published in group org.rewedigital.katana. Here's a quick overview of all available artifacts:

Artifact Description
katana-core Provides core functionality. Suitable for plain Kotlin (JVM) and Android.
katana-android Android-specific extensions like modules for Activity and Fragment, KatanaFragment etc.
katana-androidx-fragment Additional support for androidx.fragment providing a Katana-based FragmentFactory.
katana-androidx-viewmodel Enables dependency injection for AndroidX ViewModel.
katana-androidx-viewmodel-savedstate Support for AndroidX ViewModel with SavedState.

Help & Contribution

If you found a bug or want to suggest a feature, please create an issue.
If you need help, visit our Slack channel.

Further Reading

License

The MIT license (MIT)

Copyright (c) 2018-2021 REWE Digital GmbH

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Comments
  • Best way to inject dependencies into Fragment provided by parent Fragment / Activity

    Best way to inject dependencies into Fragment provided by parent Fragment / Activity

    In Android DI you quite often share dependencies between Fragments via the parent Fragment or the Activity.

    Katana allows to define dependsOn components. One would think, that you would just use that:

    class MyFragment: Fragment(), KatanaTrait {
      override val component = createComponent {
        modules = listOf(
          createSupportFragmentModule(this),
          myFragmentModule,
          // some other modules
        ),
        dependsOn = listOf(MyApp.applicationComponent, (requireActivity() as KatanaTrait).component)
      }
    
      val dependency: MyDependencySuppliedByActivity by inject()
    }
    

    But there are multiple problems with this:

    1. Getting the Activity component the way I outlined above doesn't work as the Activity isn't available when the Fragment component is created. So we would have to by lazy {} create the component or create it in onAttach/onCreate. That in turn means that we would need to either by lazy { injectNow() } all injections in the Fragment or manually set them in onAttach/onCreate as well.
    2. As Katana does not allow overrides such chains of components obviously can lead to OverrideExceptions, especially when in the case of Activity -> ParentFragment -> ChildFragment. A good example for that is Glide which should always be bound to the hosting Activity/Fragment to ensure that it stops image requests and cleans up when the context of the ImageView is gone. So it is not unusual to find one bind<Glide> per Fragment module which becomes awkward when having child Fragments as you have to name all those binds. Similar things can happen with nested NavHostFragmentss and the according NavControllers.

    An alternative which solves 2, but not 1 is to specifically inject stuff provided by the parent Fragment/Activity within the (child) Fragment module. Thus we can omit any potential overrides as we don't want them in the Fragment anyway:

    val myFragmentModule = createModule {
    
      bind<MyDependency> { singleton { (get<Fragment>(SUPPORT_FRAGMENT).activity as KatanaTrait).injectNow() }
    }
    

    But this somehow feels a bit strange as well as I suddenly exactly define from where I want to get my dependencies supplied. That part just feels more ioc with the dependsBy approach.

    Any best practises here?

    good first issue question 
    opened by ubuntudroid 19
  • ViewModel support

    ViewModel support

    First of all I'd like to thank you guys. This is my first time using dependency injection and I learned a lot from playing around with Katana. Having somewhat similar syntax to Koin, Kodein, and Dagger, made it that much easier for me to cross-reference and learn DI in general.

    Seeing as there's no support for ViewModel(?) I created my own. What do you guys think?

    /**
     * A ViewModelFactory that works alongside dependency injection.
     *
     * @param viewModel The already injected ViewModel.
     * @return A ViewModelProvider.Factory to be used with ViewModelProviders.of
     */
    class KatanaViewModelFactory(private val viewModel: ViewModel) : ViewModelProvider.Factory {
        override fun <T : ViewModel?> create(modelClass: Class<T>): T = viewModel as? T ?: modelClass.newInstance()
    }
    
    
    /**
     * Declares a [ViewModel] dependency binding as a singleton.
     * Only one instance (per component) will be created.
     *
     * The ViewModelFactory is also created here because we need the unique class name of your [ViewModel].
     * @param body Body of binding declaration
     *
     * @see Module.factory
     * @see Module.singleton
     */
    inline fun <reified T : ViewModel> Module.viewModel(crossinline body: ProviderDsl.() -> T) {
        val name : String = T::class.java.simpleName
        singleton<ViewModel>(name, body = body)
        singleton(name = "${name}Factory") { KatanaViewModelFactory(get(name)) }
    }
    
    
    /**
     * Inject the ViewModel declared from [Module.viewModel].
     *
     * The scope is tied to the host [Activity]. This is called from a [Fragment].
     * @return [ViewModel]
     */
    inline fun <reified VM : ViewModel, T> T.viewModel(): Lazy<VM> where T : KatanaTrait, T : Fragment =
            lazy { ViewModelProviders.of(requireActivity(), injectNow("${VM::class.java.simpleName}Factory")).get(VM::class.java) }
    
    
    /**
     * Inject the ViewModel declared from [Module.viewModel].
     * This is called from an [Activity].
     * @return [ViewModel]
     */
    inline fun <reified VM : ViewModel, T> T.viewModel(): Lazy<VM> where T : KatanaTrait, T : AppCompatActivity =
            lazy { ViewModelProviders.of(this, injectNow("${VM::class.java.simpleName}Factory")).get(VM::class.java) }
    
    
    /**
     * Inject the ViewModel declared from [Module.viewModel] with an assignment operator.
     *
     * The scope is tied to the host [Activity]. This is called from a [Fragment].
     * @return [ViewModel]
     */
    // This has conflicts with the above. Use this if you're using [KatanaFragmentDelegate]
    // and you're inside the onInject callback.
    /* inline fun <reified VM : ViewModel,T> T.viewModel(): VM where T: KatanaTrait, T: Fragment=
       ViewModelProviders.of(requireActivity(),injectNow("${VM::class.java.simpleName}Factory")).get(VM::class.java)
    */
    

    The module creation would look something like this

    createModule {
            singleton { ApplicationDatabase.getInstance(Application.instance) }
            singleton { get<ApplicationDatabase>().mainDao() }
            singleton { MainRepository.getInstance(get()) }
            viewModel { MainViewModel(get()) }
        }
    

    Inject it in your Activity/Fragment:

    val viewModel: MainViewModel by viewModel()
    

    Side notes (For modules that depend on activities): In fragments you can inject() and declare a Component without callbacks by using lazy. You just have to do everything in onActivityCreated because of the Activity-Fragment relationship. But I think you already know this.

    KatanaFragmentDelegate.kt: Since Fragments are instantiated before Activities, the component initialization must be delayed until the Activity was created.

    class MyFragment : Fragment(), KatanaTrait {
        // All of these are by lazy
        override val component: Component by lazy { getComponent() }
        private val viewModel: MainViewModel by viewModel()
        private val myObj: MyObject by inject()
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            // Don't put code here that relies on activity dependencies
        }
        
        override fun onActivityCreated(savedInstanceState: Bundle?) {
            super.onActivityCreated(savedInstanceState)
            // Put it here
            viewModel.doStuff()
            myObj.doStuff()
        }
    }
    
    opened by inshiro 8
  • katana-androidx-viemodel: Couldn't inline method call

    katana-androidx-viemodel: Couldn't inline method call

    Since updating Katana to Kotlin 1.3.30 (Katana version 1.6.1), usages of katana-androidx-viemodel inline functions result in a compiler exception couldn't inline method call. This seems to be a compiler issue and is tracked here.

    bug 
    opened by svenjacobs 7
  • Rename singleton

    Rename singleton

    Think about renaming singleton inside Module. The name might be misleading to some users as a singleton in Katana is related to a Component. There only exist one instance per component and not as some might think per application.

    What would be a better name?

    • scoped
    • componentScoped
    • componentSingleton
    enhancement 
    opened by svenjacobs 5
  • Single generic class singleton retreival should get injected

    Single generic class singleton retreival should get injected

    Currently, if one tries to declare a single singleton of a generic class without naming it, and runs get(), type erasure will prevent it to be resolved. The expected behavior is that if there is only one such declaration, it should be injected. If there are many, there should be an error (even if their type parameters are different) unless they are all named. If the type params don't match what the get() is injecting to, then it should just be considered if it will only crash on use (not so good), or if there is a way to catch and report this beforehand.

    bug question 
    opened by dave08 4
  • Add singletonSet to module DSL

    Add singletonSet to module DSL

    Often we have a pattern in Android to have a list of Intents that can be processed by an implementation of:

    interface TaskHandler {
    	fun canHandle(action: String): Boolean
    
    	fun run()
    }
    

    And we need to instantiate these classes with Katana, and then put them into one big list/set and then run:

    injectNow<Set<TaskHandler>>().first { it.canHandle(intent.action) }.run()
    

    Currently this can only be done by tagging (some implementations are registered multiple times with different constructor params) each singleton/factory and then using get<TaskImplementation>(TaskTag) to build the list/set in one big singleton<Set<TaskHandler>> { }, this is very tedious and error-prone.

    The proposition is to have a singletonSet<TaskHandler> { TaskOne(get(), get()) } that will just accumulate all those instances into one set that can be retrieved with get<Set<TaskHandler>>() since the instances themselves are of no real interest. Any request for an individual instance could be an error that it needs to be get as a set.

    I'm still not sure about whether this should only be a Set or maybe a List and Map too. A map could avoid instantiating all the classes, and just have the intent's action as the key and the handler for it as the value.

    This feature might not be only for Android, but in Android there's another little point to consider if the intent's other properties are needed.

    enhancement 
    opened by dave08 3
  • JCenter sunsetting

    JCenter sunsetting

    JCenter is sunsetting on May 1, 2021, per https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter/

    Katana is published on JCenter, and not on Maven Central, so anyone using it will be affected.

    Knowing that Katana is not actively developed anymore, I'm not sure anyone has any serious expectation that the library would be re-published elsewhere.

    What are your thoughts on publishing Katana on Maven Central or another channel?

    bug 
    opened by carltonwhitehead 2
  • Add `provider()` to `ProviderDsl`

    Add `provider()` to `ProviderDsl`

    Add provider() to ProviderDsl to inject a dependency's factory instead of creating a new instance for injection. Maybe it should fail-fast if trying to use provider() to inject a singleton { } declaration though...

    opened by dave08 1
  • Add getOrNull()

    Add getOrNull()

    For cases like class SomeClass(val one: IClass?, val two: IClass?) and we have an if that is supposed to decide which of the two should be injected and which should be null.

    And in the code I just do one?. ... and two?. ...

    opened by dave08 1
  • Alias functionality

    Alias functionality

    Provides an alias binding which is nothing more than a simplified factory binding of an already existing binding under a different class or name.

    Update: Also added get binding for set which adds an already declared dependency in the context of the module to a set.

    opened by svenjacobs 0
  • Clean up Gradle build files

    Clean up Gradle build files

    With the new androidx-viewmodel artifact there now is some duplication in the gradle.build.kts files. Project should have a single root Gradle file to reduce duplication.

    opened by svenjacobs 0
  • Future of Katana

    Future of Katana

    Google recently announced the new Hilt dependency injection library for Android based on Dagger. Hilt tackles many of the "problems" of Dagger – especially it's complexity – and simplifies dependency injection with Dagger. Presumably Google will advertise Hilt as the DI solution for Android. Other AndroidX support libraries will probably add Hilt support in the future, too.

    While Katana's core functionality does not depend on Android and works well in Kotlin JVM, Katana was written with Android in mind from day one. I welcome Google's approach of simplifying and unifying DI on Android. As a single developer working on Katana mostly in my spare time, I cannot compete with a team of Google engineers working on Hilt & Dagger full time 😉 As of today Katana works well and is in use in a few production Android applications successfully. If you are using Katana, there's no need to migrate to Hilt (immediately). However I recommend that any new Android project uses Hilt instead of Katana. Katana will go into maintenance mode. No new features will be developed. Critical bugs will be fixed, should they arise.

    I thank everybody for their support and hope you understand my decision. Of course pull requests for bugfixes are still very welcome 😃

    opened by svenjacobs 1
Releases(1.15.0)
Owner
REWE Digital GmbH
REWE Digital GmbH
Guice (pronounced 'juice') is a lightweight dependency injection framework for Java 6 and above, brought to you by Google.

Guice Latest release: 5.0.1 Documentation: User Guide, 5.0.1 javadocs, Latest javadocs Continuous Integration: Mailing Lists: User Mailing List Licens

Google 11.7k Jan 1, 2023
A scope tree based Dependency Injection (DI) library for Java / Kotlin / Android.

Toothpick (a.k.a T.P. like a teepee) Visit TP wiki ! What is Toothpick ? Toothpick is a scope tree based Dependency Injection (DI) library for Java. I

Stéphane Nicolas 1.1k Jan 1, 2023
Painless Kotlin Dependency Injection

KOtlin DEpendency INjection Kodein-DI is a very simple and yet very useful dependency retrieval container. it is very easy to use and configure. Kodei

null 2.9k Jan 1, 2023
:syringe: Transfuse - A Dependency Injection and Integration framework for Google Android

Transfuse Transfuse is a Java Dependency Injection (DI) and integration library geared specifically for the Google Android API. There are several key

John Ericksen 224 Nov 28, 2022
The dependency injection Dagger basics and the concepts are demonstrated in different branches

In this project we will look at the dependency injection Dagger basics and the concepts are demonstrated in different branches What is an Dependency?

Lloyd Dcosta 6 Dec 16, 2021
A multi-purpose library containing view injection and threading for Android using annotations

SwissKnife A multi-purpose Groovy library containing view injection and threading for Android using annotations. It's based on both ButterKnife and An

Jorge Martin Espinosa 251 Nov 25, 2022
A SharedPreference "injection" library for Android

PreferenceBinder A SharedPreferences binding library for Android. Using annotation processing, this library makes it easy to load SharedPreferences va

Denley Bihari 232 Dec 30, 2022
DependencyProperty is a dependency resolution library by Delegated Property.

DependencyProperty is a dependency resolution library by Delegated Property. Overview DependencyProperty is simple in defining and

wada811 10 Dec 31, 2022
A fast dependency injector for Android and Java.

Dagger A fast dependency injector for Java and Android. Dagger is a compile-time framework for dependency injection. It uses no reflection or runtime

Google 16.9k Jan 5, 2023
A fast dependency injector for Android and Java.

Dagger 1 A fast dependency injector for Android and Java. Deprecated – Please upgrade to Dagger 2 Square's Dagger 1.x is deprecated in favor of Google

Square 7.3k Jan 5, 2023
Simple Android Library, that provides easy way to start the Activities with arguments.

Warning: Library is not maintained anymore. If you want to take care of this library, propose it via Pull Request. It needs adjustmensts for newer ver

Marcin Moskała 429 Dec 15, 2022
Fast Android Development. Easy maintainance.

Fast Android Development. Easy maintenance. AndroidAnnotations is an Open Source framework that speeds up Android development. It takes care of the pl

null 11.1k Dec 31, 2022
Fast Android Development. Easy maintainance.

Fast Android Development. Easy maintenance. AndroidAnnotations is an Open Source framework that speeds up Android development. It takes care of the pl

null 11.1k Dec 31, 2022
Bind Android views and callbacks to fields and methods.

Butter Knife Attention: This tool is now deprecated. Please switch to view binding. Existing versions will continue to work, obviously, but only criti

Jake Wharton 25.7k Jan 3, 2023
Google Guice on Android, version 3.0 [RETIRED]

As of August 2016, RoboGuice is no longer supported. For nearly 5 years it was the #1 dependency injection framework on Android due to its ease-of-use

null 3.8k Dec 26, 2022
Bind Android views and callbacks to fields and methods.

Butter Knife Attention: This tool is now deprecated. Please switch to view binding. Existing versions will continue to work, obviously, but only criti

Jake Wharton 25.7k Mar 22, 2021
lite your android ! the code is on the way~

android-lite-auto lite your android, the code is on the way~ ! LiteAuto是一个代码生成框架,思路参考 JakeWharton 的开源项目 ButterKnife,在它的思路基础添加了一些自己的想法,从0到1设计并实现的。 Lite

马天宇 32 Nov 2, 2022
Depenject - a lightweight, minimalistic dependency injection library for Kotlin/JVM.

depenject depenject is a lightweight, minimalistic dependency injection library for Kotlin/JVM. Our goal is similar to flavor's to simplify the usage

Patrick 1 Mar 22, 2022
A sample Grocery Store app built using the Room, MVVM, Live Data, Rx Java, Dependency Injection (Kotlin Injection) and support Dark Mode

Apps Intro A sample Grocery Store app built using the Room, MVVM, Live Data, Rx Java, Dependency Injection (Kotlin Injection) and support Dark Mode In

Irsyad Abdillah 25 Dec 9, 2022
Lightweight Kotlin DSL dependency injection library

Warehouse DSL Warehouse is a lightweight Kotlin DSL dependency injection library this library has an extremely faster learning curve and more human fr

Osama Raddad 18 Jul 17, 2022