Loader calls onLoadFinished before Activity's onCreateOptionsMenu - android

When an AsynctaskLoader is initiated from Activity's OnCreate(), it finishes its work and calls onLoaderFinished() before action bar menu is inflated, that is before onCreateOptionsMenu() is called.
I need to alter some action menu items depending on results of loader. What approach might resolve this ?
UPDATE:
This happens On orientation change. I'm Observing this sequence in debugger:
Application Start: onCreate() -> onCreateOptionsMenu() -> onLoadFinished()
Rotate to landscape: onCreate() -> onLoadFinished() -> onCreateOptionsMenu()
Rotate back to portrait: onCreate() -> onLoadFinished() -> onCreateOptionsMenu()

I ran into the same scenario when trying to call shareActionProvider.setShareIntent() in onLoadFinished() which was dependent on shareActionProvider being initalized in onCreateOptionsMenu().
My solution was to scope both my Intent and ShareActionProvider to the Fragment, and then within each method initialize the appropriate field and set the other if it had already been defined. That way it works no matter which is called first without invalidating the menu.
Here's basically what it looks like
public class myFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {
...
private ShareActionProvider shareActionProvider;
private Intent shareIntent;
...
onCreateOptionsMenu()
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.menu_my_fragment, menu);
MenuItem menuItem = menu.findItem(R.id.action_share);
shareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(menuItem);
//in the case where onLoadFinished is called first, simply set the share intent here.
if (null != shareIntent)
{
shareActionProvider.setShareIntent(shareIntent);
}
}
onLoadFinished()
#Override
public void onLoadFinished(android.support.v4.content.Loader<Cursor> loader, Cursor data) {
... <get some data from cursor> ...
shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_TEXT, getString(R.string.share_text)+data);
//shareActionProvider may be null due to onLoadFinished called before onCreateOptionsMenu
if (null != shareActionProvider)
{
shareActionProvider.setShareIntent(shareIntent);
}

I need to alter some action menu items depending on results of loader. What approach might resolve this ?
Your scenarios #2 and #3 should be perfectly fine for you, then. By the time onCreateOptionsMenu() is called, you have your Cursor (or whatever) from your Loader and can tailor what you do in onCreateOptionsMenu().
With regards to scenario #1, have onCreateOptionsMenu() do the most likely thing, and if you determine in onLoadFinished() that this is incorrect, call invalidateOptionsMenu() to force another call to onCreateOptionsMenu(), I suppose.

This particular approach avoids invalidating entire menu.
Define one boolean flag, and one MenuItem variable for each item you want to control based on outcomes of loader results:
Let's say you want to enable New action only if loader returns any success:
private boolean mEnableNew = false;
private MenuItem mItemNew;
Save the menu item to variable when menu is created, also, apply state:
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.manage_menu, menu);
mItemNew = menu.findItem(R.id.itm__manage_menu__new);
mItemNew.setEnabled(mEnableNew);
return super.onCreateOptionsMenu(menu);
}
Finally, obtain new settings based on Loader's results, and enable disable menu item:
#Override
public void onLoadFinished(Loader<BI_Result> loader, Result result) {
//--If onCreateOptionsMenu is not yet executed,it will use this boolean value when it does
mEnableNew = result.isSuccess();
//--If onCreateOptionsMenu has executed, we apply state here
if (mItemNew != null) {
mItemNew.setEnabled(mEnableNew);
}
}

Related

How do I call onCreateMenuOptions just once when the fragment is being created

My activity contains many fragments and it has been found that every time a bottom sheet fragment opens up in the activity, the onCreateOptionsMenu function gets called again. How do I ensure that the function gets called only during the start of the Activity
I tried using add menu provider, setHasMenuOptions and invalidate function.
One way to avoid this is to check the menu has already been created or not, and only call onCreateOptionsMenu when the menu hasn't been created yet. You can use a boolean variable for this, like below code:
private boolean mOptionsMenuCreated = false;
#Override
public boolean onCreateOptionsMenu(Menu menu) {
if (!mOptionsMenuCreated) {
getMenuInflater().inflate(R.menu.your_menu, menu);
mOptionsMenuCreated = true;
}
return true;
}
You can also call invalidateOptionsMenu in your fragment's onPause method to invalidate the menu, so that onCreateOptionsMenu gets called again the next time the menu is opened.
#Override
public void onPause() {
super.onPause();
getActivity().invalidateOptionsMenu();
}

