I'm doing something like this:
public class MyActivity extends Activity {
#Override
public void onStart() {
super.onStart();
User user = new Entrance().enter();
// continue with user, since it's a valid one
}
}
// I'm using this class from many activities
public class Entrance {
public User enter() {
try {
User user = new Hub().enter();
} catch (AuthenticationFailedException ex) {
this.startActivity(new Intent(this, LoginActivity.class));
this.finish();
}
return user;
}
}
This code doesn't work because finish() doesn't throw an exception and let the execution flow continue. What is the proper design for this scenario?
You could use the Activity.isFinishing() method to seperate your flow of logic.
public void onStart() {
super.onStart();
User user = new Entrance().enter();
// continue with user, since it's a valid one
if (isFinishing()) {
...
} else {
...
}
}
Since you're using the Entrance class from multiple Activities, you should pass the Context of the Activity to the enter() method in the Entrance class. It should be as follows:
public User enter(Context context) {
try {
User user = new Hub().enter();
} catch (AuthenticationFailedException ex) {
context.startActivity(new Intent(context, LoginActivity.class));
context.finish();
}
return user;
}
Try this, first create an intent ,2nd finish the activity start next/desired activity through intent
Intent loginIntent=new Intent(MyActivity.this, LoginActivity.class);
MyActivity.this.finish();
startActivity(loginIntent);
Related
I'm working on an Android app that does the following:
Upon app start-up, it checks if a user is logged in, using AuthStateListener.
If there is a user logged in, it retrieves data from Firestore. The user data is stored in a document that I named with the following nomenclature: "User " + user's_email_ID. For example, if a user has an email ID xyz#gmail.com, his data will be stored in the document named: User xyz#gmail.com.
All documents are within the collection named "Users".
If all the fields are null/ empty in the user's data document, the app opens an Activity that asks him/her to fill all the details. Else, it takes the user to the main page (StudentMainActivity if the user is a student, or ProfessorMainActivity if the user is a professor).
Coming to my problem:
The block of code which checks whether the fields are empty has some erratic and unpredictable behavior. I'm not sure if this is a problem based on Firestore, or on the fact that data retrieval happens on a different thread.
I checked the Firestore database and saw that all fields were filled. However, when a user (who's already logged in) starts the app, the app knows that it is the same user (i.e. he's not prompted to sign in, because AuthStateListener does its job), but instead of being redirected to either StudentMainActivity or ProfessorMainActivity (the main screens), he's asked to fill his details again.
What's more confusing is that this bug doesn't always occur. There are times when the app does what is expected, i.e. take the user to the main screen, but the next time he starts the app, he's again taken to the activity that asks him to enter his details.
Source Code:
LoginActivity.java (Only the relevant parts)
//AuthStateListener is in onCreate
authStateListener = new FirebaseAuth.AuthStateListener() {
#Override
public void onAuthStateChanged(#NonNull FirebaseAuth firebaseAuth) {
FirebaseUser user = firebaseAuth.getCurrentUser();
if (user != null){
UIDEmailID = user.getEmail();
updateUI(user);
}
else{
updateUI(null);
}
}
};
private void updateUI(FirebaseUser user){
// Update UI after login
if (user != null) {
Toast.makeText(LoginActivity.this, "User " + UIDEmailID, Toast.LENGTH_LONG).show();
db.collection("Users").document("User " + UIDEmailID).get()
.addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
if (documentSnapshot.get("department") != null || // if any
documentSnapshot.get("phoneNumber") != null || // field in
documentSnapshot.get("name") != null || // Firestore is
documentSnapshot.get("studentSemester") != null || // non-null then
documentSnapshot.get("dateOfBirth") != null || // proceed to
documentSnapshot.get("university") != null) { // further activities
if (documentSnapshot.get("userType") == "Lecturer/ Professor") {
Intent intent = new Intent(LoginActivity.this, ProfessorMainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
else {
Intent intent = new Intent(LoginActivity.this, StudentMainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
} else {
Toast.makeText(LoginActivity.this, "We need some additional details before we go ahead.", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(LoginActivity.this, GFBDetailsActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Toast.makeText(LoginActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}
}
I'm sorry for the long question; I just tried to make it super descriptive. Some help would be greatly appreciated.
P.S. The reason I think this is a problem involving the usage of multiple threads is because whenever the app runs as expected (i.e. takes the user to the main screen), the toast "We need some additional details before we go ahead." appears as well. If you look at the code (the last "else" block) you will realise that it is in a seperate conditional block altogether, and thus isn't even supposed to show up if the main screen (which is in another conditional block) shows up.
EDIT 1:
I'm enclosing screenshots pertaining to the problem. Ignore the bland UI :P
This is what's expected (Comes under the second 'else' block). It is supposed to show up only if the user is logging in for the first time, i.e. does not have his data stored in a Firestore document.
The background is StudentMainActivity (inside the nested 'else'). However, even the Toast is displayed (it belongs to a seperate block altogether).
So it turns out Firestore wasn't (entirely) at fault.
Every activity in an Android application has a life span, and every time an activity is run, it goes through an elaborate sequence of lifecycle functions.
An activity's lifecycle is as follows:
Launched --> onCreate() --> onStart() --> onResume() --> Running --> onPause() --> onStop() --> onDestroy() --> Finished
I won't be digressing by going into the details of each function, because the function names are quite intuitive and self-explanatory.
As you can see in the code snippet in the question, onAuthStateChanged() is inside onCreate(). My Document ID on Firebase is of the form "User UIDEmailID", where UIDEmailID is the email ID of the user. And UIDEmailID gets updated only in onAuthStateChanged() (which, in turn, is inside onCreate()), i.e. only when the activity starts afresh, after the app has been closed and opened again.
Therefore, I updated UIDEmailID in onStart() as well, which means that every time an app is resumed, it will retrieve the email ID of the user, which can subsequently be used to retrieve the document from Firestore.
Also, I slightly tweaked my Firestore data retrieval bit of code upon advice from Nibrass H. The solution is as follows:
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
running = true;
if (savedInstanceState != null){
running = savedInstanceState.getBoolean("running");
wasrunning = savedInstanceState.getBoolean("wasrunning");
}
setContentView(R.layout.splash_screen);
firebaseAuth = FirebaseAuth.getInstance();
db = FirebaseFirestore.getInstance();
authStateListener = new FirebaseAuth.AuthStateListener() {
#Override
public void onAuthStateChanged(#NonNull FirebaseAuth firebaseAuth1) {
FirebaseUser user = firebaseAuth1.getCurrentUser();
if (user != null){
UIDEmailID = user.getEmail();
updateUI(user);
} else {
updateUI(null);
}
}
};
}
#Override
protected void onStart() {
super.onStart();
firebaseAuth.addAuthStateListener(authStateListener);
if (firebaseAuth.getCurrentUser() != null) {
UIDEmailID = firebaseAuth.getCurrentUser().getEmail();
updateUI(firebaseAuth.getCurrentUser());
} else {
updateUI(null);
}
}
#Override
protected void onRestart() {
super.onRestart();
authStateListener = new FirebaseAuth.AuthStateListener() {
#Override
public void onAuthStateChanged(#NonNull FirebaseAuth firebaseAuth1) {
FirebaseUser user = firebaseAuth1.getCurrentUser();
if (user != null) {
UIDEmailID = user.getEmail();
updateUI(user);
} else {
updateUI(null);
}
}
};
}
#Override
protected void onPause() {
super.onPause();
wasrunning = running;
running = false;
}
#Override
protected void onResume() {
super.onResume();
if (wasrunning){
running = true;
}
}
#Override
protected void onStop() {
super.onStop();
if (authStateListener != null) {
firebaseAuth.removeAuthStateListener(authStateListener);
}
}
private void updateUI(FirebaseUser firebaseUser){
if (firebaseUser != null){
Toast.makeText(this, "User " + firebaseUser.getEmail(), Toast.LENGTH_SHORT).show();
db.collection("Users").document("User " + UIDEmailID).get()
.addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
if (documentSnapshot.get("userType") != null) {
if (documentSnapshot.get("userType").equals("Lecturer/ Professor")){
Intent intent = new Intent(SplashScreenActivity.this, ProfessorMainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
finish();
startActivity(intent);
} else {
Intent intent = new Intent(SplashScreenActivity.this, StudentMainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
finish();
startActivity(intent);
}
} else {
Toast.makeText(SplashScreenActivity.this, "We need some additional details before we go ahead.", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(SplashScreenActivity.this, GFBDetailsActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
finish();
startActivity(intent);
}
}
});
}
}
I have just one Activity , when user close the application (from os clear list of recent apps) I want to send a request to my server api and change user status.
so I make IntentService and call it in my onDestroy() method of activity, but it dosn't work. how do it? is there any way else to do this(send request to server before application killed compeletly)?
my code :
Activity:
#Override
protected void onDestroy() {
Intent intent = new Intent(this, MakeOfflineIntentService.class);
intent.putExtra(Variables.INTENT_TOKEN, Token);
intent.setAction("ACTION_MAKE_OFFLINE");
startService(intent);
super.onDestroy();
}
and in my IntentService:
public class MakeOfflineIntentService extends IntentService {
private static final String ACTION_MAKE_OFFLINE = "ACTION_MAKE_OFFLINE";
private static final String EXTRA_TOKEN = Variables.INTENT_TOKEN;
public MakeOfflineIntentService() {
super("MakeOfflineIntentService");
}
public static void startActionFoo(Context context, String param1) {
Intent intent = new Intent(context, MakeOfflineIntentService.class);
intent.setAction(ACTION_MAKE_OFFLINE);
intent.putExtra(EXTRA_TOKEN, param1);
context.startService(intent);
}
#Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
final String action = intent.getAction();
if (ACTION_MAKE_OFFLINE.equals(action)) {
final String param1 = intent.getStringExtra(EXTRA_TOKEN);
retrofitBaseInformationChange(param1,Variables.OFFLINE,1);
}
}
}
private void retrofitBaseInformationChange(final String Token, final int online, int vehicle){
RetrofitCallServer retrofitCallServer = new RetrofitCallServer(WebServiceUrls.RETROFIT_INFORMATION_CHEETAH_MAN);
OnCallBackRetrofit onCallBackRetrofit = retrofitCallServer.getResponse();
Call<OBRbaseInfromationChange> call = onCallBackRetrofit.askBaseInformationChange(Token,online,vehicle);
call.enqueue(new Callback<OBRbaseInfromationChange>() {
#Override
public void onResponse(Call<OBRbaseInfromationChange> call, Response<OBRbaseInfromationChange> response) {
/*response gotten maybe success or not*/
if (response.isSuccessful()){
OBRbaseInfromationChange obr = response.body();
if(obr.code == 200){
Log.i(Variables.APP_TAG,"BaseInformationChange successful");
}
else{
Log.i(Variables.APP_TAG,"BaseInformationChange error code: "+obr.code);
}
}// end if response successful
else {
Log.i(Variables.APP_TAG,"BaseInformationChange not Successful: "+response.code());
}
}
#Override
public void onFailure(Call<OBRbaseInfromationChange> call, Throwable t) {
/*our request not sent or conversion problem*/
Log.i(Variables.APP_TAG,"onFailure BaseInformationChange: "+t.getMessage());
}
});
}
// end retrofitBaseInformationChange()
}
and finally here is in my manifest:
<service
android:name=".Services.MakeOfflineIntentService"
android:exported="false"
android:stopWithTask="false"/>
Have you tried to return START_STICKY in the onStartCommand override?
After you sent your request you can then call stopService to stop yourself.
As far as I know, even sticky services might be "recreated" when you kill the app. So maybe, an Intent is not the best way to use here.
I'd go with SharedPreferences here:
The onCreate of your app sets the key "app_offline" to "false"
The onDestroy sets this key to "true" and starts the service
The service is START_STICKY and when it finds the "app_offline" as true, sends its request, updates "app_offline" to false (resets it) and then performs a self-shutdown.
Something like that.
Hope this helps, cheers, Gris
thanks for Grisgram answer, I solve the issue and paste my code here for more complete answer :
I make a variable in SharedPreferences name IS_APP_CLOSED.
when application open in onCreate :
saveL.saveInLocalStorage(Variables.IS_APP_CLOSED,false);
startServiceToMakeOffline();
method startServiceToMakeOffline() is :
private void startServiceToMakeOffline(){
Intent intent= new Intent(this, MakeOfflineService.class);
startService(intent);
}
in onDestroy of this activity :
#Override
protected void onDestroy() {
saveL.saveInLocalStorage(Variables.IS_APP_CLOSED,true);
super.onDestroy();
}
and here is my service class :
public class MakeOfflineService extends Service {
private boolean isAppClosed = false;
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
loadInfoFromLocalStorage();
if(isAppClosed){
askServer();
}
return Service.START_STICKY;
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
private void loadInfoFromLocalStorage() {
SharedPreferences prefs = getApplicationContext().getSharedPreferences(Variables.CHEETAH_NORMAL, 0);
isAppClosed = prefs.getBoolean(Variables.IS_APP_CLOSED, false);
prefs = null;
}
// end loadInfoFromLocalStorage()
private void askServer() {
//TODO: request server than when result gotten:
stopSelf();
}
}
and here is my manifest :
<service
android:name=".Services.MakeOfflineService"
android:stopWithTask="false"/>
When trying to get the user object in a cloude-code function, the request.user object is null but only when called from a certain activity. The app consists of three acitvities: Login, main, and the activity calling the function. If I call it from the main activity, request.user gives us the user as expected, but it is null when called from the third activity.
The login code: Starts the main activity on success.
ParseUser.logInInBackground(uName, uPassword, new LogInCallback() {
#Override
public void done(ParseUser parseUser, ParseException e) {
if (parseUser != null) {
Toast.makeText(getApplicationContext(), "Success!", Toast.LENGTH_LONG).show();
if (ParseUser.getCurrentUser().isAuthenticated()) {
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
startActivity(intent);
finish();
}
}
}
});
This code runs fine, we get logged in and sent onwards.
The main activity code is way too long to post here, and the relevant part is just "statActivity(intent);", but if anyone is curious: Pastebin
The function calling the auth function. When running from main, request.user becomes the user, when running from the third activity (contaning only this function and basic android stuff), it's suddenly null.
public void onNewRandomGameClick(View v) {
ParseCloud.callFunctionInBackground("testUserAuth", new HashMap<String, Object>());
finish();
}
Lastly, the serverside code. Again, works from one activity and not the other.
Parse.Cloud.define("testUserAuth", function(request, response) {
console.log("Testing user authencation...");
if(request.user){
response.success();
console.log("We are authorized");
} else {
response.error("Authentication failed");
}
});
I'm using finish() to end a thread in my SplashScreen activity and when I do the app gets minimized before going to Activity2. How can I end the SplashScreen activity and go to Activity2 without calling finish() so the app won't minimize?
public class SplashScreen extends Activity {
/**
* The thread to process splash screen events
*/
private Thread mSplashThread;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Splash screen view
setContentView(R.layout.splash);
final SplashScreen sPlashScreen = this;
// The thread to wait for splash screen events
mSplashThread = new Thread(){
#Override
public void run(){
try {
synchronized(this){
// Wait given period of time or exit on touch
wait(5000);
}
}
catch(InterruptedException ex){
}
finish();
// Run next activity
Intent intent = new Intent();
intent.setClass(sPlashScreen, MainActivity.class);
startActivity(intent);
//stop();
}
};
mSplashThread.start();
}
/**
* Processes splash screen touch events
*/
#Override
public boolean onTouchEvent(MotionEvent evt)
{
if(evt.getAction() == MotionEvent.ACTION_DOWN)
{
synchronized(mSplashThread){
mSplashThread.notifyAll();
}
}
return true;
}
}
call finish after startActivity
mSplashThread = new Thread(){
#Override
public void run(){
try {
synchronized(this){
// Wait given period of time or exit on touch
wait(5000);
}
}
catch(InterruptedException ex){
}
// Run next activity
Intent intent = new Intent();
intent.setClass(sPlashScreen, MainActivity.class);
startActivity(intent);
//call finish after startActivity
finish();
//stop();
}
};
mSplashThread.start();
I do it like this:
private class IntentLauncher extends Thread {
#Override
/**
* Sleep for some time and than start new activity.
*/
public void run() {
try {
// Sleeping
Thread.sleep(SLEEP_TIME*1000);
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
// Start main activity
Intent intent = new Intent(SplashActivity.this, MainActivity.class);
SplashActivity.this.startActivity(intent);
SplashActivity.this.finish();
}
}
The client code for binding to a service, which is normally in the activity class; I'm trying to move it to the service class, so that the activity class would be as clean and small as possible.
i.e. basically trying to merge the code in the second box here into the first box = as much of it into the service class as possible
Single Line in Activity for Binding to Service
public class MyActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Bind to service with this line only:
AService.bindService(this);
}
}
Static bindService and ServiceConnection Moved to Service
public class AService extends Service {
public String test = "I want to see this";
public static AService aService;
private static boolean isBound;
private static Context context;
// ... IBinder, onBind etc also here on service side
public static void bindService(Context context) {
try {
Log.i(TAG, "bindService Start");
if (!isBound && context != null) {
Log.i(TAG, "Binding");
context.bindService(new Intent(context, AService.class),
serviceConnection, Context.BIND_AUTO_CREATE);
isBound = true;
Log.i(TAG, "Bound");
}
} catch (Exception e) {
Log.e(TAG, "bindService", e);
}
}
private static ServiceConnection serviceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
try {
Log.i(TAG, "onServiceConnected Start");
aService = ((AService.LocalBinder) service).getService();
if (aService != null)
Log.i(TAG, aService.test);
Log.i(TAG, "onServiceConnected Finish");
} catch (Exception e) {
Log.e(TAG, "onServiceConnected", e);
}
}
public void onServiceDisconnected(ComponentName className) {
try {
Log.i(TAG, "onServiceDisconnected");
aService = null;
} catch (Exception e) {
Log.e(TAG, "onServiceDisconnected", e);
}
}
};
public static void unbind() {
try {
Log.i(TAG, "unbind start");
if (isBound && context != null) {
Log.i(TAG, "Unbinding");
context.unbindService(serviceConnection);
isBound = false;
context = null;
Log.i(TAG, "Unbound");
}
} catch (Exception e) {
Log.e(TAG, "unbind", e);
}
}
}
But onServiceConnected is Never Called?
The log shows everything up to:
...
Bound
But NOT onServiceConnected Start or beyond
and no exceptions.
Note that when the same code was in the Activity, it works (when called with MyActivity.this)
What am I doing wrong?
Is this
AService.bindService(this);
much better than this?
bindService(new Intent(context, AService.class),
serviceConnection, Context.BIND_AUTO_CREATE);
And does ServiceConnection implementation sit in Activity really annoying your so much? I doubt that.
I don't see any point centralize everything into Service and then call a static method in the actual Service to start this Service from Activity. The best practice is to follow the standard way that Google's recommended to do things, by doing this in your way, you make your code obscure and confuse other people when reading your code (if you work in a team). It doesn't make any sense IMO.
Instead of put all your effort into isolate every single bit of service from activity, I would rather consider more on how to isolate business logic from activity and centralize them into service, and let Activity mostly focus on UI stuff.
Really hope that would help you.