Handling long values on custom preferences - android

I created a custom Preference for my Android App, a number-picker preference to be exact. It's really a shame that Android don't provide already a built-in preference for that, but we need to deal with it so I created one and since I could re-use that code in future projects I decided to make it using long values, so it could handle very large numbers, but I found something very curious.
You can store long values on the SharedPreferences but there isn't any getLong() on a TypedArray which is used to access attribute values on Android. So the work-around that I made was to get the values from the TypedArray as Strings and convert them to long. But I'm wondering if there is a better approach
Here I leave you the code snippet, feel free to use it in your projects, it's not using the NumberPicker Widget, it's built with a TextView and two buttons so you can use it on old devices.
public class NumberPickerPreference extends DialogPreference {
private long max;
private long min;
private long value;
private TextView picker;
private Button btnUp;
private Button btnDown;
private int step;
private long defValue;
public NumberPickerPreference(Context context, AttributeSet attrs) {
super(context, attrs);
setPersistent(false);
setDialogLayoutResource(R.layout.number_picker);
setPositiveButtonText(android.R.string.ok);
setNegativeButtonText(R.string.cancel);
TypedArray numberPickerType=context.obtainStyledAttributes(attrs,
R.styleable.NumberPickerPreference, 0, 0);
String maxStr = numberPickerType.getString(R.styleable.NumberPickerPreference_max);
if(maxStr==null) {
maxStr="50";
}
String minStr=numberPickerType.getString(R.styleable.NumberPickerPreference_min);
if(minStr==null) {
minStr="5";
}
step=numberPickerType.getInt(R.styleable.NumberPickerPreference_step, 1);
max=Long.parseLong(maxStr);
min=Long.parseLong(minStr);
numberPickerType.recycle();
}
#Override
protected void onBindDialogView(View v) {
picker=(TextView)v.findViewById(R.id.tvNumUpDown);
btnUp=(Button)v.findViewById(R.id.btnUp);
btnDown=(Button)v.findViewById(R.id.btnDown);
btnUp.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
add();
}
});
btnDown.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
subs();
}
});
value=getSharedPreferences().getLong(getKey(), defValue);
picker.setText(value+"");
super.onBindDialogView(v);
}
#Override
protected void onSetInitialValue(boolean restorePersistedValue,
Object defaultValue) {
long aux=min;
if(defaultValue!=null && !defaultValue.toString().isEmpty()) {
aux=Long.parseLong(defaultValue.toString());
}
defValue=(restorePersistedValue?getPersistedLong(min):aux);
}
/**
* You can provide a default value with the android:defaultValue attribute here
*/
#Override
protected Object onGetDefaultValue(TypedArray a, int index) {
return a.getString(index);
}
protected void subs() {
if(value>min) {
value-=step;
picker.setText(value+"");
}
}
protected void add() {
if(value<max) {
value+=step;
picker.setText(value+"");
}
}
#Override
protected void onDialogClosed(boolean positiveResult) {
if(positiveResult) {
getEditor().putLong(getKey(), value).commit();
}
}
}

This is quite an old question, but I will give an answer for the benefit of future readers.
Converting from a String as you have done here is a fine approach. Ultimately that is what DialogPreference would be doing anyway. The attribute in XML is a String, so whether you convert it, or it is converted for you, the data will be coming from a parsed String. Just ensure when you are making the conversion you handle invalid data / NumberFormatExceptions properly.

Related

PersistInt slowing down button response in custom preference

