In my app I have a mainactivity that implements authstatelistener, and redirects the user to a login screen if the state is changed (user signs out or timed out). In the app I have a total of about 6 other activities, which contain an navhostfragment container of many fragments.
The purpose of the activities to is to collect user data and send it to firebase.
I am confused if I need to implement authstatelistener in all these activities as well. If I do not do this, will the UI not respond to the user being signed out (and will this happen if the main activity remains in the history stack?).
I read a few questions on this and have found very different answers for different use cases. Currently I am not doing this, and my app runs just fine, but I have no clue if this will cause a bug in the future.
If you implement the auth state listener on the level of activities, then indeed you'll need to implement it in each activity. You'll typically want to implement it in a base-class in that case, and derive all your concrete activities from that.
Related
I was advised to do, in each Activity and Fragment onCreate callback, an if(signed_user_id == null) { showErrorPopIn(); startSplashScreenActivity(); return; } (a check that the user is not logged in, in which case we display an error and redirect the user from the Android app to the splash screen, and we don't execute the rest of the Activity and Fragment code). This code would be used in all activities (except SplashScreen of course) and in all Fragments, in the onCreate method. Cf. another SO question (Check if user is logged in on every activity, or only at the beginning?).
But I find it makes the code a lot heavier, and it seems to be useless. Indeed:
A hacker can change the activity code and the fragment code and therefore remove this condition, right?
And most importantly: checking that the user is logged in is only useful on the server side, not on the Android app side. Indeed, the critical points are the read and write accesses to the databases. But I don't give a damn if the not-connected user can see the graphical part of a Fragment or an Activity if this part doesn't display anything from a database or doesn't allow any modification in a database after a click on a button for example. Right?
So in the end I can remove all those if(signed_user_id == null) conditions I've put in all my Fragments and Activities?
PS 1 : signed_user_id is just FirebaseAuth::getInstance().getUser().getUId() roughly.
PS 2 : if I'm right, it means the first answer of the above linked SO question should be edited to indicate to the original poster that it's useless to do these checks in Android app side.
The problem is in this statement you make:
And most importantly: checking that the user is logged in is only useful on the server side, not on the Android app side.
There are two reasons to check whether a user is authenticated and/or authorized:
To reject unauthorized operations. This check should always happen on the server, as malicious user could bypass the check if (only) implemented on the client.
To show the correct authentication/authorization state to the user, and to allow them to change it (within the rules of your application). These checks happen on the client.
The goal of the second type of check is to show your user a clear UI, and provide them a path forward.
For example, you'll typically want to detect if the user is signed in, and if not show them the sign-in screen of your app. If you don't do this, the first time they know something is wrong is when the database rejects their operation with a "access denied" or similar message. This latter flow may be correct for your app, but it is very common to detect sign-in state on start-up and then first get the user to sign in (either anonymously or identified).
Another example could be if you have a premium user level who are allowed to write more data to your database. Of course the actual check of whether a write is allowed should be done server-side. But you might want to update the UI in some way if a non-premium user has reached their limit, for example disabling the UI elements that would update the database (and thus fail), or maybe show them a "you've reached the limits of your plan, click here to upgrade" banner.
So neither the server-side check nor the client-side check is useless, but they serve different purposes. Server-side checks are required to ensure your data stays uncorrupted, and your business rules are followed. Client-side checks are recommended to give your users a better (UI) experience.
I think this will be much easier if you are using BaseActivity and BaseFragment that all activities and fragment extends from it so you will write this code in BaseActivity/BaseFragment oncreate method only.
I'm trying to implement the MVP architecture in my app.
However, after reading some blogs and viewing some sample project samples, I'm not sure I completely understood where is the right place to detach the view, and what should be done once the view attached for the second time after an async operation.
Most of the examples I saw, just sum it all up with a view's null validation check after an async call.
I'll try to make my point clear with an example - Login/Registration by phone number (The main idea is the important thing, and not the example itself)
There is an activity which display a fragment - LoginFragment.
The user enters his phone number and tries to login.
If the user exits - he should get navigated to another activity (after entering the code received by sms..)
If the user doesn't exits, he should get navigated to registration process - RegistrationFragment.
If there was an error, a dialog with error message should appear, ErrorDialogFragment.
Now, in a happy flow where the user presses the login button and waits until the process complete, all good.
But, in a less happier flows (not so frequent ones, but definitely can't get ignored), the user presses the login button and after that presses the home button or alternatively gets a phone call.
In scenario 1, where we attach/detach the view in onCreate/onDestroy, once the async login operation finish and we should replace to RegistrationFragment or show ErrorDialogFragment, there is a chance we will meet the famous IllegalStateException:
getting exception "IllegalStateException: Can not perform this action after onSaveInstanceState"
In scenario 2, where we attach/detach the view in onResume/onPause, once the async login operation finish we won't be able to replace fragment or show a dialog because the view is already detached.
In this case, I'm not sure what is the right thing to do.
Should we go with scenario 1 and commit the transaction with commitallowingstateloss?
I'm afraid it is a bad idea.
Or Should we go with scenario 2. In this scenario, we should act accordingly when view attached again, which means saving states (RegistrationRequied, ErrorHasOccured, LoginProcessStillRunning, etc..) in the Presenter/Interactor.
Can someone can shed some light regarding this?
Thanks in advance!
Oh the joys of the Android lifecycle. I feel your pain.
In my personal experience, resorting to commitAllowingStateLoss is usually a symptom of trying to update your Ui (View) while in the background (and as you note, the ui may be destroyed).
What I would suggest is that you don't try to update your ui without checking if the activity has been backgrounded (onStop or onPause depending on the situation). If your ui has been backgrounded, remember the changes you need to make and do them when your Ui is reconnected (onStart or onResume depending on the situation).
In essence I'm saying you should follow Scenario 2. And yes. You will have to save quite a bit of state somehow.
Unfortunately this isn't easy and there are many approaches to doing this ranging from using event buses, all the way through to using RxJava.
Every approach has it's advantages and flaws and they are all really too complex to discuss in detail in a single post.
However, I have a blog post I wrote some time ago on a way of doing this in a way that doesn't require additional libraries.
It's a little out of date now, but it may give you some ideas: A Simple MVP aproach for Android
All the best.
Kind regards,
Chris.
After reading through all the questions on SO regarding this topic i am extremely annoyed by this.
Crashes will happen, no one writes perfect code. And there are apps that require a certain logical hierarchy. The best example is the login-screen idea, where you are in some activity doing something on a server and now the app crashes. After a restart, the app lost all its login-session data and saving it might not be the safest idea. So presenting the user with the login screen after a crash would be the best and most logical thing to do. The best user experience.
However Android decides to remember the Activity stack but only restart the last working activity with a "blank" application under it.
The only viable option i see would be to check in EVERY single activity if some login state is available or not and if not, start the login (launcher) activity with a clear-top or clear-task. But this almost forces you to write a base extends Activity class in which this behavior is implemented and then all activities have to extend that. But what if, for some reason, you cannot extend it because you need to extend some other type of activity?
There is android:clearTaskOnLaunch but this happens every single time the user returns from exiting via home button. There is the antagonist finishOnTaskLaunch that finished an activity every time the user presses the home button. So the Android devs are aware that sometimes one would like the app to appear in a certain state after exit but a crash seems to be exclusive to all that.
Using a custom UncaughtExceptionHandler gives me some chance to act after a crash but as the apps state is unrecoverable i can only perform certain tasks that will happen in addition to Android very own behavior.
So my simple question is, if there is any way, that is built into Android, that allows me to change the after-crash-behaviour of Android in a natural way that does not depend on the version it's running (to some extent ofc) and will result in a somewhat smooth user experience.
I would say the proper way of obtaining the result you would like would be to provide a proper parent or up navigation stack. You can read about it more here https://developer.android.com/training/implementing-navigation/ancestral.html.
But the gist of it would be to use the idea of parent activities to provide proper back navigation. If the activity already exists it will just go back to it, if it doesn't (such as in the crash scenario) it will launch the correct activity when the user navigates back.
NavUtils is a handy class to help build this behavior and is part of the support library which would work on a range of different API levels.
In the wonderful article by Chet Haase I read this advice which I find quite important:
never make a network request in your Application object. That object
may be created when one of the app’s Services or BroadcastReceivers is
started; hitting the network will turn code that does a local update
at a specific frequency into a regular DDoS.
The application I work on currently follows this (bad) practise: it performs a user login in Application.onCreate() - on a background thread of course, but still. This is a requirement: user needs to be logged in before any activity would do any other tasks, which usually depend on logged in user. I currently do this using RxJava in a way that any activity task observables are flatMapped onto an userlogin event and it works quite nice.
So if I should take that login task out of Application, where should it go? At first I thought it would be nice to use ActivityLifecycleCallbacks and watch for the first activity to be created. But this callback (onActivityCreated) will be called after creation, which is too late for me.
So I think that this should be done by creating some BaseActivity class and putting login and other initialization calls in it's first onCreate(). But I don't feel this is too good, because I'm mixing some app-wide logic in an activity class, it's smelly...
Anything I could have missed?
SplashActivity
An activity that starts the application. It checks for resources availability and if needed, obtains them. It also checks whether there is an active user session, and if there isn't performs a log in, if there are remembered credentials, or redirects the user to the Login/Register screen
BaseActivity
An activity that is specific for your app and that holds initialization and lifecycle callback code that is applicable for all your activities in the application.
It is simple I guess.
I have a User and some other objects that I need to use along many activities of my application, so I decided to create a class and extend Application to make these objects "globals", e.g. after the user does the login, I have the user object along all my activities to use.
The problem is that after the application is closed the data is not lost/reseted. So when I open my app again, it does not ask for me to do my login again. So I need to somehow close the other Application. How do I do it?
A palliative solution I found is to set the variables to null when backPressed is called from MainActivity (and my application is closed), but I know it is not right, must have a more elegant way to do it.