Skip to content

Beaver Habit Tracker Onboard

Henry Zhu edited this page Oct 2, 2024 · 1 revision

When switching from Android to iOS, I was unable to find a light-weighted but handy habit tracking app like uhabit(Loop Habit), so I decided to make one by myself :)

For the name of the project, it came from a game called "Against the Storm" (which I spent over 200 hours, highly recommended). In the game, my favourite city builder species is beaver, hoping this web app works as a beaver to save your precious moments in your fleeting life.

image

Tech Stacks

Inspired by the idea of "web UIs with plain Python" from Three Python trends in 2023, finally chose NiceGUI as the full-stack framework (based on Quasar, Tailwind CSS, FastAPI, ...).

So this web app is 100% built with Python <3

Some thoughts after several weeks of development:

  1. Good things ✅
    • WebSocket based communication between client and server, works perfectly with Python asyncio.
    • Light-weighted session based storage provided, out of the box to use.
    • Plenty of UI components provided, straightforward and highly customizable.
    • ...
  2. Worries 🤔
    • "NiceGUI follows a backend-first philosophy: It handles all the web development details" -> high network latency would be a big issue.
    • ...

Persistent Storage

As mentioned above, NiceGUI handles everything on the server side, high network latency would destroy user experiences.

Some solutions:

  1. Global CDN: Utilizing a global CDN helps mitigate network latency by distributing content across multiple edge servers located strategically worldwide.
  2. Self-host option: Providing a self-host option allows users to host the NiceGUI application on their own infrastructure or servers.
  3. ...

In order to provide flexible backend storage options, interfaces were defined with various implementations, e.g. session-based file or user-based file/database.

BTW, the code below leverages the latest features of Python 3.12: PEP 695: Type Parameter Syntax

class HabitList[H: Habit](Protocol):

    @property
    def habits(self) -> List[H]: ...

    async def add(self, name: str) -> None: ...

    async def remove(self, item: H) -> None: ...

    async def get_habit_by(self, habit_id: str) -> Optional[H]: ...


class UserStorage[L: HabitList](Protocol):
    async def get_user_habit_list(self, user: User) -> Optional[L]: ...

    async def save_user_habit_list(self, user: User, habit_list: L) -> None: ...

    async def merge_user_habit_list(self, user: User, other: L) -> L: ...