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
Related
I am creating a calculator. But there are too many setOnClickListeners() making it harder to progress. This belongs to a fragment and it also has a ViewModel. I am using dataBinding here.
If there is any way I can write less code in the below mentioned context.
if there is any confusion about the question, please write in the comment. If my approach is wrong, share in the comments
MY code:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.calculatorViewModel = viewModel
binding.lifecycleOwner = viewLifecycleOwner
viewModel.currentExpression.value = "814×122" //temporary value
viewModel.currentResult.value = "99308" //temporary value
binding.etInput.doAfterTextChanged {
viewModel.currentExpression.value = it.toString()
binding.tvOutputPreview.text = viewModel.currentExpression.value
}
binding.apply {
// Extra operators - setOnClickListener
btnClear.setOnClickListener { viewModel.onClear() }
btnAllClear.setOnClickListener { viewModel.onAllClear() }
btnPlusMinus.setOnClickListener { }
btnEqual.setOnClickListener { }
// Operators - setOnClickListener
btnDivide.setOnClickListener {
viewModel.mountOperator(btnDivide.text) }
btnMultiply.setOnClickListener { viewModel.mountOperator(btnMultiply.text) }
btnMinus.setOnClickListener { viewModel.mountOperator(btnMinus.text) }
btnPlus.setOnClickListener { viewModel.mountOperator(btnPlus.text) }
//Secondary operators - setOnClickListener
btnPercent.setOnClickListener { }
btnDecimal.setOnClickListener { }
// Numbers - setOnClickListener
btn0Num.setOnClickListener { }
btn1Num.setOnClickListener { }
btn2Num.setOnClickListener { }
btn3Num.setOnClickListener { }
btn4Num.setOnClickListener { }
btn5Num.setOnClickListener { }
btn6Num.setOnClickListener { }
btn7Num.setOnClickListener { }
btn8Num.setOnClickListener { }
btn9Num.setOnClickListener { }
}
binding.btnClear.setOnClickListener { viewModel.onClear() }
binding.btnAllClear.setOnClickListener { viewModel.onAllClear() }
binding.btnPlusMinus.setOnClickListener { }
}
That's okay to have too many click listeners.
but what you can do to make it look cleaner is you can set the click listener to this fragment or activity.
for example:
btn0Num.setOnClickListener(this)
and then implement View.OnClickListener in your class
and override the onClick method.
override fun onClick(v: View?) {
v?.let {
when(it){
btn0Num -> {
//Todo do something when the button is clicked
}
btn1Num -> {
//Todo do something when the button is clicked
}
}
}
}
Calculator crew today is it
I just posted this on another question, but if you have repetitive code like that, use a loop!
listOf(btnDivide, btnMultiply, btnMinus, btnPlus).forEach {
it.setOnClickListener { //bla bla }
}
In this case, since your buttons are logically grouped and you might need to refer to a group again, you might want to keep those lists around as a top-level variable:
// lateinit so we don't have to assign it yet, just make sure it's set
// before it's read!
lateinit var digitButtons: List<Button>
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
...
with(binding) {
digitButtons = listOf(btn0Num, btn1Num...)
}
}
then you can refer to it with things like digitButtons.forEach, or if (button in digitButtons) etc
If you're worried about creating so many click listeners, you can reuse a function:
fun handleClick(view: View) {
// do the stuff
}
digitButtons.forEach {
setOnClickListener { handleClick(it) }
}
// or with a function reference instead of a lambda
digitButtons.forEach {
setOnClickListener(::handleClick)
}
And your handling code can use those lists from earlier too
fun handleClick(view: View) {
when {
view !is Button -> return
view in digitButtons -> whateverYouDoWithThose(view)
}
}
but personally I'd go for separate functions for each button type - for the digits, call a function that handles those. For an operator, call a different function. It's easier to read than one giant "a button was clicked here's how we handle each one" function, and it's more informative when you're assigning the click listeners too, since it reads like "when clicked, do this" and handleDigitPressed is better than handleClick, in my opinion anyway!
And setOnClickListener(::clear) is definitely better, you can immediately see what that button does without having to go look it up in the general click handler function. Having separate, well-named functions can make things a lot easier to parse
I use as library "com.jaygoo.widget.RangeSeekBar" to get a Range Seek Bar.
Here's my following code XML :
<com.jaygoo.widget.RangeSeekBar
android:id="#+id/seekBarPrice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:rsb_min="1"
app:rsb_max="5000"
app:rsb_gravity="center"
app:rsb_indicator_background_color="#color/white"
app:rsb_indicator_show_mode="alwaysShow"
app:rsb_indicator_text_color="#color/darkGrey"
app:rsb_indicator_text_size="10sp"
app:rsb_mode="range"
app:rsb_progress_color="#color/honey"
app:rsb_thumb_drawable="#drawable/circle"/>
This RangeSeekBar used to specify the price range, I would like to know How can I add the "$" symbol at the indicator in my seekrangeBar as the follwonig picture :
I add the following kotlin code :
seekBarPrice.leftSeekBar.setIndicatorText("$1")
seekBarPrice.rightSeekBar.setIndicatorText("$1")
seekBarPrice.setRange(1F,5000F)
seekBarPrice.setOnRangeChangedListener(object: OnRangeChangedListener {
override fun onStartTrackingTouch(view: RangeSeekBar?, isLeft: Boolean) {
}
override fun onRangeChanged(
view: RangeSeekBar?,
leftValue: Float,
rightValue: Float,
isFromUser: Boolean
) {
Log.d("tag", "Value: $leftValue")
seekBarPrice.leftSeekBar.setIndicatorText("$".plus(leftValue.toInt()))
seekBarPrice.rightSeekBar.setIndicatorText("$".plus(rightValue.toInt()))
}
override fun onStopTrackingTouch(view: RangeSeekBar?, isLeft: Boolean) {
}
})
And my problem is solved
In your library to put % sign this way so you will change as per your requirement:
seekBarPrice.setIndicatorTextStringFormat("$%s%")
I hope it'll help you...!
I think that's the right way
rangePrice.setOnRangeChangedListener(new OnRangeChangedListener() {
#Override
public void onRangeChanged(RangeSeekBar view, float leftValue, float rightValue, boolean isFromUser)
{
}
#Override
public void onStartTrackingTouch(RangeSeekBar view, boolean isLeft) {
}
#Override
public void onStopTrackingTouch(RangeSeekBar view, boolean isLeft) {
view.setIndicatorTextDecimalFormat("$%s%");
}
});
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!
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
}
}
}
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))
}
}