Implementation of logout option in preferences in Android app and transition between activities

Kacper Bąk
6 min readFeb 11, 2023

--

While working on my project, more specifically on the User Settings UI layer, I came across a problem.

private const val TITLE_TAG = "settingsActivityTitle"

class SettingsActivity : AppCompatActivity(),
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.settings_activity)
if (savedInstanceState == null) {
supportFragmentManager
.beginTransaction()
.replace(R.id.settings, HeaderFragment())
.commit()
} else {
title = savedInstanceState.getCharSequence(TITLE_TAG)
}
supportFragmentManager.addOnBackStackChangedListener {
if (supportFragmentManager.backStackEntryCount == 0) {
setTitle(R.string.title_activity_settings)
}
}
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}

override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
// Save current activity title so we can set it again after a configuration change
outState.putCharSequence(TITLE_TAG, title)
}

override fun onSupportNavigateUp(): Boolean {
if (supportFragmentManager.popBackStackImmediate()) {
return true
}
return super.onSupportNavigateUp()
}

override fun onPreferenceStartFragment(
caller: PreferenceFragmentCompat,
pref: Preference
): Boolean {
// Instantiate the new Fragment
val args = pref.extras
val fragment = pref.fragment?.let {
supportFragmentManager.fragmentFactory.instantiate(
classLoader,
it
).apply {
arguments = args
setTargetFragment(caller, 0)
}
}
// Replace the existing Fragment with the new Fragment
if (fragment != null) {
supportFragmentManager.beginTransaction()
.replace(R.id.settings, fragment)
.addToBackStack(null)
.commit()
}
title = pref.title
return true
}

class HeaderFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.header_preferences, rootKey)
}
}

class MessagesFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.messages_preferences, rootKey)
}
}

class SyncFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.sync_preferences, rootKey)
}
}

class AboutFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.about_preferences, rootKey)
}
}
}

I would like to implement logic, to an already existing activity. The logic is supposed to be related to the logout button, so I started to analyse how to do it, because before the refactoring I took the wrong approach.

I started with creating new preference in XML (e.g. header_preferences.xml, messages_preferences.xml, etc.) to represent the log out button, for example:

<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">

<Preference
app:fragment="com.example.exchangeabletoken.ui.settings.SettingsActivity$MessagesFragment"
app:icon="@drawable/messages"
app:key="messages_header"
app:title="@string/messages_header" />
<Preference
app:fragment="com.example.exchangeabletoken.ui.settings.SettingsActivity$SyncFragment"
app:icon="@drawable/sync"
app:key="sync_header"
app:title="@string/sync_header" />

<Preference
android:key="logout_key"
android:title="Log Out" />

</PreferenceScreen>
This is what the activity tab looks like after adding my XML, to header_preferences.xml

Override the onPreferenceTreeClick method in the corresponding PreferenceFragment class (e.g. HeaderFragment, MessagesFragment, etc.) to handle the log out button click event.

The best place to put the onPreferenceTreeClick method is in the PreferenceFragment class that corresponds to the preference screen where the log out button is displayed.

For example, if you have added the log out button in the header preferences XML file (e.g. header_preferences.xml), then you should put the onPreferenceTreeClick method in the HeaderFragment class.

This allows you to handle the log out button click event in a specific preference screen, rather than handling it in the general SettingsActivity. By having it in the specific fragment, you can also easily reuse the fragment in other parts of the app, without having to worry about the log out button logic.

    class HeaderFragment : PreferenceFragmentCompat() {

override fun onPreferenceTreeClick(preference: Preference): Boolean {
return when (preference.key) {
"logout_key" -> {
// log out
Firebase.auth.signOut()
true
}
else -> super.onPreferenceTreeClick(preference)
}
}

override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.header_preferences, rootKey)
}
}

You can also replace return super.onPreferenceTreeClick(preference) with return false if you don’t want the preference to trigger the default behaviour.

The final step will be to add the appropriate logic and connect it to the logout button so that you can log out of the application.

You can add the following code to sign out the user from Firebase when the log out button is clicked:

        override fun onPreferenceTreeClick(preference: Preference): Boolean {
if (preference.key == "logout_key") {
FirebaseAuth.getInstance().signOut()
activity?.finish()
}
return super.onPreferenceTreeClick(preference)
}

Make sure to import the necessary Firebase libraries:

import com.google.firebase.auth.FirebaseAuth

