Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make it easy to get the latest activity #2

Open
rfreitas opened this issue Mar 6, 2017 · 4 comments
Open

Make it easy to get the latest activity #2

rfreitas opened this issue Mar 6, 2017 · 4 comments

Comments

@rfreitas
Copy link

rfreitas commented Mar 6, 2017

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)

@FisherKK
Copy link
Collaborator

FisherKK commented Mar 6, 2017

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?

@rfreitas
Copy link
Author

rfreitas commented Mar 6, 2017

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.

@FisherKK
Copy link
Collaborator

FisherKK commented Mar 9, 2017

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.

@rfreitas
Copy link
Author

rfreitas commented Apr 8, 2017

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
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants