Home » Android » android – Dependency Injection Into Service

android – Dependency Injection Into Service

Posted by: admin June 15, 2020 Leave a comment

Questions:

I am trying to inject dependencies into my App. Everything is working fine until I tried to inject Realm into my Service class. I started getting IllegalStateException which is obviously caused by me accessing Realm from a Thread it was created. So, this is the structure of my Dependency Injection

The AppModule

@Module
public class AppModule {

    MainApplication mainApplication;

    public AppModule(MainApplication mainApplication) {
        this.mainApplication = mainApplication;
    }

    @Provides
    @Singleton
    MainApplication getFmnApplication() {
        return mainApplication;
    }
}

The RequestModule

@Module
public class RequestModule {

    @Provides
    @Singleton
    Retrofit.Builder getRetrofitBuilder() {
        return new Retrofit.Builder()
                .baseUrl(BuildConfig.HOST)
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create(CustomGsonParser.returnCustomParser()));
    }

    @Provides
    @Singleton
    OkHttpClient getOkHttpClient() {
        return new OkHttpClient.Builder()
                .addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC))
                .connectTimeout(30000, TimeUnit.SECONDS)
                .readTimeout(30000, TimeUnit.SECONDS).build();
    }

    @Provides
    @Singleton
    Retrofit getRetrofit() {
        return getRetrofitBuilder().client(getOkHttpClient()).build();
    }

    @Provides
    @Singleton
    ErrorUtils getErrorUtils() {
        return new ErrorUtils();
    }

    @Provides
    @Singleton
    MainAPI getMainAPI() {
        return getRetrofit().create(MainAPI.class);
    }

    // Used in the Service class
    @Provides
    @Singleton
    GeneralAPIHandler getGeneralAPIHandler(MainApplication mainApplication) {
        return new GeneralAPIHandler(mainApplication, getMainAPIHandler(), getErrorUtils());
    }
}

The AppComponent

@Singleton
@Component(modules = {
        AppModule.class,
        RequestModule.class
})
public interface MainAppComponent {

    void inject(SyncService suncService);
}

The Application Class

public class MainApplication extends Application {

    private MainAppComponent mainAppComponent;

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        MultiDex.install(this);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mainAppComponent = DaggerMainAppComponent.builder()
                .appModule(new AppModule(this))
                .requestModule(new RequestModule())
                .build();
    }

    public MainAppComponent getMainAppComponent() {
        return mainAppComponent;
    }
}

GeneralAPIHandler

public class GeneralAPIHandler {

    private static final String TAG = "GeneralAPIHandler";
    private MainAPI mainAPI;
    private Realm realm;
    private ErrorUtils errorUtils;
    private Context context;

    public GeneralAPIHandler() {
    }

    public GeneralAPIHandler(MainApplication mainApplication, MainAPI mainAPI, ErrorUtils errorUtils) {
        this.mainAPI = mainAPI;
        this.realm = RealmUtils.getRealmInstance(mainApplication.getApplicationContext());
        this.errorUtils = errorUtils;
        this.context = mainApplication.getApplicationContext();
    }

    public void sendPayload(APIRequestListener apiRequestListener) {
        List<RealmLga> notSentData = realm.where(RealmLga.class).equalTo("isSent", false).findAll(); <-- This is where the error comes from

        .... Other code here
    }
}

This only happens when I’m calling it from a Service class But, it was created with the Application Context. Why is it throwing an IllegalStateException

The Service Class

public class SyncService extends IntentService {

    @Inject GeneralAPIHandler generalAPIHandler;

    @Override
    public void onCreate() {
        super.onCreate();
        ((MainApplication) getApplicationContext()).getMainAppComponent().inject(this);
    }

    /**
     * Creates an IntentService.  Invoked by your subclass's constructor.
     */
    public SyncService() {
        super("Sync");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        sendInformations();
    }

    private void sendInformations() {
        generalAPIHandler.sendPayload(new APIRequestListener() {
            @Override
            public void onError(APIError apiError){}

            @Override
            public void didComplete(WhichSync whichSync){}
        })
    }
}

Any help on what I’m doing wrong to be making Realm throw IllegalStateException would be appreciated. Thanks

How to&Answers:
@Inject GeneralAPIHandler generalAPIHandler;

@Override
public void onCreate() {
    super.onCreate();
    ((MainApplication) getApplicationContext()).getMainAppComponent().inject(this);
}

And therefore

public GeneralAPIHandler(MainApplication mainApplication, MainAPI mainAPI, ErrorUtils errorUtils) {
    this.mainAPI = mainAPI;
    this.realm = RealmUtils.getRealmInstance(mainApplication.getApplicationContext()); // <--

This code runs on the UI thread


@Override
protected void onHandleIntent(Intent intent) {
    sendInformations();
}

private void sendInformations() {
    generalAPIHandler.sendPayload(new APIRequestListener() {
    ....


public void sendPayload(APIRequestListener apiRequestListener) {
    List<RealmLga> notSentData = realm.where(RealmLga.class).equalTo("isSent", false).findAll();

This code runs on the IntentService background thread

You also wouldn’t be closing the Realm instance despite being on a non-looping background thread anyways, so it did you a favor by crashing.


Solution, you should obtain Realm instance in onHandleIntent(), and close it in finally { at the end of execution.


You might say, “but then how will I mock my Constructor argument”, the answer is use a class like

@Singleton
public class RealmFactory {
    @Inject
    public RealmFactory() {
    }

    public Realm create() {
        return Realm.getDefaultInstance();
    }
}

Answer:

a realm instance needs to be accessed only from the thread it’s created in.

Your intent service runs in a background thread. Your realm was likely created on the main thread