This question already has answers here:
Android: open activity without save into the stack
(11 answers)
Closed 2 years ago.
I have 3 activities, A: LoginAcitivty, B: SignupActivity, C: HomeActivity
and the flow is A -> B -> C.
and if I signup in the SignupActivity, then I go to HomeActivity.
but there is a problem. when I click the back button on my device. I can see the LoginActivity again.
So i thought first, when i click the sign-up button, i go back to LoginActivity
and then in the LoginActivity, i go to HomeActivity by using Intent for the HomeActivity. and also i wrote finish() in the LoginActivity.
this is my code.
<SignupActivity>
val intent = Intent(this, LoginActivity::class.java)
intent.putExtra("transfer", "transfer")
startActivity(intent)
finish()
<LoginActivity>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
if (intent.hasExtra("transfer")) {
val intent = Intent(this, HomeActivity::class.java)
startActivity(intent)
finish()
}
}
but it also doesn't work.
when i click the back button on my device in the HomeActivity, i expected that the app will end.
but i can still see the LoginActivity and i push back button again, then it's end.
plz help me.
This is the default behavior of Android activities as your default launch mode of each of the activities is standard.
We can fix it in two ways. I will explain one way which is not bothering about the launch mode thing.
LoginActivity -> You should start the SignupActivity as
activity.startActivityForResult(Intent(activity,SignupActivity::class.java), YOUR_REQUEST_CODE)
override fun onActivityResult(requestCode: Int, resultCode: Int,
data: Intent?) {
if (requestCode == YOUR_REQUEST_CODE && resultCode ==
RESULT_OK && data != null) {
// move to Home activity and `finish()` the current
//login screen
}
}
LoginActivity -> You should start the HomeActivity if login success and finish() the login screen.
Related
I am using shared preferences to login and logout the user of the application. Once the user logs in, the login screen is not showed again after the application is killed and started again. But when I enter the application and go to the screen after the login page and press back button, it shows the login screen again. I don't want the login screen to be showed again even after the back button is pressed. I want it to go completely out of the aplication on pressing back button.
Here is my code for login :
val sp = getSharedPreferences("login",MODE_PRIVATE)
if (sp.getBoolean("logged", false))
{
login()
}
loginButton.setOnClickListener() {
login()
sp.edit().putBoolean("logged", true).apply()
Log.v("Login Msg", "Login button clicked")
getusername = findViewById(R.id.usernameEditText)
var username = usernameEditText.text
Log.v("username",username.toString())
getpass = findViewById(R.id.passEditText)
var pass = passEditText.text
Log.v("pass",pass.toString()) }
fun login() {
val i = Intent(this, HomeActivity::class.java)
startActivity(i)
}
Here is the code for logout:
logoutButton.setOnClickListener(){
logout()
val sp = getSharedPreferences("login",MODE_PRIVATE)
sp.edit().putBoolean("logged", false).apply()
Log.v("Logout msg", "Logout button clicked")
}
fun logout(){
val i = Intent(this, LogInActivity::class.java)
startActivity(i)
}
Actually we can override "Back Button" method in each activity.
Below I reuse your code to manage logout when back button is pressed, perhaps this is the one you are looking for
override fun onBackPressed() {
super.onBackPressed()
val sp = getSharedPreferences("login",MODE_PRIVATE)
sp.edit().putBoolean("logged", false).apply()
logout()
}
This is happening because the login activity is still there in the back stack after going to the HomeActivity. To achieve your requirement you must remove everything from the back stack before going to the HomeActivity.
Replace your login function with the below function.
fun login() {
val i = Intent(this, MainActivity::class.java)
i.run {
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
startActivity(i)
}
Here we are basically adding a set of flags to the intent.
Intent.FLAG_ACTIVITY_CLEAR_TOP clears all the view underneath the current view
Intent.FLAG_ACTIVITY_CLEAR_TASK we are removing the existing tasks
Intent.FLAG_ACTIVITY_NEW_TASK we are creating a new task for the HomeActivity.
Intent.FLAG_ACTIVITY_CLEAR_TASK and Intent.FLAG_ACTIVITY_NEW_TASK should be used in conjunction with each other.
I am calling activity B from activity A using the ActivityResultLauncher and setting the result from activity B when the task is done. This works perfectly if orientation is not changed. The problem is when I change orientation from activity B and then setting the result, then registerForActivityResult of activity A is not called. Could someone let me know, what could be the issue?
Note: I do not face this issue if I am using startActivityForResult and onActivityResult. I changed this to ActivityResultLauncher as startActivityForResult became deprecated.
activity A sample code:
private lateinit var launcher: ActivityResultLauncher<Intent>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(layout)
setLauncherResult()
}
private fun setLauncherResult() {
launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK) {
//Do operations here
}
}
//On button click starting activity B using launcher
val intent = Intent(activityA, activityB)
launcher.launch(intent)
}
activity B sample code:
//setting result
val intent = Intent()
//set intent extras
setResult(Activity.RESULT_OK, intent)
finish()
You can save the value in onSaveInstanceState() and use setResult in onRestoreInstanceState().
Try moving the registerForActivityResult assignment of launcher to onCreateView() (override it if you haven't already). Putting it here, I'm not getting the "is attempting to register while current state is STARTED" RuntimeException in OnCreateView() as it would with onResume() or later. The advantage of OnCreateView() over OnCreate() seems to be that the launcher variable will get re-set on theme day/night changes as well as just orientation changes.
Also see the Configuration change & Process death section here for more discussion.
I know the official dox say it's safe to set it before anything else in the class, but theme changes broke .launch(), and this location appears to work for me.
Everytime orientation changes it calls onCreate and it resets your views & Other variables like launcher are re-assigned. So you can follow the example shown in official document here: https://developer.android.com/training/basics/intents/result#launch
And pull your launcher assignment out of onCreate as following (This worked for me):
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val startForResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK) {
//do your stuff here
binding.tv.text = result.data?.getStringExtra("text")
}
}
companion object {
var number : Int = 0
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.tv.setOnClickListener {
val intent = Intent(this, ActivityB::class.java)
startForResult.launch(intent)
}
}
}
If you don't want that onCreate should be called again when orientation changes. Then in your Manifest you can add configChanges:
<activity android:name=".MainActivity"
android:configChanges="orientation|screenSize">
I'm still new to Kotlin and Android and developing a project that uses shared preferences.
I have multiple Activities and a user must be logged in to use all functions of the Main Activity.
The Main Acitivity has a menu drawer with few menu items. The first one is redirecting user to Login Activity. The rest of the menu items are hidden until user is logged in.
The process is as follows:
Start with Main Activity -> The application checks if the user is logged in -> If not you must log in -> Go to Login Activity -> Logging in -> Then go back to Main Activity.
However, the main activity does not refresh at all. I mean, I don't even know how to do it. I want my main activity to refresh when I return from login activity.
So far, it only works when I close the app and reopen it. The session is likely cached in Shared preferences.
I tried to start and finish Activites by clicking certain buttons (eg After clicking login button when user passed all credentials) but it didn't work.
The following code checks that the user is logged in when the main activity starts
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val menu: Menu = navView.menu
val target2: MenuItem = menu.findItem(R.id.miItem2)
val target3: MenuItem = menu.findItem(R.id.miItem3)
//region user session
try{
if(!SharedPrefManager.getInstance(this)!!.isLoggedIn()){
target2.isVisible = false
target3.isVisible = false
Toast.makeText(applicationContext, "nie zalogowany", Toast.LENGTH_LONG).show()
}
else{
target2.isVisible = true
target3.isVisible = true
Toast.makeText(applicationContext, "zalogowany", Toast.LENGTH_LONG).show()
}
}catch (e: NullPointerException){
}
//endregion
And there is function isLoggedIn():
fun isLoggedIn(): Boolean{
val sharedPreferences: SharedPreferences = mCtx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
if(sharedPreferences.getString(KEY_USER_EMAIL, null) != null){
return true
}
return false
}
This code below takes me to the Login Activity
val btnLogInActivity : Button = findViewById(R.id.logButton)
btnLogInActivity.setOnClickListener {
val intent = Intent(this, LoginActivity::class.java)
startActivity(intent)
}
And this is the code in Login Activity (userLogin function is not important in this context):
buttonLogin.setOnClickListener {
userLogin()
newMainActivity()
}
private fun newMainActivity(){
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
}
Ps. If you need more code to fix the problem, let me know
You just have to add finish() in your code. Update your methods with the code below:
btnLogInActivity.setOnClickListener {
val intent = Intent(this, LoginActivity::class.java)
startActivity(intent)
finish()
}
And after login:
private fun newMainActivity(){
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
}
In Android/Kotlin, I am launching a new activity with startActivityForResult in my onCreate function, and get the returned variable (let's called it X) from onActivityResult outside of the onCreate function. Subsequently i want to access the variable X in the onCreate function to display it on the screen. However it never displays the data as if it were empty.
Any ideas of what I might be doing wrong? Thanks
Here is the code. When the user clicks on notesView it launches a new activity (Notes2Activity) where the user can enter its note in a full screen. Then upon validation of the note the code return to the previous activity where I am trying to edit the content of NotesView to returnNotes, but the app crashes.
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_new_contact)
val notesView: TextView
notesView = findViewById<TextView>(R.id.inputNotes)
notesView.setOnClickListener {
val intent = Intent(this, Notes2Activity::class.java)
startActivityForResult(intent,2)}}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {if (resultCode == Activity.RESULT_OK) {
if(data != null) {
val returnNotes: String = data.getSerializableExtra("notesEntered") as String
var returnNotesInputs = returnNotes
notesView.setText("")
notesView.append(returnNotes)
Log.d(TAG, "note returned to newContactActivity is: $returnNotes")}}
else if (resultCode == Activity.RESULT_CANCELED)
{Log.d(TAG, "user clicked canceled in Notes2Activity")}
}
You can save the data in a global class field(s) and call recreate() on the activity after that. This will essentially call onCreate() again on the activity and then you can use the data in the fields.
IMO, this is a bad way to handle the situation. The specific logic from onCreate() can be exported to a private function. Then you can call this private function from onActivityResult() as well as onCreate().
I have 2 apps, one a very simple toy app that exists to call the other:
const val AUTHENTICATE_CODE = 42
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
fab.setOnClickListener {
Intent(Intent.ACTION_VIEW, Uri.parse("testapp://hello.world/")) //2nd app has intent filter to intercept this.
.also { intent -> startActivityForResult(intent, AUTHENTICATE_CODE) }
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
val textView = findViewById<TextView>(R.id.hello_text)
if (requestCode == AUTHENTICATE_CODE && resultCode == Activity.RESULT_OK) {
requireNotNull(data) {
textView.text = "Error: Intent was null. Data lost."
return#onActivityResult
}
val dataExtra = data.getStringExtra("com.example.app.DATA")
requireNotNull(dataExtra){
textView.text = "Error: Intent did not contain data."
return#onActivityResult
}
Log.d("TestAppPlsIgnore", "Result Intent received")
textView.text = "Success! $dataExtra"
} else {
textView.text = "Something went wrong. Request = $requestCode; Result = $resultCode"
}
}
//...
}
The other app is a little more involved:
The activity in app 2 that the toy app launches implements the navigation library from Jetpack.
Most of the fragments that are in that activity's nav graph implement the same ViewModel. i.e. private val mainViewModel by activityViewModels<MainActivityViewModel>()
Inside the MainActivityViewModel is a LiveData<String> that we'll call data. The MainActivity of app 2 has an observer watching data similar to this:
val dataObserver = Observer<String> { data ->
val result = Intent()
result.putExtra("com.example.app.DATA", data)
Log.d("MainActivity.DataObserver", "Sending data $data")
setResult(Activity.RESULT_OK, result)
finish()
}
mainViewModel.data.observe(this, dataObserver)
In the general flow to get to a point where a string is put into data, the navigation view of the main activity will likely navigate between one or more fragments.
The expected result: When a string is added to data in app 2, the observer will create the result intent, set it as the result, and finish app 2. App 1 will receive the result and call onActivityResult, and we should display "Success!" plus some data.
What I get: The observer does work. The log statement shows the correct data was received by the observer. App 2 finishes. And app 1's onActivityResult displays the fail case, showing the correct request code, but a response code == Activity.RESULT_CANCELLED. If the requireNotNull(data) statement is moved outside the if statement, app 1 will instead show that the intent returned was null.
My questions:
RESULT_CANCELLED is not being explicitly returned, and I am attempting to return an intent with data. So that should only leave the activity crashing as a reason why RESULT_CANCELLED is being returned. Navigating across a nav graph will inevitably cause some fragments to reach the end of their lifecycle. Would Android confuse that for an activity crashing?
Why is there a null intent when onActivityResult is being called? For the most part, I'm just following what's outlined in the documentation, if a bit more verbosely.
Is this not the right way to send a simple string between two specific apps? I don't want to use share intents, because this is meant to be a more direct communication between specific apps rather than a broad communication between my app and a category of apps.
It turns out that you should not call finishAfterTransition() elsewhere in an app if you plan to use an Observer-based setup like mine to send data through startActivityForResult(). finishAfterTransition() causes a conflict with any calls to finish(), and you'll send a null result and a ResultCode of RESULT_CANCELLED.