I've created a custom preference that embeds two buttons (here I have subclassed Button as FastButton). The problem is executing the persistInt to store the preference drastically slows down the response of the button.
I had the notion of only executing the persistInt when the preference's lifecycle is ended, but could not find an appropriate method to override (i.e. there is nothing like onPause() for the Preference class).
I was also unsuccessful at trying to use AsyncTask to move the persistInt off of the UI thread.
Any suggestions about how I should go about mitigating the effect of persistInt on my UI response?
public final class StepperPreference extends Preference {
public int mCurrentValue = 1;
public int maxValue = Integer.MAX_VALUE;
public int minValue = Integer.MIN_VALUE;
private TextView mText;
private FastButton plusButton;
private FastButton minusButton;
public StepperPreference(Context context) {
super(context);
}
public StepperPreference(Context context, AttributeSet attrs) {
super(context, attrs);
parseCustomAttributes(attrs);
}
public StepperPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
parseCustomAttributes(attrs);
}
public void setmCurrentValue(int value) {
if (mCurrentValue != value) {
mCurrentValue = value;
persistInt(mCurrentValue);
}
}
private void parseCustomAttributes(AttributeSet attrs) {
int maxValueAttrInt=Integer.MAX_VALUE;
int minValueAttrInt=Integer.MIN_VALUE;
if (attrs!=null) {
TypedArray a=getContext()
.obtainStyledAttributes(attrs,
R.styleable.StepperPreference,
0, 0);
maxValueAttrInt = a.getInt(R.styleable.StepperPreference_maxValue, Integer.MAX_VALUE);
minValueAttrInt = a.getInt(R.styleable.StepperPreference_minValue, Integer.MIN_VALUE);
a.recycle();
}
if (maxValueAttrInt > minValueAttrInt) {
maxValue = maxValueAttrInt;
minValue = minValueAttrInt;
}
}
#Override
protected View onCreateView(ViewGroup parent) {
LayoutInflater li = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = (View) li.inflate(R.layout.stepper_preference, parent, false);
mText = (TextView) view.findViewById(R.id.text_view);
Context context = getContext();
int localDefaultValue = 0;
mCurrentValue = getPersistedInt(localDefaultValue);
mText.setText(Integer.toString(mCurrentValue));
plusButton = (FastButton) view.findViewById(R.id.plus_button);
plusButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (mCurrentValue < maxValue) {
mCurrentValue++;
mText.setText(Integer.toString(mCurrentValue));
persistInt(mCurrentValue);
}
}
});
minusButton = (FastButton) view.findViewById(R.id.minus_button);
minusButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (mCurrentValue > minValue) {
mCurrentValue--;
mText.setText(Integer.toString(mCurrentValue));
persistInt(mCurrentValue);
}
}
});
return view;
}
#Override
protected Object onGetDefaultValue(TypedArray a, int index) {
int localDefaultValue = 0;
Object result = a.getInt(index, localDefaultValue);
return result;
}
#Override
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
int localDefaultValue = 0;
setmCurrentValue(restoreValue ? this.getPersistedInt(localDefaultValue) : (int) defaultValue);
}
}
CheckBoxPreference, via its TwoStatePreference superclass, uses persistBoolean() for saving the preference value, much as you are using persistInt(). I do not perceive significant latency in the processing of the CheckBox. This means one of two things:
I'm a troglodyte and am incapable of seeing obvious delays in animations and such
CheckBoxPreference does not exhibit the problems that you are seeing in your StepperPreference
note: these two possibilities are not mutually exclusive
If we assume #2 to be correct, then there's something else afoot. Method tracing, to see where you are spending time "downstream" from persistInt(), may prove useful for determining what is different about StepperPreference.
From your comment, you had a listener responding to preference changes, and that was what was causing the sluggish response. "Inline" preferences, like CheckBoxPreference and StepperPreference, will be somewhat more "twitchy" than DialogPreference subclasses like ListPreference, simply because it takes less work to change the preference state (e.g., one screen tap versus 2+). As a result, listeners need to be cheaper. For example, you might hold off on doing significant work until the user has left the PreferenceFragment and so you know that the preference values are likely to be stable for at least a second or so.

Validating EditTextPreference on Android

