I'm currently writing some Android unit tests, and while I've gotten most things to work the way I want, one thing has left me kind of stumped.
I have the following code in my activity under test:
Intent result = new Intent();
result.putExtra("test", testinput.getText().toString());
setResult(Activity.RESULT_OK, result);
finish();
I'm trying to figure out how to use Instrumentation (or whatever) to be able to read the result of the activity, or get at the intent after the activity is finished.
Can anyone help?
You can use reflection and grab the values directly from the Activity.
protected Intent assertFinishCalledWithResult(int resultCode) {
assertThat(isFinishCalled(), is(true));
try {
Field f = Activity.class.getDeclaredField("mResultCode");
f.setAccessible(true);
int actualResultCode = (Integer)f.get(getActivity());
assertThat(actualResultCode, is(resultCode));
f = Activity.class.getDeclaredField("mResultData");
f.setAccessible(true);
return (Intent)f.get(getActivity());
} catch (NoSuchFieldException e) {
throw new RuntimeException("Looks like the Android Activity class has changed it's private fields for mResultCode or mResultData. Time to update the reflection code.", e);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Or you could also use Robolectric and shadow the Activity under test. Then, ShadowActivity provides you with methods to easily know if an Activity is finishing and for retrieving its result code.
As an example, one of my tests looks like this:
#Test
public void testPressingFinishButtonFinishesActivity() {
mActivity.onCreate(null);
ShadowActivity shadowActivity = Robolectric.shadowOf(mActivity);
Button finishButton = (Button) mActivity.findViewById(R.id.finish_button);
finishButton.performClick();
assertEquals(DummyActivity.RESULT_CUSTOM, shadowActivity.getResultCode());
assertTrue(shadowActivity.isFinishing());
}
You can do this by writing a special activity whose only purpose is to start the activity you are testing for result and save the result for you to assert correctness on.
For example, you could create an activity named ResultReceiverActivity. Give it three methods: getResultCode, getResultData, and getReceivedRequestCode, which can be used to verify that the tested activity returned the right values. You would create a test case that extends ActivityInstrumentationTestCase2 and the generic parameter would be ResultReceiverActivity. Calling getActivity will get you the activity instance.
public class ReturnedResultTest
extends ActivityInstrumentationTestCase2<ResultReceiverActivity> {
public void testReturnedResult() {
ResultReceiverActivity a = getActivity();
assertEquals(Activity.RESULT_OK, a.getResultCode());
assertEquals("myResult", a.getResultData().getStringExtra("test"));
assertEquals(0x9999, a.getReceivedRequestCode());
}
}
ResultReceiverActivity needs to override onActivityResult, of course, and should just store the values of that methods parameter in its fields, like so:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
this.receivedRequestCode = requestCode;
this.resultCode = resultCode;
this.resultData = data;
}
Of course, you may want to customize the activity that ResultReceiverActivity starts, and you can easily do that by using getIntent in its onCreate method. In your test case, call setActivityIntent before calling getActivity to set which Intent is used to start the activity.
I'm not sure if it is different for unit tests, but you should be able to use onActivityResult as seen here: StartingActivities. You just start the Activity with startActivityForResult(intent, requestCode) and then use
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
back in the activity that used startActivityForResult.
Related
I have a IntentService that queries the MediaStore to get songs, albums, artist, whatnot. that way if someone tries to add an entire artist and there are a lot of albums the IntentService can do the expensive operations of getting all the albums from an artist, then looping through them to get all the songs from each album and it won't take up the ui so the app can still function. My problem/question is, is there a way to send results multiple times from the same service call?
Example: Someone clicks on an artist. That starts the IntentService with the artist_id and the IntentService starts doing its work. What I would like to have happen is, the IntentService gets the first album, then the first song and sends that result back, but it also continues to process the rest of the albums and songs, then when that is done it sends them all back. I don't know if that makes sense or not, so here is an example of my current setup...
BaseActivity...
public GetSongsReceiver receiver;
public GetSongsReceiver.Receiver returnReceiver = new GetSongsReceiver.Receiver() {
#Override
public void onReceiveResult(int resultCode, Bundle resultData) {
if(resultCode == RESULT_OK) {
ArrayList<Song> songs = resultData.getParcelableArrayList("songs");
if(songs != null && songs.size() > 0) {
if(musicBound) {
musicService.addAllSongs(songs);
musicService.playSong();
}
}
}
};
...
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
//processing and such.
if(musicBound) {
musicService.setPlayType(MusicService.PlayType.ARTIST);
Intent i = new Intent(BaseActivity.this, GetPlaylistItemsService.class);
Bundle extras = new Bundle();
extras.putParcelable("key", data.getParcelableExtra("artist"));
extras.putString("service_type", "artist");
extras.putParcelable("receiver", returnReceiver);
i.putExtras(extras);
startService(i);
}
}
GetPlaylistItemsService...
#Override
protected void onHandleIntent(Intent intent) {
ResultReceiver rec = intent.getParcelableExtra("receiver");
Artist artist = intent.getParcelableExtra("key");
ArrayList<Album> albums = getAlbumsFromArtist(artist);
ArrayList<Song> songs = new ArrayList<>();
for(Album album : albums) {
songs.addAll(getSongsFromAlbum(album);
}
Bundle extras = new Bundle();
extras.putParcelableArrayList("songs", songs);
rec.send(Activity.RESULT_OK, extras);
}
What I would like to be able to do is have the "rec.send..." send more than one time. That way I can get the first album and first song, send that result back so the media player can start playing it, then process the rest in the background and add them when they are finished. That would mean the IntentService would need to be able to rec.send more than once. Is that possible or do I need to break this into 2 different IntentService calls, one to get the first item and then another to get the rest?
it won't take up the ui so the app can still function
You do not need an IntentService for this. An ordinary background thread (perhaps tied to a LiveData), AsyncTask, RxJava chain, etc. can handle this.
That would mean the IntentService would need to be able to rec.send more than once. Is that possible
Sure. It would be more efficient to use an ordinary background thread, AsyncTask, RxJava chain, etc. But, you should be able to send() as many times as you like. The results will be handed to onReceiveResult() one at a time (i.e., you call send() 6 times, you get 6 onReceiveResult() calls).
I have following code:
private void startLesson(String input) {
Intent intent = new Intent(StartovayaAktivnost.this, OsnovnayaAktivnostORM.class);
intent.putExtra("vybor_razdela", input);
startActivity(intent);
}
I want to launch different activities depending on parameter, is there a simple solution to pass a class name like
private void startLesson(String input, String activityname) {
Intent intent = new Intent(StartovayaAktivnost.this, activityname.class);
intent.putExtra("vybor_razdela", input);
startActivity(intent);
} //I know it's not gonna work
or the only way is to use embranchments like
private void startLesson(String input, String activityname) {
if (activityname.equals("OsnovnayaAktivnost"))
{
Intent intent = new Intent(StartovayaAktivnost.this, OsnovnayaAktivnost.class);
intent.putExtra("vybor_razdela", input);
startActivity(intent);
}
else if (activityname.equals("OsnovnayaAktivnostORM")) {
Intent intent = new Intent(StartovayaAktivnost.this, OsnovnayaAktivnostORM.class);
intent.putExtra("vybor_razdela", input);
startActivity(intent);
}
}
You can have a class as a parameter. Consider the following:
private void startLesson(String input, Class activityname) {
Intent intent = new Intent(StartovayaAktivnost.this, activityname);
intent.putExtra("vybor_razdela", input);
startActivity(intent);
}
You can then call your method as
startLesson("input", Main.class);
The same way as they're using classes as their parameters.
you can do something like:
Class<?> myClass = Class.forName(activityname);
Intent intent = new Intent(StartovayaAktivnost.this, myClass);
intent.putExtra("vybor_razdela", input);
startActivity(intent);
However, activityname should be the full class name.
You already have answers that show you solutions. I am more curious about your use-case though. For a similar situation I created a map of strings to classes of a certain base type. When passing activityName to that map, I would find the activity, or not. This gives you more control over what kind of classes are allowed to be loaded in this situation. In your scenario it would be harder to limit what kind of classes can be passed into Intent. But I imagine they have to adhere to certain rules that make up an activity.
Something like:
Map<String, MyBaseActivityType> activities
If you need new instances of the activity class every time, you can modify it a bit, I haven't given that part much thought.
But don't use reflection unless you really really need it. It gets messy quickly.
Found an answer while trying at random:
private void startLesson(String input, String activityname) throws ClassNotFoundException {
Intent intent = new Intent(StartovayaAktivnost.this, Class.forName(activityname));
intent.putExtra("vybor_razdela", input);
startActivity(intent);
}
Because setFragment of Facebook is working only with support.fragment, I did the following in order to overcome this when working with regular fragment:
public class NativeFragmentWrapper extends android.support.v4.app.Fragment {
private final Fragment nativeFragment;
public NativeFragmentWrapper(Fragment nativeFragment) {
this.nativeFragment = nativeFragment;
}
#Override
public void startActivityForResult(Intent intent, int requestCode) {
nativeFragment.startActivityForResult(intent, requestCode);
}
#Override
public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
nativeFragment.onActivityResult(requestCode, resultCode, data);
}
}
and in the login page where facebook button resides, I did the following:
authButton.setFragment(new new NativeFragmentWrapper(this));
in Eclipse it worked great but Android Studio 1.0.2 is complaining:
This fragment should provide a default constructor (a public constructor with no arguments). From the Fragment documentation: Every fragment must have an empty constructor, so it can be instantiated when restoring its activity's state. It is strongly recommended that subclasses do not have other constructors with parameters, since these constructors will not be called when the fragment is re-instantiated; instead, arguments can be supplied by the caller with setArguments(Bundle) and later retrieved by the Fragment with getArguments().
So I tried this:
NativeFragmentWrapper f = new NativeFragmentWrapper();
Bundle bundle = new Bundle();
f.setArguments(bundle);
authButton.setFragment(f);
but I didn't find a way to put "this" (which is a Fragment) inside the bundle in order to retrieve it in the wrapper constructor.
I can #SuppressLint("ValidFragment") but I'm sure there is a cleaner way to do it.
The best way is to just use the Fragment class from the v4 support libraries in your app, the signatures are the same, and you just need to change the import statements.
Failing that, the solution in the other post can potentially work (haven't tried it myself), and to solve your problem, just have 2 constructors (one the default, and another that takes a Fragment parameter), and just use the right constructor (i.e. not the default) when you create the wrapper object.
I'm trying to create a custom module in Appcelerator for the new Square API for Android. I have everything the way I want it, but the main problem is that I want to be able to notify the caller that the payment was successful for if it failed. The Square API says this:
After Square finishes, Android invokes Activity.onActivityResult() on the activity passed to the constructor. The request code passed to this method will be passed to onActivityResult(). The result code is Activity.RESULT_CANCELED if the payment was canceled or Activity.RESULT_OK if the payment succeeded.
I've been passing the TiContext.currentActivity to the constructor:
public SquareModule(TiContext tiContext) {
super(tiContext);
ourSquare = new Square(tiContext.getActivity());
}
And then in the method that actually runs the payment, I have this that basically tries to set the passed in callback to the onResult handlers of the current activity using the registerResultHandler in the TiActivitySupportHelper class.
public void runPayment(KrollInvocation invocation, int price, String description, KrollCallback handler) {
Log.i(LCAT, "runPayment called");
// Register the passed in function as a handler on the onResult stack
this.resultCallback = handler;
Activity activity = invocation.getTiContext().getActivity();
TiActivitySupportHelper support = new TiActivitySupportHelper(activity);
int code = support.getUniqueResultCode();
support.registerResultHandler(code, this);
// Some of the payment work here
ourSquare.squareUp(Bill.containing(advice), code);
}
The main module class implements TiActivityResultHandler and implements onResult and onError. These methods are not being called at all. And of course the passed in method isn't being called either.
For completeness, see the implementation of the onResult and onError handlers:
#Override
public void onResult(Activity activity, int requestCode, int resultCode, Intent data)
{
Log.i(LCAT, "onResult Called");
if (resultCallback == null) return;
KrollDict event = new KrollDict();
event.put(TiC.EVENT_PROPERTY_REQUEST_CODE, requestCode);
event.put(TiC.EVENT_PROPERTY_RESULT_CODE, resultCode);
event.put(TiC.EVENT_PROPERTY_INTENT, new IntentProxy(getTiContext(), data));
event.put(TiC.EVENT_PROPERTY_SOURCE, this);
resultCallback.callAsync(event);
}
#Override
public void onError(Activity activity, int requestCode, Exception e)
{
Log.i(LCAT, "onError Called");
if (resultCallback == null) return;
KrollDict event = new KrollDict();
event.put(TiC.EVENT_PROPERTY_REQUEST_CODE, requestCode);
event.put(TiC.EVENT_PROPERTY_ERROR, e.getMessage());
event.put(TiC.EVENT_PROPERTY_SOURCE, this);
resultCallback.callAsync(event);
}
And also see the Appcelerator JS calling the method in the module:
square.runPayment(2, 'Testing123', function(e) {
label1.text = 'Payment Successful!';
});
For those that come upon this question. The answer can be found in the module here:
https://github.com/hidef/Appcelerator-Square-Module (see the LaunchSquare.java class)
Basically, I used an Activity object that I created to receive the Square API's onResult update. I then was able to pass that back cleanly to the module class and hand it back via callback to the calling application.
I've got two activities, one of them is called MyActivity. I want both of them to be able to use a function located in a class othat we may call MyClass. In MyClass, I try to use an intent to launch the activity AnotherActivity. Since the constructor takes a context as parameter, I simply tried to store a context from the activity in the constructor, and then use it when I try to create my intent.
class MyClass {
private Context cxt;
MyClass(Context cxt) {
this.cxt = cxt;
}
startIntent() {
Intent intent = new Intent(cxt, AnotherActivity.class);
startActivity(intent); // this line throws a NullPointerException
}
}
The code in MyActivity to use the class is shown below:
myClassObject = new MyClass(MyActivity.this);
myClassObject.startIntent();
However, even thought none of the arguments are null (checked that with a simple if-statement), intent seems to be null and a NullPointerException is thrown. Why does it not work, and what can I do to solve the problem?
I'm quite new to Android and Java development, so please explain it as basic as you can.
cxt.startActivity(new Intent(cxt, AnotherActivity.class));
and to be sure that it's intent is NULL, and not something internal in startActivity method, you can add some checks, i.e.
Intent intent = new Intent(cxt, AnotherActivity.class);
Log.d(toString(), "intent = " + intent.toString());
cxt.startActivity(intent);
I've used almost identical code in my applications and it's worked fine.
I suspect there's something else going on that's in code you haven't shown us; I suspect there's some cut-and-paste issues --- e.g. what are you calling startActivity() on in MyClass?