Android - Put interface in Bundle

I'm working with ActionBarSherlock, I need to change some icons on the action bar when the user does some stuff. In order to that I have to save the action bar for later usage :
private Menu actionBarMenu;
...
#Override
public boolean onCreateOptionsMenu(Menu menu)
{
MenuInflater inflater = getSupportMenuInflater();
inflater.inflate(R.menu.map_activity_menu, menu);
actionBarMenu = menu;
return super.onCreateOptionsMenu(menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item)
{
actionBarMenu.findItem(R.id.follow_my_position).setIcon(R.drawable.toolbar_myposition_active);
}
Okay ! Here's where the problems begin. When I rotate the screen, I get a NullPointerException on actionBarMenu.
I know... I didn't save it before the screen was rotated, so normally I would go to onSaveInstanceState and save my Menu there, except the actionBarMenu is an interface... More specifically, the interface com.actionbarsherlock.view.Menu and I can't modify it so that it implements Parcelable or Serializable.
So how can I save an interface in the bundle ?
You should not keep reference to your action bar during configuration changes. After screen rotate, Android will recreate your activity and all UI references should be released, otherwise you will introduce reference leak.
Why dont you get your actionBarMenu reference again inside boolean onCreateOptionsMenu, after activity rotates?
You can't. However, you can still retain your member variable on an orientation change by adding the following code to your activity
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Object last = getLastNonConfigurationInstance();
if (last != null && last instanceof Menu) {
actionBarMenu = (Menu) last;
}
}
// for FragmentActivity it is onRetainCustomNonConfigurationInstance
public Object onRetainNonConfigurationInstance() {
return actionBarMenu;
};
You don't. You don't save the interface.
You save some String, or boolean, or integer, representing the action the user did.
Then you check this value and change the menu again.
Does this work before the rotate? Because a rotation just recreates the entire activity - i.e. it goes through the whole creation process again. So, I'm not sure that the recreation is your problem, because onCreateOptionsMenu(Menu menu); should be called again, anyway.
Are you sure that your actionBarMenu.findItem(R.id.follow_my_position) is returning correctly? Instead of handling it how your currently are - why not just...not store the menu and check the clicked menu item instead?
For example:
#Override
public boolean onOptionsItemSelected(MenuItem item)
{
switch(item.getItemId()){
case R.id.follow_my_position:
item.setIcon(R.drawable.toolbar_myposition_active);
}
}
or if that's not what you're looking for, check that your findItem() call finds the item:
MenuItem item = actionBarMenu.findItem(R.id.follow_my_position);
if(item != null){
item.setIcon(R.drawable.toolbar_myposition_active);
}

Interaction between Activity and Fragments

