Skip to content Skip to sidebar Skip to footer

How To Make Notifychange() Work Between Two Activities?

I have an activity ActitvityA that holds a listview populated by a CursorLoader. I want to switch to ActivityB and change some data and see those changes reflected in listview in A

Solution 1:

After a two long two days of scratching my head and altruistic engagement from pskink I painted myself a picture of what was wrong. My ActivityA is in a reality a lot more complicated. It uses ViewPager with PagerAdapter with instantiates listviews. At first I created those components in onCreate() method something like this:

@OverridepublicvoidonCreate(Bundle savedInstanceState)
{
        ...
    super.onCreate(savedInstanceState);
    // 1 .ViewPager
    viewPager = (ViewPager) findViewById(R.id.viewPager);
    ...
    viewPager.setAdapter( newMyPagerAdapter() );
    viewPager.setOnPageChangeListener(this); */
    ...
    // 2. LoadergetSupportLoaderManager().initLoader(LOADER_ID, null, this);
    ...
    // 3. CursorAdapter
    myCursorAdapter = newMyCursorAdapter(
                    this,
                    R.layout.list_item_favorites_history,
                    null,
      0);
}

Somewhere along the line I noticed that this is wrong order of creating. Why it didn't produce some error is because PagerAdapter.instantiateItem() is called aftter onCreate() finishes. I dont know why or how this caused the original problem. Maybe something did not wire correctly with listviews, adapters and content observers. I didn't dig into that.

I changed the order to:

@OverridepublicvoidonCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    ...
    // 1. CursorAdapter
    myCursorAdapter = newMyCursorAdapter(
                    this,
                    R.layout.list_item_favorites_history,
                    null,
                    0);
    ...
    // 2. LoadergetSupportLoaderManager().initLoader(LOADER_ID, null, this);
    ...
    // 3 .ViewPager
    viewPager = (ViewPager) findViewById(R.id.viewPager);
    ...
    viewPager.setAdapter( newMyPagerAdapter() );
    viewPager.setOnPageChangeListener(this); */
    ...        
}

This magically made it work in about 75% of the cases. When I studied CatLog output I noticed that ActivityA().onStop() is called at different times. When it works it is called late and I can see in logcat that onLoadFinished() executes. Sometimes ActivityA.onStop() executes right after query and then onLoadFinished() is not called at all. This brings me to what DeeV jas posted in his answer about cursors being unregistered from ContentResolver. This just might be the case. What made things to somehow came to light was the fact that simple demonstrator pskink insisted on did work and my app didn't although they were identical in key points. This brought my attention to asynchronous things and my onCreate() method. In reality my ActivityB is complicated so it gives enough time for ActivityA to stop. What I noticed also (and this did make things more difficult to sort) was that if I run my 75% version in debug mode (with no breakpoints) then the success rate falls to 0. ActivityA is stopped before cursor load finishes so my onLoadFinished() is never called and listviews are never updated.

Two key points:

  • Somehow the order of creation od ViewPager, CursorAdapter and CursorLoader is important
  • ActivityA may be (and is) stopped before cursor is loaded.

But even this is not. If I take a look at a sequence of simplified then I see that ActivityA.onStop() is executed before content provider inserts a record. I see no query while ActivityB is active. But when i return to ActivityA a query is execeuted laodFinished() follows and listview is refreshed. Not so in my app. It always executes a query while still in ActivityB, why??? This destroy my theory about onStop() being the culprit.

(Big thanks to pskink and DeeV)

UPDATE

After a lot of waisted time on this issue I finally nailed the cause of the problem.

Short description:

I have the following classes:

ActivityA - contains a list view populated via cursor loader.
ActivityB - that changes data in database
ContentProvider - content provider used for data manipulation and also used by cursorloader.

The problem:

After data manipulation in ActivityB the changes are not shown in list view in ActivityA. List view is not refreshed.

After I lot of eyeballing and studying logcat traces I have seen that things proceed in following sequence:

ActivityA is started

    ActivityA.onCreate()
        -> getSupportLoaderManager().initLoader(LOADER_ID, null, this);

    ContentProvider.query(uri)  // query is executes as it should

    ActivityA.onLoadFinished()  // in this event handler we change cursor in list view adapter and listview is populated


