How to inject into static classes using Dagger?

2019-04-20 08:20发布

问题:

I want to introduce dependency injection through Dagger to a project. The following code acts as an example to describe the problem of injection into static classes.

The static method setupTextView() is called from multiple classes:

public abstract class TextViewHelper {
    public static void setupTextView(TextView textView, 
                                     Spanned text, 
                                     TrackingPoint trackingPoint) {
        textView.setText(text, TextView.BufferType.SPANNABLE);
        textView.setMovementMethod(LinkMovementMethod.getInstance());
        textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MyApp.getTracker().track(trackingPoint);
            }
        });
    }
}

Here is one example how the helper method is used:

TextViewHelper.setupTextView(this, R.id.some_text_view, 
                             R.string.some_text,
                             TrackingPoint.SomeTextClick);

The tracking used in the helper method is provided by the application class:

public class MyApp extends Application {

    private static Tracking mTracking;

    public void onCreate() {
        super.onCreate();
        mTracking = getTracking(getApplicationContext());
    }

    private Tracking getTracking(Context context) {
        if (BuildConfig.DEBUG) {
            return new NoTracking();
        } else {
            return new NsaTracking(context);
        }
    }

    public static Tracking getTracker() {
        return mTracking;
    }

}

Now, I want to inject the tracking via Dagger. When I refactored the code I noticed that I would need to pass the tracking object from my Activity or Fragment to the static helper since I cannot directly inject into the static class:

TextViewHelper.setupTextView(this, R.id.some_text_view, 
                             R.string.some_text, 
                             TrackingPoint.SomeTextClick,
                             Tracking tracking);

This does not feel like a good design pattern - since I pass the TrackPoint and the Tracking object. How would you improve this?

回答1:

In your TextViewHelper create a static field with the tracker.

public class TextViewHelper {

    private TextViewHelper(){}

    @Inject
    static Tracking sTracker;

    public static void setupTextView(TextView textView, 
                                     Spanned text, 
                                     TrackingPoint trackingPoint) {
        textView.setText(text, TextView.BufferType.SPANNABLE);
        textView.setMovementMethod(LinkMovementMethod.getInstance());
        textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                sTracker.track(trackingPoint);
            }
        });
    }
}

Here is how to configure the module:

@Module(staticInjections = TextViewHelper.class)
public class TrackerModule {
...
}

And the most important, call injectStatics on your graph.

mObjectGraph = ObjectGraph.create(new TrackerModule());
mObjectGraph.injectStatics();

Edit:

As you noted Dagger's documentation states that static injections "should be used sparingly because static dependencies are difficult to test and reuse." It is all true, but because you asked how to inject object into utility class this is the best solution.

But if you want your code to be more testable, create a module like below:

@Module(injects = {classes that utilizes TextViewHelper})
public class TrackerModule {

      @Provides
      Tracking provideTracker() {
           ...
      }

      @Provides
      @Singleton
      TextViewHelper provideTextViewHelper(Tracking tracker) {
           return new TextViewHelper(tracker);
      }
}

Now you can remove static from TextViewHelper methods because this utility class will be injected using dagger.

public class TextViewHelper {

    private final Tracking mTracker;

    public TextViewHelper(Tracking tracker){
        mTracker = tracker;
    }

    public void setupTextView(TextView textView, 
                              Spanned text, 
                              TrackingPoint trackingPoint) {
         ...
    }
}

This is how it should be done if you want to follow good practices. Both solution will work so it's up to you to choose one.