Importantly, after these changes, it became apparent that the screen the user is redirected to after logging out is MarketActivity. The user should then be ejected to a screen where there will be no information about the previously logged-in user.

I can redirect the user to a new activity that acts as a login screen or a welcome screen after they log out. It’s possible by starting a new intent when the user clicks the logout button and sign out from Firebase.

Here is an example implementation of how to start a new intent after signing out from Firebase:

override fun onPreferenceTreeClick(preference: Preference): Boolean {
if (preference.key == "logout_key") {
FirebaseAuth.getInstance().signOut()
val intent = Intent(activity, LoginActivity::class.java)
startActivity(intent)
activity?.finish()
}
return super.onPreferenceTreeClick(preference)
}

Here, LoginActivity is the activity I want to start after the user logs out. However, this raises a new problem. Even if the user is redirected to this activity, they can still go back to the Market screen.

To ensure that the user can’t go back to the Market screen after logging out, I can call the finish() method after starting the new activity. This will remove the current activity from the activity stack, so the user won't be able to go back to it.

One solution to this issue would be to call the finish() method on the MarketActivity instance, when the user logs out. This would effectively remove the MarketActivity from the back stack, and the user would not be able to go back to it. You can do this by calling the finish() method on the MarketActivity instance inside the onPreferenceTreeClick() method when the user logs out:

override fun onPreferenceTreeClick(preference: Preference): Boolean {
if (preference.key == "logout_key") {
// Logout logic here
FirebaseAuth.getInstance().signOut()
val marketActivity = activity as MarketActivity
marketActivity.finish()

val intent = Intent(activity, LoginActivity::class.java)
startActivity(intent)
return true
}
return false
}

But in addition to this, I also had to improve MarketActivity. I had to modify the code to add a check if the user is not logged in before starting the MarketActivity:

class MarketActivity : AppCompatActivity() {
val size: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Firebase.auth.currentUser == null) {
// Redirect the user to a different activity if they are not logged in
startActivity(Intent(this, LoginActivity::class.java))
finish()
return
}
setContentView(R.layout.market_activity)
// set current user name for UI element with id "current_user_name" if user is logged in
val currentUser = Firebase.auth.currentUser
val currentUserName = findViewById<TextView>(R.id.current_user_name)
"Hello, ${currentUser?.email}!".also { currentUserName.text = it }

// Handle log out button
val logoutButton = findViewById<Button>(R.id.logoutButton)
logoutButton.setOnClickListener {
Firebase.auth.signOut()
// Redirect the user to a different activity after logging out
startActivity(Intent(this, LoginActivity::class.java))
finish()
}
// rest of the code...
}
}

With this code, if the user is not logged in, they will be redirected to the LoginActivity and the MarketActivity will not start. After logging out, the user will also be redirected to the LoginActivity.

To prevent the user from being able to go back to the MarketActivity screen after logging out, you can add the following line of code to the onCreate method of the LoginActivity:

startActivity(Intent(this, LoginActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
})

This will clear the task stack and create a new task for the LoginActivity, effectively preventing the user from navigating back to the previous screen.

There also was a casting issue. If the “activity” variable is not of type “MarketActivity”, then the app will crash with a “ClassCastException”. To avoid this, you can add a null check before the casting. I fixed it by this code:

                if (activity is MarketActivity) {
val marketActivity = activity as MarketActivity
marketActivity.finish()
}
    class HeaderFragment : PreferenceFragmentCompat() {
override fun onPreferenceTreeClick(preference: Preference): Boolean {
if (preference.key == "logout_key") {
// Logout logic here
FirebaseAuth.getInstance().signOut()

// Finish all activities in the task
val intent = Intent(activity, ChooseSignUpActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(intent)
return true
}
return false
}

override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.header_preferences, rootKey)
}
}

I created logic that restart the whole application by calling finish() on all activities in the task and then starting the main (launcher) activity again.

The FLAG_ACTIVITY_CLEAR_TASK flag will remove all activities from the task and FLAG_ACTIVITY_NEW_TASK will make the LoginActivity the new root of the task, preventing the user from going back to opened tabs in the past.

This is how I solved the problem that affected my application. As I was improving the code, I noticed how important it was to set the order of scenes appropriately and manage them skilfully.

This is just a basic example of how you can implement a log out button in your settings activity. You can modify the logic as needed based on your specific requirements.

Source code: https://github.com/53jk1/ExchangeableToken

--

--