ActivityA starts ActivityB

    ActivityA.startActivity(intent)

    ActivityB.onCreate()
        -> ContentProvider.insert(uri)      // data is changed in the onCreate() method. Retrieved over internet and written into DB.
            -> getContext().getContentResolver().notifyChange(uri, null);   // notify observers

    ContentProvider.query(uri)
    /*  We can see that a query in content provider is executed.
        This is WRONG in my case. The only cursor for this uri is cursor in cursor loader of ActivityA.
        But ActivityA is not visible any more, so there is no need for it's observer to observe. */

    ActivityA.onStop()
    /*  !!! Only now is this event executed. That means that ActivityA was stopped only now.
        This also means (I guess) that all the loader/loading of ActivityA in progress were stopped.
        We can also see that ActivityA.onLoadFinished() was not called, so the listview was never updated.
        Note that ActivityA was not destroyed. What is causing Activity to be stopped so late I do not know.*/


ActivityB finishes and we return to ActivityA

    ActivityA.onResume()

    /*  No ContentProvider.query() is executed because we have cursor has already consumed
        notification while ActivityB was visible and ActivityA was not yet stopped.
        Because there is no query() there is no onLoadFinished() execution and no data is updated in listview */

So the problem is not that ActivityA is stopped to soon but that it is stopped to late. The data is updated and notification sent somewhere between creation of ActivityB and stopping of ActivityA. The solution is to force loader in ActivityA to stop loading just before ActivityB is started.

ActivityA.getSupportLoaderManager().getLoader(LOADER_ID).stopLoading(); // <- THIS IS THE KEY
ActivityA.startActivity(intent)

This stops the loader and (I guess again) prevents cursor to consume notification while activity is in the above described limbo state. The sequence of events now is:

ActivityA is started

    ActivityA.onCreate()
        -> getSupportLoaderManager().initLoader(LOADER_ID, null, this);

    ContentProvider.query(uri)  // query is executes as it should

    ActivityA.onLoadFinished()  // in this event handler we change cursor in list view adapter and listview is populated


ActivityA starts ActivityB

    ActivityA.getSupportLoaderManager().getLoader(LOADER_ID).stopLoading();
    ActivityA.startActivity(intent)

    ActivityB.onCreate()
    -> ContentProvider.insert(uri)
        -> getContext().getContentResolver().notifyChange(uri, null);   // notify observers/*  No ContentProvider.query(uri) is executed, because we have stopped the loader in ActivityA. */

    ActivityA.onStop()
    /*  This event is still executed late. But we have stopped the loader so it didn't consume notification. */


ActivityB finishes and we return to ActivityA

    ActivityA.onResume()

    ContentProvider.query(uri)  // query is executes as it should

    ActivityA.onLoadFinished()  // in this event handler we change cursor in list view adapter and listview is populated/* The listview is now populated with up to date data */

This was the most elegant solution I could find. No need to restart loaders and such. But still I would like to hear a comment on that subject from someone with a deeper insight.

Solution 2:

I don't see anything here particularly wrong with this. As long as the Cursor is registered with the URI, the loader should be restarting itself with new information. I don't think the issue here is anything wrong with your code. I think it's the LoaderManager is unregistering the Cursor from the ContentResolver too early (it actually happens by the time onStop() is called).

Basically there's nothing you can really do about it unregistering. You can however, force restart the loader by calling LoaderManager#restartLoader(int, Bundle, LoaderCallbacks);. You can call this in onStart() (which makes the initLoader call in onCreate() useless). A more optimized approach would be to use onActivityResult(). The result of your activity is irrelevant in this case. All you're saying is that you've returned to this activity from some other activity and the data may or may not be different, so you need to reload.

protectedvoidonActivityResult(int requestCode, int resultCode,
             Intent data) {
   getSupportLoaderManager().restartLoader(LOADER_ID, null, this);
}

Then just call Context#startActivityForResult() when opening new Activities.

Post a Comment for "How To Make Notifychange() Work Between Two Activities?"