0

I have implemented simple drill-down pattern with several activities that pass data to display. But when I go back (either android back button or toolbar's back button) my app crashes in onCreate because parameters are gone.

Ok, there are hundreds similar questions. Please read on. I did investigation and even senior developer reviewed my code and he did not understood.

This is a log how it was called:

TestItemsActivity.onCreate()
TestItemsActivity.onStart()
TestItemsActivity.onResume()
TestItemsActivity.onPause()
RunTestActivity.onCreate()
RunTestActivity.onStart()
RunTestActivity.onResume()
TestItemsActivity.onSaveInstanceState()
TestItemsActivity.onStop()
RunTestActivity.onPause()
TestItemsActivity.onCreate()

The last sentence is important - why is onCreate called again? Activity lifecycle says that onResume is expected.

enter image description here

Let's look at the source code. I use appcompat 7:25.1.

TestItemsActivity

public class TestItemsActivity extends AppCompatActivity {
TestScript script;

protected void onCreate(Bundle state) {
    log.debug("onCreate()");
    super.onCreate(state);
    setContentView(R.layout.act_script_items);

    if (state != null) {
        script = (TestScript) state.getSerializable(ScriptListActivity.KEY_SCRIPT);
    } else {
        script = (TestScript) getIntent().getSerializableExtra(ScriptListActivity.KEY_SCRIPT);
    }
    ...
    gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
            Intent intent = new Intent(TestItemsActivity.this, RunTestActivity.class);
            intent.putExtra(RunTestActivity.KEY_POSITION, position);
            intent.putExtra(ScriptListActivity.KEY_SCRIPT, script);
            TestItemsActivity.this.startActivity(intent);
        }
    });
}

protected void onSaveInstanceState(Bundle state) {
    state.putSerializable(ScriptListActivity.KEY_SCRIPT, script);
    super.onSaveInstanceState(state);
}

AppCompatActivity

public class RunTestActivity extends AppCompatActivity {

protected void onCreate(Bundle state) {
    log.debug("onCreate()");
    super.onCreate(state);

    setContentView(R.layout.act_script_items);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbarCalc);
    if (toolbar != null) {
        setSupportActionBar(toolbar);

        toolbar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                moveTaskToBack(true);
            }
        });
    }

    Intent intent = getIntent();
    script = (TestScript) intent.getSerializableExtra(ScriptListActivity.KEY_SCRIPT);
}

And now the most interesting source code is the manifest:

    <activity
        android:name=".activities.TestItemsActivity"
        android:label="@string/action_academy"
        android:parentActivityName=".activities.ScriptListActivity">
    </activity>
    <activity
        android:name=".activities.RunTestActivity"
        android:label="@string/action_academy"
        android:parentActivityName=".activities.TestItemsActivity">
    </activity>

Accidentally I found that if I remove parentActivityName than android's back button does not start new activity (onCreate) and my app does not crashes! Unfortunatelly toolbar's back button disappears.

Some SO questions I read:

  1. Saving Android Activity state using Save Instance State
  2. save the state when back button is pressed
  3. How to save activity state after pressing back button?
  4. Bundle savedInstanceState is always null
  5. SavedInstantState created during onSaveInstanceState not restored in onCreate

Can somebody explains what is going there?

Complete stacktrace including parts that I removed from source code above. It fails in adapter because scripts instance is null. The activity failed to initialize it either from intent or bundle (which is null despite that onSaveInstanceState was called)

java.lang.RuntimeException: Unable to start activity ComponentInfo{lelisoft.com.lelimath.debug/lelisoft.com.lelimath.activities.TestItemsActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'java.util.List lelisoft.com.lelimath.data.TestScript.getItems()' on a null object reference
                  at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2665)
                  at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2726)
                  at android.app.ActivityThread.-wrap12(ActivityThread.java)
                  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1477)
                  at android.os.Handler.dispatchMessage(Handler.java:102)
                  at android.os.Looper.loop(Looper.java:154)
                  at android.app.ActivityThread.main(ActivityThread.java:6119)
                  at java.lang.reflect.Method.invoke(Native Method)
                  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
                  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
               Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'java.util.List lelisoft.com.lelimath.data.TestScript.getItems()' on a null object reference
                  at lelisoft.com.lelimath.adapter.TestItemAdapter.<init>(TestItemAdapter.java:97)
                  at lelisoft.com.lelimath.activities.TestItemsActivity.onCreate(TestItemsActivity.java:55)
                  at android.app.Activity.performCreate(Activity.java:6679)
                  at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1118)
                  at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2618)
                  at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2726) 
                  at android.app.ActivityThread.-wrap12(ActivityThread.java) 
                  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1477) 
                  at android.os.Handler.dispatchMessage(Handler.java:102) 
                  at android.os.Looper.loop(Looper.java:154) 

Update:

Workaround for this problem is to use a theme without a toolbar:

<activity
  android:name=".activities.CampaignActivity"
  android:label="@string/action_academy"
  android:theme="@style/AppTheme.NoActionBar">
</activity>

Unfortunatelly I need the toolbar for one activity.

Community
  • 1
  • 1
Leos Literak
  • 8,805
  • 19
  • 81
  • 156
  • Looking at your code I'd firstly move the call to super in `onSaveInstanceState()` as the first line, before any specific implementation in your own class. Also onResume is expected however not not guaranteed, if the system destroys your Activity whilst in the background (and is free to do so to free resources) then a new instance will be created. – Mark Jan 10 '17 at 21:11
  • I have tried this already. And it did not have any affect. Google has this call as the last command in their example. – Leos Literak Jan 10 '17 at 21:13
  • Post the stacktrace ... – Mark Jan 10 '17 at 21:18
  • The ordering looks ok - create a dummy project what happens with an isolated test between just 2 Actviities. – Mark Jan 10 '17 at 21:54
  • I think that this is a defect in Android support library. I found another case when back button does not work in current version though it worked nice in previous production version. – Leos Literak Jan 14 '17 at 20:03

1 Answers1

0

As you can see from the trace you provided, the activity was stopped and state was saved. Therefore later when activity is to be restored it will call onCreate().

TestItemsActivity.onSaveInstanceState()
TestItemsActivity.onStop()