Flutter Finance App - Python API Integration Guide
This guide explains how to connect the Flutter Finance App front-end with a Python API backend for the chat interface and database interactions.
Architecture Overview
1. Setting up the Python API Server
Requirements
- Python 3.8+
- Flask/FastAPI
- SQL.js connector or alternative database library
Step 1: Create a Flask API
from flask import Flask, request, jsonify
from flask_cors import CORS
import sqlite3
import json
app = Flask(__name__)
CORS(app) # Enable Cross-Origin Resource Sharing
# Database connection
def get_db_connection():
conn = sqlite3.connect('finance_app.db')
conn.row_factory = sqlite3.Row
return conn
# Create tables if they don't exist
def init_db():
conn = get_db_connection()
conn.execute('''
CREATE TABLE IF NOT EXISTS transactions (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
amount REAL NOT NULL,
type TEXT NOT NULL,
category TEXT NOT NULL,
date TEXT NOT NULL,
note TEXT,
message TEXT
)
''')
conn.execute('''
CREATE TABLE IF NOT EXISTS products (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
sku TEXT NOT NULL,
quantity INTEGER NOT NULL,
price REAL NOT NULL,
category TEXT NOT NULL,
last_updated TEXT NOT NULL,
reorder_point INTEGER DEFAULT 5,
trend TEXT DEFAULT 'stable',
demand TEXT DEFAULT 'medium'
)
''')
conn.commit()
conn.close()
# Initialize database on startup
init_db()
# Chat endpoints
@app.route('/api/messages', methods=['GET'])
def get_messages():
conn = get_db_connection()
messages = conn.execute('SELECT * FROM messages ORDER BY timestamp').fetchall()
conn.close()
return jsonify([dict(message) for message in messages])
@app.route('/api/messages', methods=['POST'])
def send_message():
data = request.json
conn = get_db_connection()
conn.execute(
'INSERT INTO messages (user_id, content, timestamp) VALUES (?, ?, ?)',
(data['user_id'], data['content'], data['timestamp'])
)
conn.commit()
conn.close()
# Here you can implement AI response logic
return jsonify({"status": "success", "message": "Message sent"})
# Transaction endpoints
@app.route('/api/transactions', methods=['GET'])
def get_transactions():
conn = get_db_connection()
transactions = conn.execute('SELECT * FROM transactions').fetchall()
conn.close()
return jsonify([dict(tx) for tx in transactions])
@app.route('/api/transactions', methods=['POST'])
def add_transaction():
data = request.json
conn = get_db_connection()
conn.execute(
'INSERT INTO transactions (id, title, amount, type, category, date, note, message) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
(data['id'], data['title'], data['amount'], data['type'], data['category'], data['date'], data.get('note'), data.get('message'))
)
conn.commit()
conn.close()
return jsonify({"status": "success"})
# Product/Stock endpoints
@app.route('/api/products', methods=['GET'])
def get_products():
conn = get_db_connection()
products = conn.execute('SELECT * FROM products ORDER BY name').fetchall()
conn.close()
return jsonify([dict(product) for product in products])
@app.route('/api/products/low', methods=['GET'])
def get_low_stock_products():
conn = get_db_connection()
products = conn.execute('SELECT * FROM products WHERE quantity <= reorder_point ORDER BY quantity').fetchall()
conn.close()
return jsonify([dict(product) for product in products])
@app.route('/api/products/<product_id>', methods=['GET'])
def get_product(product_id):
conn = get_db_connection()
product = conn.execute('SELECT * FROM products WHERE id = ?', (product_id,)).fetchone()
conn.close()
if not product:
return jsonify({"error": "Product not found"}), 404
return jsonify(dict(product))
@app.route('/api/products', methods=['POST'])
def add_product():
data = request.json
conn = get_db_connection()
try:
conn.execute(
'INSERT INTO products (id, name, sku, quantity, price, category, last_updated, reorder_point) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
(
data['id'],
data['name'],
data['sku'],
data['quantity'],
data['price'],
data['category'],
data['last_updated'],
data.get('reorder_point', 5)
)
)
conn.commit()
return jsonify({"status": "success", "message": "Product added successfully"})
except Exception as e:
conn.rollback()
return jsonify({"status": "error", "message": str(e)}), 400
finally:
conn.close()
@app.route('/api/products/<product_id>', methods=['PUT'])
def update_product(product_id):
data = request.json
conn = get_db_connection()
try:
# Check if product exists
product = conn.execute('SELECT * FROM products WHERE id = ?', (product_id,)).fetchone()
if not product:
return jsonify({"error": "Product not found"}), 404
# Update product
conn.execute(
'''
UPDATE products
SET name = ?, sku = ?, quantity = ?, price = ?,
category = ?, last_updated = ?, reorder_point = ?
WHERE id = ?
''',
(
data['name'],
data['sku'],
data['quantity'],
data['price'],
data['category'],
data['last_updated'],
data.get('reorder_point', 5),
product_id
)
)
conn.commit()
return jsonify({"status": "success", "message": "Product updated successfully"})
except Exception as e:
conn.rollback()
return jsonify({"error": str(e)}), 400
finally:
conn.close()
@app.route('/api/products/<product_id>', methods=['DELETE'])
def delete_product(product_id):
conn = get_db_connection()
try:
# Check if product exists
product = conn.execute('SELECT * FROM products WHERE id = ?', (product_id,)).fetchone()
if not product:
return jsonify({"error": "Product not found"}), 404
# Delete product
conn.execute('DELETE FROM products WHERE id = ?', (product_id,))
conn.commit()
return jsonify({"status": "success", "message": "Product deleted successfully"})
except Exception as e:
conn.rollback()
return jsonify({"error": str(e)}), 400
finally:
conn.close()
@app.route('/api/stock/analytics', methods=['GET'])
def get_stock_analytics():
conn = get_db_connection()
try:
# Get total product count
total_count = conn.execute('SELECT COUNT(*) as count FROM products').fetchone()['count']
# Get total inventory value
total_value = conn.execute('SELECT SUM(quantity * price) as value FROM products').fetchone()['value']
# Get low stock count
low_stock = conn.execute('SELECT COUNT(*) as count FROM products WHERE quantity <= reorder_point').fetchone()['count']
# Get top categories
categories = conn.execute('''
SELECT category, COUNT(*) as count, SUM(quantity * price) as value
FROM products
GROUP BY category
ORDER BY count DESC
LIMIT 5
''').fetchall()
return jsonify({
"total_count": total_count,
"total_value": total_value or 0,
"low_stock_count": low_stock,
"categories": [dict(category) for category in categories]
})
except Exception as e:
return jsonify({"error": str(e)}), 400
finally:
conn.close()
if __name__ == '__main__':
app.run(debug=True, port=5000)
Step 2: Set up a virtual environment
# Create a virtual environment
python -m venv venv
# Activate it (Windows)
venv\Scripts\activate
# Activate it (macOS/Linux)
source venv/bin/activate
# Install dependencies
pip install flask flask-cors sqlite3
2. Connecting the React App to the Python API
Step 1: Create an API Service in the React App
Create a new file src/services/api.ts:
const API_URL = 'http://localhost:5000/api';
// Messages API
export const fetchMessages = async () => {
const response = await fetch(`${API_URL}/messages`);
if (!response.ok) {
throw new Error('Failed to fetch messages');
}
return response.json();
};
export const sendMessage = async (message: {
user_id: string;
content: string;
timestamp: string;
}) => {
const response = await fetch(`${API_URL}/messages`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(message),
});
if (!response.ok) {
throw new Error('Failed to send message');
}
return response.json();
};
// Transactions API
export const fetchTransactions = async () => {
const response = await fetch(`${API_URL}/transactions`);
if (!response.ok) {
throw new Error('Failed to fetch transactions');
}
return response.json();
};
export const addTransaction = async (transaction: any) => {
const response = await fetch(`${API_URL}/transactions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(transaction),
});
if (!response.ok) {
throw new Error('Failed to add transaction');
}
return response.json();
};
// Products/Stock API
export const fetchProducts = async () => {
const response = await fetch(`${API_URL}/products`);
if (!response.ok) {
throw new Error('Failed to fetch products');
}
return response.json();
};
export const fetchLowStockProducts = async () => {
const response = await fetch(`${API_URL}/products/low`);
if (!response.ok) {
throw new Error('Failed to fetch low stock products');
}
return response.json();
};
export const fetchProductById = async (id: string) => {
const response = await fetch(`${API_URL}/products/${id}`);
if (!response.ok) {
throw new Error('Failed to fetch product');
}
return response.json();
};
export const addProduct = async (product: any) => {
const response = await fetch(`${API_URL}/products`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(product),
});
if (!response.ok) {
throw new Error('Failed to add product');
}
return response.json();
};
export const updateProduct = async (id: string, product: any) => {
const response = await fetch(`${API_URL}/products/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(product),
});
if (!response.ok) {
throw new Error('Failed to update product');
}
return response.json();
};
export const deleteProduct = async (id: string) => {
const response = await fetch(`${API_URL}/products/${id}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error('Failed to delete product');
}
return response.json();
};
export const fetchStockAnalytics = async () => {
const response = await fetch(`${API_URL}/stock/analytics`);
if (!response.ok) {
throw new Error('Failed to fetch stock analytics');
}
return response.json();
};
Step 2: Update Messages Component to use the API
Update the Messages component to fetch and send messages via the API:
import { useState, useEffect } from "react";
import { fetchMessages, sendMessage } from "../services/api";
import { Send, Search } from "lucide-react";
import AppLayout from "@/components/layout/AppLayout";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Avatar } from "@/components/ui/avatar";
import { ScrollArea } from "@/components/ui/scroll-area";
import { cn } from "@/lib/utils";
import { motion } from "framer-motion";
// Sample data for messages
const initialContacts = [
{ id: 1, name: "AI Assistant", avatar: "A", lastMessage: "How can I help with your finances?", time: "10:30 AM", unread: 2 },
{ id: 2, name: "Budget Bot", avatar: "B", lastMessage: "Your weekly spending report is ready", time: "Yesterday", unread: 0 },
{ id: 3, name: "Investment Advisor", avatar: "I", lastMessage: "Consider these stocks for your portfolio", time: "Yesterday", unread: 1 },
{ id: 4, name: "Expense Tracker", avatar: "E", lastMessage: "You've exceeded your dining budget", time: "Monday", unread: 0 },
{ id: 5, name: "Financial Coach", avatar: "F", lastMessage: "Let's review your saving goals", time: "08/12/23", unread: 0 },
];
const initialMessages = [
{ id: 1, sender: "client", text: "Hello! I need some help understanding my recent transactions.", time: "10:30 AM" },
{ id: 2, sender: "me", text: "Hi there! I'd be happy to help you analyze your spending patterns. What specifically would you like to know?", time: "10:32 AM" },
{ id: 3, sender: "client", text: "I noticed some unusual activity in my account", time: "10:33 AM" },
{ id: 4, sender: "me", text: "I can check that for you. Could you tell me which transactions look suspicious?", time: "10:35 AM" },
{ id: 5, sender: "me", text: "Based on your spending history, the transaction at 'Tech Store' for $349.99 is unusual for you.", time: "10:36 AM" },
{ id: 6, sender: "client", text: "Yes, that's the one I was concerned about. I don't remember making that purchase.", time: "10:38 AM" },
];
const Messages = () => {
const [contacts, setContacts] = useState(initialContacts);
const [selectedContact, setSelectedContact] = useState(initialContacts[0]);
const [messages, setMessages] = useState([]);
const [loading, setLoading] = useState(true);
const [newMessage, setNewMessage] = useState("");
const [searchTerm, setSearchTerm] = useState("");
useEffect(() => {
// Fetch messages when component mounts
const getMessages = async () => {
try {
setLoading(true);
const data = await fetchMessages();
setMessages(data);
} catch (error) {
console.error('Failed to fetch messages:', error);
} finally {
setLoading(false);
}
};
getMessages();
}, []);
const handleSendMessage = async () => {
if (newMessage.trim() === "") return;
const messageData = {
user_id: "user123", // Replace with actual user id
content: newMessage,
timestamp: new Date().toISOString()
};
try {
await sendMessage(messageData);
// Refetch messages or add the new one to state
setMessages([...messages, {
id: Date.now(),
sender: "me",
text: newMessage,
time: new Date().toLocaleTimeString([], {hour: '2-digit', minute: '2-digit'})
}]);
setNewMessage("");
} catch (error) {
console.error('Failed to send message:', error);
}
};
const filteredContacts = contacts.filter(contact =>
contact.name.toLowerCase().includes(searchTerm.toLowerCase())
);
return (
<AppLayout>
<div className="max-w-6xl mx-auto h-full p-4">
<h1 className="text-xl font-bold mb-4 text-slate-800">Messages</h1>
<div className="flex h-[calc(100vh-180px)] rounded-2xl overflow-hidden bg-white/80 backdrop-blur-lg border border-slate-200 shadow-lg">
{/* Contacts sidebar */}
<div className="w-full max-w-xs border-r border-slate-200 hidden md:flex flex-col">
<div className="p-3 border-b border-slate-200">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400 h-4 w-4" />
<Input
placeholder="Search contacts..."
className="pl-10 bg-slate-50 border-slate-200 text-slate-800"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
</div>
<ScrollArea className="flex-1">
{filteredContacts.map(contact => (
<div
key={contact.id}
className={cn(
"p-3 flex items-center gap-3 cursor-pointer hover:bg-slate-50 transition-colors",
selectedContact.id === contact.id ? "bg-slate-50" : ""
)}
onClick={() => setSelectedContact(contact)}
>
<Avatar className="h-10 w-10 bg-blue-600 text-white">
<div>{contact.avatar}</div>
</Avatar>
<div className="flex-1 min-w-0">
<div className="flex justify-between items-center">
<span className="font-medium truncate text-slate-800">{contact.name}</span>
<span className="text-xs text-slate-500">{contact.time}</span>
</div>
<p className="text-sm text-slate-500 truncate">{contact.lastMessage}</p>
</div>
{contact.unread > 0 && (
<div className="bg-blue-600 text-white text-xs rounded-full h-5 w-5 flex items-center justify-center">
{contact.unread}
</div>
)}
</div>
))}
</ScrollArea>
</div>
{/* Chat area */}
<div className="flex-1 flex flex-col">
<div className="p-3 border-b border-slate-200 flex items-center gap-3">
<Avatar className="h-8 w-8 bg-blue-600 text-white md:hidden">
<div>{selectedContact.avatar}</div>
</Avatar>
<div>
<h3 className="font-medium text-slate-800">{selectedContact.name}</h3>
</div>
</div>
<ScrollArea className="flex-1 p-4">
<div className="space-y-4">
{messages.map((message, index) => (
<motion.div
key={message.id}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, delay: index * 0.1 }}
className={cn(
"flex",
message.sender === "me" ? "justify-end" : "justify-start"
)}
>
<div
className={cn(
"max-w-[80%] p-3 rounded-2xl shadow-sm",
message.sender === "me"
? "bg-blue-600 text-white rounded-tr-none"
: "bg-slate-100 text-slate-800 rounded-tl-none"
)}
>
<p>{message.text}</p>
<span className={cn(
"text-xs block mt-1",
message.sender === "me"
? "text-white/70"
: "text-slate-500"
)}>
{message.time}
</span>
</div>
</motion.div>
))}
</div>
</ScrollArea>
<div className="p-3 border-t border-slate-200 flex items-center gap-2">
<Input
placeholder="Type a message..."
value={newMessage}
onChange={(e) => setNewMessage(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
handleSendMessage();
}
}}
className="flex-1 bg-slate-50 border-slate-200 text-slate-800"
/>
<Button size="icon" className="bg-blue-600 hover:bg-blue-700" onClick={handleSendMessage}>
<Send size={18} />
</Button>
</div>
</div>
</div>
</div>
</AppLayout>
);
};
3. Deploying the Python API
Option 1: Docker Deployment
Create a Dockerfile for the Python API:
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["python", "app.py"]
Create a requirements.txt file:
flask>=2.0.0
flask-cors>=3.0.10
Update the docker-compose.yml to include the Python API:
version: '3.8'
services:
app:
build: .
ports:
- "8080:80"
restart: unless-stopped
depends_on:
- api
api:
build: ./api
ports:
- "5000:5000"
restart: unless-stopped
volumes:
- ./api/data:/app/data
Option 2: Cloud Deployment
Deploy the Python API to a cloud service like:
- Heroku
- AWS Lambda + API Gateway
- Google Cloud Functions
- Azure Functions
4. Advanced Integration: AI Chat Assistant
To enhance the chat interface with AI capabilities, you can integrate with OpenAI's API:
import openai
# Set your OpenAI API key
openai.api_key = "your-api-key"
@app.route('/api/chat', methods=['POST'])
def chat():
data = request.json
user_message = data['message']
# Save user message to database
conn = get_db_connection()
conn.execute(
'INSERT INTO messages (user_id, content, timestamp, is_ai) VALUES (?, ?, ?, ?)',
(data['user_id'], user_message, data['timestamp'], 0)
)
conn.commit()
# Get AI response
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": "You are a helpful financial assistant."},
{"role": "user", "content": user_message}
]
)
ai_message = response.choices[0].message.content
# Save AI response to database
conn.execute(
'INSERT INTO messages (user_id, content, timestamp, is_ai) VALUES (?, ?, ?, ?)',
('assistant', ai_message, data['timestamp'], 1)
)
conn.commit()
conn.close()
return jsonify({
"message": ai_message,
"timestamp": data['timestamp']
})
5. Running Both Applications Together
- Start the Python API:
cd api
python app.py
- Start the React app:
npm run dev
6. Security Considerations
- Implement proper authentication for API endpoints
- Use HTTPS for production
- Validate and sanitize all user inputs
- Use environment variables for sensitive information
- Implement rate limiting to prevent abuse
7. Troubleshooting
Common Issues:
- CORS errors: Ensure CORS is properly configured in both the React app and the Python API
- Database connection errors: Check file paths and permissions
- API connection issues: Verify the API URL and port are correct
8. Resources
9. License
This integration guide is provided under the same license as the Flutter Finance App.