My MainActivity on my Android application checks if the user is logged in (this is stored in SharedPreferences) and if it's not takes the user to the LoginActivity. I am trying to test this using the following code
public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
private static final int TIME_OUT = 5000; /* miliseconds */
private MainActivity mMainActivity;
private Instrumentation mInstrumentation;
private SharedPreferences mLoginPrefs;
public MainActivityTest() {
super(MainActivity.class);
}
protected void setUp() throws Exception {
super.setUp();
setActivityInitialTouchMode(false);
mMainActivity = getActivity();
mInstrumentation = getInstrumentation();
mLoginPrefs = mInstrumentation.getTargetContext().getSharedPreferences(LoginActivity.PREFS_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = mLoginPrefs.edit();
// User is not logged in, so it should be redirect to LoginActivity
editor.putBoolean("logged_in", false);
editor.commit();
}
//...
public void testC_OpenLoginActivityIfUserIsNotLoggedIn() {
ActivityMonitor monitor = mInstrumentation.addMonitor(LoginActivity.class.getName(), null, false);
Activity nextActivity = mInstrumentation.waitForMonitorWithTimeout(monitor, TIME_OUT);
assertNotNull(nextActivity);
nextActivity.finish();
SharedPreferences.Editor editor = mLoginPrefs.edit();
// Login the user so we can continue the tests
editor.putBoolean("logged_in", true);
editor.commit();
}
But this doesn't work, the LoginActivity opens but waitForMonitorWithTimeout never returns so I got stuck on LoginActivity (I need to get back to MainActivity to do the other tests).
A code similar to this SO Question works for Button clicks, but this Activity is not loaded by any click so I am thinking maybe there is no time to the monitor to work.
I just need a way to get the actual Activity so I can make an assert and make it finish to continue my tests.
Just one other thing: I would prefer a method without using Robotium if it's possible.
In order to solve your problem, first take a look at the two most important methods for your test:
Instrumentation#addMonitor(java.lang.String, android.app.Instrumentation.ActivityResult, boolean)
Instrumentation.ActivityMonitor#waitForActivity()
According to Android API reference:
addMonitor
Add a new Instrumentation.ActivityMonitor that will be checked whenever an activity is started. The monitor is added after any existing ones; the monitor will be hit only if none of the existing monitors can themselves handle the Intent.
waitForActivity
Block until an Activity is created that matches this monitor, returning the resulting activity.
Now let's make it a bit more clear.
addMonitor should be called always before the expected activity being started, never too late.
waitForActivity should be called only after the expected activity being started, never too early, since it will block.
Back to your code:
You're calling both of them together, without any magic happening in between. So it's either too late for addMonitor, or too early for waitForActivity.
ActivityMonitor monitor = mInstrumentation.addMonitor(LoginActivity.class.getName(), null, false);
Activity nextActivity = mInstrumentation.waitForMonitorWithTimeout(monitor, TIME_OUT);
If it's too early for calling waitForActivity, it will block and fail until the timeout (because the expected activity is not hit yet), and you would never see the expected activity being started.
If it's too late for calling addMonitor, the monitoring starts after the expected activity is launched, and the expected activity is not launched again since then, so waitForActivity will block because of no hit of the monitor.
So the difference between the two cases is whether the expected activity is started or not. And for your case, I think it's too late for calling addMonitor.
The solution is very easy: just move addMonitor to a early enough position before your LoginActivity starts, maybe move it to the setUp method, like this:
mInstrumentation = getInstrumentation();
ActivityMonitor monitor = mInstrumentation.addMonitor(LoginActivity.class.getName(), null, false);
BTW, for your case, with timeout or without timeout doesn't matter.
Don't forget to remove the monitor after it's not needed anymore e.g:
#Override
protected void tearDown() throws Exception {
mInstrumentation.removeMonitor(monitor);
super.tearDown();
}
public static Activity getCurrentActivity() {
final Activity[] currentActivity = {null};
getInstrumentation().runOnMainSync(new Runnable() {
public void run() {
Collection resumedActivities = ActivityLifecycleMonitorRegistry.getInstance()
.getActivitiesInStage(RESUMED);
if (resumedActivities.iterator().hasNext()) {
currentActivity[0] = (Activity) resumedActivities.iterator().next();
}
}
});
return currentActivity[0];
}
You were on the right track. The method you need to call is:
monitor.waitForActivityWithTimeout(TIME_OUT);
instead of
mInstrumentation.waitForMonitorWithTimeout(monitor, TIME_OUT);
Related
I need to show an Activity each time a user is inactive for X amount of time. I am trying to achieve that with a custom CountDownTimer, which starts onUserInteraction in my BaseActivity:
#Override
public void onUserInteraction() {
super.onUserInteraction();
inactivityTimer.cancel();
inactivityTimer.start();
}
In my custom CountDownTimer, I start the desired Activity onFinish:
#Override
public void onFinish() {
BaseActivity baseActivity = new BaseActivity();
Log.i("TIMER ENDED: ", "NOW STARTING LOCKACTIVITY");
baseActivity.showLock();
}
And this is my showLock() method in BaseActivity
public void showLock() {
Intent intent = new Intent(getApplicationContext(), LockActivity.class);
startActivity(intent);
}
What I'm getting is a NPE every time the timer ends. (java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Context.getPackageName()' on a null object reference) even though I tried using getApplication().getBaseContext(), this, this.getBaseContext(), this.getApplicationContext(), getApplicationContext()and
getBaseContext() instead of getApplicationContext()
However, if I set the Context in the method call like this:
#Override
public void onFinish() {
BaseActivity baseActivity = new BaseActivity();
Context context = MyApplication.getInstance().getApplicationContext();
Log.i("TIMER ENDED: ", "NOW STARTING LOCKACTIVITY");
baseActivity.showLock(context);
}
And this in showLock():
public void showLock(Context context) {
Intent intent = new Intent(context, LockActivity.class);
startActivity(intent);
}
This time I get another NPE (java.lang.NullPointerException: Attempt to invoke virtual method 'android.app.ActivityThread$ApplicationThread android.app.ActivityThread.getApplicationThread()' on a null object reference).
So my question is, how do I get a proper Context every time my showLock() is called from the current Activity, which will be calling that method?
Important note: each and every Activity in my project inherits BaseActivity, which on its own inherits AppCompatActivity.
EDIT
I gave Marcin's suggestion a try and after dealing with a couple of errors I ended up using his approach. If someone else is curious and wants to know how to open an activity after X amount of inactivity this worked for me:
Since all my Activities inherit one main BaseActivity I put there a custom Handler, which holds a WeakReference to said BaseActivity. I also overrode handleMessage, where I call my desired method:
private static class InactivityHandler extends Handler {
private WeakReference<BaseActivity> baseActivityWeakReference;
private InactivityHandler(BaseActivity baseActivity) {
baseActivityWeakReference = new WeakReference<>(baseActivity);
}
#Override
public void handleMessage(Message msg) {
BaseActivity baseActivity = baseActivityWeakReference.get();
if (baseActivity != null) {
baseActivity.showLock();
}
}
}
and in onUserInteraction send a Message to the queue after some time:
#Override
public void onUserInteraction() {
super.onUserInteraction();
inactivityHandler.removeMessages(MESSAGE_WHAT, MESSAGE_TOKEN);
inactivityHandler.sendMessageDelayed(inactivityHandler.obtainMessage(MESSAGE_WHAT, MESSAGE_TOKEN), DELAY_TIME);
}
And for the curious, here is my showLock method:
public void showLock() {
Intent intent = new Intent(this, LockActivity.class);
startActivity(intent);
}
From you description I assume that after the user is inactive for some time your app needs to present a lock screen where the user needs to reenter their credentials.
Unless the whole scenario has any counting involved (for example you display an actual count down), a CountDownTimer may not be the best to perform this task.
Instead you could use a Handler. In Android, the Main Thread has it's associated message queue. Handlers are able to post messages to this queue to receive them later, at the given time.
Your example implementation could look like that:
private static class LockScreenHandler extends Handler {
private WeakReference<BaseActivity> activityRef;
public LockScreenHandler(BaseActivity activity) {
activityRef = new WeakReference<>(activity);
}
#Override public handleMessage(Message msg) {
BaseActivity activity = activityRef.get();
if (activity != null) {
activity.showLock();
} // Otherwise the activity got destroyed in the meantime
}
}
You may send either Runnables or Messages with the Handler. In our case a Message is perfectly fine. Therefore in your Base Activity you may have some Message-related fields:
private static final int MESSAGE_WHAT = 1;
private static final Object MESSAGE_TOKEN = new Object();
And then you use your handler in onUserInteraction:
#Override public void onUserInteraction() {
super.onUserInteraction();
handler.removeMessages(MESSAGE_WHAT, MESSAGE_TOKEN);
handler.postDelayed(handler.obtainMessage(MESSAGE_WHAT, MESSAGE_TOKEN), INACTIVITY_DELAY);
}
If you decide to follow your CountDownTimer solution you may follow the same technique, by creating a static inner class and giving your activity in the constructor.
Whichever way you go, it is important to note, that your BaseActivity can be destroyed by the system and improper usage of Handler (and CountDownTimer which internally relies on Handler) can prevent the reference to this activity from being destroyed and therefore lead to a memory leak. Therefore:
If you use a Handler or a CountDownTimer as an inner class, make sure it is static. Non-static inner classes hold a reference to their enclosing classes. Messages hold references to their target Handlers, so as long as the message is in the queue it's handler cannot get destroyed.
Use a WeakReference to hold your activity for the same reason as outlined above. WeakReferences are cleared if nothing else holds a reference to the given object.
An Activity is a Context itself. So just use this within an Activity.
public void showLock() {
Intent intent = new Intent(this, LockActivity.class);
startActivity(intent);
}
If this is not an option because you are overriding a function you should use MainActivity.this (when the MainActivity is the name of your activity)
MainActivity.this.startActivity(MainActivity.this, ...);
Okay, this might be stale, but i really need to understand what the best practice will be and not how to easily bypass this either by disabling screen orientation or any other means.
I have a login screen and when the user clicks on login button it should go to the server and authenticate and return a response.
My problem is if the screen rotates my fragment might not receive a callback of the response data.
I'm trying out an MVP design pattern on android.
public void registerSignInEvent(){
this.signInBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String username = usernameEdit.getText().toString();
String password = passwordEdit.getText().toString();
authPresenter.loginUser(username, password, 1);
}
});
}
I've thought of the following...
Use a service to handle the login to the server, when its done the service updates the storage e.g is_login=false or true then use a LocalBroadcastManager to broadcast the event to the view(Fragment)
so it can query the presenter to know the login state.
Use a Fragment with setRetainIntance(true); to handle the presenter initialization and the presenter will trigger callback to methods of the activity e.g onLoginSuccess //confusing myself
Problem
A. the problem with my no.1 thought is that when my loginFragment is onPause at that moment, the broadcast receiver is unregistered, so it might not receive the event. plus i don't even know if it makes sense.
B. Its looks complicated with MVP pattern
The pattern really might not matter, i don't really need code snippet tho, I just need to understand the process that best fits the situation.
NOTE: My Presenter communicates with the view(fragment/activity) via the view interface, vice-versa.
You could try storing the user in the database/sharedprefs whenever you receive the response from the login, if a rotation occurs and the login-fragment gets reattached without receiving the necessary callbacks (which is the problem you're describing) you could add a check if the user is "already" logged in (by checking if the user exists in the db/sharedprefs in onResume of the loginactivity) and forward the user to the next activity or fragment from there.
First of all I use this cool method to keep presenter alive even if activity recreated: Presenter surviving orientation changes with Loaders. It detaches and attaches activity in onStop and onStart.
Need to mention also, that your second choice with persistent fragment in widely used, e.g. by Fernando Cejas. I've learned clean architecture approach with his articles, and he uses setRetainState(true).
And still your question is driving me crazy as well. Only solution I've found so far is ugly as hell. But it should work. Idea: after work done, I check if view is attached. If so, I proceed normally. I there is no view, that we are in the middle of rotation. So I have flag, that indicate, that work is done. I turn it on. Also I cache any needed data. And wait for the next view attaching. Where I check that flag.
Here is my code snippet. I'm not proud of it thought.
class SplashPresenter extends BasePresenter<SplashView> {
private final SplashInteractor splashInteractor;
private boolean isSplashWorkStarted;
private boolean isSplashWorkFinished;
private boolean isSplashWorkError;
private Throwable splashWorkError;
#Inject
SplashPresenter(SplashInteractor splashInteractor) {
this.splashInteractor = splashInteractor;
}
#Override
public void attachView(SplashView mvpView) {
super.attachView(mvpView);
if (isSplashWorkFinished) {
getMvpView().showApplicationUi();
} else if (isSplashWorkError) {
getMvpView().showError(splashWorkError.getMessage());
}
}
void executeSplashWork() {
if (!isSplashWorkStarted) {
splashInteractor.execute(new SplashInteractorSubscriber());
isSplashWorkStarted = true;
}
}
#Override
public void onDestroyed() {
splashInteractor.unsubscribe();
}
private final class SplashInteractorSubscriber extends Subscriber<Void> {
#Override
public void onCompleted() {
if (isViewAttached()) {
getMvpView().showApplicationUi();
} else {
isSplashWorkFinished = true;
}
}
#Override
public void onError(Throwable e) {
if (isViewAttached()) {
getMvpView().showError(e.getMessage());
} else {
isSplashWorkError = true;
splashWorkError = e;
}
}
#Override
public void onNext(Void v) {
}
}
}
I am developing an Android application and I need show remind dialog from time to time in according to variable from shared preferences. If user does not want to see reminder message, he can disable this option also in my PreferenceActivity. I am using AsyncTask class to show remind message to user from time to time . User can press options button at every moment to go to PreferenceActivity to enable/disable reminder option and set pause time value between reminds, and the go back to activity. So I am checking all values in onResume method. And if users does not want to see remind messages I need to finish current working AsyncTask, or if user changed value between reminds, I need to restart current AsyncTack with new pause value. But I see an unpredictable behavior of my AsyncTack: sometimes it stops, sometimes not and continue working and showing message)))), sometimes it works and sometimes - not))))). Here is a piece of code:
This is my AsyncTack class
private class ReadReminderTask extends AsyncTask<Long, Void, Void> {
private long mRemindTime;
private volatile boolean running = true;
public ReadReminderTask(){
}
#Override
public Void doInBackground(Long... params){
mRemindTime = params[0];
while (running){
try{
Thread.sleep(mRemindTime);
publishProgress();
}
catch (Exception e){
e.printStackTrace();
}
}
return null;
}
#Override
protected void onProgressUpdate(Void... progress) {
super.onProgressUpdate(progress);
// showReminder is a method where I show remind message
showReminder();
}
#Override
protected void onCancelled() {
running = false;
}
}
This is onResume method of my Activity where I need to show reminder message:
if(!settings.getBoolean("needToRemind", false)) {
mReadReminderTask.cancel(true);
} else if(settings.getBoolean("needToRemind", false)) {
mReadReminderTask = new ReadReminderTask();
mReadReminderTask.execute(settings.getLong("timeRemind", 1));
}
Can anyone help me with my problem?
That's really not such a great way to do that. The "unpredictable behavior" happens because you leave the task running even after your Activity is stopped. The call to cancel in your onResume is not only too late, it actually is not doing anything at all.
Have a look at Handler.postDelayed. You want something like this:
In your onCreate
hdlr = new Handler();
reminder = new Runnable() { public void run() { showReminder(); } }
EDITED TO ADD ESSENTIAL 2ND PARAM TO postDelayed
... in your onResume:
hdlr.postDelayed(reminder, mRemindTime);
... and, in your onPause:
hdlr.removeCallbacks(reminder);
Please note that AsyncTask.cancel() does not really cancel running task. Instead it only sets a flag to notify that cancel has been requested. It is up to AsyncTask implementation if and when to take this flag into account.
Your current code does not check for cancellation after Thread.sleep returns. Correct implementation would be something like:
public Void doInBackground(Long... params){
mRemindTime = params[0];
while (!isCancelled()){
try{
Thread.sleep(mRemindTime);
if(!isCancelled())
publishProgress();
}
catch (Exception e){
e.printStackTrace();
}
}
return null;
}
Also attribute 'running' and onCancelled() method in your asynctask are not needed. You can use method 'isCancelled()' instead.
It's generally a bad solution. AsyncTasks are not great in handling config changes.
Depending on how long the delays are, you can use AlarmManager.setInexactRepeating in order to show these dialogs. You can create new PendingIntent using PendingIntent.getService or PendingIntent.getBroadcast depending on how you'd like to implement showing dialog – (from Service you will be able to show only system-like dialogs).
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent intentStart = new Intent();
intentStart.setAction(SHOW_DIALOG_ACTION);
PendingIntent pendingIntentStart = PendingIntent.getBroadcast(context, 0, intentStart, 0);
And register a BroadcastReceiver which will handle showing the dialog.
If the delays are not long, you can create a custom ThreadPoolExecutor which will do what you want.
I don't think AsyncTask should be used for that. Thread.sleep(mRemindTime) is also very ugly! If you need a timer use CountDownTimer.
this is my current code
public class ButtonActivity extends Activity {
int count = 0;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
final Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
button.setText("Got Pressed:" + ++count);
}
});
}
}
My question why after each call of onDestroy() and the subsequent call of onCreate(), the count gets reset. I looked up on onDestroy() and saw "that is there to let your app have a final chance to clean things up before the activity does get destroyed but it does not mean that the activity will be called" My initial idea was that count is an instance variable and that onCreate() creates a new instance of my class ButtonActivity. Can anyone confirm or refute my intial thoughts ?
If you don't want it reset - make it static: currently the counter is per object
Making count static will solve your problem. (because static member don't belong to a particular instance)
But to be more precise : onCreate don't create a new instance of your activity. In fact : Android-OS create a new instance and give you a chance to initialize it by calling onCreate(...).
And, more generally : you can be sure that onCreate() will never be called twice on the same instance: if you see that onCreate() is called it's because Android-OS has just created a new instance of your Activity (it can be done for many reason, like screen rotation, ...).
More about activity lifecycle here (probably one of the most important concept to understand when you do Android development !)
Storing static data in your activity class, as was suggested here, is a bad practice
and should be avoided.
Why?
Because Android may destroy background activities TOGETHER with their static data
whenever it feels resources are running low. Thus you may declare static fields inside you activity, but you will not get the static behavior you aim for,
What can you do instead:
Option 1:
Create a custom Application class and place your static data there. You will
not even have to declare it as static since Application obj is a singleton and is
guaranteed to stay in memory for as long the the app is alive.
public class MyApplication extends Application {
private DataClass mydata;
// probably with a getter & setter
}
Remember to declare your activity in the manifest:
<application
android:name="mypackage.MyApplication" <--------
.....
Option 2: Serialize your data to shared preferences. This is what they are for.
I'm working with a fairly common situation right now - download some data over the web, then update a view to display it. Clearly, I want to do the web download in the background, and then update the view on the main UI thread. Now looking at my code, I'm a little worried about my Activity and its UI elements being killed off before I update them. Here's the essence of what I have in mind:
Thread update = new Thread() {
public void run() {
final Data newData = requestData();
if (newData != null) {
post(new Runnable() {
public void run() {
Toast.makeText(MyClass.this, "I'll do things here that depend on my context and views being valid", Toast.LENGTH_SHORT).show();
}
});
}
}
};
update.start();
It seems possible that while I'm downloading data, the activity may be destroyed. What happens then? Will my thread continue to execute? Will I end up trying to access dead objects?
Usually I do this by AsycTask, but the work seemed simple enough this time to just inline the threads-launching-threads stuff. Will I make things any better by using an AsyncTask instead?
If your Context is an Activity, you can check if it is finishing or has finished with the isFinishing() method:
if ( context instanceof Activity ) {
Activity activity = (Activity)context;
if ( activity.isFinishing() ) {
return;
}
}
Toast.makeText(context, "I'll do things here that depend on my context and views being valid", Toast.LENGTH_SHORT).show();
What you really want to use is an AsyncTaskLoader. These are my new favorite classes in the Android API. I use them all the time and they were made to solve problems just like this. You won't have to worry about when to stop your download or anything like that. All the threading logic is taken care of for you, including telling the thread to stop if the activity has been closed. Just say what it is you want to do in the loadInBackground() method. Note that if you are developing for an API lower than 3.0, you can still access all the loaders via the Android Support Package.
If you use anonymous classes, they will have an internal reference to the outer class, so it's not like it becomes inaccessible all of a sudden because other references have been cleared. AsyncTask actually doesn't change anything, it uses similar mechanics for notifying about results.
You can use loaders, they are designed to be in sync with the activity lifecycle. They are available only since Android 3.0, but you can use support package to work with them on any device with 1.6 or later.
There is even a simpler solution, you can just use a boolean field which indicates whether activity has gone away. You should set this field in onPause() (or whenever you think you won't need the notifications anymore) and check for it when you show toast. You won't even have to use synchronization, since this field is confined to the main thread, so it's absolutely safe. By the way, if you change this field somewhere else than in onDestroy(), don't forget to add a statement which resets your field back in the counterpart method.
public class MyActivity extends Activity {
private boolean activityDestroyed = false;
#Override
protected void onDestroy() {
activityDestroyed = true;
}
private void updateData() {
new Thread() {
#Override
public void run() {
final Data newData = requestData();
if (newData == null) return;
runOnUiThread(new Runnable() {
public void run() {
if (activityDestroyed) return;
Toast.makeText(MyActivity.this, "Blah",
Toast.LENGTH_SHORT).show();
}
});
}
}.start();
}
}
I usually use Weak Reference to avoid leaking context in views
Weak Reference for Context
private var mContext: WeakReference<Context?>? = null
Assign Context
mContext = WeakReference(appContext)
Get Context
mContext .get()
Validate Context
if (mContext?.get() is Activity &&
(mContext?.get() as Activity).isFinishing){
return
}
Kurtis is right. However, if you REALLY want to keep it simple, you can try this:
class MyActivity extends Activity {
static MyActivity context;
#Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
MyActivity.context = this;
}
#Override
public void onDestroy() {
super.onDestroy();
MyActivity.context = null;
}
}
And then you just use MyActivity.context in your class (and check for null there). If you want the toast to not even show up when your app is in the background, use onPause/onResume instead.
Again, this is the quick and lazy approach. AsyncTask or AsyncTaskLoader is how you should be doing things.