mahdi pishguy mahdi pishguy - 1 month ago 8
Java Question

Android use dependency injection for simple custom class

after search on web for learning about this feature most topics or post was using dependency injection for

Retrofit
or other android useful libraries, but i have some custom class which i want to use that with DI and i can't done it, for example i have simple custom class for using
SharePreference
and i'm using with that as an Singleton class

in my code i can't assign correct Dagger to component on
SpApplication
class to use that on activities or fragments

public class SP {

private SharedPreferences preferences;
private Context context;

public SP(Context context) {
this.context = context;
}

private SharedPreferences getPrefs() {
return preferences = PreferenceManager.getDefaultSharedPreferences(context);
}

public String getString(SharedPrefsTypes propertyName) {
return getPrefs().getString(propertyName.toString(), "");
}


public int getInt(SharedPrefsTypes propertyName) {
return getPrefs().getInt(propertyName.toString(), 0);
}
...
public enum SharedPrefsTypes {
Login
}
}


now i'm trying to use DI for that:

AppModules class:

@Module
public class AppModules {
private Context context;

public AppModules(Context context) {
this.context = context;
}

@Provides
@Singleton
SP provideSharePreferences() {
SP sharePreference = new SP(context);
return sharePreference;
}
}


ApplicationComponent
class:

@Component(modules = AppModules.class)
public interface ApplicationComponent {
void inject(ActivityMain activity);
}


SpApplication
class:

public class SpApplication extends Application {
private static SpApplication self;
private ApplicationComponent component;

@Override
public void onCreate() {
super.onCreate();

self = this;
component = DaggerApplicationComponent.builder().build();
}


public static SpApplication get(Context context) {
return (SpApplication) context.getApplicationContext();
}

public static SpApplication getInstance() {
return self;
}

public ApplicationComponent getComponent() {
return component;
}
}


and my
ActivityMain
class:

public class ActivityMain extends AppCompatActivity {
@Inject
SP sharePreference;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
((SpApplication) getApplication()).getComponent().inject(this);
sharePreference.setInt(SP.SharedPrefsTypes.Login, 0);
}
}


I get this error:

android.app.Application cannot be cast to com.pishguy.yendir.SpApplication


Thanks in advance

Answer

I guess you are trying to inject into ActivityMain, but since you did not provide its source, let me show you how you could inject into SpApplication. Then just copy the relevant parts into your Activity.

Few things I think you need to change.

Module:

Your AppModules class is generally OK, but I just suggest you to change the way you make use of Context - don't use it as a field, but inject it as any other service. It looks like this:

@Module
public class AppModules {
    private Context context;

    public AppModules(Context context) {
        this.context = context;
    }

    @Provides // this can be non-scoped because anyway the same instance is always returned
    Context provideContext() {
        return this.context;
    }

    @Provides
    @Singleton
    SP provideSharePreferences(Context context) {
        return new SP(context); // use method-local Context
    }
}

Component:

  1. If component injects scoped services (@Singleton is a scope), the component itself must be scoped
  2. If you want to inject into SpApplication class, then declare it as an injection client in the component

Taking into account these two points, ApplicationComponent should look like this:

@Singleton // injects @Singleton scoped services
@Component(modules = AppModules.class)
public interface ApplicationComponent {
    void inject(SpApplication application); // SpApplication is DI client (injection target)
}

Client:

I'd change few things in your SpApplication class:

  1. The way you instantiate ApplicationComponent is incorrect
  2. This is not a bug, but you don't really need get(Context) and getInstance() methods

In addition, since I'm showing how you inject into SpApplication, I will add the injection logic as well (which you should copy to your actual clients).

So, SpApplication (which is DI client) should look similar to this:

public class SpApplication extends Application {

    @Inject SP sp; // the dependency that should be injected

    private ApplicationComponent component;

    @Override
    public void onCreate() {
        super.onCreate();

        getComponent().inject(this); // this is when the actual injection takes place
    }


    public ApplicationComponent getComponent() {
        if (component == null) {
            // this is the way Dagger components should be instantiated
            component = DaggerApplicationComponent.builder()
                .appModules(new AppModules(this))
                .build();
        }
        return component;
    }
}

If you perform the above changes, I tend to believe that you'll be all right.

BTW, I recently completed a blog post about Dagger 2 scopes. You might want to check it if you are going to be serious about dependency injection.

Comments