Code Monkey home page Code Monkey logo

toothpick's Introduction

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.

It is a full-featured, runtime based, but reflection free, implementation of JSR 330.

What does Toothpick offer ?

//a typical Toothpick scope tree during the execution of an Android app.

           @ApplicationScope 
             /          |    \  
            /           |     \
           /            |      \
   @ViewModelScope      |   Service 2
         /              | 
        /            Service 1  
       /            
 @Activity1Scope
      /
     /
Activity 1
   /   \
  /   Fragment 2
 /
Fragment 1

Scopes offer to compartmentalize memory during the runtime of an app and prevent memory leaks. All dependencies created via Toothpick, and available for injections, will be fully garbage collected when this scope is closed. To learn more about scopes, read TP wiki.

Toothpick is :

Examples

This is the example:

Setup

The latest version of TP is provided by a badge at the top of this page.

For Android :

#android setup using gradle 5.5.1
buildscript {
  repositories {
    google()
    jcenter()
  }
  dependencies {
    classpath 'com.android.tools.build:gradle:3.4.x'
  }
}

...
#for java
dependencies {
  implementation 'com.github.stephanenicolas.toothpick:toothpick-runtime:3.x'
  // and for android -> implementation 'com.github.stephanenicolas.toothpick:smoothie-androidx:3.x'
  annotationProcessor 'com.github.stephanenicolas.toothpick:toothpick-compiler:3.x'

  //highly recommended
  testImplementation 'com.github.stephanenicolas.toothpick:toothpick-testing-junit5:3.x'
  testImplementation 'mockito or easymock'
}

#for kotlin
dependencies {
  implementation 'com.github.stephanenicolas.toothpick:ktp:3.x'
  kapt 'com.github.stephanenicolas.toothpick:toothpick-compiler:3.x'

  //highly recommended
  testImplementation 'com.github.stephanenicolas.toothpick:toothpick-testing-junit5:3.x'
  testImplementation 'mockito or easymock'
}

For java:

<!--java setup with maven -->
  <dependencies>
    <dependency>
      <groupId>com.github.stephanenicolas.toothpick</groupId>
      <artifactId>toothpick-compiler</artifactId>
      <version>3.x</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>com.github.stephanenicolas.toothpick</groupId>
      <artifactId>toothpick-runtime</artifactId>
      <version>3.x</version>
      <scope>compile</scope>
    </dependency>
    
    <!-- highly recommended-->
    <dependency> 
      <groupId>com.github.stephanenicolas.toothpick</groupId>
      <artifactId>toothpick-testing</artifactId>
      <version>3.x</version>
      <scope>test</scope>
    </dependency>
    <dependency>
    <easymock or mockito>
    </dependency>
  </dependencies>

Support

TP is actively maintained and we provide support to questions via the Toothpick-di tag on Stack Over Flow.

Ask questions on Stack Over Flow while keeping the GitHub issue board for real issues. Thx in advance !

Talks & Articles

Wanna know more ?

Visit Toothpick's wiki !

Alternative Dependency Injection (DI) engines for Android

Libs / Apps using TP 2

  • Okuki is a simple, hierarchical navigation bus and back stack for Android, with optional Rx bindings, and Toothpick DI integration.
  • KotlinWeather is a simple example of using ToothPick with Kotlin and gradle integration using kapt.

Credits

TP 1 & 3 have been developped by Stephane Nicolas and Daniel Molinero Reguera. Most of the effort on version 2 has been actively supported by Groupon. Thanks for this awesome OSS commitment !

toothpick's People

Contributors

alaershov avatar davidsun avatar dlemures avatar efung avatar eygraber avatar kazucocoa avatar kelsos avatar ligi avatar matnazaroff avatar osipxd avatar phelat avatar powturns avatar pshmakov avatar stephanenicolas avatar terrakok avatar xanderblinov avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

toothpick's Issues

Question: Runtime Discovery of Unbound Class Performance

Is there a significant performance gain to binding classes? For example, should I bind the following dependency (as opposed to just letting it get discovered at runtime), for performance purposes?

@ContextSingleton
public class MaterialDialogBuilderFactory {
  private final Activity activity;

  @Inject
  public MaterialDialogBuilderFactory(Activity activity) {
    this.activity = activity;
  }

  public MaterialDialog.Builder get() {
    return new MaterialDialog.Builder(activity);
  }
}

Impossible to customize the shared prefs name

Just add a param to smoothie application module by overloading the constructor, then pass the param to the provider.

