2202 words
11 minutes
How to Build a Habit Tracker App with Streamlit and Python

Building a Habit Tracker App with Streamlit and Python#

Building tools to support personal development or project management often involves creating applications that can track progress over time. A habit tracker is a prime example, providing a visual and interactive way to monitor the consistency of activities. Developing such an application from scratch can be a complex undertaking, requiring expertise in frontend development, backend logic, and database management. However, using Python and the Streamlit library simplifies this process significantly, allowing for the creation of interactive web applications with minimal code and no need for separate frontend frameworks.

Streamlit is an open-source Python library specifically designed for machine learning and data science teams to create and share beautiful, custom web apps in minutes. Its strength lies in its simplicity and Python-native approach, enabling developers to build interactive interfaces directly from Python scripts. This makes it an excellent choice for prototyping and deploying simple, data-driven applications like a habit tracker.

Why Choose Streamlit for a Habit Tracker?#

Developing a habit tracking application requires handling data input, storage, display, and potentially visualization. Traditional web development stacks involve multiple languages and frameworks. Streamlit streamlines this by:

  • Rapid Prototyping: Turn data scripts into shareable web apps quickly. The iterative development cycle with Streamlit is very fast.
  • Python Ecosystem: Leverage the extensive Python libraries for data handling (pandas), visualization (Matplotlib, Plotly, Altair), and file operations.
  • No Frontend Expertise Required: Write the entire application logic and interface solely in Python. Streamlit handles the HTML, CSS, and JavaScript automatically.
  • Interactivity: Easily add sliders, buttons, text inputs, checkboxes, and other widgets to build interactive elements.
  • Data Flow: Streamlit’s data flow model simplifies state management for many common application patterns.

Compared to building a mobile app or using a complex web framework like Django or Flask with a separate frontend, Streamlit offers a path to a functional application much faster, especially for internal tools or personal projects.

Core Concepts for a Streamlit Habit Tracker#

Understanding a few key concepts is essential before building the application:

  • Streamlit Script Execution: A Streamlit app runs from top to bottom whenever a user interacts with a widget. State needs to be managed carefully.
  • Widgets: Interactive elements like st.button, st.checkbox, st.date_input, st.text_input are used for user interaction.
  • st.session_state: This dictionary-like object allows persistent state across user interactions within a single user’s session. It’s crucial for remembering data and application state.
  • Data Structure: How the habit data is organized. A common and efficient structure is a pandas DataFrame or a list of dictionaries, storing habit names, dates, and completion status.
  • Data Persistence: Streamlit apps reset when the script re-runs or the server restarts. Saving data to a file (like CSV or JSON) or connecting to a database is necessary for long-term storage. For a simple app, saving to a local file is often sufficient.
  • Visualization: Presenting habit consistency visually using charts or calendars can significantly improve the app’s usability.

Building the Habit Tracker: A Step-by-Step Guide#

This guide outlines the process of creating a basic habit tracker using Streamlit and Python, focusing on core functionality: adding habits, tracking completion for a specific date, and saving/loading data.

Step 1: Project Setup#

Begin by installing the necessary libraries. The primary library is streamlit, and pandas is highly recommended for efficient data handling.

Terminal window
pip install streamlit pandas

Create a Python file (e.g., habit_tracker_app.py) where the application code will reside.

Step 2: Data Structure Design#

Design how the habit data will be stored. A pandas DataFrame is suitable, with columns to record essential information.

Column NameData TypeDescription
Habit NamestringThe name of the habit (e.g., “Read for 30 mins”)
DatedateThe date the habit was tracked for
CompletedbooleanWhether the habit was completed on that date

Initialize the DataFrame or load it from a persistent source. Using st.session_state to hold the DataFrame is key for maintaining data within a session without constantly re-loading from storage during interactions.

import streamlit as st
import pandas as pd
from datetime import date
# Initialize session state for data
if 'habit_data' not in st.session_state:
# Create an empty DataFrame if no data exists
st.session_state['habit_data'] = pd.DataFrame(columns=['Habit Name', 'Date', 'Completed'])
# Ensure 'Date' column is datetime type for easier handling
st.session_state['habit_data']['Date'] = pd.to_datetime(st.session_state['habit_data']['Date'])
# Placeholder function to load data (implement file loading later)
def load_data():
# In a real app, load from CSV/JSON/DB here
# For this example, we rely on session_state unless implementing persistence
pass # No loading needed if using only session_state
# Placeholder function to save data (implement file saving later)
def save_data():
# In a real app, save to CSV/JSON/DB here
st.session_state['habit_data'].to_csv('habit_data.csv', index=False)
st.success("Data saved!")
# Load data when app starts (if implementing persistence)
# load_data()