In my application the main activity hosts two fragments and attached to acticity as ActionBarTabs. using the following code. NOTE: activity and the 2 fragments are defined in seperate xml layout files (See the picture at the bottom)
private void createActionTabs() {
ActionBar actionBar = getActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
//Status Tab
String strStatus = getResources().getString(R.string.lblStatus);
ActionBar.Tab statusTab = actionBar.newTab();
statusTab.setText(strStatus);
TabListener<SFrag> sFTabListener = new TabListener<SFrag>(this, strStatus, SFrag.class);
statusTab.setTabListener(sFTabListener);
actionBar.addTab(statusTab);
//Controller Tab attached the same way
.....
}
The ActionBar Items (start and refresh) are added using
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main_menu, menu);
return true;
}
Now coming to my problem:
I want to update the data shown on the Status Fragment at application load, fragment resume and click of refresh menu item from the action bar. Now when I try to access the Status fragment from Main Activity using the following code
SFrag frag = (SFrag) getFragmentManager().findFragmentById(R.id.st_frag);
Log.d(TAG, "In Activity SFrag is " + (frag == null ? "null" : "not null"));
if (frag != null) {
//calls the method to update data
fragment.updateStatusData(statusInformation);
}
The getFragmentManager().findFragmentById methods always returns null. I even tried the onResume method of the activity, the fragment objects is still returned as null. So how do I access the fragment and thus accessa method of that fragment from host acticty.
Secondly, I am trying to use the action_service (its shown as Start button just for this mockup) in action bar as a toggle for satrting or stopping a background service. I can easily update the title/icon for start menu item from onOptionsItemSelected method (I save the current status running/stoppede in shared-preferences). but when I try to accees the menuItem at onStart/onResume of the activity by using
MenuItem mi = (MenuItem) findViewById (R.id.action_service);
it always returns null. So How Can I access action_service menu Item in onResume/onStart to update it.
My Application looks like this
First of all if you want to declare MenuItem, you should do it in this way :
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getSupportMenuInflater().inflate(R.menu.main_activity, menu);
MenuItem mRefresh = menu.findItem(R.id.refresh_menu_item);
return true;
}
About updating your Fragment on applications load or Activity's start, just set selected the tab which holds your Fragment and put the code which will load the data in your Fragment on it's onStart() or onActivityCreated() method. You can override onOptionsItemSelected() in your Fragment, so you can update your views not from your Activity which holds your Fragment, but from it's own class.
Edit: Here is an example how you should handle menu from your Fragment :
public class MyFragment extends Fragment {
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(true); // Do not forget this!!!
}
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.first_fragment, menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
switch (item.getItemId()) {
case R.id.action_refresh:
// Do You stuff here
break;
}
return super.onOptionsItemSelected(item);
}
}
Using this you can add and use MenuItem's in your Fragment.
Hope this helps!
I can answer the MenuItem part just not the other part.
for the menu item as per the docs You can safely hold on to menu (and any items created from it), making modifications to it as desired, until the next time onCreateOptionsMenu() is called.
for example
#Override
public boolean onCreateOptionsMenu(Menu menu) {
actionMenu = menu;
return true;
}
so basically anytime you want to change one of the items you can do this
MenuItem item = actionMenu.getItem(0);

ActionBarSherlock - Indeterminate Progress

