How to set up dependency injection using Dagger for things other than Activities and Fragments?
Asked Answered
S

5

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:

Scintilla answered 20/9, 2013 at 1:0 Comment(7)
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 processesFayalite
@EugenMartynov And different processes other then the UI thread are not thought to be injected?Scintilla
Please don't mix processes with threads. You won't be able to access initialized application instance in different processFayalite
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?Bucella
I figured out how to inject Context if you want the Application context hereDizon
@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.Scintilla
Off-topic, but you should really avoid using the Application class when inflating layouts or you'll screw up themes.Lowney
T
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
Thermocouple answered 3/10, 2013 at 8:54 Comment(0)
R
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.

Rodrigues answered 20/9, 2013 at 16:49 Comment(4)
Your ObjectGraph is very helpful. Please note that there is a parameter error also pointed out by David Santiago Turiño.Scintilla
Do you have an answer regarding AsyncTask and static helper methods?Scintilla
@Scintilla 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.Rodrigues
@Scintilla you got it, for the BaseFragmentActivity and BaseListFragment, that's exactly how I use it.Rodrigues
G
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

Gann answered 15/11, 2013 at 11:38 Comment(0)
D
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

Dizon answered 15/1, 2014 at 23:55 Comment(3)
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?Lissettelissi
@user2713030 Static functions don't belong in testable code. You can't inject dependencies provided by statics.Dizon
Is there an extra provideResources() in your code?Lejeune
T
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.

Trochophore answered 22/9, 2013 at 21:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.