Step 3: Add New Habits#

Provide a way for users to define the habits they want to track. A text input and a button are suitable widgets.

st.title("Simple Habit Tracker")
# Section to add new habits
st.header("Add New Habit")
new_habit_name = st.text_input("Enter habit name:")
if st.button("Add Habit"):
if new_habit_name:
# Add the new habit name to a list of unique habits
# We need a separate list/set of unique habit names for display
if 'unique_habits' not in st.session_state:
st.session_state['unique_habits'] = []
if new_habit_name not in st.session_state['unique_habits']:
st.session_state['unique_habits'].append(new_habit_name)
st.success(f"Habit '{new_habit_name}' added!")
else:
st.warning(f"Habit '{new_habit_name}' already exists.")
else:
st.warning("Please enter a habit name.")

Note: The unique habits list is stored separately from the tracking data for simpler display and selection later.

Step 4: Track Habits for a Specific Date#

Allow users to select a date and mark habits as completed or not completed for that date. A date input and checkboxes are effective here.

st.header("Track Habits")
# Select date for tracking
selected_date = st.date_input("Select a date:", date.today())
if 'unique_habits' in st.session_state and st.session_state['unique_habits']:
st.subheader(f"Habits for {selected_date.strftime('%Y-%m-%d')}")
# Filter data for the selected date
date_data = st.session_state['habit_data'][st.session_state['habit_data']['Date'].dt.date == selected_date]
updated_completions = {}
for habit in st.session_state['unique_habits']:
# Check if the habit is already recorded for this date
habit_record = date_data[date_data['Habit Name'] == habit]
initial_status = False
if not habit_record.empty:
initial_status = habit_record['Completed'].iloc[0]
# Use a unique key for each checkbox
checkbox_key = f"checkbox_{habit}_{selected_date}"
is_completed = st.checkbox(f"Completed: {habit}", value=initial_status, key=checkbox_key)
updated_completions[habit] = is_completed
# Button to save tracking changes for the selected date
if st.button(f"Save Tracking for {selected_date.strftime('%Y-%m-%d')}"):
# Prepare data to add/update
records_to_add = []
# Remove existing records for this date to avoid duplicates before adding new ones
st.session_state['habit_data'] = st.session_state['habit_data'][
(st.session_state['habit_data']['Date'].dt.date != selected_date) | # Keep records from other dates
(~st.session_state['habit_data']['Habit Name'].isin(updated_completions.keys())) # Keep records for habits not being tracked on this date
].copy() # Use .copy() to avoid SettingWithCopyWarning
for habit, completed_status in updated_completions.items():
records_to_add.append({'Habit Name': habit, 'Date': selected_date, 'Completed': completed_status})
if records_to_add:
# Append new records
new_data = pd.DataFrame(records_to_add)
new_data['Date'] = pd.to_datetime(new_data['Date']) # Ensure date is datetime
st.session_state['habit_data'] = pd.concat([st.session_state['habit_data'], new_data], ignore_index=True)
st.success(f"Tracking updated for {selected_date.strftime('%Y-%m-%d')}")
else:
st.info("Add some habits first in the 'Add New Habit' section.")

This section displays checkboxes for each defined habit. The state of the checkbox is initialized based on existing data for the selected date. Clicking “Save Tracking” updates or adds records in the st.session_state['habit_data'] DataFrame.

Step 5: Display Tracking Data#

Show the raw tracking data in a table for verification.

st.header("Tracking Data")
st.dataframe(st.session_state['habit_data'])

Step 6: Data Persistence (Saving and Loading)#

Implement functions to save the habit_data DataFrame to a file and load it when the app starts. A simple CSV file is used here.

Modify the initial setup (Step 2) and add buttons for saving/loading.