It's clear that for showing/hiding the indeterminate progress you have to use this method:
itemMenu.setActionView(...);
So:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_refresh:
item.setActionView(R.layout.indeterminate_progress_action);
runAsyncTask();
...
}
}
What is not clear to me is: how to set the action view back to null. I don't want to keep a reference to a MenuItem, I don't think it is correct since I don't want to assume anything about the OptionsMenu lifecycle.
How should I set back the action view to null on onPostExecute?
I do it like this :
refresh = false; // the future state of your indeterminate progress
invalidateOptionsMenu(); // trigger the recreation of the menu and call onCreateOptionsMenu(Menu)
then in your onCreateOptionsMenu(Menu) method :
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
{
super.onCreateOptionsMenu(menu, inflater);
MenuItem refreshItem = menu.add(Menu.NONE, R.id.action_bar_refresh, 1, "Refresh");
refreshItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
if(refresh)
refreshItem.setActionView(R.layout.indeterminate_progress_action);
else
refreshItem.setIcon(R.drawable.ic_menu_refresh);
}
How about invalidateOptionsMenu()? Your onCreateOptionsMenu() would probably always set it to null, then.
I believe you could just call setActionView(null) on that MenuItem
Then refresh the ActionBar with invalidateOptionsMenu()
There is no need to manipulate specific menu items if you would prefer not to.
I use a different approach that leverages off the system's indeterminate progress bar functionality (as ported to ActionBarSherlock). I explain it here to offer more options to future readers to use whichever way works best for them.
My base fragment has a method I call to turn my loading UI on & off. Here is a trimmed down version:
private void setLoading(final boolean isLoading)
{
final SherlockFragmentActivity sherlockActivity = getSherlockActivity();
sherlockActivity.runOnUiThread(new Runnable()
{
public void run()
{
// show loading progress bar
sherlockActivity
.setSupportProgressBarIndeterminateVisibility(isLoading);
// hide the rest of the menu
setMenuVisibility(!isLoading);
}
});
}
For this to work, your activity must be configured to use the correct style - call this from your SherlockFragmentActivity.onCreate() method:
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
The final trick is that on pre-HoneyComb devices, this causes the progress bar to show immediately by default (instead of being hidden by default from HC & above).
you must set it to be invisible
you must also create a Sherlock Action Bar instance before this code will work
The onCreate() thus becomes:
protected void onCreate(Bundle arg0)
{
super.onCreate(arg0);
// allow window to show progress spinner in the action bar
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
getSupportActionBar();
setSupportProgressBarIndeterminateVisibility(false);
}
For more details on this you can check my answer to
Intermediate Progress doesn't work with ActionBarSherlock running on Gingerbread (here).
If you don't want to keep a reference to the menu item, then you can simply keep a reference to the Menu object instead. A reference to the Menu is guaranteed to remain valid until the next time onCreateOptionsMenu() is called as mentioned in the docs (http://developer.android.com/reference/android/app/Activity.html#onCreateOptionsMenu(android.view.Menu)).
Then call:
MenuItem menuItem = mMenu.findItem(R.id.favourite_payment);
menuItem.setActionView(null);
Or alternatively if using AppCompat:
MenuItem menuItem = mMenu.findItem(R.id.favourite_payment);
MenuItemCompat.setActionView(menuItem, null);
There is no need to call invalidateOptionsMenu().

How can I get the options menu of my Activity?

In some methods of my Activity I want to check the title of menu or know if it is checked or not. How can I get Activity's menu. I need something like this.getMenu()
Be wary of invalidateOptionsMenu(). It recreates the entire menu. This has a lot of overhead and will reset embedded components like the SearchView. It took me quite a while to track down why my SearchView would "randomly" close.
I ended up capturing the menu as posted by Dark and then call onPrepareOptionsMenu(Menu) as necessary. This met my requirement without an nasty side effects. Gotcha: Make sure to do a null check in case you call onPrepareOptionsMenu() before the menu is created. I did this as below:
private Menu mOptionsMenu;
#Override
public boolean onCreateOptionsMenu(final Menu menu) {
mOptionsMenu = menu
...
}
private void updateOptionsMenu() {
if (mOptionsMenu != null) {
onPrepareOptionsMenu(mOptionsMenu);
}
}
Call invalidateOptionsMenu() instead of passing menu object around.
you could do it by passing the Menu object to your Activity class
public class MainActivity extends Activity
{
...
...
private Menu _menu = null;
#Override
public boolean onCreateOptionsMenu(Menu menu)
{
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.activity_main, menu);
_menu = menu;
return true;
}
private Menu getMenu()
{
//use it like this
return _menu;
}
}
There several callback methods that provide menu as a parameter.
You might wanna manipulate it there.
For example:
onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo)
onCreateOptionsMenu(Menu menu)
onCreatePanelMenu(int featureId, Menu menu)
There several more, best you take a look in activity documentation and look for your desired method:
http://developer.android.com/reference/android/app/Activity.html
As far as I could understand what you want here is which may help you:
1. Refer this tutorial over option menu.
2. Every time user presses menu button you can check it's title thru getTitle().
3. Or if you want to know the last menu item checked or selected when user has not pressed the menu button then you need to store the preferences when user presses.
Android now has the Toolbar widget which was a Menu you can set/get. Set the Toolbar in your Activity with some variation of setSupportActionBar(Toolbar) for stuff like onCreateOptionsMenu from a Fragment for example. Thread revived!

Categories

Resources