I have read the many answers on this question but my question is asking where I place the code. I am looking to validate that a number is greater than 100 in the EditTextPreference. This is the code I use to populate the preferences:
public class SettingsFrag extends PreferenceFragment{
//Override onCreate so that the code will run when the activity is started.
#Override
public void onCreate(Bundle savedInstanceState){
//Call to the super class.
super.onCreate(savedInstanceState);
//add the preferences from the XML file.
addPreferencesFromResource(R.xml.preferences);
}
}
Is it in here I add the validation or would I have to create another class?
preferences.xml:
<EditTextPreference
android:key="geofence_range"
android:title="Geofence Size"
android:defaultValue="500"
android:inputType="number"
android:summary="Geofence Size Around User Location"
android:dialogTitle="Enter Size (meters):" />
Add setOnPreferenceChangeListener for EditTextPreference after addPreferencesFromResource to validate data input for User:
EditTextPreference edit_Pref = (EditTextPreference)
getPreferenceScreen().findPreference("geofence_range");
edit_Pref.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
#Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
// put validation here..
if(<validation pass>){
return true;
}else{
return false;
}
}
});
Note: This answer is based on the deprecated android.preference.EditTextPreference.
Huh. Another one of those things that should be so darned easy in Android but isn't. Other answers just silently prevent writing back the result to preferences, which seems a bit shoddy. (Showing toast is less shoddy, but is still shoddy).
You'll need a custom preference to do this. Customize onValidate to suit your needs.
package com.two_play.extensions;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.preference.EditTextPreference;
import android.util.AttributeSet;
import android.view.View;
import android.widget.EditText;
public class ValidatingEditTextPreference extends EditTextPreference {
public ValidatingEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public ValidatingEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ValidatingEditTextPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ValidatingEditTextPreference(Context context) {
super(context);
}
#Override
protected void showDialog(Bundle state) {
super.showDialog(state);
AlertDialog dlg = (AlertDialog)getDialog();
View positiveButton = dlg.getButton(DialogInterface.BUTTON_POSITIVE);
getEditText().setError(null);
positiveButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
onPositiveButtonClicked(v);
}
});
}
private void onPositiveButtonClicked(View v) {
String errorMessage = onValidate(getEditText().getText().toString());
if (errorMessage == null)
{
getEditText().setError(null);
onClick(getDialog(),DialogInterface.BUTTON_POSITIVE);
getDialog().dismiss();
} else {
getEditText().setError(errorMessage);
return; // return WITHOUT dismissing the dialog.
}
}
/***
* Called to validate contents of the edit text.
*
* Return null to indicate success, or return a validation error message to display on the edit text.
*
* #param text The text to validate.
* #return An error message, or null if the value passes validation.
*/
public String onValidate(String text)
{
try {
Double.parseDouble(text);
return null;
} catch (Exception e)
{
return getContext().getString(R.string.error_invalid_number);
}
}
}
It seems more elegant to me to disable the "OK" button instead of allowing the user to press it but then discard their input and show the error. getDialog seems to be gone in androidx.preference, but this seems to work for me instead:
final EditTextPreference p = new EditTextPreference(context);
p.setOnBindEditTextListener(new EditTextPreference.OnBindEditTextListener() {
#Override
public void onBindEditText(#NonNull final EditText editText) {
editText.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable editable) {
String validationError;
try {
// ... insert your validation logic here, throw on failure ...
validationError = null; // All OK!
} catch (Exception e) {
validationError = e.getMessage();
}
editText.setError(validationError);
editText.getRootView().findViewById(android.R.id.button1)
.setEnabled(validationError == null);
}
});
}
});
Here is my Kotlin implementation for androidx.preference.* based on Vladimir Panteleev's answer:
class CustomPreference : EditTextPreference {
// ...
private fun applyValidation() = setOnBindEditTextListener { editText ->
editText.doAfterTextChanged { editable ->
requireNotNull(editable)
// TODO Add validation magic here.
editText.error = if (criteria.isValid()) {
null // Everything is fine.
} else {
if (criteria.getErrorMessage() == null) "Unknown validation error"
else resources.getString(criteria.getErrorMessage())
}
}
}
}
The handy doAfterTextChanged extension is part of the androidx.core:core-ktx library.

Integrating Google Analytis to android app

https://developers.google.com/analytics/devguides/collection/android/v2/#manifest "Add the send methods to the onStart() and onStop() methods of each of your Activities as in the following example:"
Here is the question, I have no onStart and onStop methods in my main class. Should I put that piece of code in all of my methods? Or only in specific ones? I have a lot of methods in my class (probably should do something about it...):
package com.something.smth;
import something.com;
#SuppressLint("DefaultLocale")
public class Main extends Activity implements View.OnClickListener {
EditText input;
LinearLayout ll;
#Override
protected void onCreate(Bundle savedInstanceState) {
something
}
private void whatToDo() {
something
}
#Override
public void onClick(View v) {
something
}
private void prefdata() {
something
}
private void printAll(int i, int examNumb) {
something
}
private void printOutFirst(String lesson, String type, int monthD,
int dayD, int hourD) {
something
}
private void printOutSecond(int monthD, int dayD, int hourD) {
something
}
private void timeleft(int mDate, int dDate, int hDate) {
something
}
#Override
public boolean onCreateOptionsMenu(android.view.Menu menu) {
something
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
something
}
}
Also, should I put that piece of code in all of my class'es or only in my main (above) class?
Thanks in advance.
You definitely should create those methods and put the appropriate GA calls in there. Another option would be to build a base class and extend it so you're not duplicating code. I have some other tricks outlined here:
http://www.aydabtudev.com/2011/03/google-analytics-tricks-for-android.html

