29

I started setting up dependency injection using Dagger as follows. Please feel encouraged to correct my implementation since I might have mistakes in there! The implementation follows the android-simple example provided by the project. In the following you can see how I successfully added dependency injection for Activities and Fragments. I try to keep it easy for now so I decided to inject Timber as a logger substitution for Android's log util.

import android.app.Application;
import java.util.Arrays;
import java.util.List;
import dagger.ObjectGraph;
import com.example.debugging.LoggingModule;

public class ExampleApplication extends Application {

    private ObjectGraph mObjectGraph;

    protected List<Object> getModules() {
        return Arrays.asList(
                new AndroidModule(this),
                new ExampleModule(),
                new LoggingModule()
        );
    }

    private void createObjectGraphIfNeeded() {
        if (mObjectGraph == null) {
            Object[] modules = getModules().toArray();
            mObjectGraph = ObjectGraph.create(modules);
        }
    }

    public void inject(Object object) {
        createObjectGraphIfNeeded();
        mObjectGraph.inject(object);
    }
}

By now the AndroidModule is not used anywhere it but might be helpful when a Context and LayoutInflater is needed e.g. in CursorAdapters.

import android.content.Context;
import android.view.LayoutInflater;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;

/**
 * A module for Android-specific dependencies which require a {@link Context} 
 * or {@link android.app.Application} to create.
 */
@Module(library = true)
public class AndroidModule {
    private final ExampleApplication mApplication;

    public AndroidModule(ExampleApplication application) {
        mApplication = application;
    }

    /**
     * Allow the application context to be injected but require that it be 
     * annotated with {@link ForApplication @Annotation} to explicitly 
     * differentiate it from an activity context.
     */
    @Provides @Singleton @ForApplication Context provideApplicationContext() {
        return mApplication;
    }

    @Provides @Singleton LayoutInflater provideLayoutInflater() {
        return (LayoutInflater) mApplication
            .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }
}

I am not sure what application-specific providers would go here. I stay with logging for now.

import dagger.Module;

@Module(
        complete = false
)
public class ExampleModule {
    public ExampleModule() {
         // TODO put your application-specific providers here!
    }
}

I prepared LoggingModule which provides access to Timber.

package com.example.debugging;

import javax.inject.Singleton;

import dagger.Module;
import dagger.Provides;
import com.example.BuildConfig;
import com.example.activities.BaseFragmentActivity;
import com.example.activities.DetailsActivity;
import com.example.fragments.BaseListFragment;
import com.example.fragments.ProfilesListFragment;
import timber.log.Timber;

@Module(injects = {
        // Activities
        BaseFragmentActivity.class,
        DetailsActivity.class,
        // Fragments
        BaseListFragment.class,
        ProfilesListFragment.class
})
public class LoggingModule {

    @Provides @Singleton Timber provideTimber() {
        return BuildConfig.DEBUG ? Timber.DEBUG : Timber.PROD;
    }
}

The base class for Activities injects itself into the object graph ...

package com.example.activities;

import android.os.Bundle;    
import com.actionbarsherlock.app.SherlockFragmentActivity;    
import javax.inject.Inject;
import com.example.ExampleApplication;
import timber.log.Timber;

public abstract class BaseFragmentActivity extends SherlockFragmentActivity {

    @Inject Timber mTimber;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // ...
        super.onCreate(savedInstanceState);
        ((ExampleApplication) getApplication()).inject(this);
    }
}

... and any sub class benefits from Timber being already present.

package com.example.activities;

import android.os.Bundle;
import com.example.R;

public class DetailsActivity extends BaseFragmentActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_details);
        mTimber.i("onCreate");
        // ...
    }
}

Same for Fragments: the base class does the dirty job ...

package com.example.fragments;

import android.os.Bundle;
import com.actionbarsherlock.app.SherlockListFragment;
import javax.inject.Inject;
import com.example.ExampleApplication;
import timber.log.Timber;

public abstract class BaseListFragment extends SherlockListFragment {

