I have a preferences screen (Fragment) that has some preferences set from an xml file. The only thing i call in the Fragment is addPreferencesFromResource(R.xml.pref_main); in the onCreate method.
Now, everything works well except for the "summary" section in my preferences, for instance if you have an EditTextPreference and you enter some text, that text should be visible under the preference in smaller letters.
I'm using a custom controll for a preference (but it also doesn't work for any of the official Preferences), that extends DialogPreference. If i set the summary like this:
#Override
protected void onDialogClosed(boolean positiveResult) {
if (positiveResult) {
setSummary("Some summary");
}
}
It works but only as long as i dont leave the screen, when i return it's not there anymore.
Any ideas?
The preference screen does not automatically display the summary data. You need to do it in code. Here is a code fragment you could use in your onCreate() method. Add it after the call to addPreferencesFromResource(R.xml.pref_main);
for(int i = 0; i < getPreferenceScreen().getPreferenceCount(); i++) {
initializeSummary(getPreferenceScreen().getPreference(i));
}
The initializeSummary() method is like this:
private void initializeSummary(Preference p)
{
if(p instanceof ListPreference) {
ListPreference listPref = (ListPreference)p;
p.setSummary(listPref.getEntry());
}
if(p instanceof EditTextPreference) {
EditTextPreference editTextPref = (EditTextPreference)p;
p.setSummary(editTextPref.getText());
}
}
As #Anthony had mentioned in the comments there might be PreferenceCategory instances in your preferences XML. We can handle them recursively as follows.
class SettingsFragment : PreferenceFragmentCompat() {
/**
* Initialises the summary of EdittextPreference's and ListPreference's
* that might be nested in PreferenceCategories.
*/
private fun summarize(pref: Preference) {
when (pref) {
is PreferenceCategory -> {
for (i in 0 until pref.preferenceCount) {
summarize(pref.getPreference(i))
}
}
is EditTextPreference -> pref.summary = pref.text
is ListPreference -> pref.summary = pref.entry
else -> {
Log.w("Summarize", "Ignoring preference of type ${pref::class.java}")
}
}
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
for (i in 0 until preferenceScreen.preferenceCount) {
summarize(preferenceScreen.getPreference(i))
}
}
Related
I have some custom DialogFragments in my Android application. I would prevent the user from opening multiple dialogs, and I would also avoid disabling the button which shows the dialog once it's pressed or using a variable or it.
So I was trying to make an extension for the fragment which checks for shown fragments via their tags like this:
fun Fragment.isFragmentVisible(tag: String): Boolean {
val fragment = childFragmentManager.findFragmentByTag(tag)
if (fragment != null && fragment.isVisible) {
return true
}
return false
}
And before showing the dialog to check it like this:
binding.destination.editText?.setOnClickListener {
if (!isFragmentVisible(ShopsDialog.TAG)) {
ShopsDialog.newInstance(this, "Destinazione") { bundle ->
val shopId = bundle.getString("shopId")
viewModel.setDestination(shopId)
}
}
}
While the newInstance is:
fun newInstance(fragment: Fragment, title: String, bundle: (Bundle) -> Unit) = ShopsDialog().apply {
arguments = Bundle().apply {
putString(ARG_TITLE, title)
}
fragment.setFragmentResultListener(KEY_CALLBACK_BUNDLE) { requestKey: String, bundle: Bundle ->
if (requestKey == KEY_CALLBACK_BUNDLE) {
bundle(bundle)
}
}
show(fragment.childFragmentManager, TAG)
}
But this seems not to work, I've tried even to use requireActivity().supportFragmentManager instead childFragmentManager but still nothing...
Before showing bottomsheetdialogfragment , i think you can check it like this
if(childFragmentManager.findFragmentByTag(ShopsDialog::class.java.simpleName) == null){
// show your bottom sheet dialog fragment
}
If you are going to show bottomSheetDialogFragment in Fragment then use childFragmentManager and if it is activity then use supportFragmentManager
I am building an app where there is a settings screen which I build with Andreoid Jetpack Preference library. I want to travel from the preference screen into a more detailed fragment in order to personalize email. But I don know how to handle the click on the preference, I have managed to import the method provided from the library but I dont know how to implement it as there is no information available. I have the function onPreferenceClick but i dont know how to build its logic. It shall return false when it is not clicked and true when it is.
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
if (onPreferenceClick(preferenceScreen.getPreference(1))) {
findNavController().navigate(R.id.editMailFragment)
}
override fun onPreferenceClick(preference: Preference?): Boolean {
preference?.setOnPreferenceClickListener {
}
}
return
}
If you using PreferenceFragmentCompat, you can override onCreatePreferences to handle click on preference:
#Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.your_preference);
Preference preferenceMap = findPreference("your_preference_key");
preferenceMap.setOnPreferenceClickListener(
new Preference.OnPreferenceClickListener() {
#Override
public boolean onPreferenceClick(Preference arg0) {
findNavController().navigate(R.id.editMailFragment)
return true;
}
});
}
I have solved it, here is the code if someone has the same trouble.
val showValueListener = Preference.OnPreferenceClickListener {
findNavController().navigate(R.id.editMailFragment)
true
}
findPreference<Preference>("email")?.onPreferenceClickListener = showValueListener
My Goal: Setting night mode through preferences and updating the UI in real time.
So far: Done. BUT when I click back to the headers' preference screen I cannot get back in the different preferences' screens again.
To elaborate: My settings are pretty simple at that point. I'm following the preset set by Android Studio (3.1.4) of SettingsActivity, having a template of AppCompatPreferenceActivity. I have one main screen and two deeper ones.
My first screen has two choices: General and About.
Upon selecting General, I load a GeneralPreferenceFragment with one Switch preference, that of "Night Mode".
If I set it on, it switches the theme real time and when I go back, it's also done on my first settings' screen.
The problem: When I do change the theme, go back to the main SettingsScreen and I try to revisit either General or About screens, I cannot go deeper any more! If I switch the preference an even number so that I end up to the initial theme, I can visit them like nothing has happened.
SettingsActivity class
class SettingsActivity : AppCompatPreferenceActivity() {
// To be used for live changes in night mode
private var mCurrentNightMode: Int = 0
private val TAG = "SettingsActivity"
private var mThemeId = 0
/**
* Doing this hack to add my own actionbar on top since it wasn't there on its own
*/
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
Log.d(TAG, "onPostCreate")
val root = findViewById<View>(android.R.id.list).parent.parent.parent as LinearLayout
val bar = LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false) as Toolbar
root.addView(bar, 0) // insert at top
bar.setNavigationOnClickListener {finish()}
}
override fun setTheme(resid: Int) {
super.setTheme(resid)
mThemeId = resid
}
override fun onCreate(savedInstanceState: Bundle?) {
if (delegate.applyDayNight() && (mThemeId != 0) ) {
// If DayNight has been applied, we need to re-apply the theme for
// the changes to take effect. On API 23+, we should bypass
// setTheme(), which will no-op if the theme ID is identical to the
// current theme ID.
if (Build.VERSION.SDK_INT >= 23) {
onApplyThemeResource(theme, mThemeId, false);
} else {
setTheme(mThemeId);
}
}
super.onCreate(savedInstanceState)
Log.d("SettingsActivity", "onCreate")
setupActionBar()
// Storing the current value so if we come back we'll check on postResume for any changes
mCurrentNightMode = getCurrentNightMode();
}
// Comparing current and last known night mode value
private fun hasNightModeChanged(): Boolean {
return mCurrentNightMode != getCurrentNightMode()
}
private fun getCurrentNightMode(): Int {
return resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
}
override fun onPostResume() {
super.onPostResume()
// Self-explanatory. When I load back into this screen, if there's a change in the theme, load it back!
if(hasNightModeChanged()) {
recreate()
}
}
/**
* Set up the [android.app.ActionBar], if the API is available.
*/
private fun setupActionBar() {
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
/**
* {#inheritDoc}
*/
override fun onIsMultiPane(): Boolean {
return isXLargeTablet(this)
}
/**
* {#inheritDoc}
*/
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
override fun onBuildHeaders(target: List<PreferenceActivity.Header>) {
loadHeadersFromResource(R.xml.pref_headers, target)
}
/**
* This method stops fragment injection in malicious applications.
* Make sure to deny any unknown fragments here.
*/
override fun isValidFragment(fragmentName: String): Boolean {
return PreferenceFragment::class.java.name == fragmentName
|| GeneralPreferenceFragment::class.java.name == fragmentName
|| DataSyncPreferenceFragment::class.java.name == fragmentName
|| NotificationPreferenceFragment::class.java.name == fragmentName
|| AboutFragment::class.java.name == fragmentName
}
/**
* This fragment shows general preferences only. It is used when the
* activity is showing a two-pane settings UI.
*/
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
class GeneralPreferenceFragment : PreferenceFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
addPreferencesFromResource(R.xml.pref_general)
setHasOptionsMenu(true)
// Bind the summaries of EditText/List/Dialog/Ringtone preferences
// to their values. When their values change, their summaries are
// updated to reflect the new value, per the Android Design
// guidelines.
findPreference("night_mode").setOnPreferenceChangeListener { preference, newValue ->
val booleanValue = newValue as Boolean
if(booleanValue) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
}
activity.recreate()
true
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.itemId
if (id == android.R.id.home) {
startActivity(Intent(activity, SettingsActivity::class.java))
return true
}
return super.onOptionsItemSelected(item)
}
}
.
.
.
companion object {
// (didn't change anything here)
}
The problem occurs I think on my "recreate" calls. It's like the preference list's onItemClickListener is null or something similar.
Can anyone help?
EDIT: Simplified, now all my logic in in SettingsActivity class, I needn't have it in the abstract class
I can't believe that I solved it by adding delayedRecreate instead of recreate (was trying different things from another question - this one):
override fun onPostResume() {
super.onPostResume()
// Self-explanatory. When I load back into this screen, if there's a change in the theme, load it back!
if(hasNightModeChanged()) {
delayedRecreate()
}
}
private fun delayedRecreate() {
val handler = Handler()
handler.postDelayed(this::recreate, 1)
}
However, I don't really like the flicker it does when the screen is a bit delayed in recreating. If anyone has any other clues, it would be highly appreciated!
When I copy and paste Code B (Java Code) into Android Studio 3.1.2, I choose to convert to Kotlin code automatically.
So I get the shown Code A in Kotlin, but with the following error. Why?
Why is that error occurring when Android Studio converts the Java code into Kotlin code automatically?
BTW, Code B (Java code) works well.
Error
java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter state
at ui.DialogChangePassword.showDialog(DialogChangePassword.kt)
Code A (Kotlin Code)
class DialogChangePassword(context: Context, attrs: AttributeSet) : DialogPreference(context, attrs) {
private var mView: View? = null
init {
dialogLayoutResource = R.layout.item_custom_password_dialog
}
override fun onCreateDialogView(): View? {
// TODO Auto-generated method stub
mView = super.onCreateDialogView()
return mView
}
override fun onDialogClosed(positiveResult: Boolean) {
super.onDialogClosed(positiveResult)
}
override fun showDialog(state: Bundle) {
// Call show on default first so we can
// override the handlers
super.showDialog(state)
val d = dialog as AlertDialog
d.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
if (IsInputOKAndSavePassword()) {
d.dismiss()
}
}
}
private fun IsInputOKAndSavePassword(): Boolean {
return true
}
}
Code B (Java Code)
public class DialogChangePassword extends DialogPreference {
private View mView;
public DialogChangePassword(Context context, AttributeSet attrs) {
super(context, attrs);
setDialogLayoutResource(R.layout.item_custom_password_dialog);
}
#Override
protected View onCreateDialogView() {
// TODO Auto-generated method stub
mView = super.onCreateDialogView();
return mView;
}
#Override
protected void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
}
#Override
protected void showDialog(Bundle state) {
// Call show on default first so we can
// override the handlers
super.showDialog(state);
final AlertDialog d = (AlertDialog) getDialog();
d.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(
new View.OnClickListener() {
#Override
public void onClick(View v) {
if (IsInputOKAndSavePassword()){
d.dismiss();
}
}
});
}
private boolean IsInputOKAndSavePassword(){
boolean result=true;
return result;
}
}
Kotlin treats null as a first class citizen by distinguishing nullable types (e.g. Bundle?) vs non-nullable types (e.g. Bundle).
as you know, a Bundle instance can be null depending on what phase of the component's lifecycle you're currently in. for example, the first time an Activity instance is created, onCreate() is called with a null Bundle because there is no state to be restored. if that same Activity is re-created due to a configuration change, however, onCreate() can be called with a non-null Bundle instance (in which you may have stored some data to help recreate the associated screen).
given as much, the showDialog signature should be written in such a way that allows for the possibility that state could possibly be null, like so:
override fun showDialog(state: Bundle?) {
...
}
hope that helps!
I have a RecyclerView list, and an items passes to it's adapter. Now when I click a row in this list, it will show the details of the pressed item.
Let's say I need to toggle the selected item as liked or disliked, as each item in the dataset will have isLiked property with either true or false value.
Now if I changed the isLiked value from false to true, I need to reflect the same change to the recyclerview in the parent - or the previous activity in the stack.
User will click 'back' from device and back to the list. But the changes made in the details activity is not reflecting on the previous list.
I was thinking of using Redux for state management, but not sure what is the ideal solution for this issue.
Any idea please?
As seen here, you need to call mAdapter.notifyDataSetChanged(); after you return from your "detail view", whatever it is.
Use event bus for this Any problem paste the code
take refrence
http://greenrobot.org/eventbus/
First you need Pojo to get Notified suppose
public class NewsCategoryEvent extends RealmObject {
#PrimaryKey
String selectedCategory;
boolean isSelected;
public boolean isSelected() {
return isSelected;
}
public void setSelected(boolean selected) {
isSelected = selected;
}
public String getSelectedCategory() {
return selectedCategory;
}
public void setSelectedCategory(String selectedCategory) {
this.selectedCategory = selectedCategory;
}
}
Then Notify EventBus after making changes in your activity in any method as:
//newsCategoryEvent is object of your pojo
NewsCategoryEvent selectedNewsCategoryEvent = new NewsCategoryEvent ();
newsCategoryEvent.setSelected(true);
EventBus.getDefault().post(newsCategoryEvent);
When you back pressed you need to register and notify changes in your
First Register your Eventbus in onStart();
#Override
protected void onStart() {
super.onStart()
EventBus.getDefault().register(this);
}
Outside onCreate(): notifed recyclar view is updated
#Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(NewsCategory event) {
newsListRecyclerViewAdapter.notifyDataSetChanged();
}
and in onStop() unregister EventBus as
#Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
If you want that your recycler view catch up changes you do on your details screen,
be sure you have correctly defined DiffCallback on your recycler view so that isLike has been checked.
For example if you have you DiffCallback like:
class YourListAdapter : PagedListAdapter<YourItem, YourViewHolder>(DiffCallback) {
companion object DiffCallback : DiffUtil.ItemCallback<YourItem>() {
override fun areItemsTheSame(oldItem: YourItem, newItem: YourItem): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: YourItem, newItem: YourItem): Boolean {
return oldItem.id == newItem.id && oldItem.isLike == newItem.isLike
}
}
}