If we don't do that, it's not possible later on to change the name of the @Inject SharedPrefs instance.

This is HIGH priority for MGA, it blocks QA's automated tests.

Enhancement - Use annotations to bypass bind method

Using the bind method is a good way of saying this class implements this interface for this scope.

My suggestion though is to allow developers to use an annotation on the concrete class instead of the bind method if they like, e.g.:

@Implements(scope="scope_name")
public class SomeClass {}

Advantages is that it is easier to find, as bind can be anywhere in the code, but with the annotation you know exactly where to find it.

Disadvantage is that it is very static (compile time), might be hard to read if it implements for multiple different scopes

Rename error messages not to mention Registries when a Factory is not found

Registries are not always required anymore:

Caused by: toothpick.registries.NoFactoryFoundException: No factory could be found for class com.groupon.merchant.auth.cloud.CloudAccount. Check that registries are properly setup with annotation processor arguments, or use annotations correctly in this class.

Typo? on Binding overrides in children scopes

regarding wiki https://github.com/stephanenicolas/toothpick/wiki/Modules-&-Bindings#binding-overrides-in-children-scopes

- Scope S0 : IFoo.class --> Foo.class
+ Scope S0 : IFoo.class --> Foo1.class
  \
   \
  Scope S1 : IFoo.class --> Foo2.class
    \  
     \
    Scope S2

OR

- Toothpick.inject(a, s0); // => a.foo is an instance of Foo1
+ Toothpick.inject(a, s0); // => a.foo is an instance of Foo
Toothpick.inject(a, s1); // => a.foo is an instance of Foo2
Toothpick.inject(a, s2); // => a.foo is an instance of Foo2

which is correct ? or current one is true ?

Factory Created for Non-Injected Class

I have a class MainActivity (which is an Activity). It has injected members, but it itself is never injected anywhere, yet in the generated code there is a Factory implementation for it:

public final class MainActivity$$Factory implements Factory<MainActivity> {
  private MemberInjector<MainActivity> memberInjector = new com.staticbloc.location.mock.activity.MainActivity$$MemberInjector();

  @Override
  public MainActivity createInstance(Scope scope) {
    scope = getTargetScope(scope);
    MainActivity mainActivity = new MainActivity();
    memberInjector.inject(mainActivity, scope);
    return mainActivity;
  }

  @Override
  public Scope getTargetScope(Scope scope) {
    return scope;
  }

  @Override
  public boolean hasScopeAnnotation() {
    return false;
  }

  @Override
  public boolean hasScopeInstancesAnnotation() {
    return false;
  }
}

This also happens when using a FactoryRegistry:

public final class FactoryRegistry extends AbstractFactoryRegistry {
  public FactoryRegistry() {
    addChildRegistry(new toothpick.smoothie.FactoryRegistry());
  }

  public <T> Factory<T> getFactory(Class<T> clazz) {
    switch(clazz.getName().replace('$','.')) {
      case ("com.staticbloc.location.mock.activity.MainActivity"):
      return (Factory<T>) new com.staticbloc.location.mock.activity.MainActivity$$Factory();
      default:
      return getFactoryInChildrenRegistries(clazz);
    }
  }
}

Unused local variable inside Factories

Unused local variable inside Factories that can make CI findbug hooks complain:

public AndroidAccountIntents createInstance(Scope scope) {
     scope = getTargetScope(scope);
     AndroidAccountIntents androidAccountIntents = new AndroidAccountIntents();
     return androidAccountIntents;
}

Only happens when scope is not used. Can we avoid that?

Scope annotations + Bindings

We don't allow to bind implementations which contain "scope annotations".

For instance, the following is forbidden:

interface A {
    ...
}

@Singleton
class B implements A {
    ...
}

class XModule extends Module {
    public ModuleWithNamedBindings() {
        bind(A.class).to(B.class);
    }
}

However, it seems a valid use case...

Should we allow it?

