Reputation: 603
In case of a test that crosses multiple activities, is there a way to get current activity?
getActivtiy() method just gives one activity that was used to start the test.
I tried something like below,
public Activity getCurrentActivity() {
Activity activity = null;
ActivityManager am = (ActivityManager) this.getActivity().getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> taskInfo = am.getRunningTasks(1);
try {
Class<?> myClass = taskInfo.get(0).topActivity.getClass();
activity = (Activity) myClass.newInstance();
}
catch (Exception e) {
}
return activity;
}
but I get null object.
Upvotes: 57
Views: 49007
Reputation: 4019
Based on https://stackoverflow.com/a/50762439/6007104 here's a Kotlin version of a generic util for accessing current Activity:
class CurrentActivityDelegate(application: Application) {
private var cachedActivity: Activity? = null
init {
monitorCurrentActivity(application)
}
fun getCurrentActivity() = cachedActivity
private fun monitorCurrentActivity(application: Application) {
application.registerActivityLifecycleCallbacks(
object : Application.ActivityLifecycleCallbacks {
override fun onActivityResumed(activity: Activity) {
cachedActivity = activity
Log.i(TAG, "Current activity updated: ${activity::class.simpleName}")
}
override fun onActivityCreated(activity: Activity?, savedInstanceState: Bundle?) {}
override fun onActivityStarted(activity: Activity?) {}
override fun onActivityPaused(activity: Activity?) {}
override fun onActivityStopped(activity: Activity?) {}
override fun onActivitySaveInstanceState(activity: Activity?, outState: Bundle?) {}
override fun onActivityDestroyed(activity: Activity?) {}
})
}
}
And then simply use like so:
@Before
fun setup() {
currentActivityDelegate = CurrentActivityDelegate(activityTestRule.activity.application)
}
Upvotes: 3
Reputation: 1885
public static Activity getActivity() {
final Activity[] currentActivity = new Activity[1];
Espresso.onView(AllOf.allOf(ViewMatchers.withId(android.R.id.content), isDisplayed())).perform(new ViewAction() {
@Override
public Matcher<View> getConstraints() {
return isAssignableFrom(View.class);
}
@Override
public String getDescription() {
return "getting text from a TextView";
}
@Override
public void perform(UiController uiController, View view) {
if (view.getContext() instanceof Activity) {
Activity activity1 = ((Activity)view.getContext());
currentActivity[0] = activity1;
}
}
});
return currentActivity[0];
}
Upvotes: 6
Reputation: 595
The Android team has replaced ActivityTestRule
with ActivityScenario. We could do activityTestRule.getActivity()
with ActivityTestRule
but not with ActivityScenario. Here is my work around solution for getting an Activity
from ActivityScenario (inspired by @Ryan and @Fabian solutions)
@get:Rule
var activityRule = ActivityScenarioRule(MainActivity::class.java)
...
private fun getActivity(): Activity? {
var activity: Activity? = null
activityRule.scenario.onActivity {
activity = it
}
return activity
}
Upvotes: 16
Reputation: 83
I improved @Fabian Streitel answer so you can use this method without ClassCastException
public static Activity getCurrentActivity() {
final Activity[] activity = new Activity[1];
onView(isRoot()).check((view, noViewFoundException) -> {
View checkedView = view;
while (checkedView instanceof ViewGroup && ((ViewGroup) checkedView).getChildCount() > 0) {
checkedView = ((ViewGroup) checkedView).getChildAt(0);
if (checkedView.getContext() instanceof Activity) {
activity[0] = (Activity) checkedView.getContext();
return;
}
}
});
return activity[0];
}
Upvotes: 2
Reputation: 1520
I couldn't get any of the other solutions to work, so I ended up having to do this:
Declare your ActivityTestRule
:
@Rule
public ActivityTestRule<MainActivity> mainActivityTestRule =
new ActivityTestRule<>(MainActivity.class);
Declare a final
Activity array to store your activities:
private final Activity[] currentActivity = new Activity[1];
Add a helper method to register with the application context to get lifecycle updates:
private void monitorCurrentActivity() {
mainActivityTestRule.getActivity().getApplication()
.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(final Activity activity, final Bundle savedInstanceState) { }
@Override
public void onActivityStarted(final Activity activity) { }
@Override
public void onActivityResumed(final Activity activity) {
currentActivity[0] = activity;
}
@Override
public void onActivityPaused(final Activity activity) { }
@Override
public void onActivityStopped(final Activity activity) { }
@Override
public void onActivitySaveInstanceState(final Activity activity, final Bundle outState) { }
@Override
public void onActivityDestroyed(final Activity activity) { }
});
}
Add a helper method to get the current activity
private Activity getCurrentActivity() {
return currentActivity[0];
}
So, once you've launched your first activity, just call monitorCurrentActivity()
and then whenever you need a reference to the current activity you just call getCurrentActivity()
Upvotes: 9
Reputation: 5371
The accepted answer may not work in many espresso tests. The following works with espresso version 2.2.2 and Android compile/target SDK 27 running on API 25 devices:
@Nullable
private Activity getActivity() {
Activity currentActivity = null;
Collection resumedActivities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(RESUMED);
if (resumedActivities.iterator().hasNext()){
currentActivity = (Activity) resumedActivities.iterator().next();
}
return currentActivity;
}
Upvotes: -1
Reputation: 1339
Solution proposed by @lacton didn't work for me, probably because activity was not in a state that was reported by ActivityLifecycleMonitorRegistry
.
I even tried Stage.PRE_ON_CREATE
still didn't get any activity.
Note: I could not use the ActivityTestRule
or IntentTestRule
because I was starting my activity using activitiy-alias
and didn't make any sense to use the actual class in the tests when I want to test to see if the alias works.
My solution to this was subscribing to lifecycle changes through ActivityLifecycleMonitorRegistry
and blocking the test thread until activity is launched:
// NOTE: make sure this is a strong reference (move up as a class field) otherwise will be GCed and you will not stably receive updates.
ActivityLifecycleCallback lifeCycleCallback = new ActivityLifecycleCallback() {
@Override
public void onActivityLifecycleChanged(Activity activity, Stage stage) {
classHolder.setValue(((MyActivity) activity).getClass());
// release the test thread
lock.countDown();
}
};
// used to block the test thread until activity is launched
final CountDownLatch lock = new CountDownLatch(1);
final Holder<Class<? extends MyActivity>> classHolder = new Holder<>();
instrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
ActivityLifecycleMonitorRegistry.getInstance().addLifecycleCallback(lifeCycleCallback);
}
});
// start the Activity
intent.setClassName(context, MyApp.class.getPackage().getName() + ".MyActivityAlias");
context.startActivity(intent);
// wait for activity to start
lock.await();
// continue with the tests
assertTrue(classHolder.hasValue());
assertTrue(classHolder.getValue().isAssignableFrom(MyActivity.class));
Holder
is basically a wrapper object. You can use an array or anything else to capture a value inside the anonymous class.
Upvotes: 0
Reputation: 1302
If you have the only Activity in your test case, you can do:
Rule
@Rule
public ActivityTestRule<TestActivity> mActivityTestRule = new ActivityTestRule<>(TestActivity.class);
Activity
:mActivityTestRule.getActivity()
That's a piece of pie!
Upvotes: -2
Reputation: 2810
I like @Ryan's version as it doesn't use undocumented internals, but you can write this even shorter:
private Activity getCurrentActivity() {
final Activity[] activity = new Activity[1];
onView(isRoot()).check(new ViewAssertion() {
@Override
public void check(View view, NoMatchingViewException noViewFoundException) {
activity[0] = (Activity) view.getContext();
}
});
return activity[0];
}
Please be aware, though that this will not work when running your tests in Firebase Test Lab. That fails with
java.lang.ClassCastException: com.android.internal.policy.DecorContext cannot be cast to android.app.Activity
Upvotes: 15
Reputation: 2366
In Espresso, you can use ActivityLifecycleMonitorRegistry
but it is not officially supported, so it may not work in future versions.
Here is how it works:
Activity getCurrentActivity() throws Throwable {
getInstrumentation().waitForIdleSync();
final Activity[] activity = new Activity[1];
runTestOnUiThread(new Runnable() {
@Override
public void run() {
java.util.Collection<Activity> activities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED);
activity[0] = Iterables.getOnlyElement(activities);
}});
return activity[0];
}
Upvotes: 41
Reputation: 8268
If all you need is to make the check against current Activity
, use may get along with native Espresso one-liner to check that expected intent was launched:
intended(hasComponent(new ComponentName(getTargetContext(), ExpectedActivity.class)));
Espresso will also show you the intents fired in the meanwhile if not matching yours.
The only setup you need is to replace ActivityTestRule
with IntentsTestRule
in the test to let it keep track of the intents launching. And make sure this library is in your build.gradle
dependencies:
androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.1'
Upvotes: 30