# ... (previous imports and initial session_state setup) ...
DATA_FILE = 'habit_data.csv'
UNIQUE_HABITS_FILE = 'unique_habits.json' # Using JSON for simplicity for unique habits
# Function to load data from CSV
def load_data():
try:
df = pd.read_csv(DATA_FILE)
df['Date'] = pd.to_datetime(df['Date']).dt.date # Store date as date object
return df
except FileNotFoundError:
return pd.DataFrame(columns=['Habit Name', 'Date', 'Completed'])
# Function to save data to CSV
def save_data(df):
df['Date'] = df['Date'].astype(str) # Convert date objects to string for saving
df.to_csv(DATA_FILE, index=False)
import json
# Function to load unique habits from JSON
def load_unique_habits():
try:
with open(UNIQUE_HABITS_FILE, 'r') as f:
return json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
return []
# Function to save unique habits to JSON
def save_unique_habits(habits_list):
with open(UNIQUE_HABITS_FILE, 'w') as f:
json.dump(habits_list, f)
# Initialize session state OR load data
if 'habit_data' not in st.session_state:
st.session_state['habit_data'] = load_data()
if 'unique_habits' not in st.session_state:
st.session_state['unique_habits'] = load_unique_habits()
# Also, initialize unique habits from loaded data if file was empty
if not st.session_state['unique_habits'] and not st.session_state['habit_data'].empty:
st.session_state['unique_habits'] = st.session_state['habit_data']['Habit Name'].unique().tolist()
st.title("Simple Habit Tracker")
# Add Save Button
if st.button("Save All Data"):
# Convert date column back to datetime objects before saving if they were stored as date objects
# Or ensure consistent storage format.
# For this example, we load as date.date and convert to string for saving CSV.
# Let's refine date handling for consistency. Store as datetime objects in session state.
st.session_state['habit_data']['Date'] = pd.to_datetime(st.session_state['habit_data']['Date']) # Ensure datetime in session state
save_data(st.session_state['habit_data'])
save_unique_habits(st.session_state['unique_habits'])
st.success("Data and habits saved!")
# ... (Add New Habit section - needs minor modification to save unique habits) ...
st.header("Add New Habit")
new_habit_name = st.text_input("Enter habit name:")
if st.button("Add Habit"):
if new_habit_name:
if 'unique_habits' not in st.session_state:
st.session_state['unique_habits'] = []
if new_habit_name not in st.session_state['unique_habits']:
st.session_state['unique_habits'].append(new_habit_name)
save_unique_habits(st.session_state['unique_habits']) # Save unique habits immediately
st.success(f"Habit '{new_habit_name}' added! Remember to Save All Data periodically.")
else:
st.warning(f"Habit '{new_habit_name}' already exists.")
else:
st.warning("Please enter a habit name.")
# ... (Track Habits section - no major changes needed, it operates on session_state['habit_data']) ...
st.header("Track Habits")
# Select date for tracking
selected_date = st.date_input("Select a date:", date.today())
if 'unique_habits' in st.session_state and st.session_state['unique_habits']:
st.subheader(f"Habits for {selected_date.strftime('%Y-%m-%d')}")
# Ensure comparison with datetime objects if session state stores datetime
date_data = st.session_state['habit_data'][st.session_state['habit_data']['Date'].dt.date == selected_date]
updated_completions = {}
for habit in st.session_state['unique_habits']:
habit_record = date_data[date_data['Habit Name'] == habit]
initial_status = False
if not habit_record.empty:
initial_status = habit_record['Completed'].iloc[0]
checkbox_key = f"checkbox_{habit}_{selected_date}"
is_completed = st.checkbox(f"Completed: {habit}", value=initial_status, key=checkbox_key)
updated_completions[habit] = is_completed
if st.button(f"Save Tracking for {selected_date.strftime('%Y-%m-%d')}"):
# Convert selected_date to datetime for consistent comparison/storage
selected_datetime = pd.to_datetime(selected_date)
# Remove existing records for this date and habit name
st.session_state['habit_data'] = st.session_state['habit_data'][
(st.session_state['habit_data']['Date'].dt.date != selected_datetime.date()) |
(~st.session_state['habit_data']['Habit Name'].isin(updated_completions.keys())) # This part is optional depending on desired behavior
].copy()
records_to_add = []
for habit, completed_status in updated_completions.items():
# Only add if completed (optional, could add False too)
# Adding both True/False allows explicit tracking of non-completion
records_to_add.append({'Habit Name': habit, 'Date': selected_datetime, 'Completed': completed_status})
if records_to_add:
new_data = pd.DataFrame(records_to_add)
st.session_state['habit_data'] = pd.concat([st.session_state['habit_data'], new_data], ignore_index=True)
st.session_state['habit_data']['Date'] = pd.to_datetime(st.session_state['habit_data']['Date']) # Ensure datetime type after concat
st.success(f"Tracking updated for {selected_date.strftime('%Y-%m-%d')}")
else:
st.info("Add some habits first in the 'Add New Habit' section.")
# ... (Tracking Data Display section - no changes) ...
st.header("Tracking Data")
st.dataframe(st.session_state['habit_data'])