However, not all cases are valid (that's why it is not allowed right now)...

How can I reference a dependency bound in a parent module from another child module.

I would like to have a dependency (for example, a configured instance of OkHttpClient) declared in a module added to an application-wide scope. I would then like to have other modules that are added to more narrow scopes use that dependency to instantiate other dependencies (for example, a Retrofit api implementation configured to use the OkHttpClient). Can I inject dependencies into modules? Or do I need to create some kind of wrapper or factory class that gets the parent dependency injected?

TP runtime dependency doesn't add TP dependency transitively

When migration our german app @grpn, the TP runtime dependency is added via:

    compile 'com.github.stephanenicolas.toothpick:toothpick-runtime:1.0.0-RC2'

and it didn't seem to add the TP dependency to the app, we had to add:

    compile 'com.github.stephanenicolas.toothpick:toothpick:1.0.0-RC2'

which should not be needed.

But this is strange as TP runtime pom file: http://search.maven.org/#artifactdetails%7Ccom.github.stephanenicolas.toothpick%7Ctoothpick-runtime%7C1.0.0-RC2%7Cjar

states

<dependency>
      <groupId>com.github.stephanenicolas.toothpick</groupId>
      <artifactId>toothpick-generated-core</artifactId>
      <version>1.0.0-RC2</version>
      <scope>compile</scope>
    </dependency>

and TP generated code pom: http://search.maven.org/#artifactdetails%7Ccom.github.stephanenicolas.toothpick%7Ctoothpick-generated-core%7C1.0.0-RC2%7Cjar

states

<dependency>
      <groupId>com.github.stephanenicolas.toothpick</groupId>
      <artifactId>toothpick</artifactId>
      <version>1.0.0-RC2</version>
      <scope>compile</scope>
    </dependency>

so, why doesn't toothpick get added to the project ?

Move Smoothie to its own repo

Due to gradle using alphabetical ordering of modules, smoothie gets compiled first.
There is no way to do it differently, we should move smoothie to its own repo, this will create serious issues sooner or later.

TODO before releasing 1.0.0-RC1

Codewise :

  • increase test coverage to 99% + (currently 95%)
  • add cycle detection at runtime
  • add debug and prod mode ^
  • create GH readme
  • create useful wiki
  • prepare article for announcement
  • fix maven publishing
  • release to central
  • ask for external reviews

Compilation warning for all graph roots

As graph roots don't need to be injected, they don't need a factory, thus an empty constructor is not needed (for android: activities, fragments, views, ...).

We display several compilation warnings that make it difficult to see real problems or warning:

.../../DealListItemView.java:32: warning: 
The class ....DealListItemView has injected fields but has no injected constructor, 
and no public default constructor. Toothpick can't create a factory for it.
public class DealListItemView extends RelativeLayout {

MemberInjector Visibility issues.

When migration our german app @grpn, @dpreussler found the following issue:

public packageX.A     contains @Inject members (has member injector)
       / \   (extends)
        |
public packageY.B      NOT contains @Inject member (no member injector)
       / \
        |
package packageY.C      contains @Inject members (has member injector)

The generated memberInjector class for packageX.A will call the member injector class of packageY.C. Therefore, It fails.

MemberInjector<C> superMemberInjector = new packageY.C$$MemberInjector();

Is there any way to inject a generic type?

I know the documentation says that this is not supported, but I'm wondering if there is any workaround to simulate this. I cannot find any DI solution for Android that supports injecting a generic type like this:

public class MyActivity<P extends BasePresenter> extends Activity {
    @Inject
    P presenter;
}

Thank you!

Does not work with Java7

when using JDK 7 (tested with "1.7.0_51", "1.7.0_80")
the apk build does not contain classes, when starting:

java.lang.RuntimeException: Unable to instantiate application com.groupon.merchant.MerchantApplication: java.lang.ClassNotFoundException: Didn't find class "com.groupon.merchant.MerchantApplication" on path: DexPathList[[zip file "/data/app/com.groupon.redemption-1/base.apk"],nativeLibraryDirectories=[/data/app/com.groupon.redemption-1/lib/x86, /vendor/lib, /system/lib]]

Bug in wiki

Trying out toothpick and it looks really promising!

I stumbled on a issue in wiki in Setting up bindings in a scope:

Instead of bind(IFoo.class).to(new Foo());, it should be bind(IFoo.class).toInstance(new Foo());

Keep on doing the good work!

No Factory for Class with Scope Annotation and No @Inject

I have a class:

@ActivitySingleton
public class MapFragmentProvider implements Provider<SupportMapFragment> {
  @Override
  public SupportMapFragment get() {
    return SupportMapFragment.newInstance();
  }
}

Where ActivitySingleton is:

@Scope
@Documented
@Retention(RUNTIME)
public @interface ActivitySingleton {
}

But no Factory is being created for it unless I add an @Inject constructor.

Scope and qualifier annotations still need RUNTIME retention

After the merge of
#121
(which has been included in RC6)

we will still have to retain at runtime the qualifier and the scope annotations:

  • scope annotations are checked by the development configuration to ensure that a scoped class (annotated with a scope annotation) is not bound explicitely by a module (of a different scope). The workaround could be, in this configuration, to use the factory as it contains this information at compile time.
  • qualifiers annotations are checked when used in toothpick.config.Binding#withName(java.lang.Class) . This could also be included inside a configuration. In this case, we don't generate any meta-information at compile time for qualifier classes and we cannot rely on compile time to do the check without it...

As a reminder: we want to get rid of runtime retentions as they fill in the first dex and every class using them as well...

Factory code generator should strip the generic part of dependencies

In a class using a dependency that is generics such as:

public class DaoGoodsBrowseByCategory extends GrouponBaseDao<GoodsBrowseByCategory> {

    @Inject
    public DaoGoodsBrowseByCategory(ConnectionSource connectionSource, DatabaseTableConfig tableConfig) throws SQLException {
        super(connectionSource, tableConfig);
    }

where DatabaseTableConfig is

public class DatabaseTableConfig<T>

TP then generates the following Factory

public final class DaoGoodsBrowseByCategory$$Factory implements Factory<DaoGoodsBrowseByCategory> {
  @Override
  public DaoGoodsBrowseByCategory createInstance(Scope scope) {
    scope = getTargetScope(scope);
    ConnectionSource param1 = scope.getInstance(ConnectionSource.class);
    DatabaseTableConfig<T> param2 = scope.getInstance(DatabaseTableConfig.class);
    DaoGoodsBrowseByCategory daoGoodsBrowseByCategory = new DaoGoodsBrowseByCategory(param1, param2);
    return daoGoodsBrowseByCategory;
  }

We should just strip off the <T> of DatabaseTableConfig<T> param2 = scope.getInstance(DatabaseTableConfig.class);

Feedback from samuel

  • Presenter annotation in RX MVP sample should be defined outside of Rx MVP Activity. It gives the feeling right now that the scope has to be defined and is linked in the activity.

Member Injectors & Inject Methods & Inheritance

There are some issues regarding the Methods Injects, overrides and inheritance:

class A {
    @Inject C c;

    @Inject
    public void init() {
        .
    }
}

class B extends A {
    @Inject D d;

    @Override
    public void init() {
        .
    }
}

1) Initialization should use the order: First inject @Inject annotated fields, then call @Inject annotated methods.

However, for the example if we inject B, the order is:

C (A field) -> init (B METHOD) -> D (B field)

Which is wrong. Inside its init method, B can assume all fields are injected, and this didn't happen yet.

2) If B was like this:

class B extends A {
    @Override
    public void init() {
        .
    }
}

The member injector would not be generated, but we need it. We could add @Injectto init, but @Override is not mandatory and if it is omitted, init would be called twice during initialization.

3) Let's imaging now a slightly different example:

class A {
    @Inject C c;

    public void init() {
        .
    }
}

class B extends A {
    @Inject D d;

    @Override
    @Inject
    public void init() {
        .
    }
}

Will init be called when injecting B using its member injector?

Android util openScopes methods

For Android applications ToothPick will be commonly used in Activities and Fragments in the following way:

SampleActivity.java

public class SampleActivity extends Activity {

   @Inject ContextNamer contextNamer;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
      scope = Toothpick.openScopes(getApplication(), this);
      scope.installModules(new SmoothieActivityModule(this));
      super.onCreate(savedInstanceState);
      Toothpick.inject(this, scope);

      // ... do something
   }

   // ... more stuff

   @Override
   protected void onDestroy() {
      Toothpick.closeScope(this);
      super.onDestroy();
   }

   // ... more stuff
}

