title: AsyncTaskLoader onLoadFinished with a pending task and config change
body: <p>I'm trying to use an <code>AsyncTaskLoader</code> to load data in the background to populate a detail view in response to a list item being chosen.  I've gotten it mostly working but I'm still having one issue.  If I choose a second item in the list and then rotate the device <em>before the load for the first selected item has completed</em>, then the <code>onLoadFinished()</code> call is reporting to the activity being stopped rather than the new activity.  This works fine when choosing just a single item and then rotating.</p>
  
  <p>Here is the code I'm using.  Activity:</p>
  
  <pre><code>public final class DemoActivity extends Activity
          implements NumberListFragment.RowTappedListener,
                     LoaderManager.LoaderCallbacks&lt;String&gt; {
  
      private static final AtomicInteger activityCounter = new AtomicInteger(0);
  
      private int myActivityId;
  
      private ResultFragment resultFragment;
  
      private Integer selectedNumber;
  
      @Override
      public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
  
          myActivityId = activityCounter.incrementAndGet();
          Log.d("DemoActivity", "onCreate for " + myActivityId);
  
          setContentView(R.layout.demo);
  
          resultFragment = (ResultFragment) getFragmentManager().findFragmentById(R.id.result_fragment);
  
          getLoaderManager().initLoader(0, null, this);
  
      }
  
      @Override
      protected void onDestroy() {
          super.onDestroy();
          Log.d("DemoActivity", "onDestroy for " + myActivityId);
      }
  
      @Override
      public void onRowTapped(Integer number) {
          selectedNumber = number;
          resultFragment.setResultText("Fetching details for item " + number + "...");
          getLoaderManager().restartLoader(0, null, this);
      }
  
      @Override
      public Loader&lt;String&gt; onCreateLoader(int id, Bundle args) {
          return new ResultLoader(this, selectedNumber);
      }
  
      @Override
      public void onLoadFinished(Loader&lt;String&gt; loader, String data) {
          Log.d("DemoActivity", "onLoadFinished reporting to activity " + myActivityId);
          resultFragment.setResultText(data);
      }
  
      @Override
      public void onLoaderReset(Loader&lt;String&gt; loader) {
  
      }
  
      static final class ResultLoader extends AsyncTaskLoader&lt;String&gt; {
  
          private static final Random random = new Random();
  
          private final Integer number;
  
          private String result;
  
          ResultLoader(Context context, Integer number) {
              super(context);
              this.number = number;
          }
  
          @Override
          public String loadInBackground() {
              // Simulate expensive Web call
              try {
                  Thread.sleep(5000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
  
              return "Item " + number + " - Price: $" + random.nextInt(500) + ".00, Number in stock: " + random.nextInt(10000);
          }
  
          @Override
          public void deliverResult(String data) {
              if (isReset()) {
                  // An async query came in while the loader is stopped
                  return;
              }
  
              result = data;
  
              if (isStarted()) {
                  super.deliverResult(data);
              }
          }
  
          @Override
          protected void onStartLoading() {
              if (result != null) {
                  deliverResult(result);
              }
  
              // Only do a load if we have a source to load from
              if (number != null) {
                  forceLoad();
              }
          }
  
          @Override
          protected void onStopLoading() {
              // Attempt to cancel the current load task if possible.
              cancelLoad();
          }
  
          @Override
          protected void onReset() {
              super.onReset();
  
              // Ensure the loader is stopped
              onStopLoading();
  
              result = null;
          }
  
      }
  
  }
  </code></pre>
  
  <p>List fragment:</p>
  
  <pre><code>public final class NumberListFragment extends ListFragment {
  
      interface RowTappedListener {
  
          void onRowTapped(Integer number);
  
      }
  
      private RowTappedListener rowTappedListener;
  
      @Override
      public void onAttach(Activity activity) {
          super.onAttach(activity);
  
          rowTappedListener = (RowTappedListener) activity;
      }
  
      @Override
      public void onActivityCreated(Bundle savedInstanceState) {
          super.onActivityCreated(savedInstanceState);
  
          ArrayAdapter&lt;Integer&gt; adapter = new ArrayAdapter&lt;Integer&gt;(getActivity(),
                                                                    R.layout.simple_list_item_1,
                                                                    Arrays.asList(1, 2, 3, 4, 5, 6));
          setListAdapter(adapter);
  
      }
  
      @Override
      public void onListItemClick(ListView l, View v, int position, long id) {
          ArrayAdapter&lt;Integer&gt; adapter = (ArrayAdapter&lt;Integer&gt;) getListAdapter();
          rowTappedListener.onRowTapped(adapter.getItem(position));
      }
  
  }
  </code></pre>
  
  <p>Result fragment:</p>
  
  <pre><code>public final class ResultFragment extends Fragment {
  
      private TextView resultLabel;
  
      @Override
      public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
          View root = inflater.inflate(R.layout.result_fragment, container, false);
  
          resultLabel = (TextView) root.findViewById(R.id.result_label);
          if (savedInstanceState != null) {
              resultLabel.setText(savedInstanceState.getString("labelText", ""));
          }
  
          return root;
      }
  
      @Override
      public void onSaveInstanceState(Bundle outState) {
          super.onSaveInstanceState(outState);
  
          outState.putString("labelText", resultLabel.getText().toString());
      }
  
      void setResultText(String resultText) {
          resultLabel.setText(resultText);
      }
  
  }
  </code></pre>
  
  <p>I've been able to get this working using plain <code>AsyncTask</code>s but I'm trying to learn more about <code>Loader</code>s since they handle the configuration changes automatically.</p>
  
  <hr>
  
  <p><strong>EDIT</strong>: I think I may have tracked down the issue by looking at the source for <a href="http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.3_r1/android/app/LoaderManager.java?av=f#552">LoaderManager</a>.  When <code>initLoader</code> is called after the configuration change, the <code>LoaderInfo</code> object has its <code>mCallbacks</code> field updated with the new activity as the implementation of <code>LoaderCallbacks</code>, as I would expect.</p>
  
  <pre><code>public &lt;D&gt; Loader&lt;D&gt; initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks&lt;D&gt; callback) {
      if (mCreatingLoader) {
          throw new IllegalStateException("Called while creating a loader");
      }
  
      LoaderInfo info = mLoaders.get(id);
  
      if (DEBUG) Log.v(TAG, "initLoader in " + this + ": args=" + args);
  
      if (info == null) {
          // Loader doesn't already exist; create.
          info = createAndInstallLoader(id, args,  (LoaderManager.LoaderCallbacks&lt;Object&gt;)callback);
          if (DEBUG) Log.v(TAG, "  Created new loader " + info);
      } else {
          if (DEBUG) Log.v(TAG, "  Re-using existing loader " + info);
          info.mCallbacks = (LoaderManager.LoaderCallbacks&lt;Object&gt;)callback;
      }
  
      if (info.mHaveData &amp;&amp; mStarted) {
          // If the loader has already generated its data, report it now.
          info.callOnLoadFinished(info.mLoader, info.mData);
      }
  
      return (Loader&lt;D&gt;)info.mLoader;
  }
  </code></pre>
  
  <p>However, when there is a pending loader, the main <code>LoaderInfo</code> object also has an <code>mPendingLoader</code> field with a reference to a <code>LoaderCallbacks</code> as well, and this object is never updated with the new activity in the <code>mCallbacks</code> field.  I would expect to see the code look like this instead:</p>
  
  <pre><code>// This line was already there
  info.mCallbacks = (LoaderManager.LoaderCallbacks&lt;Object&gt;)callback;
  // This line is not currently there
  info.mPendingLoader.mCallbacks = (LoaderManager.LoaderCallbacks&lt;Object&gt;)callback;
  </code></pre>
  
  <p>It appears to be because of this that the pending loader calls <code>onLoadFinished</code> on the old activity instance.  If I breakpoint in this method and make the call that I feel is missing using the debugger, everything works as I expect.</p>
  
  <p>The new question is: Have I found a bug, or is this the expected behavior?</p>