android - how to keep watch on one value

The scenario is, I am developing an app which reads integer value from file and store it in integer. And value that I get from that file is always changing cause it is cpu frequency value.
How do I keep watch on this value from my activity ?
The ways in my mind are through broadcast receiver, service, observer etc.
But dont know how do I implement..
You can use a listener:
public class CpuValueReader {
private CpuValueReaderListener listener = null;
int cpuValue;
public void setListener(CpuValueReaderListener listener) {
this.listener = listener;
}
public void startReading() {
// this is an exmaple, you may read the file here
// found value:
setValue(theNewValue);
}
private setValue(int value) {
if (value != cpuValue) {
cpueValue = value;
if (listener != null)
listener.cpuValueChanged(value);
}
}
}
the interface:
public inteface CpuValueReaderListener {
public void cpuValueChanged(int newValue);
}
Using it (just an example):
CpuValueReader instance = new CpuValueReader ();
instance.setListener(new CpuValueReaderListener() {
#Override
public void cpuValueChanged(int newValue) {
// do cool things with new value
}
});
I achieve this using chronometer tick listner. So on every tick of chronometer I read the file and set value to text view
chronometer = (Chronometer)findViewById(R.id.chronometer1);
chronometer.setOnChronometerTickListener(new OnChronometerTickListener()
{
#Override
public void onChronometerTick(Chronometer arg0)
{
//read file here and set the value to my textView
}
});
chronometer.start();

Android TimePicker in PreferenceScreen -> read the values

I have a custom DialogPreference. The Dialog is called from a PreferenceScreen.
All works fine, the Dialog starts and shows the TimePicker.
But how do I get the selected values?
First of all, I tried to write the selected hours in the summary of the Preference. (therefore the var xxx :)
Later on, I want to save the values in SharedPreferences.
This is what I have for now:
public class Calendar extends DialogPreference implements
TimePicker.OnTimeChangedListener {
TimePicker tp;
int xxx;
public Calendar(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public Calendar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize();
}
private void initialize() {
setPersistent(true);
}
#Override
protected View onCreateDialogView() {
tp = new TimePicker(getContext());
tp.setIs24HourView(true);
return tp;
}
#Override
public void onTimeChanged(TimePicker arg0, int arg1, int arg2) {
}
#Override
public void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
if (positiveResult) {
// getEditor().
setTitle(getTitle());
setSummary(Integer.toString(xxx));
}
}
private TimePicker.OnTimeChangedListener mTimeSetListener =
new TimePicker.OnTimeChangedListener() {
#Override
public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
xxx=hourOfDay;
}
};
}
Thanks a lot and best regards
Thanks for asking this question, it provided me with an important answer on how to create a DialogPreference.
I hope I might also have an answer for you. I modified your code a little bit and I can now store the time selected from the Dialog:
#Override
protected View onCreateDialogView() {
this.tp = new TimePicker(getContext());
this.tp.setIs24HourView(true);
final String storedValue = getPersistedString("07:00");
final String[] split = storedValue.split(":");
this.tp.setCurrentHour(Integer.parseInt(split[0]));
this.tp.setCurrentMinute(Integer.parseInt(split[1]));
return this.tp;
}
#Override
public void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
if (positiveResult) {
final String result = this.tp.getCurrentHour() + ":" + this.tp.getCurrentMinute();
persistString(result);
}
}
When the dialog is shown I retrieve the stored value and simply set the currentHour and currentMinute fields of the TimePicker. The other way round when the dialog is closed. Since I control both the format on the way in as well as on the way out there should not be a problem with illegal values.
Was this what you were looking for?
To store the value in shared pref, implement on preference Change Listener.
note that preference should be default Shared preference
preferences = PreferenceManager.getDefaultSharedPreferences(context);
editor = preferences.edit();
inside onprefchange:
if (preference.getKey().equals(getString(R.string.reminder_end_time_key))){
editor.putString("End_Date", String.valueOf(newValue));
editor.apply();
endTimePickerPreference.setSummary((CharSequence)newValue);
}

Categories

Resources