This issue is giving me nightmares :(
Please consider the following code snippet:
MainActivity.java
public class MainActivity extends BaseActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
}
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
mMenu = menu;
return super.onPrepareOptionsMenu(menu);
}
protected void updateActionBar() {
mMenu.findItem(R.id.action_some).setVisible(state); <---------------- mMenu is null
}
}
BaseActivity.java
public abstract class BaseActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
updateActionBar(); <---------------- This causes crash
}
protected abstract void updateActionBar();
}
Problem
After debugging, I found that dynamic dispatch of updateActionBar() happens before call to onPrepareOptionsMenu(). Hence, Menuinflation is kind of delayed and mMenu is not updated.
Question
How do I make sure that mMenu gets populated before call to updateActionBar()?
Note: For sake of focusing on the core problem and improve readability, I have pasted only portion of code that causes crash.
How do I make sure that mMenu gets populated before call to updateActionBar()?
Call updateActionBar() sometime later, such as in an override of onPrepareOptionsMenu() in BaseActivity.
updateActionBar() is being called inside a listener and moving the whole listener inside onresume doesn't make sense
Where the listener is does not matter. What event you are listening for matters. Apparently, you are using a listener for some event that can occur before onPrepareOptionsMenu(). That will not work. Either:
Choose a different event, or
Do not register the listener until at least onPrepareOptionsMenu() has been called, or
If the event is triggered, and onPrepareOptionsMenu() has not been called, instead of actually doing your work, set a boolean value (e.g., needToDoMenuStuff) to true, and then do that work in onPrepareOptionsMenu()
Something like this might serve you well:
final LinearLayout layout = (LinearLayout) findViewById(R.id.yourLayout); // might not be linearLayout
ViewTreeObserver vto = layout.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// Code to be executed after layout complete. (updateActionBar)
}
});
Related
Have some problem with android finish() methods.
I have one parent-class activity. Lets call it ParentActivity. All other activities in my project extends ParentActivity. Each time on ParentActivity.onCreate there are some statement, and I want to stop activity from executing if it fails. But when I call finish() in parent, I cant stop executing onCreate method on its child. Something like that:
public class ParentActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!someStatement) finish();
}
public class Test extends ParentActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("TAG", "I dont want this code!");
}
}
Surely, I can just verify in parent activity its status each time, but I dont think its a good idea.
public class Test extends RexActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (isFinishing()) return; /// It works - but it bad :(((
Log.d("TAG", "I dont want this code!");
}
}
Can I somehow stop executing onCreate method on child activity from its parent? Many thanks for any help!
I'm not sure if I got your question right. As you have some grammatical issues.
The onCreate statements are always executed. You can either have a Boolean in ParentActivity to stop the code from executing in ChildActivity#onCreate().
You can try making your onCreate() code more modular by dividing it into functions so that it's not called.
Let me know what works for you.
Best option is to Use finish() in your splash screen just before you create your second activity,
I've managed to work around an issue in which an extra icon appears in the action bar after a fragment transaction, but it was a clumsy solution and I wonder if there is a better way to solve it.
I have a fragment class hierarchy in which ContentFragment is an abstract fragment which occupies the whole screen (except for the action bar) and its subclasses contribute with additional action bar icons (by means of their respective onCreateOptionsMenu() and onOptionsItemSelected() methods). E.g. ContentFragmentA contributes with icon A, ContentFragmentB with icon B, ContentFragmentB may or may not be a child class of ContentFragmentA (if it is, then the action bar will contain both icon A and icon B side by side), and so on.
Initially (after the user has just logged in) the screen contains only ContentFragmentA and the action bar has icon A. As the user navigates through the app other content fragments (or more precisely fragment transactions) are added to the back stack and icons are correspondingly added or removed from the action bar.
It all behaves nicely until the user decides to log out which prompts the app to clear the whole back stack (bringing ContentFragmentA back after the oldest transaction is rolled back) and immediately add a LoginContentFragment, which contributes with a New Profile icon to the action bar. However at this moment icon A is also being shown beside the New Profile icon and I don't want it to be shown; that is the issue I'm facing. It should go away when the user logs out.
I solved the issue by clearing the back stack as usual and then including an extra transaction which replaces the ContentFragmentA with a blank, icon-less content fragment with setHasOptionsMenu(false), so icon A will be gone when the blank fragment is replaced with the Login fragment. But I find this clumsy and think there might be a better way.
I have tried out calling Menu.clear() in the ContentFragment superclass and Activity.supportInvalidateOptionsMenu() in the fragment replacement step but neither seems to work. Menu.clear() in particular will just make all the icons go away leaving none in the action bar.
Does anyone know an alternative?
Relevant code:
ContentFragment.java:
public abstract class ContentFragment extends Fragment {
public static interface Callbacks {
public abstract void setCurrentContentFragment(ContentFragment contentFragment);
}
protected Callbacks mCallbacks;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
#Override
public void onStart() {
super.onStart();
mCallbacks = (Callbacks)getActivity();
mCallbacks.setCurrentContentFragment(this);
}
#Override
public void onStop() {
super.onStop();
mCallbacks = null;
}
public boolean handleBackPressed() {
return false;
}
}
ContentFragmentA.java:
public abstract class ContentFragmentA extends ContentFragment {
protected abstract void handleIconATouched();
...
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
if (menu.findItem(R.id.icon_a) == null) {
inflater.inflate(R.menu.icon_a, menu);
}
super.onCreateOptionsMenu(menu, inflater);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.icon_a) {
handleIconATouched();
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
}
BlankFragment.java:
public class BlankFragment extends LoggedInContentFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(false);
}
}
In MainActivity.java:
...
private void addContentFragmentNotAddingTransactionToBackStackIfCurrentFragment(ContentFragment fragment, boolean clearBackStack) {
// mCurrentContentFragment changes as the back stack is cleared, thus addToBackStack is calculated before the clearBackStack() step.
boolean addToBackStack = (false == clearBackStack && (mCurrentContentFragment != null && fragment.getClass() != mCurrentContentFragment.getClass()));
if (clearBackStack) {
clearBackStack();
}
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if (addToBackStack) {
ft.addToBackStack(null);
}
ft.replace(R.id.container, fragment);
ft.commit();
getSupportFragmentManager().executePendingTransactions();
}
public void clearBackStack() {
while (getSupportFragmentManager().getBackStackEntryCount() > 0) {
getSupportFragmentManager().popBackStackImmediate();
}
// This is the step I would like to avoid
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.container, new BlankFragment());
ft.commit();
getSupportFragmentManager().executePendingTransactions();
}
...
I solved the issue. It was a problem with the fragment transaction back stack. I was mixing transactions which were added to the back stack with ones that weren't, and these were causing the back stack popping to work in an unexpected way and prevent fragments from being correctly removed so they were adding extra icons to the action bar. The issue is explained in this SO question. The solution I adopted is this one.
I've written a pretty large custom view which overrides onSaveInstanceState() and onRestoreInstanceState(Parcelable state).
I wanted to populate a LinearLayout with my custom view, so I wrote the following code:
public class MainActivity extends Activity {
private LinearLayout mRootLayout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRootLayout = (LinearLayout) findViewById(R.id.root_layout);
int i;
// Test: adding 10 instances of MyCustomView.
for (i = 0; i < 10; ++i) {
MyCustomView cv = new MyCustomView(this);
// I set an ID for this view so that onSaveInstanceState() and
// onRestoreInstanceState(Parcelable state) will be called
// automatically.
cv.setId(++i);
mRootLayout.addView(cv);
}
}
// ...
}
It works fine - mRootLayout is indeed being populated with 10 instances of MyCustomView, and each instance of MyCustomView is being properly restored after, for example, screen rotation.
I've noticed that due to the fact that MyCustomView is pretty large, my code is being heavy on the UI thread.
To solve the issue and take some effort off of the UI thread, I decided to use a custom AsyncTask, which will create an instance of MyCustomView in doInBackground() and add it to the the main layout ( mRootLayout ) in onPostExecute().
The following code is my custom AsyncTask:
private class LoadMyCustomViewTask extends AsyncTask<Void, Void, MyCustomView> {
private Context mContext;
private LinearLayout mLayoutToPopulate;
private int mId;
public LoadMyCustomViewTask(Context context, LinearLayout layout, int id) {
mContext = context;
mLayoutToPopulate = layout;
mId = id;
}
#Override
protected MyCustomView doInBackground(Void... params) {
MyCustomView cv = new MyCustomView(mContext);
return cv;
}
#Override
protected void onPostExecute(MyCustomView result) {
result.setId(mId);
mLayoutToPopulate.addView(result);
}
}
In MainActivity I use it as follows:
public class MainActivity extends Activity {
private LinearLayout mRootLayout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRootLayout = (LinearLayout) findViewById(R.id.root_layout);
int i;
for (i = 0; i < 10; ++i) {
new LoadMyCustomViewTask(this, mRootLayout, ++i).execute();
}
}
// ...
}
This code works too, but there is only one problem - MyCustomView is not being restored at all.
For debug purposes I put a Log.d(...) in MyCustomView's onSaveInstanceState() and in onRestoreInstanceState(Parcelable state), and I've noticed that onRestoreInstanceState(Parcelable state) isn't being called.
Do you have any idea why onRestoreInstanceState(Parcelable state) isn't being called when I use an AsyncTask to populate mRootLayout, but it is indeed being called when I create MyCustomView completely on the UI thread?
Thank you.
Edit: I'm posting the methods onSaveInstanceState() and onRestoreInstanceState() of MyCustomView
#Override
protected Parcelable onSaveInstanceState() {
debug("onSaveInstanceState()");
Bundle state = new Bundle();
state.putParcelable(_BUNDLE_KEY_PARENT_STATE, super.onSaveInstanceState());
state.putBooleanArray(_BUNDLE_KEY_CLICKED_VIEWS, mClickedViews);
return state;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
debug("onRestoreInstanceState(Parcelable state)");
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
mClickedViews = bundle.getBooleanArray(_BUNDLE_KEY_CLICKED_VIEWS);
state = bundle.getParcelable(_BUNDLE_KEY_PARENT_STATE);
}
super.onRestoreInstanceState(state);
}
View state restoration begins at the root view and moves down to all of the child views attached at that time. This can be seen in the ViewGroup.dispatchRestoreInstanceState method. This means that Android can only restore your views if they are part of the view hierarchy at the time Activity.onRestoreInstanceState is called.
Using the AsyncTask, you are creating your views asynchronously and then scheduling them to be added some time later when the main looper is idle. Considering the lifecycle, Android only lets your AsyncTask.onPostExecute run after Activity.onStart, Activity.onRestoreInstanceState, Activity.onResume, etc. are called. Your views are being added to the layout too late for automatic restoration to take place.
If you add log statements to those methods mentioned above, as well as to your AsyncTask.onPostExecute, you will be able to see how the ordering/timing plays out in reality. The following code runs after Activity.onRestoreInstanceState even though it all happens on the main thread, simply because of the scheduling:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
new Handler().post(new Runnable() {
#Override
public void run() {
Log.i("TAG", "when does this run?");
}
});
...
}
Smells like a false observation... creating a view on a background thread should not affect your activity lifecycle.
That said, doing anything at all with View objects on background threads is a no-no and I'm surprised you got this far with such an approach. All View code should be quick and avoid blocking. If you have long-running work to do then separate that work into the background thread, post the results of that complex computation to the main thread, and keep all the actual View/presentation stuff on the main thread where it belongs.
I remember having read about how onSaveInstanceState()/onRestoreInstanceState() work a time ago and it was not a "fixed-situation-rules" thing. As I can't find the references to it at this moment, I'll try to explain it with my own words:
Basically, a factor on which depends the calling of those methods is the resources left that the device has. Both methods will get in action when a second Activity gets focused and the first one gets killed due to lack of resources. Concretely, onRestoreInstanceState should be triggered when that Activity was killed and restarted, so it gets the previous state.
Although you've not posted your MyCustomView implementation, my guess is that when you do that entirely on the main UI Thread, you're involving some action that makes the MainActivity lose its focus and once the MyCustomView is created, it needs to restore its state. Doing this in a separate thread (as AsyncTask does) makes creating those Views in paralell, so your main Activity doesn't lose its focus and thus it doesn't get killed, so those methods are not called.
Concluding this, don't worry if those methods are not always called as they don't have to be called everytime, just when needed, and that doesn't mean there's something going wrong.
I recommend you to connect SDK sources to your IDE and walk there with debugger through the whole process related with the View class onRestoreInstanceState(). Since I don't have your code, looking at sources I can only guess what might have gone wrong, but from what I see, that might be related to problem:
1) try to set an Id to every view you generate
2) try to use Fragment as a host to your views (instead of MainActivity).
Right now I have my ApplicationActivity, this activity is responsible for managing multiple views (GLSurfaceViews). Can / Should I have all the views set the renderer to a "global" renderer?
Code:
public class ApplicationActivity extends Activity
{
private static final String TAG = ApplicationActivity.class.getSimpleName();
private final Stack<Screen> screens = new Stack<Screen>();
private GlRenderer glRenderer;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Log.d(TAG, "Main Activity Created");
setupGraphics();
ChangeScreen(new MainMenu(this, glRenderer)); //Creating a new Screen sets the renderer
}
private void setupGraphics()
{
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
glRenderer = new GlRenderer(this);
}
public void Draw() //Is called by the glRenderer onDrawFrame() { mainActivity.Draw() }
{
}
}
Its the same activity switching between GLSurfaceViews and by my knowledge I believe that the method setRenderer sets the view renderer and then starts the rendering thread (creating a new thread) but I don't want to recreate the thread every time I switch between views - may create potential problems.
So in the end I want a Renderer class just to keep graphics sepreate from business logic and such but, I don't know if using one Renderer is even possible, without setting the thread again?
You can only use Multiple Views with the same Renderer only if you properly switch out between them with GLSurfaceView.onPause() / .onResume();
My specific case:
#Override
protected void onPause() //Overrides onPause from Activity
{
surfaceViews.peek().onPause();
super.onPause();
}
So everytime the activity pauses I would have to pause the current View. And if the Activity resumes then resume the View also.
I also have a method called SetView which will either (pause and remove then change to another View) or (pause and then change to another View) this is accomplished using a Stack
public void SetView(View screen)
{
if (!screens.empty())
{
screens.peek().onPause();
screens.pop();
}
screens.push(screen);
setContentView(screens.peek());
}
Of course though because we are using Views instead of Activities now we must Override the onBackPressed() to go back to previous Views.
#Override
public void onBackPressed()
{
if (screens.size() == 1)
super.onBackPressed();
else
{
screens.pop();
setContentView(screens.peek());
screens.peek().onResume();
}
}
By doing new GLRenderer() you create new instance of your class. So there is no problem to have the same renderer used in different activities.
EDIT: I seem to misunderstand your question - if you want many GL surfaces visible at once, then no, it is not possible. But it got nothing to do with reusing renderer code.
This is really weird all other listeners work like onClick etc.. but this listener doesnt seem to be working, heres my code:
public class HeloActivity extends Activity implements OnGenericMotionListener{
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
View root = findViewById(R.id.root );
root.setOnGenericMotionListener(this);
}
#Override
public boolean onGenericMotion(View v, MotionEvent event) {
// TODO Auto-generated method stub
Log.d( "special",v.toString() );
return false;
}
}
why is this not working?
This is an old question, but I'm currently looking at the same thing so I'll add this for future reference.
It might be because Activity implements an onGenerericMotionEvent method which receives motion events, but you're also implementing the method from View - onGenericMotion in the same class.
The Android docs say to implement one or the other.
I've not tried how you're doing it, but I know it works by using a different class that implements OnGenericMotionListener and using setOnGenericMotionListener on the root view.
You do realize that you both implements the listener and then attach it to the view? At the very least it is redundant....
Thank you.