Skip to content
This repository has been archived by the owner on Jul 16, 2023. It is now read-only.

Latest commit

 

History

History
259 lines (198 loc) · 7.49 KB

README.md

File metadata and controls

259 lines (198 loc) · 7.49 KB

Di

di di-android

Small Dependency Injection library for quick prototyping and fast builds (reflection-based, no annotation processing). Everything is inside a scope. Scopes are organized in a tree structure with ability to be closed. No dependency can out-live its scope. Even singleton.

implementation 'ru.noties:di:0.7.0'
implementation 'ru.noties:di-android:0.7.0'

Key points

  • everything lies within a scope
  • scopes are structured in a tree
  • scopes are closable
  • closing a scope closes all of its children
  • scopes hold bindings (provided dependencies)
  • 2 kinds of bindings:
    • explicit, defined via code in a Module
    • implicit, automatically created by the library
  • no binding can outlive scope (even a singleton binding)
  • implicit binding always stored inside a scope that requested it and is not shared with siblings
  • child can request explicit binding from a parent (up to the root)
  • no parent can access children's bindings
  • child can override parent's explicit bindings
  • no thermosiphon
  • nor thermosiphon2

Quick start

public class App extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        // this method returns an instance, so we actually can
        // store it if we would require so additional handling
        // final Di di = Di.root(...).accept(...);
        Di.root(configuration, "App", new AppModule(this))
            .accept(ActivityInjector.init(this));
    }
}

Create a root instance of Di. Please note that #root is a factory method that returns a new root instance each time it is called. There is no static cache. Then we want to provide this root instance to our siblings. We are using ActivityInjector class from di-android module.

public class MainActivity extends FragmentActivity implements Di.Service {
    
    @Inject
    private App app;

    @Override
    public void init(@NonNull Di di) {
        di
                .inject(this)
                .accept(FragmentInjector.init(getSupportFragmentManager()));
    }
}

A class can call di.inject(this) only if it implements Di.Service.

public interface Service {
    void init(@NonNull Di di);
}

When creating a root instance one can specify modules that provide bindings.

public class AppModule extends Module {

    private final App app;

    AppModule(@NonNull App app) {
        this.app = app;
    }

    @Override
    public void configure() {
        bind(Application.class).with(() -> app).asSingleton();
    }
}

Bindings

Explicit

Explicit binding is a binding that is explicitly stated in a module.

  • bind(MyClass.class) -> simply bind concrete implementation of a class. It will be constructed by the library. It have to have an empty constructor with @Inject annotation
  • bind(CharSequence.class).as(String.class) -> provides String when CharSequence is requested. Class will be constructed by the library
  • bind(CharSequence.class).with(Provider<CharSequence>) -> when CharSequence is requested specified Provider will be called to return an instance

Bindings can have @Named and custom @Qualifier annotations

  • bind(MyClass.class).named("my-name")
  • bind(MyClass.class).qualifier(MyQualifier.class) (there is no support for custom @Qualifier annotation with parameters)

Bindings can have additional modifiers:

  • bind(MyClass.class).asSingleton() -> will have this binding a singleton inside requested scope (and its children)
  • bind(MyClass.class).asLazy() -> will bind Lazy<MyClass>
  • bind(MyClass.class).asProvider() -> will bind Provider<MyClass>

These modifiers can be combined:

// Lazy<Provider<MyClass>>
bind(MyClass.class)
    .asLazy()
    .asProvider()
    .asSingleton();

Alongside with concrete implementations one can bind generic types:

bind(new TypeToken<List<String>>(){}.getType())
    .with(() -> Arrays.asList("one", "two", "three"));

Implicit

Implicit binding is constructed by the library. Implicit binding cannot specify what scope it operates, nor specify modifiers (like asSingleton). It will always be created in a scope that it is requested. And each time this binding is requested a new instance will be returned. To change that consider using explicit binding.

The only requirement for implicit binding is to have an empty constructor annotated with @Inject annotation.

public class MyClass implements OnInjected {

    @Inject
    private Action action;

    @Inject
    public MyClass() {
    }

    @Override
    public void onInjected() {
        action.apply();
    }
}

This binding can request own dependencies which will be provided by the scope that this binding is operating:

public interface Action {
    void apply();
}

And two implementations:

public class ActionNoOp implements Action {

    @Inject
    public ActionNoOp() {
    }

    @Override
    public void apply() {

    }
}

public class ActionOp implements Action {

    @Inject
    public ActionOp() {
    }

    @Override
    public void apply() {
        throw new RuntimeException();
    }
}
public class Root implements Di.Service {

    public static void main(String[] args) {
        final Root root = new Root();
        Di
                .root("Root", new Module() {
                    @Override
                    public void configure() {
                        bind(Action.class).as(ActionNoOp.class);
                    }
                })
                .accept(root::init);
    }

    // here it will be `MyClass{ActionNoOp}`
    @Inject
    private MyClass myClass;

    @Override
    public void init(@NonNull Di di) {
        final Sibling sibling = new Sibling();
        di
                .inject(this)
                .accept(sibling::init);

    }
}
public class Sibling implements Di.Service {

    // here it will be `MyClass{ActionOp}`
    @Inject
    private MyClass myClass;

    @Override
    public void init(@NonNull Di di) {
        di
                .fork("Sibling", new Module() {
                    @Override
                    public void configure() {
                        bind(Action.class).as(ActionOp.class);
                    }
                })
                .inject(this);
    }
}

Scopes

Scopes are organized in a tree structure. To create a scope one call di#fork on a Di instance that will become the parent for newly created one.

After a fork is created it can be closed (clearing internal cache, all bindings) which will also close all of its children.

  Copyright 2018 Dimitry Ivanov (mail@dimitryivanov.ru)

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.