I want to use a DropDownPreference for my settings page but despite looking all over the internet, there doesn't seem to be any decent tutorial on how to do this. Does anyone know what should go in the onPreferenceChange method? I previously used a RadioButton but now want to use a DropDownPreference for easier implementation and maintenance.
Activity
class SettingsActivity : AppCompatActivity(), FragmentSettings.PreferenceXchangeListener {
private var mCurrentValue: Boolean = false // False is the default value
override fun onCreate(savedInstanceState: Bundle?) {
val mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
mCurrentValue = mSharedPreferences.getBoolean("preference_dark", false)
if (mCurrentValue) {
setTheme(R.style.MyDarkSettingsTheme)
} else {
setTheme(R.style.MyLightSettingsTheme)
}
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
val viewllSettingsContainer = settings_container
val root = viewllSettingsContainer.rootView
if (mCurrentValue) {
root.setBackgroundColor(Color.BLACK)
} else {
root.setBackgroundColor(Color.WHITE)
}
val settingsFragment = FragmentSettings()
supportFragmentManager
.beginTransaction()
.replace(R.id.settings_container, settingsFragment)
.commit()
}
override fun onXchange(value:Boolean) {
when {
mCurrentValue != value -> {
mCurrentValue = value
recreate()
}
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
val intent = parentActivityIntent
intent?.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
onBackPressed()
true
}
else ->
super.onOptionsItemSelected(item)
}
}
}
Fragment
class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChangeListener,
Preference.OnPreferenceClickListener {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.app_preferences)
}
}
I think you're over-complicating it. There is no need to involve the fragment in listening to changes to a preference it won't be handling and passing that back to the activity. You can register a preference change listener for all preferences in your Activity and respond accordingly there.
class SettingsActivity : AppCompatActivity(),
SharedPreferences.OnSharedPreferenceChangeListener {
override fun onCreate(savedInstanceState: Bundle?) {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
sharedPreferences.registerOnSharedPreferenceChangeListener(this)
//...
}
override fun onDestroy() {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
//...
super.onDestroy()
}
override fun onSharedPreferenceChanged(_: SharedPreferences, key: String) {
when (key){
"preference_dark" -> recreate()
}
}
}
Related
I am making a simple app in kotlin and would like to add an additional option for night mode like this
example from Material Files app
I was thinking of adding a style and applying it programmatically, But I don't know how to do it properly.
Here's my code:
MainActivity
override fun onCreate(savedInstanceState: Bundle?) {
WindowCompat.setDecorFitsSystemWindows(window, false)
super.onCreate(savedInstanceState)
//It is called after super.onCreate because otherwise isNightMode does not return the correct value
when (ThemeHelper.nightModeChoice(this)) {
"nightModeFollowSystem" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
"nightModeOn" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
"nightModeOff" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
}
override fun onResume() {
super.onResume()
//If MainActivity is recreated return to SettingsFragment to have a nice animation
if (intent.extras != null) {
if (intent.extras!!.getBoolean("TEMA_CAMBIATO")) {
intent.putExtra("TEMA_CAMBIATO", false)
navController.navigate(R.id.SettingsFragment)
}
}
}
ThemeHelper,
this part is a bit messy, but it serves to prevent the activity from being recreated unnecessarily.
object ThemeHelper {
private fun isNightMode(context: Context): Boolean {
return context.resources.configuration.uiMode.and(Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
}
fun nightModeChoice(context: Context): String? {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getString("modTema", "nightModeFollowSystem")
}
fun recreate(context: Context, activity: Activity) {
var flag = true
if (isNightMode(context) && nightModeChoice(context) == "nightModeOn") {
Log.d("THEME_HELPER", "Non c'è bisogno di aggiornare")
flag = false
}
if (!isNightMode(context) && nightModeChoice(context) == "nightModeOff") {
Log.d("THEME_HELPER", "Non c'è bisogno di aggiornare")
flag = false
}
if (flag) {
val intent = activity.intent
intent?.putExtra("TEMA_CAMBIATO", true)
activity.finish()
activity.overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
activity.startActivity(intent)
}
}
}
SettingsFragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
PreferenceManager.getDefaultSharedPreferences(requireContext()).registerOnSharedPreferenceChangeListener(this)
}
override fun onDestroyView() {
super.onDestroyView()
PreferenceManager.getDefaultSharedPreferences(requireContext()).unregisterOnSharedPreferenceChangeListener(this)
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if (isAdded) {
if (key == "modTema") {
ThemeHelper.recreate(requireContext(), requireActivity())
}
}
}
I solved in this way btw:
ThemeHelper
fun applyAmoled(context: Context, view: View){
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
if (prefs.getBoolean("modAmoled", false) && isNightMode(context)){
view.setBackgroundColor(Color.BLACK)
}
}
MainActivity after super.onCreate
ThemeHelper.applyAmoled(this, findViewById(R.id.content_main))
It's not the best solution but it works, I just need to recreate activity when needed
So I have my settings activity set up with my < PreferenceScreen > for choosing the app settings.
What I want to do is have my changes do immediate effect, and not on app restart.
Naturally I wanted to use OnSharedPreferenceChangeListener , but if I try to put a Log in onSharedPreferenceChanged, it never procs on changed selection. My plan was to recreate() on preference changed, so my preference apply function would proc on onCreate() with that recreate().
The problem is, as previously stated that onSharedPreferenceChanged never procs.
I tried registering the listener as it was stated somewhere, but it didn't help.
Can anyone help me?
class SettingsActivity : AppCompatActivity(),
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback,
SharedPreferences.OnSharedPreferenceChangeListener {
val Tag = "My Activity:"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val prefMen = SharedPreferencesManager(this)
prefMen.loadTheme()
setContentView(R.layout.activity_settings)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.content_preference, MainPreference()).commit()
} else {
title = savedInstanceState.getCharSequence(TAG_TITLE)
}
supportFragmentManager.addOnBackStackChangedListener {
if (supportFragmentManager.backStackEntryCount == 0) {
setTitle("Settings")
}
}
setUpToolbar()
}
override fun onStart() {
super.onStart()
getPreferences(MODE_PRIVATE).unregisterOnSharedPreferenceChangeListener(this)
}
override fun onDestroy() {
super.onDestroy()
getPreferences(MODE_PRIVATE).unregisterOnSharedPreferenceChangeListener(this)
}
override fun onSaveInstanceState(outState: Bundle, outPersistentState: PersistableBundle) {
super.onSaveInstanceState(outState, outPersistentState)
outState.putCharSequence(TAG_TITLE, title)
}
override fun onSupportNavigateUp(): Boolean {
if (supportFragmentManager.popBackStackImmediate()) {
return true
}
return super.onSupportNavigateUp()
}
private fun setUpToolbar() {
supportActionBar?.setTitle("Settings")
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
}
class MainPreference : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences, rootKey)
}
}
override fun onPreferenceStartFragment(
caller: PreferenceFragmentCompat?,
pref: Preference?
): Boolean {
val args = pref?.extras
val fragment = pref?.fragment?.let {
supportFragmentManager.fragmentFactory.instantiate(
classLoader,
it
).apply {
arguments = args
setTargetFragment(caller, 0)
}
}
fragment?.let {
supportFragmentManager.beginTransaction().replace(R.id.content_preference, it)
.addToBackStack(null).commit()
}
title = pref?.title
return true
}
companion object {
private val TAG_TITLE = "PREFERENCE_ACTIVITY"
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
recreate()
}
}
I see two mistakes. The first is that you never register your listener. You have called unregisterOnSharedPreferenceChangeListener() in your onStart() instead of registerOnSharedPreferenceChangeListener().
Secondly, you are not listening to the same SharedPreferences as the settings Fragment is using. PreferenceFragmentCompat by default uses the default shared preferences of the whole application. But you are listening to preferences retrieved with Activity.getPreferences(), which are not the default preferences, but rather a SharedPreferences instance that is named after the Activity.
So you should switch to using default shared preferences, because the private shared preferences of the Activity will not be as easy to get a reference to in other activities.
I would also register in onCreate() instead of onStart() so you aren't registering multiple times. I don't know if that really matters, but the documentation doesn't specify what happens when you repeatedly register the same listener. Maybe it would fire the callback repeatedly, which could end up wasting your time hunting the bug later.
override fun onCreate() {
// ...
PreferenceManager.getDefaultSharedPreferences(this)
.registerOnSharedPreferenceChangeListener(this)
}
override fun onDestroy() {
super.onDestroy()
PreferenceManager.getDefaultSharedPreferences(this)
.unregisterOnSharedPreferenceChangeListener(this)
}
It is possible to specify the SharedPreferences that will be used by PreferenceFragmentCompat, but the default preferences are the easiest to use for settings that you want to access from across your application.
setTargetFragment() is deprecated in Java, and I don't understand the correct replacement for it as android documentation uses it and is outdated. I believe the FragmentManager is the correct replacement for it. I am using the deprecated setTargetFragment function in my settings Preferences to create multiple preference screens. To do so, I originally followed the guide here which confusingly uses setTargetFragment in the example. Below is my code:
build.gradle (Module: app)
...
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.preference:preference:1.1.1'
...
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var settingsButton: ImageButton
override fun onCreate(savedInstanceState: Bundle?) {
...
settingsButton = findViewById(R.id.settingsButtonMain)
settingsButton.setOnClickListener {
settingsClicked()
}
...
}
private fun settingsClicked() {
val settingsIntent = Intent(this, SettingsActivity::class.java)
startActivity(settingsIntent)
}
}
SettingsActivity.kt
class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
override fun onCreate(savedInstanceState: Bundle?) {
...
supportFragmentManager.beginTransaction().replace(R.id.settings, SettingsFragment(this)).commit()
supportFragmentManager.addOnBackStackChangedListener {
if(supportFragmentManager.backStackEntryCount == 0) {
title = "App Settings"
}
}
supportActionBar?.setDisplayHomeAsUpEnabled(true)
...
}
class SettingsFragment(cont: Context) : PreferenceFragmentCompat() {
private var context1: Context = cont
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
}
}
class Screen2PreferencesFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.screen2_preferences, null)
}
}
override fun onPreferenceStartFragment(caller: PreferenceFragmentCompat?, pref: Preference): Boolean {
val args: Bundle = pref.extras
val fragment: Fragment = supportFragmentManager.fragmentFactory.instantiate(classLoader, pref.fragment)
fragment.arguments = args
fragment.setTargetFragment(caller, 0) //TROUBLE AREA. WHAT IS THE CORRECT REPLACEMENT HERE?
supportFragmentManager.beginTransaction().replace(R.id.settings, fragment).addToBackStack(null).commit()
return true
}
override fun onSupportNavigateUp(): Boolean {
if(supportFragmentManager.popBackStackImmediate()) {
return true
}
return super.onSupportNavigateUp()
}
}
Include this dependency:
implementation 'androidx.fragment:fragment-ktx:1.3.0-beta01'
Use setFragmentResultListener instead of setTargetFragment():
override fun onPreferenceStartFragment(caller: PreferenceFragmentCompat?, pref: Preference): Boolean {
val args: Bundle = pref.extras
val fragment: Fragment = supportFragmentManager.fragmentFactory.instantiate(classLoader, pref.fragment)
fragment.arguments = args
supportFragmentManager.beginTransaction().replace(R.id.settings, fragment).addToBackStack(null).commit()
supportFragmentManager.setFragmentResultListener("requestKey") { key, bundle ->
if (key == "requestKey") {
// Get result from bundle
}
}
return true
}
And in your fragment that returns a result:
// Insert result in a bundle
setFragmentResult("requestKey", bundle)
For the unresolved issue when trying to use setFragmentResult referring to the answer above, you could try using the fragmentManager as follows.
This is for Xamarin:
ParentFragmentManager.SetFragmentResult("requestKey", bundle);
I use 2 fragments inside my MainActivity, one is a preferences fragment and the other (default one) is a home fragment. I wanted to make sure that fragment doesn't get recreated if same item is selected in navigation bar. however now I have 2 problems:
1. If I try to change theme from my preferences the bottom navigation bar will stop working
2. Switching to preferences fragment doesn't change title in action bar but it should (I tested and it worked before implementing this the fragment replacement prevention .
Here are some codes:
MainActivity.kt
class MainActivity : ThemeActivity() {
private val homeFragment: HomeFragment = HomeFragment()
private val settingsFragment: SettingsFragment = SettingsFragment()
private var currentFragment: Fragment? = null
private var activeFragment: Int = R.id.navigation_home
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
savedInstanceState?.let {
activeFragment = it.getInt(CURRENT_FRAGMENT, R.id.navigation_home)
}
val toolbar: MaterialToolbar = findViewById(R.id.home_toolbar)
setSupportActionBar(toolbar)
val prefs = getSharedPreferences("prefs", MODE_PRIVATE)
val firstStart = prefs.getBoolean("firstStart", true)
if (firstStart) {
showSecurityDialog()
}
when (activeFragment) {
R.id.navigation_home -> currentFragment = homeFragment
R.id.navigation_settings -> currentFragment = settingsFragment
}
if (savedInstanceState == null) {
supportFragmentManager
.beginTransaction()
.add(R.id.frame_layout, settingsFragment).hide(settingsFragment)
.add(R.id.frame_layout, homeFragment).hide(homeFragment)
.show(currentFragment!!)
.commit()
}
val navView: BottomNavigationView = findViewById(R.id.bottom_nav)
navView.setOnNavigationItemSelectedListener{
setFragments(it.itemId)
}
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.toolbar_menu, menu)
return super .onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.about -> {
val intent = Intent(this, AboutActivity::class.java)
startActivity(intent)
true
}
else -> {
super.onOptionsItemSelected(item)
}
}
private fun showSecurityDialog() {
AlertDialog.Builder(this)
.setTitle("Welcome!")
.setMessage("Before we implement a proper security system to check whether app was modified or not, please be sure that you downloaded manager from vanced.app/github")
.setPositiveButton("close"
) { dialog, _ -> dialog.dismiss() }
.create().show()
val prefs = getSharedPreferences("prefs", MODE_PRIVATE)
val editor = prefs.edit()
editor.putBoolean("firstStart", false)
editor.apply()
}
private fun setFragments(itemId: Int): Boolean {
activeFragment = itemId
when (itemId) {
R.id.navigation_home -> {
if (currentFragment is HomeFragment) {
return false
}
supportFragmentManager
.beginTransaction()
.hide(currentFragment!!)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.show(homeFragment)
.commit()
currentFragment = homeFragment
}
R.id.navigation_settings -> {
if (currentFragment is SettingsFragment) {
return false
}
supportFragmentManager
.beginTransaction()
.hide(currentFragment!!)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.show(settingsFragment)
.commit()
currentFragment = settingsFragment
}
}
return true
}
companion object{
const val CURRENT_FRAGMENT = "current_fragment"
}
}
PreferenceFragment.kt
class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences, rootKey)
(activity as MainActivity).supportActionBar?.title = getString(R.string.title_settings)
val updateCheck: Preference? = findPreference("update_check")
val themeSwitch: ListPreference? = findPreference("theme_mode")
themeSwitch?.setOnPreferenceChangeListener { _, _ ->
when (themeSwitch.value){
"LIGHT" -> {
activity?.setTheme(R.style.LightTheme_Blue)
activity?.recreate()
}
"DARK" -> {
activity?.setTheme(R.style.DarkTheme_Blue)
activity?.recreate()
}
"FOLLOW" -> {
when (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
Configuration.UI_MODE_NIGHT_YES ->{
activity?.setTheme(R.style.DarkTheme_Blue)
activity?.recreate()
}
Configuration.UI_MODE_NIGHT_NO -> {
activity?.setTheme(R.style.LightTheme_Blue)
activity?.recreate()
}
}
}
else -> {
activity?.setTheme(R.style.LightTheme_Blue)
activity?.recreate()
}
}
true
}
}
}
I tried to lookup on the web but couldn't find anything useful, I'm pretty sure the problem is with the activity recreation but I don't really know how to fix the issue.
Switched to Navigation Components. It's way better than manually making fragment transactions
I'm new in Kotlin Developement. I'm trying to start a new activity from the main activity but the only thing it displays is a blank message after the toast message. But I really don't understand where the problem comes from.
Here's MainActivity:
class MainActivity : AppCompatActivity() {
private fun replaceFragment(fragment: Fragment){
val fragmentTransaction = supportFragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.fragment_container, fragment)
fragmentTransaction.commit()
}
private val onNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener {
item -> when (item.itemId) {
R.id.nav_home -> {
//replaceFragment(HomeFragment())
return#OnNavigationItemSelectedListener true
}
R.id.nav_search -> {
replaceFragment(SearchFragment())
return#OnNavigationItemSelectedListener true
}
R.id.nav_person -> {
replaceFragment(ProfileFragment())
return#OnNavigationItemSelectedListener true
}
R.id.nav_favorites -> {
replaceFragment(FavoritesFragment())
return#OnNavigationItemSelectedListener true
}
}
false
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navView: BottomNavigationView = findViewById(R.id.bottom_navigation)
navView.setOnNavigationItemSelectedListener(onNavigationItemSelectedListener)
navView.setSelectedItemId(R.id.nav_home)
if (!imgurClient.isConnected) {
Toast.makeText(applicationContext,"You are not connected.",Toast.LENGTH_SHORT).show()
val intentToWebView = Intent(this, LoginActivity::class.java)
startActivity(intentToWebView)
}
}
}
Here's LoginActivity:
eclass LoginActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
setContentView(R.layout.activity_login)
}
}
Activity_login.xml contains a textview which displays a text in white and the background is purple.
Can you help me please?
You have to override the correct onCreate method without the PersistableBundle as a second argument:
class LoginActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
}
}
You've overridden the wrong method in your LoginActivity. It should be:
class LoginActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState, persistentState)
setContentView(R.layout.activity_login)
}
}
You have to override the correct onCreate method. It should be
class LoginActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
}
}