I Built the Calendar from Scratch
Why I didn't use a library, and what happened next. Infinite scrolling, timeline views, drag-and-drop, and an endless battle with crashes.
I Built the Calendar from Scratch
Can’t you just use a library for a calendar? That’s what I thought at first too.
But the libraries I tried wouldn’t do what I needed. Weekly infinite scroll wasn’t supported, or customizing what goes inside each cell hit a wall once things got complex.
So I decided to build it from scratch. Looking back, it wasn’t courage — I just didn’t know what I didn’t know.
Weekly Infinite Scroll
The basic structure is a vertically infinite list of weeks. It pre-generates 60 weeks above and below today, and appends 20 more when you reach the edges.
Sounds straightforward, but adding items above the current scroll position causes the screen to jump. Appending below is seamless, but prepending above makes everything lurch. Fixing that took real time.
Then there’s data. Only tasks within ±12 weeks of the visible area are pre-fetched; the rest loads dynamically as you scroll. When data grows, you have to be careful about what you keep in memory.
The War with Crashes
I’ll be honest: the calendar produced the most crashes of any feature.
The worst was a Hermes garbage collector issue. If the list rendered before data arrived, the transition from an empty array to populated data would corrupt the initial scroll position and kill the app. Hard to reproduce, hard to diagnose.
The fix was simple — don’t render the list until data is ready. A straightforward solution, but the path there was long. There was a SQLite sync issue, a render function recreation problem, and more. Fix one thing, another breaks. Fix that, something else breaks.
This was the hardest stretch. “Why did I decide to build this myself?” crossed my mind at least three times a day.
The Timeline View
Seeing tasks in a list and seeing them laid out across hours feel completely different. I wanted to glance at a day and immediately know what’s in the morning and what’s in the afternoon.
So I built a 24-hour timeline view. Hour marks run vertically, tasks appear as blocks at their scheduled times, and a red line moves in real-time showing the current moment.
Overlapping events were tricky. When multiple tasks occupy the same time slot, they need to split horizontally — and the number of columns depends on how many overlap simultaneously. I wrote an algorithm that detects time-based overlaps and assigns columns per group.
It might not be perfect, but being able to see the shape of a day at a glance felt genuinely useful.
Drag and Drop
Once you have blocks on a timeline, the natural question is: can I drag these around?
Long-press to grab, drag to move — blocks snap to a 15-minute grid. Grab the top or bottom edge to resize. While dragging, the time range updates in real-time: “09:30 – 10:45.”
I added haptic feedback too. A small vibration when the drag starts, giving you a “got it” sensation. Without it, something felt missing.
A ghost of the original position stays visible during the drag, so you keep the context of “moving from here to there.” Without that reference point, it’s easy to lose track.
Weekday Preservation
In the date detail screen, you can swipe left and right between weeks. When you do, the weekday should stay the same. If you’re on Wednesday and swipe to next week, you should land on next Wednesday — not Sunday.
Seems obvious, but since weeks start on Sunday, naive index math gets it wrong. Small issues like this add up into “something feels off about this app.”
Red Text for Holidays
Showing holidays in red is a tiny feature, but the difference between having it and not is bigger than you’d think. The moment you look at the calendar, “oh, that’s a day off” registers instantly.
It wasn’t hard to build — fetch holiday data, map it by date, check during cell rendering. But after adding it, the calendar finally felt like a proper calendar.
Performance of Every Single Cell
A calendar shows dozens of cells on one screen. If every cell re-renders whenever you select a date, it stutters.
So I created small derived states per cell. “Is this cell the selected date?”, “Is this cell today?”, “Is this cell a holiday?” — each computed independently. When the date changes, only cells whose values actually changed re-render.
Without this optimization, scrolling has a subtle hitch. Barely visible, but the people who notice it, notice it.
Looking Back
Over 400 commits related to the calendar. It’s the feature I’ve spent the most time on — by far.
Whether skipping a library was the right call, I still don’t know. I could have shipped faster, for sure. But I ended up with a calendar that moves exactly the way I want.
What I learned from chasing down crash after crash: things that look simple are the hardest. Everyone uses a calendar every day, so expectations are high, and anything slightly off is immediately noticed.
There are surely parts that could be better. If something feels strange when you use it, please let me know.
It’s a calendar you use every day — I hope it gets a little better each day too.