Step 7: Basic Visualization#

Add a simple visualization to show progress, such as completion count per habit.

st.header("Habit Progress")
if not st.session_state['habit_data'].empty:
# Calculate completion counts per habit
completion_counts = st.session_state['habit_data'].groupby('Habit Name')['Completed'].sum().reset_index()
if not completion_counts.empty:
st.subheader("Total Completions per Habit")
# Use Streamlit's built-in bar chart
st.bar_chart(completion_counts.set_index('Habit Name'))
# Optional: Show completion rate over time for a selected habit
if 'unique_habits' in st.session_state and st.session_state['unique_habits']:
st.subheader("Completion Rate Over Time")
selected_habit_viz = st.selectbox("Select a habit to visualize:", st.session_state['unique_habits'])
if selected_habit_viz:
habit_data_filtered = st.session_state['habit_data'][st.session_state['habit_data']['Habit Name'] == selected_habit_viz].copy()
if not habit_data_filtered.empty:
# Sort by date
habit_data_filtered = habit_data_filtered.sort_values(by='Date')
# Create a time series of completion status (1 for True, 0 for False)
habit_data_filtered['Completed_Value'] = habit_data_filtered['Completed'].astype(int)
st.line_chart(habit_data_filtered.set_index('Date')['Completed_Value'])
else:
st.info("No data tracked yet for this habit.")
else:
st.info("Track some habits to see progress.")

This adds a bar chart showing the total number of times each habit has been marked as completed and a line chart showing completion status over time for a selected habit. Streamlit’s built-in charting functions (st.bar_chart, st.line_chart) are convenient for quick visualizations using pandas DataFrames.

Step 8: Running the App#

Save the Python file (e.g., habit_tracker_app.py). Open a terminal or command prompt, navigate to the directory where the file is saved, and run the command:

Terminal window
streamlit run habit_tracker_app.py

This command starts a local web server, and your habit tracker application will open in a web browser.

Practical Application Example#

Consider an individual aiming to improve consistency in several areas: daily coding practice, reading a chapter of a book, and taking a 30-minute walk. They could use the Streamlit habit tracker to:

  1. Add Habits: Input “Coding Practice”, “Read Book”, “Walk 30 mins” into the “Add New Habit” section.
  2. Track Daily: Each evening, they open the app, select the current date, and check the boxes for the habits they completed.
  3. Monitor Progress: Use the “Habit Progress” section to see how many times they’ve completed each habit in total and observe the trend over time using the line chart.
  4. Persistence: Click “Save All Data” periodically to ensure their tracking history is stored in the habit_data.csv file.

This demonstrates how the simple Streamlit app provides a functional, visual tool for self-monitoring without the overhead of more complex development environments. The flexibility of Python and Streamlit allows for future expansion, such as adding features like streak counting, goal setting, or more sophisticated visualizations.

Summary: Key Takeaways for Building a Habit Tracker with Streamlit#

  • Streamlit enables rapid development of interactive web applications using only Python.
  • st.session_state is essential for managing data and application state across user interactions within a session.
  • A pandas DataFrame provides a flexible structure for storing habit tracking data (Habit Name, Date, Completed).
  • Data persistence requires saving the DataFrame to a file (like CSV) or a database and loading it on application start.
  • Streamlit widgets (st.text_input, st.button, st.date_input, st.checkbox) are used to build the user interface for adding habits and tracking completion.
  • Basic visualizations can be easily added using Streamlit’s built-in charting functions or by integrating libraries like Matplotlib, Plotly, or Altair.
  • Running the app is straightforward using the streamlit run command.
  • The approach is highly suitable for personal tools, internal dashboards, or prototypes where rapid development and Python integration are priorities.
How to Build a Habit Tracker App with Streamlit and Python
https://dev-resources.site/posts/how-to-build-a-habit-tracker-app-with-streamlit-and-python/
Author
Dev-Resources
Published at
2025-06-29
License
CC BY-NC-SA 4.0