I'm at an impasse. I'm using Dagger 2 for dependency injection, but I'm losing state when the app goes into the background. Here is the scenario: the app starts up and creates the dependencies. All works perfectly as long as the app stays in the foreground. However, there is a scenario when the app has to go into the background. When it comes back, the values stored in one of my injected classes are lost.
For my injected classes that have no dependencies of their own, everything seems to recover correctly. However, there is one injected class that has an injected dependency, and this is the one that doesn't recover. Here is how I am setting it up:
AppComponent.java
@Singleton
@Component(
modules = {
AppModule.class
}
)
public interface AppComponent {
SessionKeyExchangerService provideSessionKeyExchangerService();
AESCipherService provideCipherService();
void inject(LoginActivity loginActivity);
}
AppModule.java
@Module
public class AppModule {
@Provides @Singleton
AESCipherService provideCipherService() {
return new AESCipherService();
}
@Provides @Singleton
SessionKeyExchangerService provideSessionKeyExchangerService(AESCipherService service) {
return new SessionKeyExchangerService(service);
}
}
And then when I go to inject these dependencies, I do it like this:
LoginActivity.java
@Inject
SessionKeyExchangerService sessionKeyExchangerService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
Injector.INSTANCE.getAppComponent().inject(this);
if (savedInstanceState != null) {
sessionKeyExchangerService = savedInstanceState.getParcelable(SESSION_KEY_PARCEL);
Log.d(Constants.TAG, "session key retrieved in on create: " + sessionKeyExchangerService.getCipherService().getBase64EncodedSessionKey());
}
}
So, my fundamental question is how to maintain the state of a Dagger 2 injected class. I'm happy to share more code, but this is the essential idea.
Thanks for any help.
EDIT Assuming that what I've done above is OK, let me move on to how I save and retrieve the values stored in those injected objects. This will show that there is a problem somewhere.
When I go into the background, and then come back, I can see that I get a new PID. I can also see that I am able to store and retrieve the injected values correctly in the LoginActivity class. However, other classes that also have a reference to the injected value now have different values meaning that their reference is to a different memory location, right?
My best guess as to where I am going wrong is in LoginActivity onCreate where I am restoring the sessionKeyExchangerService
value from the saved parcel. I think I am creating new values that are not recognized across the app as injected dependencies, but I don't know why this is wrong or how to fix it.
This code is also in LoginActivity.java:
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(SESSION_KEY_PARCEL, sessionKeyExchangerService);
Log.d(Constants.TAG, "session key saved: " + sessionKeyExchangerService.getCipherService().getBase64EncodedSessionKey());
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
sessionKeyExchangerService = savedInstanceState.getParcelable(SESSION_KEY_PARCEL);
Log.d(Constants.TAG, "session key retrieved in on restore state: " + sessionKeyExchangerService.getCipherService().getBase64EncodedSessionKey());
}
Here is the console output that illustrates the issue. Notice 1) how the PID changes after onStop()
is called, and 2) how the class Authenticator
(which has a reference to AESCipherService
has a different session key value:
1398-1398/com.mysite.myapp D/MYTAG﹕ on save instance state
1398-1398/com.mysite.myapp D/MYTAG﹕ session key saved: 93Zuy8B3eos+eCfBQk9ErA==
1398-1398/com.mysite.myapp D/MYTAG﹕ on stop
3562-3562/com.mysite.myapp D/MYTAG﹕ session key retrieved in on create: 93Zuy8B3eos+eCfBQk9ErA==
3562-3562/com.mysite.myapp D/MYTAG﹕ on start
3562-3562/com.mysite.myapp D/MYTAG﹕ session key retrieved in on restore state: 93Zuy8B3eos+eCfBQk9ErA==
3562-3562/com.mysite.myapp D/MYTAG﹕ authenticator class says that the session key is: 28HwdRCjBqH3uFweEAGCdg==
SessionKeyExchangerService.java
protected SessionKeyExchangerService(Parcel in) {
notifyOn = in.readString();
sessionKeyExchangeAttempts = in.readInt();
MAX_SESSION_KEY_EXCHANGE_ATTEMPTS = in.readInt();
sessionKeyExchangeHasFailed = (in.readByte() == 1);
cipherService = in.readParcelable(AESCipherService.class.getClassLoader());
}
public static final Creator<SessionKeyExchangerService> CREATOR = new Creator<SessionKeyExchangerService>() {
@Override
public SessionKeyExchangerService createFromParcel(Parcel in) {
return new SessionKeyExchangerService(in);
}
@Override
public SessionKeyExchangerService[] newArray(int size) {
return new SessionKeyExchangerService[size];
}
};
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(notifyOn);
dest.writeInt(sessionKeyExchangeAttempts);
dest.writeInt(MAX_SESSION_KEY_EXCHANGE_ATTEMPTS);
dest.writeByte((byte) (hasSessionKeyExchangeFailed() ? 1 : 0));
dest.writeParcelable(cipherService, flags);
}
AESCipherService.java
protected AESCipherService(Parcel in) {
sessionKeyBytes = in.createByteArray();
ivBytes = in.createByteArray();
sessionId = in.readLong();
mIsSessionKeyEstablished = (in.readByte() == 1);
verbose = (in.readByte() == 1);
}
public static final Creator<AESCipherService> CREATOR = new Creator<AESCipherService>() {
@Override
public AESCipherService createFromParcel(Parcel in) {
return new AESCipherService(in);
}
@Override
public AESCipherService[] newArray(int size) {
return new AESCipherService[size];
}
};
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeByteArray(sessionKeyBytes);
dest.writeByteArray(ivBytes);
dest.writeLong(sessionId);
dest.writeByte((byte) (isSessionKeyEstablished() ? 1 : 0));
dest.writeByte((byte) (verbose ? 1 : 0 ));
}
With AssistedInject we can tell Dagger which constructor argument we want to assist with by annotating the argument with @Assisted annotation and instead of using @Inject, to inject the ViewModel, we will use @AssistedInject. The next step is to make AssistedInject generate the factory for this ViewModel.
First, let’s include the Gradle dependencies: With AssistedInject we can tell Dagger which constructor argument we want to assist with by annotating the argument with @Assisted annotation and instead of using @Inject, to inject the ViewModel, we will use @AssistedInject.
Activity class has two methods you must override when you want to save activity state. Values saved are stored in a Bundle object that is essentially a key-value pairs map and passed into the onCreate method of every Android Activity.
The factory takes an instance of GithubApi and SavedStateRegistryOwner instance as a 2nd argument, which is in this case, the Activity. This 2nd argument is very important since it ties the saved state to the current component (either Activity or Fragment).
Injecting values means, that you don't assign the value yourself. This said,
@Inject
SessionKeyExchangerService sessionKeyExchangerService;
// then in onCreate() after the injection
sessionKeyExchangerService = savedInstanceState.getParcelable(SESSION_KEY_PARCEL);
is not what you want to do.
If your SessionKeyExchangerService
depends on some saved state, you will have to pass it into your module.
AppModule
seems to be the wrong place to provide the SessionKeyExchangerService
. You should probably outsource to some SessionModule
which you then can swap, as I think is well explained here. In this sample, the UserModule lifecycle is managed by the app, and not dagger.
By providing a module with a constructor you can hence pass in your Parcelable
state from the savedInstanceState
.
Without knowing your whole project, I think you can greatly reduce the complexity and probably should not save state in the activity, but rather use SharedPreferences
or plain files. This would also remove the need for maintaining the module lifecycle with your activity state.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With