Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dagger 2 - dependency is not singleton when using custom scope

I have custom scope:

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface PerActivity { }

and component which uses this annotation:

@PerActivity
@Component(modules = {ActivityModule.class}, dependencies = AppComponent.class)
public interface ActivityComponent {

    void inject(MainActivity mainActivity);

}

and module which provides MainPresenter dependency:

@Module
public class ActivityModule {

    @PerActivity
    @Provides
    public MainPresenter provideMainPresenter(){
        return new MainPresenter();
    }

}

This is MainActivity to which MainPresenter is injected:

public class MainActivity extends AppCompatActivity implements MainView {

    @PerActivity
    @Inject
    MainPresenter mainPresenter;

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

        DaggerActivityComponent.builder()
                .appComponent(DaggerAppComponent.builder().build())
                .build()
                .inject(this);

    }

}

My problem: presenter is recreated after a configuration change (orientation change).

My question: how to make it create only once for one activity?

like image 961
Joe Rakhimov Avatar asked Mar 06 '23 00:03

Joe Rakhimov


1 Answers

Dagger creates Java code that doesn't do any magic. It's some classes that can do some stuff, but they don't read/write global state or have other side effects. If you create a component, then you create all the objects that it contains. A @PerActivity scoped object provided by one component will never be the same as the one provided from another component of the same scope.

MyComponent.create().getPresenter() == MyComponent.create().getPresenter() // always false (with same scope)

This said, you have one major problem in your code sample, namely that you have an AppComponent, but you're keep recreating it, which is most likely not what you intended or should do. This will re-create every @Singleton for every Activity.

DaggerActivityComponent.builder()
    .appComponent(DaggerAppComponent.builder().build()) // DON'T !!
    .build()
    .inject(this);

You need to cache this component in your Application class and retrieve it, e.g. something like ((MyApp) getApplication).getComponent(). Otherwise none of your @Singleton classes get reused.


All this said, a presenter that is scoped @PerActivity will always be a new object when you create a new ActivityComponent (DaggerActivityComponent.build()) in your onCreate method. You recreate the component, and that new component will create new objects. This is the intended behavior, because otherwise you will get memory leaks and other unintended side effects. (Imagine the presenter kept a reference to the activity! Memory will leak, and UI updates will crash or be ignored.)

To reuse the same presenter you have to move the presenter up in scope. You can either make the presenter a @Singleton and keep it in your AppComponent, or you can try to find a way to add another layer of components in-between (AppComponent -> ActivityCacheComponent -> ActivityComponent), but most project that I have seen won't add this in-between layer as it adds a lot of complexity. You either have singletons that hold data and should be long-lived, or you have presenters that are cheap to create and can be easily recreated along with the Activity.

like image 56
David Medenjak Avatar answered Apr 30 '23 13:04

David Medenjak