Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I inject an object into Android Priority Job Queue with Dagger 2?

I'm trying to integrate Retrofit 2 and Android Priority Job Queue by using Dagger 2.

It's likely I'm using the wrong pattern here (I'm new to Java and Android), but I'm trying to access a Dagger-created Retrofit instance from an object that will be serialized then deserialized before execution (Android Job Queue serializes jobs that are persisted to disk). The Retrofit instance is created by an Application Dagger component, because I'm using SharedPreferences in one of its dependencies.

I can't pass Retrofit to the job when it's created because Retrofit itself cannot be serialized.

Application also can't be serialized, so I can't reference the Application Dagger component from the job when it runs (since I cannot inject with ((MyApplication) myApplication).component().inject(this); like I can do from Activities, because the Application object doesn't exist in the deserialized job.)

I want to use the Retrofit instance used by the rest of the app for efficiency instead of creating another instance just for the job. Is it even possible?

I'm not sure if using Provider<T> or a Factory might help because that's beyond my understanding right now, but I'd love a hint about how to structure things if that's the case!

EDIT: This how you create a job with Android Priority Job Queue. I've modified a sample to indicate how I'd like the injection to be working. The job is serialized on onAdded() and deserialized before it's run with onRun():

// A job to send a tweet
public class PostTweetJob extends Job {

    @Inject MyService webservice;
    public static final int PRIORITY = 1;
    private String text;

    public PostTweetJob(String text) {
        // This job requires network connectivity,
        // and should be persisted in case the application exits before job is completed.
        super(new Params(PRIORITY).requireNetwork().persist());
    }
    @Override
    public void onAdded() {
        // Job has been saved to disk.
    }
    @Override
    public void onRun() throws Throwable {
        // Job logic goes here
        webservice.postTweet(text);
    }
    @Override
    protected void onCancel() {
        // Clean up
    }
}
like image 515
snafu109 Avatar asked Jan 26 '16 19:01

snafu109


1 Answers

Here's the simplest solution that I could manage.

First, create a BaseJob class. This will be the injection target:

public abstract class BaseJob extends Job {
    // annotate fields that should be injected and made available to subclasses
    @Inject MyService service;

    protected BaseJob(Params params) {
        super(params);
    }
}

It's declared abstract so there's no need to override any of the abstract methods declared in the Job class. Instead, methods will be overridden in the jobs that inherit from BaseJob.

Create a job that inherits from BaseJob. Any fields injected into BaseJob are available for use:

public class MyActualJob extends BaseJob {
    public static final int PRIORITY = 1;

    public MyActualJob() {
        super(new Params(PRIORITY).requireNetwork().persist());
    }

    @Override
    public void onAdded() {
        // job added to queue
    }

    @Override
    public void onRun() throws Throwable {
        // do the work
        // service will be injected into BaseJob, so you can use it here
        final Call<User> call = service.getUser();
        call.execute();
    }

    @Override
    protected void onCancel() {
        // clean up
    }
}

Finally, to ensure things are linked up, add a DependencyInjector to JobManager when it's created. This injects into the job's BaseJob:

DependencyInjector dependencyInjector = new DependencyInjector() {
    @Override
    public void inject(Job job) {
        // this line depends on how your Dagger components are setup;
        // the important part is to cast job to BaseJob
        ((MyApplication) app).component().inject((BaseJob) job);
    }
};
Configuration configuration = new Configuration.Builder(getApplicationContext())
        .injector(dependencyInjector)
        .build();
JobManager jobManager = new JobManager(getApplicationContext(), configuration);

Why not skip using BaseJob and inject directly into MyActualJob? This will work, however if there are several jobs that are injection targets, I believe you would have to use instanceof to check what kind of job was being created and cast job to the correct class when creating DependencyInjector:

DependencyInjector dependencyInjector = new DependencyInjector() {
    @Override
    public void inject(Job job) {
        if (job instanceof MyActualJob) {
            ((MyApplication) app).component().inject((MyActualJob) job);
        } else if (job instanceof MyRealJob) {
            ((MyApplication) app).component().inject((MyRealJob) job);
        } else if (job instanceof MyBetterJob) {
            ((MyApplication) app).component().inject((MyBetterJob) job);
        }
    }
};

In my case most if not all jobs need access to the same global objects, so it's cleaner to subclass BaseJob and use that as the sole injection target.

like image 172
snafu109 Avatar answered Oct 02 '22 05:10

snafu109