Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
104 views
in Technique[技术] by (71.8m points)

android - How to handle uninitialized resources required for Dagger injection in onCreate after activity re-creation?

I’m adding Dagger to my legacy Android application and trying to figure out how to deal with dependencies that rely on non-Dagger objects that are created during normal app initialization but are not recreated during activity restart after backgrounding + inactivity.

Existing code

In the old code, during the authentication codepaths, we construct a “session” object that contains tokens and other things needed to create authenticated Retrofit objects. Certain activities rely on that session object being present, and there is no normal way for the user to reach them without going through the authentication codepath. However, if the application goes inactive and then is resumed later, the platform will attempt to directly re-create the activity without going through the authentication codepath, so the session object will be null.

To protect against this, historically each activity that relies on being logged in derives from “SessionActivity”, whose onCreate checks if we’re logged in, and if not finishes the activity and launches the app’s entry point. (which will trigger the “session” object to be re-created; the user will have to navigate back to that screen again but at least there’s no crash)

class SessionActivity {

@Override
protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);

    if( LegacySingleton.getInstance().session == null ) {
    {
        Intent intent = new Intent(this, LauncherActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
        finish();
        return;
    }
}

}

New code

I’m adding Dagger now, and want to be able to inject a Retrofit object that’s built using that session object.

@Provides
fun provideRetrofit(): Retrofit {
    return LegacySingleton.getInstance().session!!.authenticatedRetrofit
}

and then an example activity that relies on being logged in will have an onCreate like

@Override
protected void onCreate(Bundle savedInstanceState)
{
    ((MyComponentProvider) getApplicationContext()).getComponent().inject(this);
    super.onCreate(savedInstanceState);
}

There is an obvious problem: if LegacySingleton.getInstance().session returns null (as it will in the re-creation path) then we’ll crash. Is there a common pattern for how to address this?

Attempt 1:

My first inclination was to throw an IllegalStateException in provideRetrofit if session is null, and then activities that rely on being logged in would say

@Override
protected void onCreate(Bundle savedInstanceState)
{
    try {
       ((MyComponentProvider) getApplicationContext()).getComponent().inject(this);
   } catch (ex: IllegalStateException) {
        Intent intent = new Intent(this, LauncherActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
        finish();
        return;
   }        
    super.onCreate(savedInstanceState);
}

(obviously this would be factored into helper methods but written out here for simple clarity)

However, this won’t work because Android requires that all calls to onCreate call through to super.onCreate.

Attempt 2:

I could instead say

@Override
protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    try {
       ((MyComponentProvider) getApplicationContext()).getComponent().inject(this);
   } catch (ex: IllegalStateException) {
        Intent intent = new Intent(this, LauncherActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
        finish();
        return;
   }        
}

but this breaks the dagger principle of injecting before super.onCreate. (and then ensuring that dependencies are ready in case super.onCreate calls back into the derived class and requires one of those dependencies) So this might work in some cases, but if anyone starts relying on those dependencies in super.onCreate then I’ll hit NPEs at runtime. Not great.

Attempt 3:

I could execute the injection and keep track of whether it succeeded, something like

@Override
protected void onCreate(Bundle savedInstanceState)
{
    Val injectionSucceeded = try {
       ((MyComponentProvider) getApplicationContext()).getComponent().inject(this);
       true
   } catch (ex: IllegalStateException) {
       false
   }
    super.onCreate(savedInstanceState);
   If (!injectionSucceeded)
        Intent intent = new Intent(this, LauncherActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
        finish();
        return;
   }        
}

But has the same problem as attempt 2; if injection failed then I’m calling into super.onCreate anyway and not all dependencies may have been injected.

Attempt 4:

I could have instead provide Nullable objects

@Provides
fun provideRetrofit(): Retrofit? {
    return LegacySingleton.getInstance().session?.authenticatedRetrofit
}

but then every client would need to check nullability on the dependencies before using them which would be a huge pain. (I’m not even sure if this is possible)

Codelab suggestion:

Google’s Dagger code lab alludes to this problem: https://developer.android.com/codelabs/android-dagger#12

Important: Doing conditional field injection (as we're doing in?MainActivity.kt?when injecting only if the user is logged in) is very dangerous. The developers have to be aware of the conditions and you risk getting?NullPointerExceptions when interacting with injected fields. To avoid this issue, we can add some indirection by creating a SplashScreen that routes to either Registration, Login or Main depending on the state of the user.

But this doesn’t seem practical for an existing app that has a dozen of SessionActivity subclasses that want to inject themselves with Retrofit objects.

Does anyone have a better idea?

question from:https://stackoverflow.com/questions/65891215/how-to-handle-uninitialized-resources-required-for-dagger-injection-in-oncreate

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)
Waitting for answers

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...