Skip to content

Make it easy to get the latest activity #2

Open

Description

@rfreitas

First, thanks for this tool! IdlingResource is a total mess.

Now to business, from the documentation I was not able to understand how to get the latest activity.

However, I found a different way, which follows:

    //ref: http://stackoverflow.com/a/38990078/689223
    static private Activity getCurrentActivity(){
        Collection<Activity> resumedActivity = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED);

        for(Activity act : resumedActivity){
            return act;
        }
        return null;
    }

    static private Activity getCurrentActivitySafe(){
        Callable<Activity> callable = new Callable<Activity>() {
            @Override
            public Activity call() throws Exception {
                return getCurrentActivity();
            }
        };

        FutureTask<Activity> task = new FutureTask<>(callable);
        getInstrumentation().runOnMainSync(task);

        try {
            return task.get(); // Blocks
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

Example on how to use:

@Override
public boolean checkCondition() {
     Activity currentActivity = getCurrentActivitySafe();
    (...)
}

getCurrentActivitySafe() has to be called instead getCurrentActivity() because checkCondition can run outside of the UI thread, causing an error.

My suggestion is to make getCurrentActivitySafe() an utility function, or to rename it to getCurrentActivity() and make it a method of Instruction.
Another alternative is to make checkCondition a method that receives the current activity as an argument, like this checkCondition(Activity currentActivity)

Activity

  1. FisherKK commented on Mar 6, 2017

    @FisherKK
    Collaborator

    Hello :)
    Thank you for enjoying simple thing as ConditionWatcher. Your proposition about retrieving Activity in test thread is interesting. It's simply waiting for ActivityLifecycleMonitor to return non-null instance.

    You said that you don't get how I am doing it. Well my solution is to write your own runner and extend Application object used by your app under test.

    public class MyAppTestRunner extends AndroidJUnitRunner {
    
        @Override
        public Application newApplication(ClassLoader cl, String className, Context context)
                throws InstantiationException, IllegalAccessException, ClassNotFoundException {
            return super.newApplication(cl, MyAppTestApplication.class.getName(), context);
        }
    }
    

    So now my app will use MyAppTestApplication instead of MyAppApplication. And in MyAppTestApplication I use interface of ActivityLifecycleCallbacks. Whenever any Activity will call onResume the callback will trigger and save new reference to Activity. I can get access to it anytime.

    public class MyAppTestApplication extends MyAppApplication implements Application.ActivityLifecycleCallbacks {
    
        private Activity currentActivity;
    
        @Override
        public void onCreate() {
            super.onCreate();
            registerActivityLifecycleCallbacks(this);
        }
    
        @Override
        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
       
        }
    
        @Override
        public void onActivityStarted(Activity activity) {
    
        }
    
        @Override
        public void onActivityResumed(Activity activity) {
            currentActivity = activity;
        }
    
        @Override
        public void onActivityPaused(Activity activity) {
    
        }
    
        @Override
        public void onActivityStopped(Activity activity) {
    
        }
    
        @Override
        public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
    
        }
    
        @Override
        public void onActivityDestroyed(Activity activity) {
        }
    
        public Activity getCurrentActivity() {
            return currentActivity;
        }
    
        public static MyAppTestApplication getInstance(){
            return ((MyAppTestApplication)(InstrumentationRegistry.getTargetContext().getApplicationContext()));
        }
    }
    

    And with that you can do static imports of MyAppTestApplication.getInstance().getCurrentActivity() which will allow you to simply call getCurrentActivity() wherever you want. I agree with the fact that it can be null - but since last year I never had problem with this as I am aware of it and I simply use null checks :) Well. I think I like your idea - it is a little bit more complicated but takes care of your null checks. I would like to run it with my code for a while and see if there are no any problems/side effects.

    What do you think about memory leak threat in both cases?

  2. rfreitas commented on Mar 6, 2017

    @rfreitas
    Author

    Didn't know about that solution, so thanks for the share ;) you should put it in the docs! :)

    Between the two solutions I don't know which one is more is more reliable, I would have to go deeper into it to find out. The one you proposed is a bit too verbose, though.

    Nonetheless, I don't mind so much which solution gets picked, what I do think is important is to make it easy for the user to get the current activity. So whatever solution we go with, it should be wrapped in a function and exposed as an utility function (or passed as an argument, if it's commonly used).

    Memory leaks wise it looks to me that both solution are the same, the getCurrentActivitySafe might be a bit safer, since it holds no references.

  3. FisherKK commented on Mar 9, 2017

    @FisherKK
    Collaborator

    Did your solution work for you?

    For me it doesn't seems to block anything and I receive massive amount of null pointers from getCurrentActivity() in Instructions.

  4. rfreitas commented on Apr 8, 2017

    @rfreitas
    Author

    It did, but I made a slight alteration.

    static public void performWhenVisible(final int viewId, ViewAction... viewActions) {
            try {
                ConditionWatcher.waitForCondition(new ViewVisibleInstruction(viewId));
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
    
            ViewInteraction viewInteraction = onView(allOf(withId(viewId)));
            viewInteraction.perform(viewActions);
        }
    
        static public class ViewVisibleInstruction extends SafeInstruction {
            static final String TAG = ViewVisibleInstruction.class.getName();
            int mViewId;
    
            ViewVisibleInstruction(int viewId) {
                mViewId = viewId;
            }
    
            @Override
            public String getDescription() {
                return TAG;
            }
    
            @Override
            public boolean checkConditionSafe() {
                Log.d(TAG, "isIdleNow");
                Activity currentActivity = getCurrentActivity();
                Log.d(TAG, "currentActivity:"+currentActivity);
                if (currentActivity==null) return false;
    
                View intendedView = currentActivity.findViewById(mViewId);
                Log.d(TAG, "intendedView:"+intendedView);
                boolean isVisible = intendedView!=null && intendedView.isShown();
                Log.d(TAG,"checkCondition:"+isVisible);
                return isVisible;
            }
        }
    
        static abstract public class SafeInstruction extends Instruction {
            static final String TAG = SafeInstruction.class.getName();
            @Override
            public boolean checkCondition() {
                Callable<Boolean> callable = new Callable<Boolean>() {
                    @Override
                    public Boolean call() throws Exception {
                        return checkConditionSafe();
                    }
                };
    
                FutureTask<Boolean> task = new FutureTask<>(callable);
                getInstrumentation().runOnMainSync(task);
    
                try {
                    return task.get(); // Blocks
                } catch (Exception e) {
                    Log.e(TAG, "checkConditionSafe FutureTask exception", e);
                    throw new RuntimeException(e);
                }
            }
    
            abstract public boolean checkConditionSafe();
        }
    
    
        //ref: http://stackoverflow.com/a/38990078/689223
        static private Activity getCurrentActivity(){
            Collection<Activity> resumedActivity = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED);
    
            for(Activity act : resumedActivity){
                return act;
            }
            return null;
        }
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions