I have a firstActivity that launches the secondActivity, where in the secondActivity I have a loading Dialog (not AsyncTask), and I need to make Espresso wait until the dialog disappears before it continues with the test.
Where do I have to implement the IdlingResource? How can I make it wait for the dismissDialog() function?
Here is what I've tried to do:
class DocumentLoadingIdlingResource implements IdlingResource {
private ResourceCallback callback;
#Override
public String getName() {
return "Documnet loading idling resource";
}
#Override
public boolean isIdleNow() {
Activity activity;
try {
activity = getCurrentActivity();
} catch (Throwable e) {
return false;
}
if(activity.getClass().getName().equals(EditorActivity.class.getName())
&& activity.loadingDialogShowing() == false) {
return false;
}
return true;
}
#Override
public void registerIdleTransitionCallback(ResourceCallback callback) {
this.callback = callback;
}
}
Activity getCurrentActivity() throws Throwable {
getInstrumentation().waitForIdleSync();
final Activity[] activity = new Activity[1];
runTestOnUiThread(new Runnable() {
#Override
public void run() {
java.util.Collection<Activity> activites = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED);
activity[0] = com.google.common.collect.Iterables.getOnlyElement(activites);
}});
return activity[0];
}
This class is implemented in the test class.
There are a few problems here:
Your isIdleNow() calls getCurrentActivity() which calls waitForIdleSync() and runTestOnUiThread(). isIdleNow Javadoc says: "Espresso will always call this method from the main thread, therefore it should be non-blocking and return immediately." So this won't work as is, but you could call getActivitiesInStage directly from isIdleNow.
Your other issue is that you store the reference to ResourceCallback but never invoke onTransitionToIdle, also you should allow for the possibility of more than one ResourceCallback being registered and call onTransitionToIdle on all of the callbacks.
You can do the following:
Copy/Paste IdlingResource into your app as com.mycompany.IdlingResource.
Then have your Activity implement that interface and make sure to call onTransitionToIdle when the dialog goes away and make sure isIdleNow returns false iff the dialog is showing.
In your test code, write a "IdlingResourceAdapter" that wraps com.mycompany.IdlingResource and turns it into an Espresso IdlingResource and register that with Espresso.
This will be simpler once this issue is implemented: https://code.google.com/p/android-test-kit/issues/detail?id=71
I stumbled upon this question in my search for a similar answer. Using concepts from Stefano Dacchille's article on IdlingResources, I built the following idling resource that waits for a specific Activity to be active before firing. In my case, I know the dialog is showing when a fragment with a specific tag exists. This isn't the same as the OP's test, but the concepts should translate well.
public class BusyWhenFragmentExistsInActivityIdlingResource implements IdlingResource {
private FragmentActivity activity = null;
private final String fragmentTag;
private ResourceCallback resourceCallback;
private boolean wasIdleLastTime = true; // Start off as idle
private final String name;
// Need this strong reference because ActivityLifecycleMonitorRegistry won't hold one
private final ActivityLifecycleCallback activityLifecycleCallback;
public BusyWhenFragmentExistsInActivityIdlingResource(
final Class<? extends FragmentActivity> clazz,
final String fragmentTag
){
name = BusyWhenFragmentExistsInActivityIdlingResource.class.getSimpleName()+" "+clazz.getSimpleName();
this.fragmentTag = fragmentTag;
activityLifecycleCallback = new ActivityLifecycleCallback() {
#Override
public void onActivityLifecycleChanged(Activity activity, Stage stage) {
if (!FragmentActivity.class.isAssignableFrom(activity.getClass())) {
return;
}
FragmentActivity fragmentActivity = (FragmentActivity) activity;
if (!clazz.isAssignableFrom(fragmentActivity.getClass())) {
return;
}
switch (stage){
case RESUMED:
BusyWhenFragmentExistsInActivityIdlingResource.this.activity = fragmentActivity;
break;
case STOPPED:
BusyWhenFragmentExistsInActivityIdlingResource.this.activity = null;
break;
}
}
};
ActivityLifecycleMonitorRegistry.getInstance()
.addLifecycleCallback(activityLifecycleCallback);
}
#Override
public String getName() {
return name;
}
#Override
public boolean isIdleNow() {
if (activity==null) {
return wasIdleLastTime = true;
}
boolean isIdleThisTime = activity
.getSupportFragmentManager()
.findFragmentByTag(fragmentTag)==null;
if (!wasIdleLastTime && isIdleThisTime && resourceCallback!=null){
resourceCallback.onTransitionToIdle();
}
return wasIdleLastTime = isIdleThisTime;
}
#Override
public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
this.resourceCallback = resourceCallback;
}
}
To use it, add something similar to this to your test:
#Before
public void setUp() throws Exception {
registerIdlingResources(new BusyWhenFragmentExistsInActivityIdlingResource(
SomeOtherActivity.class,
BaseActivity.LOADING_DIALOG
));
}
Related
I recently got following example where we are passing the action name to the method as string and then the method decides the function that needs to be called.
is this a good way of solving problem or is there some better way as well
public static final String ACTION_CHARGING_REMINDER = "charging-reminder";
public static void executeTask(Context context, String action) {
if (ACTION_INCREMENT_WATER_COUNT.equals(action)) {
incrementWaterCount(context);
} else if (ACTION_DISMISS_NOTIFICATION.equals(action)) {
NotificationUtils.clearAllNotifications(context);
} else if(ACTION_CHARGING_REMINDER.equals(action)){
issueChargeReminder(context);
}
}
I'd do something like this. This can be extended as much as you want, and obviously just an example:
static abstract class ActionHandler {
private String action;
public ActionHandler(String action) {
this.action = action;
}
public boolean canHandleAction(String input) {
return this.action.equals(input);
}
public abstract void handleAction();
}
static class OneActionHandler extends ActionHandler {
public OneActionHandler(String action) {
super(action);
}
#Override
public void handleAction() {
//...
}
}
static class TwoActionHandler extends ActionHandler {
public TwoActionHandler(String action) {
super(action);
}
#Override
public void handleAction() {
//...
}
}
static class Test {
private ActionHandler[] handlers;
public Test() {
handlers = new ActionHandler[]{new OneActionHandler("action1"), new TwoActionHandler("action2")};
}
public void handleAction(String action) {
for(ActionHandler i : handlers) {
if(i.canHandleAction(action)) {
i.handleAction();
break;
}
}
}
}
This sounds a lot like the react/redux, action/reduction pattern.
Reducers specify how the application's state changes in response to
actions sent to the store. Remember that actions only describe what
happened, but don't describe how the application's state changes.
I am trying to send an update to my Activity from my GCMServiceListener so, I am using RxJava/RxAndroid And created a BusClass for handling sending and Observers
public class ClientBus {
//private final PublishSubject<Object> _bus = PublishSubject.create();
// If multiple threads are going to emit events to this
// then it must be made thread-safe like this instead
private final Subject<Object, Object> _bus = new SerializedSubject<>(PublishSubject.create());
public void send(Object o) {
_bus.onNext(o);
}
public Observable<Object> toObserverable() {
return _bus;
}
public boolean hasObservers() {
return _bus.hasObservers();
}
}
And in my Application Class I did this to initialize the BusClass
private ClientBus clientBus;
public ClientBus getRxBusSingleton() {
if (clientBus == null) {
clientBus = new ClientBus();
}
return clientBus;
}
In the activity I want to receive the message, I registered a CompositeSubscription and get a reference to my ClientBus class from the Application Class
clientBus = ((MyApplication) getApplicationContext()).getRxBusSingleton();
#Override
protected void onStart() {
super.onStart();
initSubscriptions();
}
#Override
protected void onStop() {
super.onStop();
_subscriptions.unsubscribe();
}
void initSubscriptions() {
_subscriptions = new CompositeSubscription();
_subscriptions.add(clientBus.toObserverable().subscribe(new Action1<Object>() {
#Override
public void call(Object event) {
Log.e("New Event", "Event Received");
if (event instanceof MyGcmListenerService.Message) {
String msg = ((MyGcmListenerService.Message) event).getMessage();
if (msg.equals("Update Available")) {
scheduleArrayList = getSchedules();
scheduleAdapter = new ScheduleAdapter(getApplicationContext(), scheduleArrayList, ScheduledUberActivity.this);
scheduledList.setAdapter(scheduleAdapter);
scheduleAdapter.notifyDataSetChanged();
} else if (msg.equals("Refresh")) {
fetchTrips();
}
}
}
}));
}
And from the MyGcmListenerService class I did this when I get a new notification
private void sendRefreshNotif() {
if (clientBus.hasObservers()) {<--It enters the if cause the Log prints. But, the activity doesn't get the message
Log.e("Obervers", "Observers aren't null");
clientBus.send(new Message("Refresh"));
}
}
What I don't understand is why isn't it working here? I use it to interact between activities and fragments. I closed my application to check if the notification comes in, It'll enter this block if (clientBus.hasObservers()) { but it didn't and starting the app and testing the Observer, it notices there's an active Observer. Any help? Thanks.
It seems like you used different instances of the ClientBus class in CompositeSubscription and MyApplication.
Try to make a singleton from ClientBus class, it works fine for me.
public class ClientBus {
public ClientBus(SingletonAccessor accessor) {}
private static ClientBus instance;
private static class SingletonAccessor{}
public static ClientBus getInstance() {
if (instance == null) instance = new ClientBus(new SingletonAccessor());
return instance;
}
private final Subject<Object, Object> mBus = new SerializedSubject<>(PublishSubject.create());
public void send(Object o) {
mBus.onNext(o);
}
public Observable<Object> toObserverable() {
return mBus;
}
public boolean hasObservers() {
return mBus.hasObservers();
}
}
I have an activity. It contains fragment, and this fragment has RecyclerView. Some data loading from server and showing inside first item of RecyclerView.
I need setup IdlingResource for this item. So when the data will be loaded from server and showing inside this item, tests must starting.
How to setup IdlingResource for item inside RecyclerView?
IdlingResource on RecyclerView
I'm not sure that's how we use it, but it works...
In YourFragment:
public interface RecyclerViewHaveDataListener {
void recyclerViewHaveData();
}
private RecyclerViewHaveDataListener callbackIdl;
#VisibleForTesting
public void registerOnCallBackIdl(RecyclerViewHaveDataListener callbackIdl){
this.callbackIdl = callbackIdl;
if (adapter.getItemCount() > 0){
this.callbackIdl.recyclerViewHaveData();
this.callbackIdl = null;
}
}
// Use this where you add your datas to your RecyclerView by your adapter
if (callbackIdl != null && adapter.getItemCount() > 0){
callbackIdl.recyclerViewHaveData();
callbackIdl = null;
}
In Your test package:
public class RecyclerViewIdlingRes implements IdlingResource, YourFragment.RecyclerViewHaveDataListener {
private String name;
private boolean isIdle;
private volatile ResourceCallback resourceCallback;
public RecyclerViewIdlingRes(String name) {
this.name = name;
}
#Override
public String getName() {
return name;
}
#Override
public boolean isIdleNow() {
return isIdle;
}
#Override
public void registerIdleTransitionCallback(ResourceCallback callback) {
this.resourceCallback = callback;
}
#Override
public void recyclerViewHaveData() {
if (resourceCallback == null){
return;
}
isIdle = true;
resourceCallback.onTransitionToIdle();
}
}
in your test:
#Test
public void itemIsComming(){
RecyclerViewIdlingRes mIdl = new RecyclerViewIdlingRes("idlingRecyclerView");
IdlingRegistry.getInstance().register(mIdl);
Fragment fragment = activity.getSupportFragmentManager().findFragmentById(R.id.activity_main_frame_layout);
if (fragment instanceof YourFragment) {
((YourFragment)fragment).registerOnCallBack(mIdl);
}
onIdle();
IdlingRegistry.getInstance().unregister(mIdl);
// DO YOUR STUFF...
}
The good approach will be to implement your own IdlingResource that will notify Espresso when data loading is finished. You can find some example here - How to use Espresso Idling Resource for network calls
I am migrating from Volley to a custom implementation using Retrofit, but I'm trying to add to my implementation some of the Volley features that I liked, for example
RequestQueue.cancel(String tag)
If the Request has the requested tag, then it's canceled by setting a boolean value, mCanceled, to true. The run method checks this value and returns if it's true.
To be able to reproduce this with Retrofit I should be able to use my custom class implementing Runnable instead of the default one, where I have a mTag and a mCanceled field.
Moreover, Volley was also able to set such flag inside the active Threads and immediately stop them. My cancelAll method, that I've already implemented, just drains the queue to another queue, but isn't able to access the active threads.
Is it possible to achieve the same results with Retrofit and ThreadPoolExecutor?
I think I've found a nicer solution: instead of blocking the Runnable of the requests, I am blocking the Callback execution.
I have extended the Callback interface:
public interface CustomCallbackInterface<T> extends Callback<T> {
public String getTag();
public String setTag(String tag);
public void cancel();
public boolean isCanceled();
}
so that each Callback has a tag and a cancel flag. Then the success method starts with:
public class CustomCallback<ConvertedData> implements CustomCallbackInterface<ConvertedData>{
//failure...
#Override
public void success(ConvertedData cd, Response response) {
if(isCanceled()) return;
// ....
}
}
Every time I make a new request, I store the created CustomCallback inside a List cancel just iterates the list and calls cancel() on the items with the same tag.
I've implemented an easy to use class based on Vektor88 answer
public abstract class CancelableCallback<T> implements Callback<T> {
private static List<CancelableCallback> mList = new ArrayList<>();
private boolean isCanceled = false;
private Object mTag = null;
public static void cancelAll() {
Iterator<CancelableCallback> iterator = mList.iterator();
while (iterator.hasNext()){
iterator.next().isCanceled = true;
iterator.remove();
}
}
public static void cancel(Object tag) {
if (tag != null) {
Iterator<CancelableCallback> iterator = mList.iterator();
CancelableCallback item;
while (iterator.hasNext()) {
item = iterator.next();
if (tag.equals(item.mTag)) {
item.isCanceled = true;
iterator.remove();
}
}
}
}
public CancelableCallback() {
mList.add(this);
}
public CancelableCallback(Object tag) {
mTag = tag;
mList.add(this);
}
public void cancel() {
isCanceled = true;
mList.remove(this);
}
#Override
public final void success(T t, Response response) {
if (!isCanceled)
onSuccess(t, response);
mList.remove(this);
}
#Override
public final void failure(RetrofitError error) {
if (!isCanceled)
onFailure(error);
mList.remove(this);
}
public abstract void onSuccess(T t, Response response);
public abstract void onFailure(RetrofitError error);
}
Usage example
rest.request(..., new CancelableCallback<MyResponse>(TAG) {
#Override
public void onSuccess(MyResponse myResponse, Response response) {
...
}
#Override
public void onFailure(RetrofitError error) {
...
}
});
// if u need to cancel all
CancelableCallback.cancelAll();
// or cancel by tag
CancelableCallback.cancel(TAG);
I'm trying to track down a new null pointer exception which is appearing in my ACRA logs and which I can't reproduce. Here's the relevant code:
public class MyApplication extends Application {
public void onCreate() {
DataManager.instance().initializeData(this);
}
}
public class DataManager {
private static DataManger instance = new DataManger();
private List<DataModel> dataModels;
private List<I_Callback> callbacks = new ArrayList<I_Callback>();
private boolean isInitialized = false;
private DataManager(){}
public static DataManager instance() {
return instance;
}
public void initializeData(Context context) {
new DataManagerInitializer().execute(context);
}
public void setDataModels(List<DataModel> models) {
dataModels = models;
}
public void synchronized registerInitializeCallbacks(I_Callback callback) {
if (isInitialized) {
callback.executeCallback();
} else {
callbacks.add(callback);
}
}
public void synchronized setInitialized() {
isInitialized = true;
for (I_Callback callback:callbacks) {
callback.executeCallback();
}
callbacks.clear();
}
}
public class DataManagerInitializer extends AsyncTask<Context, Void, Void>{
protected Void doInBackground(Context... contexts){
List<DataModel> dataModels = new ArrayList<DataModel>();
/*various code to create DataModel objects and add to dataModels list*/
DataManager.instance().setDataModels(dataModels);
return null;
}
protected void onPostExecute(Void result) {
DataManager.instance().setInitialized();
}
}
public class ActivityA extends Activity implements I_Callback{
public void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.graphical_layout);
DataManager.instance().registerInitializeCallbacks(this);
}
public void executeCallback() {
/* wire up button to call Activity B */
}
}
public class ActivityB extends Activity {
public void onCreate(Bundle savedInstanceState) {
List<DataModel> dataModels = DataManager.instance().getDataModels();
/* The following line of code throws a null pointer exception
in the stack trace*/
for (int i=0; i < dataModels.size(); i++){
/* do something with the data model */
}
}
}
To break down the above more simply, the application is launched which kicks off the initializion of the data manager singleton. ActivityA, the main activity, launches and waits for the data manager to complete initialization before allowing any actions, wiring up any events, etc. From ActivityA, its not possible to get to ActivityB without the call back method executing and ActivityB is only reachable from ActivityA. The only way for the list of data models to be null in the DataManager is for it to not have been initialized, but I'm struggling to see how this is possible. Any suggestions on how my null pointer may have occurred?
private static DataManger instance = new DataManger();
...
public static DataManager instance() {
return instance;
}
Is where the problem is. So your instance variable is getting garbage collected. As it is instantiated when it is declared, it is not being appropriately re-instantiated. So, try this instead:
private static DataManger instance = null;
...
public static DataManager instance() {
if (instance == null){
instance = new DataManager();
}
return instance;
}
This will ensure the call to instance() (usually called getInstance() but this is only convention), will return a valid single instance of the datamanager. Try to avoid instantiating global variables with their declaration, to avoid this specific problem.
Let's assume that:
you are interacting with the Activity B
press the home button:
start playing with other apps (consuming memory)
at some point the so needs memory and it's gonna start garbage collecting objects, included your "instance".
If that happens when you launch your app the framework will resume the activity B and the npe will happen.
You need to re-create the instance (in the activity B) if it is null.