I am currently learning Robolectric to test for Android and I am having trouble obtaining my application's menu.
Right now, Robolectric's getOptionsMenu() is returning null. The code itself works fine but the test always returns null for the options menu.
My code is the following
#Test
public void onCreateShouldInflateTheMenu() {
Intent intent = new Intent();
intent.setData(Uri.EMPTY);
DetailActivity dActivity = Robolectric.buildActivity(DetailActivity.class, intent).create().get();
Menu menu = Shadows.shadowOf(dActivity).getOptionsMenu(); // menu is null
MenuItem item = menu.findItem(R.id.action_settings); // I get a nullPointer exception here
assertEquals(menu.findItem(R.id.action_settings).getTitle().toString(), "Settings");
}
Does anyone know why Robolectric is returning null? Did I miss any dependencies?
The onCreateOptionsMenu will be called after oncreate so to make sure that you can see your menu try
Robolectric.buildActivity(DetailActivity.class, intent).create().resume().get();
or you can make sure the activity is visible
Robolectric.buildActivity(DetailActivity.class, intent).create().visible().get();
From docs
What’s This visible() Nonsense?
Turns out that in a real Android app, the view hierarchy of an
Activity is not attached to the Window until sometime after onCreate()
is called. Until this happens, the Activity’s views do not report as
visible. This means you can’t click on them (amongst other unexpected
behavior). The Activity’s hierarchy is attached to the Window on a
device or emulator after onPostResume() on the Activity. Rather than
make assumptions about when the visibility should be updated,
Robolectric puts the power in the developer’s hands when writing
tests.
So when do you call it? Whenever you’re interacting with the views
inside the Activity. Methods like Robolectric.clickOn() require that
the view is visible and properly attached in order to function. You
should call visible() after create().
Android: When is onCreateOptionsMenu called during Activity lifecycle?
Related
I have some flows where a fragment opens another fragment to do staff in a dedicated screen. Every time I open the new fragment Roboletric is not able to "see" the stuff in the new fragment. Sometimes adding inRoot{ isDialog } solves the problem, other times it doesn't.
Any idea on how to work on this? Using espresso + actual device everything works correctly but I would want to move the tests to roboletric to use JVM instead of a real device.
To test fragments that are opened after a button is pressed, you can use the Robolectric framework, which allows you to perform unit testing on Android app code. Here are the general steps you can follow:
Create a test class that extends the RobolectricTestRunner class.
In the test class, create a method that simulates the button press and opens the fragment.
Use the startFragment method provided by Robolectric to start the fragment and add it to the activity.
Use the getSupportFragmentManager method to get the fragment manager and use the findFragmentById method to find the fragment that is currently displayed.
Use assertNotNull to check that the fragment is not null, indicating that it has been successfully opened.
Perform any additional tests on the fragment's views or behavior as needed.
Here is an example of what the test method might look like:
#Test
public void testFragmentOpensAfterButtonPress() {
// Simulate button press
button.performClick();
// Start the fragment
startFragment(new MyFragment());
// Find the fragment
MyFragment fragment = (MyFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_container);
// Assert that the fragment is not null
assertNotNull(fragment);
// Perform additional tests on the fragment's views or behavior
...
}
I have an issue with showing DialogFragment inside Fragment. It does not matter what I try, I cannot get it to work everytime.
My layout consist of activity with navigation drawer and inside activity I am showing fragments. On those fragments I must show DialogFragment but for that, I must get context/application context/activity/whatever (depends on current year and weather) to show it.
In most cases it works ok but sometimes, even if fragment is constantly showed to user (no configuration changes or anything) it sometimes happen that fragment is not attached. I am getting that error in production as well so it is not just my device.
Good practice for that would be to check with method isAdded() and if activity is not attached then do nothing. I am not sure why this is good practice because users would not be happy if they loose their work since "save dialog" will not be shown just because Google is giving good practices as terrible ones!
So far I have this (among million combinations) inside my fragment:
private void showStoreDialog() {
if (someConditionIsOk) {
StringBuilder sb = new StringBuilder();
sb.append(getString(R.string.someText));
sb.append(MINIMUM_LENGTH);
sb.append(getString(R.string.someText2));
Toast.makeText(getActivity(), sb.toString(), Toast.LENGTH_LONG).show();
return;
}
...
Exception occures on this line: sb.append(getString(R.string.someText));
Cause: android.support.v4.app.Fragment.requireContext (Fragment.java:614)
android.support.v4.app.Fragment.getResources (Fragment.java:678)
android.support.v4.app.Fragment.getString (Fragment.java:700)
solutions.lunalabs.gpsracer.fragments.RecordingFragment.showStoreDialoglog (RecordingFragment.java:152)
I know I could solve this by routing through activity with callbacks but I do not want this because I should route all dialogs through activity even if it is needed only in one fragment. Is there a safe solution to this?
Thank you!
I have an application where whenever I exit the application via the home hardware button, it should return to the last state the application is in. However, when I launch the application again, the application shows a white screen with only my header bar. And when I click on the header bar's button, the application crashes with the IllegalStateException where the application cannot find the method for the button clicked.
I am currently implementing with Sherlocks Fragment, where the header bar is an action bar. I'm also using HTC Rhyme, Version 2.3 (Gingerbread). The following is the codes for the addition of fragments into my main app.
Codes to add the fragments within the onCreate method in the activity:
FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
Bundle bMain = getIntent().getExtras();
String statusCheck = "";
if (bMain != null) {
statusCheck = bMain.getString("statusCheck");
}
if (statusCheck.equals("web")) {
MyWebViewFragment webfrag = new MyWebViewFragment();
trans.add(R.id.container,webfrag, "WebViewFragment");
} else if(statusCheck.equals("traveloguelist")) {
MyTravelogueListFragment travelfrag = new MyTravelogueListFragment();
trans.add(R.id.container,travelfrag, "TravelogueListFragment");
}
trans.commit();
This is the codes when I change a fragment:
MyTravelogueListFragment travelfrag = new MyTravelogueListFragment();
getSupportFragmentManager().beginTransaction().replace(R.id.container, travelfrag).addToBackStack(null).commit();
[Edit]
I realized after much reading and running that the main issuei have is that upon resuming the application, the activity is actually created again. Thus, some of the parameters i passed in does not get registered, resulting in the wrong display. I THINK this is the error that is causing that to happen:
Previously focused view reported id "myresID" during save, but can't be found during restore.
However, I don't know how you force the application to remember the previous state of the fragment? Or is there any other way around this problem?
I'm still very stuck with this problem. Will really appreciate it if someone can help me!
After much trial and error and many readings, I finally found a way to sort of solve my problem.
From what I understand, this problem will occur due to the Activity's life cycle. The comment by Tseng in this forum was quite comprehensive:
http://forum.unity3d.com/threads/127794-Android-Apps-crashing-on-resume
It seems that during the time when other applications are invoked when a certain activity is onPause/onStop, Android might free up some of its memory the activity is currently holding on to if there is insufficient memory required. In this case, all the current objects or variable the paused activity is having will be destroyed. Thus, when the activity is back on focus, the onCreate is actually invoked again. Thus, the activity will have no idea which fragment I am currently require.
However, I realized that it will always call the saveInstanceState which is essentially a bundle object. So I did the following:
onSaveInstanceState method
#Override
public void onSaveInstanceState(Bundle bundle) {
//activityFrag is a string object that tells me which fragment i am in currently
bundle.putString("statusCheck", activityFrag);
}
onCreate method
if (savedInstanceState != null) {
getSupportFragmentManager().popBackStack(null, getSupportFragmentManager().POP_BACK_STACK_INCLUSIVE);
//return;
statusCheck = savedInstanceState.getString("statusCheck");
} else {
statusCheck = b.getString("statusCheck");
}
What I have done is to remove all the fragments I have stacked thus far to remove any issues where there is missing information needed. So this is like starting anew again. The status check just determine which fragment the user has last visited.
After much testing, it seems like it does solve my problem. though I wouldn't say it is perfect. One of the main downfall I have is that whenever I change my fragment, I have to update and change my statusCheck to make sure the correct fragment will be called. However, I have to admit this way is a little unorthodox and might not be very correct.
If any of you have any better ideas, please feel free to share!
You can try to implement following:
Use putFragment to save all fragments, currently located in FragmentManager, into bundle in onSaveInstanceState;
And then you can use getFragment to get all previously stored fragments back from bundle in onRestoreInstanceState.
Also... you'll probably need some HashMap that will help to determine the hierarchy of the fragments (in case you have containers and contained fragments) to be saved into bundle as well.
Also... when restoring from bundle you'll need to know keys for all fragment you've put there earlier. Probably, the easiest way is simply to organize an array of keys and put them into bundle when saving the fragment into instance.
This way your saving and restoring will be complete and centralized.
I put a couple of breakpoints in onCreate (one at the beginning, and one at the end of the method), and I also put one at the beginning of onCreateOptionsMenu. The onCreate method is called first, and before it finishes onCreateOptionsMenu is called.
I'm trying to separate the Fragment navigation code in my app, so I have a couple of objects that I delegate onCreateOptionsMenu to depending on if the app is running on phone/tablet (I'm using screen size to determine this, my layout file for large screens has a View I check for after the layout is inflated). The problem I'm having is, I create these objects in onCreate, and I'm getting a null pointer exception when I reference the object in onCreateOptionsMenu.
The onCreate method is called first, and before it finishes onCreateOptionsMenu is called.
That will be true on devices and apps with an official Honeycomb-style action bar. If there is no action bar, onCreateOptionsMenu() should not get called until the user calls up the menu, typically by pressing the MENU button.
(I'm using screen size to determine this, my layout file for large screens has a View I check for after the layout is inflated)
That test will break very shortly, once Ice Cream Sandwich ships. From what I can tell, ICS phones will have action bars (though perhaps not system bars).
In my case on Android 2.3 and with FragmentActivity from v4-support library the order of life-cycle methods invoke is following:
07-18 18:29:21.629 20183-20183/? I/onCreate:
07-18 18:29:21.719 20183-20183/? I/onStart:
07-18 18:29:21.719 20183-20183/? I/onResume:
07-18 18:29:21.739 20183-20183/? I/onCreateOptionsMenu:
I found if in onResume() I call
invalidateOptionsMenu();
then onCreateOptionsMenu(Menu menu) is called afterward - as per the activity life cycle (I think that's the correct term here), as indicated by #tir38
#Override
public void onResume() {
super.onResume();
invalidateOptionsMenu();
}
Addition in above answer,
In case of ICS and Honeycomb onCreateOptionsMenu is called after onCreate and onPostCreate while in Gingerbread and earlier versions it is called after onCreate but before onPostCreate. Thats the only difference I found.
In my experience ActionBarActivity from support v7 onCreateOptionsMenu() called in setContentView() method in the middle of onCreate() it is appear on 4.1.1.
But on 4.4 another story onCreateOptionMenu() called after onCreate(). Also I don't know it may be immediately after, maybe not. But is fact after. I didn't test on other versions but 4.1.1 is first where I had have a trouble with init order.
i suggest to create a callback-function in your fragment to avoid timing issues with onResume() and onCreateOptionsMenu().
doing the following works flawless for me:
create and add your fragment to your activity
leave a reference of this fragment in your activity
create a public method doSomethingWithTheMenu() in your fragment
call this method from within your activity when onCreateOptionsMenu(Menu menu) is called.
example:
#Override
public boolean onCreateOptionsMenu(Menu menu) {
if (this.myFragment != null) {
this.myFragment.doSomethingWithTheMenu(menu);
}
return true;
}
I have an activity that on it's onCreate method it does:
registerForContextMenu(theView);
and in onCreateContextMenu:
super.onCreateContextMenu(menu, v, menuInfo);
menu.add(blablabla);
This works great, but the problem is that the context menu disappears when the screen rotates.
How to fix this?
Thanks for reading!
Here's the solution:
The contextMenu disappeared because by default when rotating android calls destroy() and then onCreate() but :
If you don't want Android to go through the normal activity destroy-and-recreate process; instead, you want to handle recreating the views yourself, you can use the android:configChanges attributes on the element in AndroidManifest.xml.
<activity
android:name=".SmsPopupActivity"
android:theme="#android:style/Theme.Dialog"
android:launchMode="singleTask"
android:configChanges="orientation|keyboardHidden"
android:taskAffinity="net.everythingandroid.smspopup.popup">
</activity>
This way my contextMenu is not closed when my phone rotates, because onCreate() method is not called.
See also:
Developing Orientation-Aware Android Applications
activity-restart-on-rotation-android
According to the Android developers blog:
The Activity class has a special method called onRetainNonConfigurationInstance(). This
method can be used to pass an
arbitrary object your future self and
Android is smart enough to call this
method only when needed. [...]
The implementation can be summarized
like so:
#Override public Object
onRetainNonConfigurationInstance() {
final LoadedPhoto[] list = new LoadedPhoto[numberOfPhotos];
keepPhotos(list);
return list; }
In the new activity, in onCreate(),
all you have to do to get your object
back is to call
getLastNonConfigurationInstance(). In
Photostream, this method is invoked
and if the returned value is not null,
the grid is loaded with the list of
photos from the previous activity:
http://android-developers.blogspot.com/2009/02/faster-screen-orientation-change.html?utm_source=eddie
I may be wrong, but from what I know you cant persist it, however (this is the part where i may be wrong in) you could open the menu dynamically after you rotate. Giving the illusion of persistence.