Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dagger - Accessing Singleton object from other class

I've been working to understand and set up Dagger to handle dependency injections for my Android project. My single (no pun intended) objective is to create singleton objects that I can access across my application. I have successfully set up the objects in the initial activity. Where I am stuck is in accessing those objects from other classes. Here is my setup thus far:

Initial App Activity

public class SplashScreenActivity extends AppCompatActivity {

    @Inject SessionKeyExchangerService exchangerService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splash_screen);

        AppComponent component = DaggerAppComponent.builder().appModule(new AppModule()).build();

        // establish the session id as a singleton object
        exchangerService = component.provideSessionKeyExchangerService();

        // test whether I can access the singleton from another class
        exchangerService.sendEncryptedKeyToServer();
    } 

Component Class

@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {

    SessionKeyExchangerService provideSessionKeyExchangerService();

    AESCipherService provideCipherService();
}

Module Class

@Module
public class AppModule {

    @Provides @Singleton
    AESCipherService provideCipherService() {
        return new AESCipherService();
    }

    @Provides @Singleton
    SessionKeyExchangerService provideSessionKeyExchangerService(AESCipherService service) {
        return new SessionKeyExchangerService(service);
    }
}

AESCipherService

public class AESCipherService {

    private Long sessionId;

    public AESCipherService() {
        sessionId = makeSessionId();
        Log.d(Constants.TAG, "Session ID: " + Long.toString(sessionId));
    }

    private Long makeSessionId() {
        // this generates a random unsigned integer in the space {0, 2^32-1)
        Random random = new Random();
        return random.nextLong() & 0xffffffffL;
    }

    public Long getSessionId() {
        return sessionId;
    }
}

SessionKeyExchanger Class

public class SessionKeyExchangerService {

    private static SessionKeyExchangerService exchanger;
    private AESCipherService cipherService;

    @Inject
    public SessionKeyExchangerService(AESCipherService cipherService) {
        this.cipherService = cipherService;
    }

    public void sendEncryptedKeyToServer () {

        // the next line is almost certainly part of the problem
        // but I don't know how to fix!!!
        AppComponent component = DaggerAppComponent.builder().appModule(new AppModule()).build();

        AESCipherService cipherService = component.provideCipherService();

        Long sessionID = cipherService.getSessionId();
        Log.d(Constants.TAG, "singleton verification: " + (Long.toString(sessionID)));
    }

Here is some sample output:

Session ID: 217186720
singleton verification: 790090968

Clearly I'm not accessing the same object. I realize that at least part of the part of the issue stems from the way that I call the new operator in the AESCipherService when I am attempting to get a reference to the AppComponent class, but I don't know how to get this reference any other way.

How do I fix this? Thanks!

like image 472
AndroidDev Avatar asked Oct 19 '22 04:10

AndroidDev


1 Answers

 AppComponent component = DaggerAppComponent.builder().appModule(new AppModule()).build();

Nononononono. That won't be a Singleton. Scoped providers work only per component, which means you must use a single component across your application to have @Singleton scoped modules that actually share the same scoped provider. In this case, you'd be creating a new component each time your activity is created.

You need to create them like this:

public enum Injector {
    INSTANCE;

    private AppComponent appComponent;

    static {
        INSTANCE.appComponent = DaggerAppComponent.create();
    }

    public getAppComponent() {
        return appComponent;
    }
}

You could also subclass Application and create one there in onCreate().

Also

@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {
    SessionKeyExchangerService provideSessionKeyExchangerService();
    AESCipherService provideCipherService();

    void inject(SplashScreenActivity splashScreenActivity); //does NOT support base class injection! Concrete classes only!
}

Then

public class SplashScreenActivity extends AppCompatActivity {

    @Inject SessionKeyExchangerService exchangerService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splash_screen);

        Injector.INSTANCE.getAppComponent().inject(this);

        // establish the session id as a singleton object
        // exchangerService = component.provideSessionKeyExchangerService(); //totally not needed

        // test whether I can access the singleton from another class
        exchangerService.sendEncryptedKeyToServer();

Also, you're using @Module-based instance creation, so lose the @Inject on your constructor in

@Inject
public SessionKeyExchangerService(AESCipherService cipherService) {
    this.cipherService = cipherService;
}

And also

public void sendEncryptedKeyToServer () {

    // the next line is almost certainly part of the problem
    // but I don't know how to fix!!!
    //AppComponent component = DaggerAppComponent.builder().appModule(new AppModule()).build(); //you don't need this here at all

    //AESCipherService cipherService = component.provideCipherService(); //already provided in constructor
like image 105
EpicPandaForce Avatar answered Oct 27 '22 08:10

EpicPandaForce