Calendar
Calendar copied to clipboard
Cancel setupAsync
I've tried using calendarView.setupAsync
, but I've noticed that my view might be destroyed before this function completes. For example, I start drawing Fragment that contains the calendar, and I realize that the user should be redirected to the login view, so I navigate them there. I've noticed that I get a lot of null pointer exceptions, so I tried cancelling and cleaning the calendar view onDestroyView function, but that doesn't seem to cancel the job that starts the async thread. Is there a different call I can do on the destroy view to cancel the async job? Thanks
The calendar cancels the job when the view is detached from the window, an alternative would be to return the job so the caller can cancel when appropriate. Can I see the stack trace of the NPEs you're getting? This way I can figure out what exactly is null, maybe I need to improve the internal job cleanup.
I see the following error:
2022-06-28 14:52:15.680 23489-23559/com.mycopilot.copilotapp E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-1
Process: com.mycopilot.copilotapp, PID: 23489
java.lang.NullPointerException
at com.mycopilot.copilotapp.today.calendar.CalendarFragment.getBinding(CalendarFragment.kt:61)
at com.mycopilot.copilotapp.today.calendar.CalendarFragment.access$getBinding(CalendarFragment.kt:48)
at com.mycopilot.copilotapp.today.calendar.CalendarFragment$onViewCreated$4.invoke(CalendarFragment.kt:99)
at com.mycopilot.copilotapp.today.calendar.CalendarFragment$onViewCreated$4.invoke(CalendarFragment.kt:94)
at com.kizitonwose.calendarview.CalendarView$setupAsync$1$1.invokeSuspend(CalendarView.kt:707)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:226)
at android.os.Looper.loop(Looper.java:313)
at android.app.ActivityThread.main(ActivityThread.java:8663)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:567)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1135)
Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@e20508e, Dispatchers.Default]
My code looks like the following:
// This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
_binding = FragmentCalendarBinding.bind(view)
...
binding.calendarView.setupAsync(
currentMonth.minusMonths(1),
currentMonth.plusMonths(3),
DayOfWeek.SUNDAY) {
// Scroll to current date.
binding.calendarView.scrollToDate(todayViewModel.uiState.value.currentDate)
It fails in this line where it's trying to scroll to view.
I also have the following code in the destroy:
override fun onDestroyView() {
super.onDestroyView()
binding.calendarView.adapter = null
_binding = null
}
Thank you for looking into this.
A workaround you can use for now is to call onDetachedFromWindow()
on the calendarView before setting the binding to null. This allows the view to cancel the job internally. But because the method is currently protected you will not be able to call it directly.
You need to extend the class to make the method public:
class MyCalendarView : CalendarView {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
public override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
}
}
Then use the new class you created in your XML instead of CalendarView. Now you can call the method:
override fun onDestroyView() {
super.onDestroyView()
binding.calendarView.onDetachedFromWindow()
_binding = null
}
I've noticed that this error has come back although I'm using the suggested solution. This happens if I navigate away from the main fragment -- for example when I've detected that the user needs onboarding, or some other overlay.
binding.weekCalendarView.setupAsync(
currentMonth.minusMonths(10),
currentMonth.plusMonths(1),
DayOfWeek.SUNDAY) {
// Scroll to current date.
selectedDate = todayViewModel.currentDate.value
binding.weekCalendarView.scrollToDate(selectedDate)
...
On destroy function:
override fun onDestroyView() {
super.onDestroyView()
binding.weekCalendarView.onDetachedFromWindow()
binding.weekCalendarView.adapter = null
_binding = null
}
Error
java.lang.NullPointerException: null cannot be cast to non-null type com.kizitonwose.calendarview.ui.CalendarAdapter
at com.kizitonwose.calendarview.ui.CalendarLayoutManager.getAdapter(CalendarLayoutManager.kt:20)
at com.kizitonwose.calendarview.ui.CalendarLayoutManager.access$getAdapter$p(CalendarLayoutManager.kt:16)
at com.kizitonwose.calendarview.ui.CalendarLayoutManager$scrollToDay$1.run(CalendarLayoutManager.kt:52)
This seems to be a bug in the library, the completion is still called even though the job is canceled. The fix will be to check if the job is active before calling the completion block in the library.
I will fix this in the library but not sure when, a workaround for you will be to check again in the completion block itself before calling scrollToDate
. Something like this:
binding.weekCalendarView.setupAsync(
currentMonth.minusMonths(10),
currentMonth.plusMonths(1),
DayOfWeek.SUNDAY) {
// Scroll to current date.
selectedDate = todayViewModel.currentDate.value
// This should be a sufficient workaround
if (_binding != null && binding.weekCalendarView.isAttachedToWindow) {
binding.weekCalendarView.scrollToDate(selectedDate)
}
}
In version 1.1.0, a cancellation block is provided to cancel the job.