startActivityForResult not working properly with launchMode singleInstance
Asked Answered
R

2

18

I'd like Activities on my application's Activity stack to only have one instance. I have several screens which are ListActivities and I'd like to not go through the pain and suffering of updating the lists in a previous instance of the ListActivity when another instance of that ListActivity is changed (added to, edited, removed from, etc) (or is there an easy way to do this?).

Note: I've read that singleTop will accomplish this (though it destroys the Activity if you hit the back button), but it does not work. I have a menu and if I go to my Inbox screen, then I go to my QuickList screen, and then I go to my Inbox screen again, it creates a new Inbox Activity.

Right now, on my ListActivities, I have launchMode set to singleInstance. The problem is: If I launch another Activity using startActivityForResult, the onActivityResult handler fires right away (before the new Activity is created). When I perform the necessary action on the next screen to return the result, the onActivityResult handler does not fire.

What is going on?

Here is how I fire the new Activity:

Intent intentLaunchQuickList = new Intent(ActivityMyList.this, ActivityQuickList.class);
startActivityForResult(intentLaunchQuickList, REQUEST_QUICKLIST);

Here is how I return the result:

@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
    super.onListItemClick(l, v, position, id);
    QuickListItem qlItem = m_Adapter.getItem(position);
    if (qlItem != null && qlItem.getQLId() != -1) {
        Intent data = new Intent();
        data.putExtra("ql_id", qlItem.getQLId());
        if (getParent() == null) {
            setResult(Activity.RESULT_OK, data);
        }
        else {
            getParent().setResult(Activity.RESULT_OK, data);
        }
    }
    finish();
}

Here is my onActivityResult handler:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_QUICKLIST) {
        if (resultCode == Activity.RESULT_OK) {
            Bundle extras = data.getExtras();
            if (extras != null) {
                int id = extras.getInt("ql_id");
                if (id > 0) {
                    launchQLItemsThread(id);
                }
            }
        }
    }
}
Roe answered 29/7, 2010 at 21:11 Comment(0)
L
23

From the documentation of startActivityForResult: "For example, if the activity you are launching uses the singleTask launch mode, it will not run in your task and thus you will immediately receive a cancel result." singleInstance activities are the same way.

In other words, if you want to use sAFR, you will need to handle multiple activity instances. What I would advise is storing the list state for your ListActivity instances in onPause to some app-global spot (a singleton or whatever), and loading from there in onResume. Then, even if multiple ListActivity instances will get created, the top one will always update the data before the older ones get resumed, and the lists will always appear current to the user.

Note that you should be doing that anyway if your data is meant to be persistent, because your whole process can be killed by the system any time after an onPause call, and if you haven't saved any changes somewhere by the time that returns, they are liable to get silently lost under some -- often rare and unpredictable -- circumstances. In this case you want to be using local files or SQLite databases, not persisting to the network. onPause needs to return quickly because the user can't interact with the system while it's running, so save to local storage and then sync to the network at some other time, perhaps via a service launched by onPause.

Lazybones answered 29/7, 2010 at 22:3 Comment(6)
Thank you for your response. I understand what you want me to do, however, won't it cause problems? I'll obviously need to do the same thing in the onSaveInstanceState and onRestoreInstanceState handlers. If on onResume and onPause I read and save info from the database, my application will unnecessarily read from the database and read from the SavedInstanceState should it ever hit the onRestoreInstanceState handler, yes? Also, is this really what is common practice for Android devs? Saving ListActivity data to a singleton for multiple instances of the ListActivity? Again, thanks for helpingRoe
Also, could you explain why singleTop is not working for me? Or do I not understand it?Roe
SavedInstanceState is only useful for restoring the state of a particular instance of an activity; use it to save scroll position, etc, not list contents. What you want to do is have all instances of your activity share some state, so you want to put that state in a database if it's persistent or in a static member if it's just a cache of data from the network. I suggest the latter based on what you've said; just use a DB for holding data you still need to send out over the network in the future so you don't lose it.Lazybones
I think I'll be throwing everything in a database. I'm going to need to cache all this data so it can be made available offline. I do use a singleton at the moment for some things. For example, after the user logs in, I set up a singleton with username, password, user id. But the data for a lot of my screens will need to be available offline. From experience, do you recommend me making my Activities functional before worrying about persisting data (and syncing multiple Activity instances)? Or should I go ahead and build the database as I build the rest of the application's screens. ThanksRoe
I would suggest defining your data model as Java classes first, closely building the activity UI implementation on top of those to drive the design. The actual implementation behind the data model classes can be simple (not persistent and stuff) and fleshed out later. The important first step is to get the data model in place to correctly drive the UI. What I find often works the best is to have a static singleton instance of the data model class, which each activity retrieves in onCreate(). Then it is trivial to let that data propagate across all different activity instances.Shadwell
You may also find that you data model needs to become more active -- having its own thread doing work like pulling down network data, performing callbacks to the activities when data has changed for them to update their UI. I often take care of this by having resume() and pause() methods on the data model which the activities call in onResume() and onPause(). There will only be one client activity at a time, and it can provide a callback interface to resume() and at that time refresh its UI to match the current data.Shadwell
N
2

I have several screens which are ListActivities and I'd like to not go through the pain and suffering of updating the lists in a previous instance of the ListActivity when another instance of that ListActivity is changed (or is there an easy way to do this?).

Use a consistent model. For example, your data is hopefully in a database. Each ListActivity has a Cursor on the portion of the database it needs. Have that Cursor be a "managed Cursor" (via startManagingCursor()), and your ListViews will update automatically in onResume(). You then make your changes to your model via the database.

I have a menu and if I go to my Inbox screen, then I go to my QuickList screen, and then I go to my Inbox screen again, it creates a new Inbox Activity.

That's what it is supposed to do. Quoting the documentation:

The "standard" and "singleTop" modes differ from each other in just one respect: Every time there's new intent for a "standard" activity, a new instance of the class is created to respond to that intent. Each instance handles a single intent. Similarly, a new instance of a "singleTop" activity may also be created to handle a new intent. However, if the target task already has an existing instance of the activity at the top of its stack, that instance will receive the new intent (in an onNewIntent() call); a new instance is not created. In other circumstances — for example, if an existing instance of the "singleTop" activity is in the target task, but not at the top of the stack, or if it's at the top of a stack, but not in the target task — a new instance would be created and pushed on the stack.

(boldface added for emphasis)

Right now, on my ListActivities, I have launchMode set to singleInstance.

Please do not do this.

Nildanile answered 29/7, 2010 at 23:7 Comment(2)
Thank you for your response. Could you please provide me with links to some helpful information on managed cursors and databases? I've yet to implement a database in an Android application.Roe
At the moment (I began this application last week and it is my first application) I am just pulling data from my web services and populating the list. Obviously creating a new instance of the Activity is then going to need to pull data from the web service again. This is why I began using singleInstance. I understand the logic behind what you're saying. I've just yet to implement anything like that; so some resources would be helpful. Thanks againRoe

© 2022 - 2024 — McMap. All rights reserved.