    @Inject Timber mTimber;

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        ((ExampleApplication) getActivity().getApplication()).inject(this);
    }

}

... and the sub class benefits from its super class.

package com.example.fragments;

import android.os.Bundle;

public class ProfilesListFragment extends BaseListFragment {

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        // TODO This might be a good example to inject resources
        // in the base class. But how?
        setEmptyText(getResources()
           .getString(R.string.profiles_list_no_content));
        mTimber.i("onActivityCreated");
        // ...
    }

}

So far so good. But how can inject Timber into BaseCursorAdapter, BaseContentProvider, BaseSQLiteOpenHelper, BaseService, BaseAsyncTask and static helper methods?

The deprecated android example by Christopher Perry points out how to inject an Adapter into a ListFragment but not how to inject Context, Resources, LayoutInflater, Cursor into the (Cursor)Adapter or just Timber.


References:

RonU
  • 5,525
  • 3
  • 16
  • 13
JJD
  • 50,076
  • 60
  • 203
  • 339
  • That would give complexity for testing but you could make access to your application (or object graph itself) through static methods. But you should be really careful since you can run providers, services and application in different processes – Eugen Martynov Sep 20 '13 at 09:53
  • @EugenMartynov And different processes **other then the UI thread** are **not** thought to be injected? – JJD Sep 20 '13 at 12:12
  • Please don't mix processes with threads. You won't be able to access initialized application instance in different process – Eugen Martynov Sep 21 '13 at 13:52
  • I don't see the code for BaseService, for instance, but: can't you use dagger to provide a BaseService instance and let it do the injection of Timber? – David Santiago Turiño Sep 23 '13 at 09:51
  • 1
    I figured out how to inject `Context` if you want the Application context [here](https://github.com/christopherperry/dagger/blob/master/examples/android/src/dagger/module/ApplicationModule.java) – Christopher Perry Jan 15 '14 at 19:33
  • @androidHunter Please create a **new question** on StackOverflow providing a detailed description of the problem, relevant code snippets and the exact error message. Feel free to leave a link to your post as a comment here. – JJD Mar 16 '16 at 11:30
  • Off-topic, but you should really avoid using the Application class when inflating layouts or you'll screw up themes. – Saket Jun 19 '17 at 05:18

5 Answers5

8

Check out Andy Dennie's examples for injecting in different scenarios:

https://github.com/adennie/fb-android-dagger

Some points where I inject:

  • Activity, Service, and Fragment subclasses: in onCreate
  • BroadcastReceiver subclasses (includes, e.g. AppWidgetProvider): in onReceive
Brais Gabin
  • 5,827
  • 6
  • 57
  • 92
orip
  • 73,323
  • 21
  • 116
  • 148
5

tl;dr There's a lot going on in this question, but it might be helped with a Gist of mine. If you can get a Context somewhere you can inject it based off of the ObjectGraph maintained by your Application class.


Edit by JJD:

The ObjectGraph in the Gist can be integrated as follows:

public class ExampleApplication extends Application
        implements ObjectGraph.ObjectGraphApplication {

/* Application Lifecycle */
    @Override
    public void onCreate() {
        // Creates the dependency injection object graph
        _object_graph = ObjectGraph.create(...);
    }

/* ObjectGraphApplication Contract */
    @Override
    public void inject(@Nonnull Object dependent) {
        _object_graph.inject(dependent);
    }

    /** Application's object graph for handling dependency injection */
    private ObjectGraph _object_graph;
}

...

public abstract class BaseFragmentActivity extends SherlockFragmentActivity {
    @Inject Timber _timber;

    @Override
    protected void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        ObjectGraph.inject(this);
    }
}

...

public abstract class BaseListFragment extends SherlockListFragment {    
    @Inject Timber _timber;

    @Override
    public void onActivityCreated(Bundle icicle) {
        super.onActivityCreated(icicle);
        ObjectGraph.inject(this);
    }
}

