I have a custom TimePickerDialog that extends from Android's. This dialog has an interface OnTimeSetListener that has to be implemented by its Context or parent Fragment.
CustomTimePickerDialog.java
public class CustomTimePickerDialog extends DialogFragment implements
android.app.TimePickerDialog.OnTimeSetListener {
...
private OnTimeSetListener listener;
public static TimePickerDialog newInstance(int hour, int minutes) {
TimePickerDialog fragment = new TimePickerDialog();
return fragment;
}
#NonNull
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new android.app.TimePickerDialog(getContext(), this, 0, 0, true);
}
#Override
public void onTimeSet(TimePicker timePicker, int hour, int minutes) {
listener.onTimeSet(hour, minutes);
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
Object parent = getParentFragment() == null
? context
: getParentFragment();
if (parent instanceof OnTimeSetListener) {
listener = ((OnTimeSetListener) parent);
} else {
throw new RuntimeException(parent + " must implement OnTimeSetListener");
}
}
public interface OnTimeSetListener {
void onTimeSet(int hour, int minutes);
}
}
The problem is that I need to extends this class again to change the onTimeSet() behaviour, like this:
LimitedCustomTimePickerDialog.java
public class LimitedCustomTimePickerDialog extends CustomTimePickerDialog {
#Override
public void onTimeSet(TimePicker timePicker, int hour, int minutes) {
int standardMinutes = minutes < 30 ? 0 : 30;
super.onTimeSet(timePicker, hour, standardMinutes);
}
}
But it is never called! How can I achieve this?
You implemented interface both side so who is calling onTimeSet? Just implement where you want to use this method.
Related
I have complex application with tons of fragments and sub-fragments, and I need listeners to listen in a fragment, not in activity. This usually works, but not with date or time pickers.
Here is the sample application with activity with one fragment inflated - TestFragmentMain. That inflated fragment have two more fragments - one with single EditText (TestFragment_InputBox) and another with single TextView (TestFragment_DateBox) and it is used to listen events (TextChangeListener and DateChangeListener).
On text change in first fragment, all works like a charm, main fragment is receiving result.
However, on date change, I receive and error:
java.lang.NullPointerException: Attempt to invoke interface method 'void com.gmail.xxx.xxx.test.DateChangeListener.dateChanged(int, int, int)' on a null object reference
at com.gmail.xxx.xxx.test.TestFragment_DatePicker.onDateSet(TestFragment_DatePicker.java:32)
I really do not understand why. Any help is appreciated.
Main activity:
public class TestClass extends AppCompatActivity {
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test_activity);
TestFragmentMain testFragmentMain = new TestFragmentMain();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.test_fragment_container, testFragmentMain);
ft.commitAllowingStateLoss();
}
}
Main fragment, with listeners
public class TestFragmentMain extends Fragment implements TextChangeListener, DateChangeListener {
#Override
public View onCreateView(#NotNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.test_fragment, container, false);
TestFragment_InputBox testFragmentInputBox = new TestFragment_InputBox();
FragmentTransaction ft = getChildFragmentManager().beginTransaction();
ft.replace(R.id.test_fragment_inputbox_container, testFragmentInputBox);
ft.commitAllowingStateLoss();
TestFragment_DateBox testFragmentDateBox = new TestFragment_DateBox();
ft = getChildFragmentManager().beginTransaction();
ft.replace(R.id.test_fragment_date_container, testFragmentDateBox);
ft.commitAllowingStateLoss();
return view;
}
#Override
public void onAttach(#NotNull Context context) {
super.onAttach(context);
}
#Override
public void textChanged() {
Log.d("LISTEN","Text has been changed...");
}
#Override
public void dateChanged(int year, int month, int day) {
Log.d("LISTEN","Date has been changed to ...");
}
}
Fragment with input box:
public class TestFragment_InputBox extends Fragment {
private TextChangeListener textChangeListener;
#Override
public View onCreateView(#NotNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.test_input_box, container, false);
EditText editText = view.findViewById(R.id.input_view);
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 s) {
textChangeListener.textChanged();
}
});
return view;
}
#Override
public void onAttach(#NotNull Context context) {
super.onAttach(context);
try {
textChangeListener = (TextChangeListener) getParentFragment();
} catch (ClassCastException e) {
e.printStackTrace();
}
}
}
Fragment with date box:
public class TestFragment_DateBox extends Fragment implements DateChangeListener {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.test_date_box, container, false);
TextView textView = view.findViewById(R.id.date_view);
textView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
DialogFragment datePicker = new TestFragment_DatePicker();
datePicker.show(getActivity().getSupportFragmentManager(), "datePicker");
}
});
return view;
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
}
#Override
public void dateChanged(int year, int month, int day) {
Log.d("LISTEN","Date has been changed in box fragment with box ...");
}
}
Date Picker fragment
public class TestFragment_DatePicker extends DialogFragment implements DatePickerDialog.OnDateSetListener {
public DateChangeListener dateChangeListener;
#NotNull
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Calendar c = Calendar.getInstance();
int year = c.get(Calendar.YEAR);
int month = c.get(Calendar.MONTH);
int day = c.get(Calendar.DAY_OF_MONTH);
return new DatePickerDialog(Objects.requireNonNull(getContext()), this, year, month, day);
}
#Override
public void onDateSet(DatePicker view, int year, int month, int day) {
dateChangeListener.dateChanged(year,month,day);
}
#Override
public void onAttach(#NotNull Context context) {
super.onAttach(context);
try {
dateChangeListener = (DateChangeListener) getParentFragment();
} catch (ClassCastException e) {
e.printStackTrace();
}
}
}
And listeners:
public interface TextChangeListener {
void textChanged();
}
public interface DateChangeListener {
void dateChanged(int year,int month,int day);
}
Your fragment TestFragment_DateBox calls the date picker and inherits DatePickerDialog.OnDateSetListener
public class TestFragment_DateBox extends Fragment implements DatePickerDialog.OnDateSetListener
and show the date picker fragment like so
TestFragment_DatePicker().show(this.childFragmentManager, "datepicker")
and override onDateSet
#Override
public void onDateSet(DatePicker view, int year, int month, int day) {
// As you are in the calling fragment you can use the values as you see fit
TextView textView = view.findViewById(R.id.date_view);
textView.text = year.toString()
}
In TestFragment_DatePicker have a listener
private lateinit var listener: DatePickerDialog.OnDateSetListener
then override onAttach
override fun onAttach(context: Context) {
super.onAttach(context)
// Verify that the dialog parent implements the callback interface
try {
// Instantiate the OnDateSetListener so we can send events to the host
listener = parentFragment as DatePickerDialog.OnDateSetListener
} catch (e: ClassCastException) {
// The parent doesn't implement the interface, throw exception
throw ClassCastException(("$context must implement OnDateSetListener"))
}
}
Apologies for the mix of java and kotlin but you get the idea!
The problem is that you not inflate your TestFragment_DatePicker which is a DialogFragment which is a Fragment but return a DatePickerDialog which is an AlertDialog which is a Dialog which don't have a mParentFragment. That's why getParentFragment() return null.
This looks like a bad architecture design that we inherited from old versions of android.
I have found a solution.
In TestFragmentMain, TestFragment_DateBox need to be transacted with same FragmentManager as DatePicker is. And this is the code in TestFragmentMain:
TestFragment_DateBox testFragmentDateBox = new TestFragment_DateBox();
FragmentTransaction ft2 = this.getChildFragmentManager().beginTransaction();
ft2.replace(R.id.test_fragment_date_container, testFragmentDateBox);
ft2.commitAllowingStateLoss();
Change is also in TestFragment_DateBox when opening DatePicker:
DialogFragment datePicker = new TestFragment_DatePicker();
datePicker.setTargetFragment(this, 0);
datePicker.show(this.getFragmentManager(), "datePicker");
We need to run setTargetFragment in order to work.
Now this test works.
I'm new to coding writing a small application to select time from a custom time picker and get time from it and use it.I'm getting a NullRefrenceError.
My xml file:
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:key="init_Settings">
<PreferenceCategory
android:title="Set-Time">
<com.lambdahash.sonic.ui_app.fragments.TimePick
android:id="#+id/from_time"
android:title="Time from"
android:defaultValue="--:--"
android:summary="--:--"
android:key="time-from"
/>
<com.lambdahash.sonic.ui_app.fragments.TimePick
android:id="#+id/to_time"
android:title="Time to"
android:summary="--:--"
android:defaultValue="--:--"
android:key="time-to"
/>
</PreferenceCategory>
Here TimePick is a java class that works perfectly fine for picking time.
The code:
public class TimePick extends DialogPreference {
private int lastHour=0;
private int lastMinute=0;
private TimePicker picker=null;
public static int getHour(String time) {
String[] pieces=time.split(":");
return(Integer.parseInt(pieces[0]));
}
public static int getMinute(String time) {
String[] pieces=time.split(":");
return(Integer.parseInt(pieces[1]));
}
public TimePick(Context ctxt, AttributeSet attrs) {
super(ctxt, attrs);
setPositiveButtonText("Set");
setNegativeButtonText("Cancel");
}
#Override
protected View onCreateDialogView() {
picker=new TimePicker(getContext());
return(picker);
}
#Override
protected void onBindDialogView(View v) {
super.onBindDialogView(v);
picker.setCurrentHour(lastHour);
picker.setCurrentMinute(lastMinute);
}
#Override
protected void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
if (positiveResult) {
lastHour=picker.getCurrentHour();
lastMinute=picker.getCurrentMinute();
String time=String.valueOf(lastHour)+":"+String.valueOf(lastMinute);
if (callChangeListener(time)) {
persistString(time);
}
}
}
#Override
protected Object onGetDefaultValue(TypedArray a, int index) {
return(a.getString(index));
}
}
This is where I'm getting error:
public class generalSettings extends PreferenceActivity{
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.general_settings);
TimePicker time_from = (TimePicker) findViewById(R.id.from_time);
TimePicker time_to = (TimePicker) findViewById(R.id.to_time);
time_from.setOnTimeChangedListener(new TimePicker.OnTimeChangedListener() {
#Override
public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
//ts.timeFrom();
Log.d("TESTF:",hourOfDay+":"+minute+"\n");
}
});
}
}
Getting
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TimePicker.setOnTimeChangedListener(android.widget.TimePicker$OnTimeChangedListener)' on a null object reference
How do I workaround this?
Try this code
Preference time_from = findPreference("time-from");
time_from.setOnTimeChangedListener(new TimePicker.OnTimeChangedListener() {
#Override
public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
//ts.timeFrom();
Log.d("TESTF:",hourOfDay+":"+minute+"\n");
}
});
Preference time_to= findPreference("time-to");
time_from.setOnTimeChangedListener(new TimePicker.OnTimeChangedListener() {
#Override
public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
//ts.timeTo();
Log.d("TESTF:",hourOfDay+":"+minute+"\n");
}
});
You should be designing it differently. You should be extending the TimePick class, once for the time_from and once for time_two. Set the onClickListener in the extended class, not in the PreferencesScreen class. You should do that by overriding the onCreateDialogView function, like this.
#Override protected View onCreateDialogView() {
picker=super.onCreateDialogView();
picker.setOnTimeChangedListener(...);
return(picker);
}
Then in your XML, put in your Extended class, rather than the TimePick class.
RxTextView.textChanges(editText)
.map(CharSequence::toString)
.debounce(200, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(input -> {
output = //...do something with input
editText.setText(ouput)
}));
When I setText(output) it goes in loop. To set the text I first need to remove listener and then set listener again. How can I do this using RxJava?
When I setText(output) it goes in loop. To set the text I first need to remove listener and then set listener again. How can I do this using RxJava?
To meet the requirement I managed to extend the RxBinding source code as follows.
EditableTextViewTextObservable.java:
public class EditableTextViewTextObservable extends InitialValueObservable<CharSequence> {
private final TextView view;
EditableTextViewTextObservable(TextView view) {
this.view = view;
}
#Override
protected void subscribeListener(Observer<? super CharSequence> observer) {
EditableTextViewTextObservable.Listener listener = new EditableTextViewTextObservable.Listener(view, observer);
observer.onSubscribe(listener);
view.addTextChangedListener(listener);
}
#Override protected CharSequence getInitialValue() {
return view.getText();
}
final static class Listener extends MainThreadDisposable implements TextWatcher {
private final TextView view;
private final Observer<? super CharSequence> observer;
Listener(TextView view, Observer<? super CharSequence> observer) {
this.view = view;
this.observer = observer;
}
#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 s) {
if (!isDisposed()) {
view.removeTextChangedListener(this);
observer.onNext(s);
view.addTextChangedListener(this);
}
}
#Override
protected void onDispose() {
view.removeTextChangedListener(this);
}
}
}
EditableRxTextView.java:
public final class EditableRxTextView {
#CheckResult
#NonNull
public static InitialValueObservable<CharSequence> textChanges(#NonNull TextView view) {
return new EditableTextViewTextObservable(view);
}
}
Usage:
EditableRxTextView.textChanges(editText)
.map(CharSequence::toString)
.debounce(200, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(input -> {
output = //...do something with input
editText.setText(ouput)
}));
My solution was to use editText.getText().replace(...) as to not trigger the TextWatcher when setting the text
I have managed to make a date picker class but now I want the value from the date picker dialog to be returned to the main activity class. Below is the code for the date picker dialog.
public class DateChooser extends DialogFragment implements DatePickerDialog.OnDateSetListener{
public int y,m,d;
public Dialog onCreateDialog(Bundle savedInstanceState)
{
final Calendar c = Calendar.getInstance();
int year = c.get(Calendar.YEAR);
int month = c.get(Calendar.MONTH);
int day = c.get(Calendar.DAY_OF_MONTH);
return new DatePickerDialog(getContext(),this,year,month,day);
}
#Override
public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) {
Toast.makeText(getContext(), "Toast!", Toast.LENGTH_SHORT).show();
setDay(dayOfMonth);
setMonth(month);
setYear(year);
}
I have called this class from the mainactivity.
public void dateSelect(View view)
{
DateChooser dateC = new DateChooser();
dateC.show(getSupportFragmentManager(),"tag");
}
Do this:
Add this to DateChooser Class:
private DateSelectedListener mListener;
#Override
public void onDateSet(DatePicker view, int year, int month, int day) {
// Do something with the date chosen by the user
mListener.onDateSelected(view, year, month, day);
}
public void display(FragmentManager manager, String tag, DateSelectedListener listener) {
super.show(manager, tag);
mListener = listener;
}
public interface DateSelectedListener {
void onDateSelected(DatePicker view, int year, int month, int day);
}
in your Activity do this:
public class MyActivity extends AppCompatActivity implements DateChooser.DateSelectedListener {
//onCreate or whatever other method:
dateChooser.display(
getActivity().getSupportFragmentManager(),
"datePicker", this);
}
Good luck!!!
DatePicker datePicker = (DatePicker) findViewById(R.id.datePicker1);
int day = datePicker.getDayOfMonth();
int month = datePicker.getMonth() + 1;
int year = datePicker.getYear();
String dateInString=year+"-"+month+"-"+day;
DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
Date date = formatter.parse(dateInString);
System.out.println(date);
You can create an interface e.g. like this
public interface DateListener {
void onDateSelected(int day, int month, int year);
}
and implement it in your activity
public class YourActivity extends Activity implements DateListener {...}
then you have to implement the method in your activity...
In your Fragment add the following field:
private DateListener dateListener;
and add this code to your Fragment's onAttach:
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (!(getActivity() instanceof DateListener)) {
throw new ClassCastException("The activity inflating this fragment must implement DateListener!");
}
dateListener = (DateListener) getActivity();
}
Now you have set your activity as DateListener and can use it in the result of the DatePicker
I have a custom class called DatePicker who extends DialogFragment and implements DatePickerDialog.OnDateSetListener, but my problem is because y need to listen the onDateChangeMethod and the DialogFragment only have DialogInterface.OnCancelListener and DialogInterface.OnDismissListener.
How can I do that? I tried to implement OnDateChangedListener and override the onDateChange method but it did not work. Here is the code:
public class DatePicker extends DialogFragment implements
DatePickerDialog.OnDateSetListener, OnDateChangedListener {
private OnDateSetListener _onDateSetListener;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
_onDateSetListener = (OnDateSetListener) activity;
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Date initialValue = null;
String[] buttons = getArguments().getStringArray("buttons");
Long initialValueLong = getArguments().getLong("initialValue");
if (initialValue == null) {
initialValue = new Date();
}
Calendar calendar = GregorianCalendar.getInstance();
calendar.setTimeInMillis(initialValueLong);
DatePickerDialog dialog = new DatePickerDialog(getActivity(), this,
calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH),
calendar.get(Calendar.DATE));
return dialog;
}
#Override
public void onDateSet(android.widget.DatePicker view, int year,
int monthOfYear, int dayOfMonth) {
_onDateSetListener.onDateSet(new GregorianCalendar(year, monthOfYear,
dayOfMonth).getTime(), getArguments().get("tag"));
}
#Override
public void onDateChanged(android.widget.DatePicker view, int year,
int monthOfYear, int dayOfMonth) {
// THIS METHOD NEVER CALLED
}
}
It looks like you haven't declared an onDateSetListener in your class, and you're also casting the host activity to the wrong type. The Dialog class itself should be the OnDateSetListener, and the host activity should be cast to something else.
Try to structure the class like this:
public class DatePickerDialogFragment extends DialogFragment implements
DatePickerDialog.OnDateSetListener
{
public static interface DatePickedListener
{
public void onDatePicked(int selectedYear, int selectedMonth, int selectedDay);
}
private DatePickedListener listener;
#Override
public void onAttach(Activity activity)
{
// when the fragment is initially attached to the activity, cast the
// activity to the callback interface type
super.onAttach(activity);
try
{
listener = (DatePickedListener) activity;
}
catch (ClassCastException e)
{
throw new ClassCastException(activity.toString() + " must implement " + DatePickedListener.class.getName());
}
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState)
{
Bundle b = getArguments();
int year = b.getInt("set_year");
int month = b.getInt("set_month");
int day = b.getInt("set_day");
return new DatePickerDialog(getActivity(), this, year, month, day);
}
#Override
public void onDateSet(DatePicker view, int setYear, int setMonth, int setDay)
{
// when the date is selected, send it to the activity via its callback interface method
listener.onDatePicked(setYear, setMonth, setDay);
}
}
I found the solution, just only need to get de DatePicker and set DatePicker.init method
android.widget.DatePicker datePicker = dialog.getDatePicker();
datePicker.init(calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH), calendar.get(Calendar.DATE),
new OnDateChangedListener() {
public void onDateChanged(android.widget.DatePicker view,
int year, int month, int day) {
_onDateSetListener.dateChange(new GregorianCalendar(
year, month, day).getTime(), getArguments()
.get("tag"));
}
});