Dependency Injection in Legacy Code

Dependency Injection in Legacy Code

This post explains how you can inject dependencies into Android legacy singletons, or any classes that aren’t conforming to your current Android architecture, using Dagger & Hilt.

I’ll cut straight to the chase, if you have a reference to Context, (which you usually always do in spaghetti legacy code) then you can use your normal DI graph to request dependencies. If you can’t get a Context, then stop reading now. Sorry! 🙂

Working with legacy code bases can be quite the challenge, and if you have your new Modern Android Development architecture that is all singing all dancing using Dagger & Hilt, then you will want to use that everywhere (even when modifying the legacy code).

A typical Legacy singleton could look like this:

public class FooBarCenterManager {
    private static FooBarCenterManager mInstance;

    private FooBarCenterManager() {
        // Init stuff
    }

    public static FooBarCenterManager getInstance() {
        if (mInstance == null) {
            mInstance = new FooBarCenterManager();
        }
        return mInstance;
    }

    // Other code
}

This legacy class getInstance may be used in multiple places and the class may be a lot more complex. Here I am trying to show an idiomatic example.

Let’s say for argument sake you want to add a Set<String> to this legacy FooBarCenterManager. The Strings in the Set<String> are all declared in other dagger modules and available with DI, but this class isn’t injected.

You can use Hilt’s EntryPoint for these situations.

An entry point is the boundary between code that is managed by Hilt and code that is not. It is the point where code first enters into the graph of objects that Hilt manages. Entry points allow Hilt to use code that Hilt does not manage to provide dependencies within the dependency graph.

Define an interface (GetInstanceEntryPoint) that is annotated with @EntryPoint create a function for each binding type that you want and include qualifiers. Then add @InstallIn to specify the component in which to install the entry point. Here we use a SingletonComponent, later when we use Context it needs to be an ApplicationContext for this to work.

@EntryPoint
@InstallIn(SingletonComponent::class)
interface GetInstanceEntryPoint {
    fun analyticsTags(): Set<String>
}

Once you have defined the EntryPoint you can use that in your legacy code:

public class FooBarCenterManager {
    private static FooBarCenterManager mInstance;

    private final Set<String> analyticsTags;

    private FooBarCenterManager(Set<String> analyticsTags) {
        this.analyticsTags = analyticsTags;
        // Init stuff
    }

    public static FooBarCenterManager getInstance(Context context) {
        if (mInstance == null) {
            GetInstanceEntryPoint hiltEntryPoint = EntryPointAccessors.fromApplication(
                context.getApplicationContext(),
                GetInstanceEntryPoint.class
            );
            Set<String> analyticsTags = hiltEntryPoint.analyticsTags();
            mInstance = new FooBarCenterManager(analyticsTags);
        }
        return mInstance;
    }

    // Other code
}

And that’s it! Assuming you already have the Dagger modules setup to provide the dependencies you need, this custom EntryPoint definition that uses Context is all that is required to grab those dependencies and use them in that narly Legacy code.

Any questions, you can find me on:

Threads @Blundell_apps

Leave a Reply

Your email address will not be published. Required fields are marked *