Similar works for BaseCursorAdapter, BaseContentProvider and BaseService.

Community
  • 1
  • 1
Dandre Allison
  • 5,975
  • 5
  • 42
  • 56
  • Your `ObjectGraph` is very helpful. Please note that there is a [parameter error](https://gist.github.com/imminent/5222625#file-objectgraph-java-L59) also [pointed out by David Santiago Turiño](https://gist.github.com/imminent/5222625/#comment-913246). – JJD Sep 23 '13 at 12:21
  • Do you have an answer regarding `AsyncTask` and `static` helper methods? – JJD Sep 23 '13 at 18:52
  • @JJD for `AsyncTask`, you just need to pass you have to approaches, inject the dependencies in the class that uses the `AsyncTask` and reference those fields in the `AsyncTask`. Or pass the `Context` into the constructor of the `AsyncTask` and inject the dependencies through that `Context` (in my case `inject(context, this);`). Dagger doesn't support method injection of any sort, but there is a static injection for static classes, which is recommended to be avoided. – Dandre Allison Sep 24 '13 at 17:06
  • @JJD you got it, for the `BaseFragmentActivity` and `BaseListFragment`, that's exactly how I use it. – Dandre Allison Sep 24 '13 at 17:14
2

We had to do this very same thing (i.e. inject a logger everywhere). We ended up making a very small static wrapper (thus available everywhere) that wraps a class that is injected via dagger statically.

package com.example.secret;

import javax.inject.Inject;

import com.example.interfaces.Logger;

public class LoggerProvider {

    @Inject
    static Logger logger;

    public LoggerProvider() {

    }

    public static Logger getLogger() {
            return logger;
    }

}

Logger implements your logging interface. To inject it at the application level you need:

   graph = ObjectGraph.create(getModules().toArray());
   graph.injectStatics();

Code here: https://github.com/nuria/android-examples/tree/master/dagger-logger-example

nurieta
  • 1,615
  • 15
  • 6
2

Injection of Context, Resources and LayoutInflater (passing the application context when you new this up in Application).

@Module(complete = false)
public class AndroidServicesModule {
  private final Context context;

  public AndroidServicesModule(@ForApplication Context context) {
    this.context = context;
  }

  @Provides @Singleton Resources provideResources() {
    return context.getResources();
  }

  @Provides @Singleton LocationManager provideLocationManager() {
    return (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
  }

  @Provides @Singleton LayoutInflater provideLayoutInflater() {
    return (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  }

  @Provides @Singleton Resources provideResources() {
    return context.getResources();
  }

  @Provides @ForApplication Context provideContext() {
    return context;
  }
}

Of course you should probably qualify the context with an annotation specifying it's the application context (for example @ForApplication).

If you need the equivalent of Roboguice's @InjectResource(int) I can't think of anything offhand. Butterknife seems like the right lib to add this to. See here

Christopher Perry
  • 38,891
  • 43
  • 145
  • 187
  • Why not using something simple like `MyApplication extends Application`, then provide static functions to get `Resources`, `Inflater` etc? Is it because you have another `AndroidServiceModule` that you load while running unit test? – mbmc Apr 20 '14 at 18:13
  • @user2713030 Static functions don't belong in testable code. You can't inject dependencies provided by statics. – Christopher Perry Apr 20 '14 at 22:11
  • Is there an extra `provideResources()` in your code? – Clint Jun 25 '14 at 00:41
0

You can add these constructions to ExampleApplication class:

private static ExampleApplication INSTANCE;

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

    INSTANCE = this;
    mObjectGraph = ObjectGraph.create(getModules());
    mObjectGraph.injectStatics();
    mObjectGraph.inject(this);
}

public static ExampleApplication get() {
    return INSTANCE;
}

After that you are able to inject any object (denoted by this) with one simple line:

ExampleApplication.get().inject(this)

This should be called in constructor for objects you create manually or onCreate (or analog) for those that are managed by Android System.

colriot
  • 1,978
  • 1
  • 14
  • 21