I'm trying to write some tests with the new android-test-kit (Espresso). But I can't find any information on how to check if a view is displayed and perform some actions on it (like clicking buttons, e.t.c.). Note that the view I need to check if it exists or not. If it does perform action on the view and if not proceed to the next view.
Any help would be appreciated. I just need a link, or some example code for the basics:
Check if the view exists
If yes,Perform action
If not, proceed to next screen
You can use a classic option "try/catch":
try {
onView(withText("Text")).check(matches(isDisplayed()));
//perform some actions on this view
} catch (NoMatchingViewException notExist) {
//proceed to the next screen
}
Object currentActivity;
#Nullable
private Activity getCurrentActivity() throws Throwable {
getInstrumentation().waitForIdleSync();
getInstrumentation().runOnMainSync(new Runnable() {
public void run() {
Collection resumedActivities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(RESUMED);
if (resumedActivities.iterator().hasNext()) {
currentActivity = resumedActivities.iterator().next();
}
}
});
return (Activity) currentActivity;
}
with this, you can get the Activity that is currently displayed. After that by doing something like this, you can make a safe code section
HOMESCREEN:
{
for (; ; ) {
if (getCurrentActivity() != null) {
//check if it is the required screen
if (getCurrentActivity().getLocalClassName().toLowerCase().contains("homescreen")) {
//if it is the required screen, break the
//loop and continue execution
break HOMESCREEN;
} else {
//wait for 2 seconds and run the loop again
sleep(2000);
}
} else {
break HOMESCREEN;
}
}
}
sleep(2000) is a custom function which just calls thread.sleep like shown below:
private void sleep(long milliseconds) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
throw new RuntimeException("Cannot execute Thread.sleep()");
}
}
You must control the behavior of your test. So, you must have to add some preconditions, or create the #Rule that will control the behavior, for example by adding parameters depending on which your view will be displayed or not.
Related
I have no formal coding education so please let me know if something I did is considered bad coding
I am using a bottom navigation view for a train schedule application I am making. One of the options is a nearby functionality - when nearby is clicked on the bottom navigation drawer, the NearbyFragment is launched and my server gets a request via OKHttp automatically without additional user interaction
The problem is that if you switch from Nearby to another button on the bottom navigation view OR if you tapped the nearby button multiple times, the app would crash. Initially, this was because the runOnUiThread method wouldn't be able to find the UI thread since I moved on to a different fragment (and presumably the main thread no longer existed). I tried fixing this by creating a 1 second delay before the getLocation method was called in onViewCreated
// Delay before calling on location to prevent crashing when rapidly switching from Nearby Fragment
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
if (getActivity() != null) {
getLocation();
}
}
}, 1000);
I also wrapped the above, as well as OKHttp onSuccess and onFailure methods in an if statement (getActivity != null) to check that the UI thread still existed before proceeding
#Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
final String myResponse = response.body().string();
if (getActivity() != null) {
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
processJSON(myResponse);
}
});
}
}
}
I would still get crashes associated with the onFailure method with NullPointerExceptions and cannot find views (such as the snackbar) IF I changed from the NearbyFragment to a different fragment after 1 second of loading but before the loading had finished, so I wrapped the onFailure in a try/catch to catch all exceptions and ignore them. This feels like a hack, but my app no longer crashes/acts as expected when switching from from the NearbyFragment to another fragment via bottom navigation.
#Override
public void onFailure(Call call, IOException e) {
try {
mSwipeToRefresh.setRefreshing(false);
Log.e("Main", e.toString());
mSnackbar = Snackbar.make(getActivity().findViewById(R.id.swipe_container_nearby), "ERROR!", Snackbar.LENGTH_INDEFINITE);
snackBarCoordinator(mSnackbar);
mSnackbar.show();
} catch (Exception j) {
Log.e("Failed Get Closest Stations ", j.toString());
}
}
Is there a cleaner was to stop the crashes without the combination of three failsafes i.e., handler.postDelayed, (getActivity() != null), and an empty try/catch block?
To know whether activity or fragment is on UI.
For Activity, you can call isFinishing()
For Fragment call isAttached().
i.e before show loader, snack bar or any widgets wrap with if(...) {}
if(isAttached()) { snackbar.show(); }
I have a simple login page where the user enters a password, and that password is used to decrypt some data and create the main activity. The process of generating the key, calling the database and creating the main activity takes about 5 seconds so I want to show a progress wheel on the login screen immediately after the user clicks the login button.
Unfortunately since android handles UI refreshes in a non-blocking way the progress bar won't appear before the login function runs. I can't seem to find a way to force a blocking UI refresh for a view in Android. invalidate() and postInvalidate() both won't work since these simply notify Android that a redraw should happen at some point in the future.
Here is some sample code to explain what I'm trying to accomplish:
try {
progressBar.setVisibility(View.VISIBLE);
passwordEditText.setEnabled(false);
Key key = doLogin(passwordEditText.getText()); // Long operation
Intent intent = new Intent(getActivity(), MainActivity.class);
intent.putExtra("Key", key);
startActivity(intent);
getActivity().finish();
} catch (Exception e) {
passwordEditText.setError(getString(R.string.login_error));
Log.e("Login", e.getMessage());
} finally {
progressBar.setVisibility(View.INVISIBLE);
passwordEditText.setEnabled(true);
}
If it's not possible to override the default behaviour and force an immediate blocking redraw, then how best can I best implement a progress wheel while the doLogin() method runs?
I can't seem to find a way to force a blocking UI refresh for a view in Android
Correct. That is not how the Android view system works.
then how best can I best implement a progress wheel while the doLogin() method runs?
Have doLogin() be performed on a background thread. Update the UI on the main application thread when the background work finishes. Take into account that your UI may no longer exist (e.g., user pressed BACK) or may be replaced (e.g., user rotated the device or otherwise triggered a configuration change) while that background work is going on.
In modern Android app development, the three main approaches for doing this are to use a ViewModel and LiveData along with:
RxJava
Coroutines (for app development in Kotlin)
Your own background thread
Fixed the issue. Here's my solution. It may be a little overdone, but I tried to make it easily expandable so it can be applied to other similar scenarios.
AsyncTask for login:
static class LoginTask extends AsyncTask<String, Void, LoginTask.LoginTaskResultBundle> {
private TaskActions mActions;
LoginTask(#NonNull TaskActions actions) {
this.mActions = actions;
}
#Override
protected void onPreExecute() {
mActions.onPreAction();
}
#Override
protected LoginTaskResultBundle doInBackground(String... args) {
try {
Key key = doLogin();
return new LoginTaskResultBundle(LoginTaskResultBundle.SUCCEEDED, key);
} catch (Exception e) {
return new LoginTaskResultBundle(LoginTaskResultBundle.FAILED, e);
}
}
#Override
protected void onPostExecute(LoginTaskResultBundle result) {
if (result.getStatus() == LoginTaskResultBundle.SUCCEEDED)
mActions.onPostSuccess(result);
else
mActions.onPostFailure(result);
}
// Result Bundle
class LoginTaskResultBundle {
static final int FAILED = 0;
static final int SUCCEEDED = 1;
private int mStatus;
private Exception mException;
private Key mKey;
LoginTaskResultBundle(int status, Key key) {
mStatus = status;
mKey = key;
mException = null;
}
LoginTaskResultBundle(int status, Exception exception) {
mStatus = status;
mException = exception;
}
int getStatus() {
return mStatus;
}
Exception getException() {
return mException;
}
Key getKey() {
return mKey;
}
}
// Interface
interface TaskActions {
void onPreAction();
void onPostSuccess(LoginTaskResultBundle bundle);
void onPostFailure(LoginTaskResultBundle bundle);
}
}
Sample call to the LoginAsyncTask:
new LoginTask(
new LoginTask.TaskActions() {
#Override
public void onPreAction() {
showProgressBar();
}
#Override
public void onPostSuccess(LoginTask.LoginTaskResultBundle bundle) {
launchMainActivity();
}
#Override
public void onPostFailure(LoginTask.LoginTaskResultBundle bundle) {
hideProgressBar();
passwordEditText.setError(bundle.getException().getMessage());
Log.e("Login", bundle.getException().getMessage());
}
} )
.execute(passwordEditText.getText().toString());
Within my activity I am trying to use validation within a method (shown below) to carry out two functionalities:
Display toast as error message if there is an error condition
Bring user back to main menu using intents.
The above is working however after the validation is carried out the program flow within that activity is still being carried out. (see second code block below).
How can I ensure that nothing else in the activity is executed after, if the validation block is called in my method? I.e. just return to MainMenu, dont execute rest of code in finally block.
Method containing validation:
public void getAverageAttentionValue() {
// validation to ensure that no dividing by zero
if (totalofAttLevels < 1) {
new Thread() {
public void run() {
StroopGame.this.runOnUiThread(new Runnable() {
public void run() {
//display error message
Toast.makeText(
StroopGame.this,
"Headset unable to read values, please re-connect",
Toast.LENGTH_LONG).show();
(new Handler()).postDelayed(new Runnable() {
public void run() {
Intent openActivity = new Intent(
"com.example.brianapp.MainMenu");
startActivity(openActivity);
device.close();
}
}, 2000);
}
});
}
}.start();
} else {
averageAttLevel = totalofAttLevels / attCount;
attMax = Collections.max(AttentionValues);
}
}
Calling the above method in finally block and the code that is still being called afterwards:
finally {
//calling method containing validation
getAverageAttentionValue();
//I DONT WANT THE CODE BELOW TO BE EXECUTED IF
//THE VALIDATION IN METHOD IS CALLED
// write data from session to SQLite db
writeToDatabase();
// stop audio
tenSecs.stop();
// declaring activity to open
Intent openActivity = new Intent(
"com.example.brianapp.StroopResults");
openActivity.putExtra("singleScore", single);
// Start activity
startActivity(openActivity);
device.close();
}
}
You can do it as :
Take a member variable for e.g boolean isValidateCalled = false; in the your activity. Then set that variable to true if your validate() method is called. And then in finally
finally {
//calling method containing validation
getAverageAttentionValue();
// Check whether validate was called,if not,proceed
if(!isValidateCalled)
{
// write data from session to SQLite db
writeToDatabase();
// stop audio
tenSecs.stop();
// declaring activity to open
Intent openActivity = new Intent(
"com.example.brianapp.StroopResults");
openActivity.putExtra("singleScore", single);
// Start activity
startActivity(openActivity);
}
device.close();
}
I finally got my app working, i just have one issue which i would like to correct.
I have a button which controls a thread that runs a couple function in the background. The functions in the background eventually stop the thread whenever a certain value is reached. What i am having issues doing is pressing that same button again to just stop the thread manually. Currently I can only start the thread and wait for itself to finish. I am able to do other things in the app, so the thread is running on its own, i just want to kill it manually.
public void onMonitorClick(final View view){
if (isBLEEnabled()) {
if (!isDeviceConnected()) {
// do nothing
} else if (monitorvis == 0) {
showMonitor();
DebugLogger.v(TAG, "show monitor");
//monitorStop = 4;
Kill.runThread(); // I want a function here that would kill the
// thread below, or is there something that
// can be modified in runThread()?
// I did try Thread.Iteruppted() without luck
shutdownExecutor();
} else if (monitorvis == 1) {
hideMonitor();
DebugLogger.v(TAG, "hide monitor");
monitorStop = 0;
runThread(); //The running thread that works great on its own
}
}
else {
showBLEDialog();
}
}
private void runThread() {
new Thread() {
int i;
public void run() {
while (monitorStop != 3) { //This is where the thread stops itself
try {
runOnUiThread(new Runnable() {
#Override
public void run() {
((ProximityService.ProximityBinder) getService()).getRssi();
rssilevel = ((ProximityService.ProximityBinder) getService()).getRssiValue();
mRSSI.setText(String.valueOf(rssilevel) + "dB");
detectRange(rssilevel);
}
});
Thread.sleep(750);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
On first look, you could simply set monitorStop = 3, which would cause the thread to eventually stop after it's timeout completes.
The problem with this, is that I presume if you push the button again or your code modifies monitorStop at some point in the future, then the thead you wanted dead, might stay alive. ie: monitorStop will need to stay equal to three for at least 750ms to assure the thread will comlete it's loop and die.
The correct way to do this would be to create your thread as a new class with it's own monitorStop parameter. When you create the thread, you would keep a reference to it and modify the thread's monitorStop parameter. This way the thread would finish without interruption. If you wanted to create a new thread, then this would not affect the old thread from finishing appropriately.
i have an rss feed that comes via an XML. There are several events that are returned with information about them. The events are returned with tags...for eg: ....info...
as soon as i encounter tag, i want to update the listview that i am using to show the events.
So the user does not see the loading progress dialog, rather he sees the events getting added to a list.
How do i do this.
thank you in advance.
Here's pseudo codeish example for one way of doing this using SAX parser;
// MyParserThread is assumed to be inner class of Activity here.
private class MyParserThread extends Thread implements MyParserObserver {
private MyParser mParser;
public MyParserThread() {
mParser = new MyParser();
mParser.setObserver(this);
}
public void run() {
try {
// load xml
mParser.parse(xml);
} catch (Exception ex) {
}
}
public void onMyParserEvent(final DataReceivedFromParsing data) {
runOnUiThread(new Runnable() {
public void run() {
// update data to your UI.
}
});
}
public void cancel() {
mParser.cancel();
}
}
And in your parser you're implementing ContentHandler
public void cancel() {
mCancelled = true;
}
public void startElement(....) {
if (mCancelled) {
// If you want to stop Thread from running, all you have to do
// is make parsing stop.
throw new SAXException("Cancelled");
}
....
}
And triggering parsing once your onCreate is called would be;
public void onCreate(...) {
...
mParserThread = new MyParserThread();
mParserThread.start();
...
}
Now this isn't perfect but hopefully gives some idea how to do Thread handling for this purpose. Fundamentally you just have start it, and adding 'cancel' functionality is somewhat more of a bonus - e.g. for cases in which Activity is destroyed while your Thread is running.