*SampleFragment.java

public class SampleFragment extends Fragment {

   @Inject ContextNamer contextNamer;

   @Override
   public void onCreate(Bundle savedInstanceState) {
      scope = Toothpick.openScopes(getActivity().getApplication(), getActivity(), this);
      scope.installModules(new SmoothieFragmentModule(this));
      super.onCreate(savedInstanceState);
      Toothpick.inject(this, scope);

      // ... do something
   }

   // ... more stuff

   @Override
   protected void onDestroy() {
      Toothpick.closeScope(this);
      super.onDestroy();
   }

   // ... more stuff
}

I have noticed that these two calls will be a constant in every project that uses TP:

// Activity
scope = Toothpick.openScopes(getApplication(), this);
// Fragment
scope = Toothpick.openScopes(getActivity().getApplication(), getActivity(), this);

Would it make sense to have a util ToothPick class for Android that masks this? For instance:

public class ToothpickAndroidUtils {

   public static Scope openActivityScope(Activity activity) {
      final Scope scope = Toothpick.openScopes(activity.getApplication(), activity);
      scope.installModules(new SmoothieActivityModule(activity));
      return scope;
   }

   public static Scope openActivityScope(Activity activity, Object... names) {
      final Scope scope = Toothpick.openScopes(activity.getApplication(), activity, names);
      scope.installModules(new SmoothieActivityModule(activity));
      return scope;
   }

