Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to @Provide an Activity for the MortarActivityScope, without leaking the Activity on orientation changes?

I have a Mortar application, with a MortarActivityScope as the first child under the root scope. The MortarActivityScope has an ActivityScope which @Provides an activity for injected classes:

@Module(addsTo = ApplicationModule.class, injects = {Foo.class, SomePresenter.class, AnotherPresenter.class})
public class ActivityModule {

    private final Activity activity;

    public ActivityModule(Activity activity) {
        this.activity = activity;
    }

    @Provides Activity provideActivity() {
        return activity;
    }
}

public class Foo {
    private final Activity activity;
    @Inject(Activity activity) {
        this.activity = activity;
    }
    public void doSomethingWithActivity() {
       // do stuff with activity: findViewById(), getWindow(), mess with action bar etc.
    }
}

This is fine until an orientation change happens. In the Mortar sample project, the Activity scope is not destroyed on orientation changes. This is presumably to allow @Singleton presenters, screens etc. to persist across orientation changes. You can see this in the onDestroy() method in the sample project's main activity:

@Override protected void onDestroy() {
    super.onDestroy();

    actionBarOwner.dropView(this);

    // activityScope may be null in case isWrongInstance() returned true in onCreate()
    if (isFinishing() && activityScope != null) {
      MortarScope parentScope = Mortar.getScope(getApplication());
      parentScope.destroyChild(activityScope);
      activityScope = null;
    }
  }
}

However, doing it this way means that the old ObjectGraph persists across orientation changes. I have observed that Mortar.requireActivityScope does not replace the module from the old activity scope with the new module provided by the new Blueprint. Instead, the object graph retains a reference to the previous module, including the destroyed Activity.

public class MyActivity extends Activity implements Blueprint {

    @Inject foo;

    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        MortarScope parentScope = Mortar.getScope(getApplication());
        activityScope = Mortar.requireActivityScope(parentScope, this);
        Mortar.inject(this, this);

        foo.doSomethingWithActivity(); //fails, because activity injected by object graph is destroyed
    }

    @Override
    public String getMortarScopeName() {
        return getClass().getName();
    }

    @Override
    public Object getDaggerModule() {
        return new ActivityModule(this);
    }
}

The Mortar sample activity seems to get around this by not including a @Provides Activity method in the main module. But shouldn't the MortarActivityScope be able to inject an Activity? What's the preferred way to do this, without losing all of your singleton objects (Presenter objects, etc.) on orientation change?

like image 554
weefbellington Avatar asked Oct 23 '14 17:10

weefbellington


1 Answers

Don't allow anyone to inject the Activity, that can't be made safe. Instead inject a presenter that is tied to the Activity.

How to handle onActivityResult() with Mortar includes an example of an Activity owning a presenter. Other parts of your app, including other presenters, can then inject that one and ask it to do whatever it is they need done that requires dealing with the activity.

And there is no need to tie all activity-specific work into a single activity presenter. Our activity has several presenters that broker its services to the rest of the app.

like image 114
rjrjr Avatar answered Oct 18 '22 08:10

rjrjr