Get Current Activity in Espresso android
Asked Answered
S

11

57

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.

Sammie answered 1/7, 2014 at 18:49 Comment(1)
I would be happy to have the activity which has started the test but sadly you didn't specify the class name for getActivtiy();Trihydric
H
41

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];
}
Hover answered 2/7, 2014 at 6:22 Comment(4)
With updates to espresso is there anyway this could be null? I'm not able to get a non-null value from it currently at all, but I use to be able to.Ronna
Use this code for espresso 2.0 qathread.blogspot.com/2014/09/…Bewail
Here is a similar solution in Kotlin: https://mcmap.net/q/304053/-espresso-how-to-get-current-activity-to-test-fragmentsOceanography
Is this approach relevant for AndroidX Test?Pasteboard
F
30

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'
Fortis answered 4/12, 2015 at 9:0 Comment(2)
I get I/TestRunner: android.support.test.espresso.base.DefaultFailureHandler$AssertionFailedWithCauseError: Wanted to match 1 intents. Actually matched 0 intents. Even tho current activity is the same I assert. Espresso 2.2.2. @Rule public final ActivityTestRule<MainScreen> main = new ActivityTestRule<>(MainScreen.class); And assertion: intended(hasComponent(new ComponentName(getTargetContext(), MainScreen.class)));Udo
how is this answering the question here?Parathion
O
16

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
}
Overhear answered 4/1, 2019 at 14:55 Comment(2)
careful with threading when getting activity with this approachIbex
If I'm testing an end-to-end flow of several activities, is there a way to get the currently visible activity using ActivityScenario? I only manage to get the initial one.Gillispie
F
15

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
Fulgurant answered 1/1, 2017 at 12:1 Comment(9)
Did you manage to find out why this was failing on Firebase Test Lab?Tooling
I don't know specifically why they are doing this, but the view does not hold the activity as its context but instead this DecorContext. Couldn't find any reference to that anywhere, unfortunatelyFulgurant
I have just started using Espresso and I need an idling resource which is not in the first activity. So I am extending idling resource and want to check if my progress bar is showing. The best way I can see to do this is to get the activity and query this state. I was hoping to run these tests on Firebase.Tooling
I haven't done anything with idling resources yet, but onView() also works after switching to a different activity. Can you maybe just use that inside your idling resource?Fulgurant
Regarding FireBase issue, use: activity[0] = (Activity) view.findViewById(android.R.id.content).getContext();Uncinate
You won't be able to use this inside an idling resource. The check call will actually check for idle: uiController.loopMainThreadUntilIdle(); So you'll hit an infinite loop where the idling resource is waiting to get the current activity, which won't be returned until the UI thread is idle, which won't happen because you have a registered idling resource. Found this out the hard way =(Saida
This also happened on my local device - so i used the activity[0] = (Activity) view.findViewById(android.R.id.content).getContext(); method from user2999943 and it workedKt
Why are you creating an array if you always get one activity?Heidiheidie
@Heidiheidie because in Java references to variables outside a Lambda are "effectively final" i.e. you can't assign to these variables. The array is a workaround for that. Try replacing it with a simple variable in your favourite IDE to see the error for yourselfFulgurant
R
9

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()

Robichaud answered 8/6, 2018 at 14:5 Comment(1)
Best solution! Thank youCeremony
T
6
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];
}
Thackeray answered 8/11, 2016 at 21:28 Comment(2)
One question: Why are you using an array?Heidiheidie
@Heidiheidie because the currentActivity is used inside a anonymus function, thus have to be final, if final, you can initialise it only in constructor, because of that, you use an "Array".Lorineloriner
C
3

Based on https://mcmap.net/q/303574/-get-current-activity-in-espresso-android 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)
}
Ceremony answered 1/9, 2020 at 8:5 Comment(0)
M
2

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];
}
Main answered 14/9, 2018 at 6:54 Comment(0)
V
0

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.

Vertumnus answered 23/11, 2017 at 3:5 Comment(0)
A
-1

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;
}
Autolycus answered 16/11, 2017 at 3:48 Comment(1)
java.lang.IllegalStateException: Querying activity state off main thread is not allowed.Laundress
I
-2

If you have the only Activity in your test case, you can do:

1. declare you test Rule

@Rule
public ActivityTestRule<TestActivity> mActivityTestRule = new ActivityTestRule<>(TestActivity.class);

2. get you Activity:

mActivityTestRule.getActivity()

That's a piece of pie!

Imperious answered 4/3, 2017 at 3:55 Comment(2)
from the question: "getActivtiy() method just gives one activity that was used to start the test." as opposed to "is there a way to get current activity?", so you didn't answer the questionSwindell
Ow, sorry :-) I just was googling how to get an activity and found this question. Cause the question doesn't describe the solution, I just created a new tab and continued my searches. Then, when I had found the solution, I started to close tabs. It seems like I just shared my solution here without any back mind. I hope, it's not so bad to be placed here :-)Imperious

© 2022 - 2024 — McMap. All rights reserved.