   public static Scope openFragmentScope(Fragment fragment) {
      final Scope scope = Toothpick.openScopes(fragment.getActivity().getApplication(), fragment.getActivity(), fragment);
      scope.installModules(new SmoothieFragmentModule(fragment));
      return scope;
   }

   public static Scope openFragmentScope(Fragment fragment, Object... names) {
      final Scope scope = Toothpick.openScopes(fragment.getActivity().getApplication(), fragment.getActivity(), fragment, names);
      scope.installModules(new SmoothieFragmentModule(fragment));
      return scope;
   }
}

Classes generated in the last round, so they are not processed

Some classes are generated in the last round, so they are not processed.

Example:

[ERROR] [system.err] warning: File for type 'toothpick.data.FooWithProvider$$MemberInjector' 
created in the last round will not be subject to annotation processing.

Lazies & Providers injected through constructors or methods are wrongly generated in Factory and Member Injector

Lazies and Providers injected through constructors or methods are wrongly generated for Factories and Member Injectors. Wrong type parameter:

public class A {
    @Inject
    public A(@Lazy<B> b) {
        ...
    }

    @Inject
    public void init(@Provider<C> c) {
        ...
    }
}

Generates:

// Code generated for Lazy (Factory and Member Injector)
Lazy<T> param1 = scope.getLazy(B.class)

// Code generated for Provider (Factory and Member Injector)
Provider<T> param1 = scope.getProvider(C.class)

It should generate something like:

Lazy<B> param1 = scope.getLazy(B.class)
Provider<C> param1 = scope.getProvider(C.class)

Binding explanation sucks

At https://github.com/stephanenicolas/toothpick/wiki/Scopes#scopes-instances-creation-and-injections

a binding : is way to express that a class Foo is associated to an implementation Bar, which we denote Foo --> Bar. It means that writing @Inject Foo a; will return a Bar.

First time I read I was trying to associate with the Bar in the first example. After the second time I read it I noticed they have nothing to do. What about:

a binding : is way to express that a class Class is associated to an instance Instance, which we denote Class --> Instance. It means that writing @Inject Class a; will return an Instance.

or use an example with a real life scenario.

Encourage the reflection free configuration

One of the main gains of TP is speed.
However, the reflection free configuration seems to be just an alternative.

Should we update the documentation to encourage its usage more?

Untyped Lazy or Provider exception

When Lazies don't include the type parameter, we just get an internal exception (OutOfIndex...):

@Inject Lazy lazy;
@Inject Provider provider;

java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
    at com.sun.tools.javac.util.List.get(List.java:476)
    at toothpick.compiler.common.ToothpickProcessor.getKindParameter(ToothpickProcessor.java:422)
    at toothpick.compiler.common.ToothpickProcessor.getInjectedType(ToothpickProcessor.java:277)
...

We should throw a proper error instead.

Stubs for testing

Use case:
We want to inject a Stub inside the ToothPickTestModule in a "nice way".
If we use @mock from the mocking library, the mock is created by it.

Possible solutions:

  • Create annotation @Stub. Any field that contains that annotation will be bound but not injected by the mocking library.
  • Create TP own @mock annotation, we wouldn't need to change more code. It could we be used when want the object to be bound inside the ToothPickTestModule but it won't be recognized by the mocking library.

Change the optimistic factory creation

Tp should not try to produce a factory for a class it doesn't compile. Otherwise multiple compilation units would create the same factories, which leads to a complex exclusion chain in all compilation units .

Custom scope annotations are not processed by the compiler

Singleton is processed and we create relaxed factories in case it is annotating a class.

But it's not the case for custom scope annotations (like context singleton). I don't think we can actually do that without saying that the annotation processor should intercept every annotation, which is considered a bad practice. Maybe we can have a look at Dagger 2 and see how they deal with this as they also have custom scopes working well.

ToothPick --> Toothpick

rename everywhere, in code and doc. I think we will have to checkout the wiki locally and fix all typos, the source code will be easier to refactor.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.