Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Where should I move a library initialization when using Dagger 2?

I have this code that initializes Calligraphy default configuration.

public class MyApplication extends Application {

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

        // The initialization I want to move
        CalligraphyConfig.initDefault(new CalligraphyConfig.Builder()
                        .setDefaultFontPath("fonts/MyFont.ttf")
                        .build()
        );
    }
}

I want to use Dagger 2 in my project but I don't fully understand what classes should I create and where to move this code in order to keep the project clean ?

like image 463
Yohan D Avatar asked Mar 06 '16 08:03

Yohan D


1 Answers

In short, you probably wouldn't move anything. The problem with this library is that it uses static methods for initialization and utilization. Static methods are a pain when trying to do dependency injection.

The Library (or why you would not change anything)

It looks like this library is 'just' about switching the used fonts by wrapping the context. As such it does not really provide business logic to your project, but just adds to your views / UI.

Injecting a dependency rather than just calling static methods is most useful if you either want to be able to unit test (inject mocks) or easily swap modules / behavior. In the case of globally changing fonts, both seems less likely.

If on the other hand you really need (or want to) be able to test it, or just have a clean design...

...wrap it

Static methods are a pain, because you can not have objects holding the logic. Unless you wrap them. To properly do DI with static methods, you would have to define your own interface.

public interface CalligraphyManager {
    /**
     *  Called on app start up to initialize
     */
     void init();

     // other methods, like wrapping context for activity
     Context wrap(Context context);
}

You now have some manager to access the static methods. The implementation should be fairly simple, since you want to do proper DI the application context and path needed for init() would be passed into the constructor of your implementation. The creation of your manager can thus be handled by your ApplicationModule—just add some provides method

@Singleton
@Provides
// You would also have to provide the path from somewhere or hardcode it
// left as an exercise for the reader
CalligraphyManager provideCalligraphyManager(Context context, String path) {
    return new ActualCalligraphyManager(context, path);
}

Your application would then look something like this:

public class MyApplication extends Application {

    @Inject
    CalligraphyManager mCalligraphy;

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

        mComponent = DaggerAppComponent.builder()
                .appModule(new AppModule(this))
                .build();
        mComponent.inject(this);

        // call the initialization
        mCalligraphy.init();
    }
}

Everything else is as usual. You have a singleton object in your application components graph, you can thus inject the same object into your activities and call `wrap´ where appropriate.

What about testing / mocking?

Since the whole reason of doing this is to make it 'testable', you can now easily provide a mock / stub object.

Create another implementation of the manager where init() would just do nothing, and wrap(Context) would just return the same context—a simple stub object.

like image 80
David Medenjak Avatar answered Oct 21 '22 11:10

David Medenjak