Python+Flutter前后端分离开发跨平台待办事项APP实战

news/2025/2/25 7:35:46

以下是一个简单的示例,展示如何使用 Flutter 开发一个待办事项应用,并通过 FastAPI 和 SQLAlchemy 作为后端接口。为了简化代码,所有的 Flutter 前端代码将写在一个 main.dart 文件中。

1. 后端部分(FastAPI + SQLAlchemy)

首先,我们需要创建一个简单的 FastAPI 后端服务,用于处理待办事项的增删改查操作。

安装依赖

在你的 Python 环境中安装以下依赖:

pip install fastapi uvicorn sqlalchemy

创建 FastAPI 应用

创建一个文件 main.py,并写入以下代码:

python">from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from sqlalchemy import create_engine, Column, Integer, String, Boolean
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

# 数据库配置
DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

# 待办事项模型
class TodoItem(Base):
    __tablename__ = "todo_items"
    id = Column(Integer, primary_key=True, index=True)
    title = Column(String, index=True)
    completed = Column(Boolean, default=False)

# 创建数据库表
Base.metadata.create_all(bind=engine)

# Pydantic 模型
class TodoItemCreate(BaseModel):
    title: str

class TodoItemUpdate(BaseModel):
    title: str = None
    completed: bool = None

# FastAPI 应用
app = FastAPI()

# 获取数据库会话
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

# 获取所有待办事项
@app.get("/todos/")
def read_todos():
    db = SessionLocal()
    items = db.query(TodoItem).all()
    return [{"id": item.id, "title": item.title, "completed": item.completed} for item in items]

# 创建待办事项
@app.post("/todos/")
def create_todo(item: TodoItemCreate):
    db = SessionLocal()
    new_item = TodoItem(title=item.title, completed=False)
    db.add(new_item)
    db.commit()
    db.refresh(new_item)
    return {"id": new_item.id, "title": new_item.title, "completed": new_item.completed}

# 更新待办事项
@app.put("/todos/{todo_id}")
def update_todo(todo_id: int, item: TodoItemUpdate):
    db = SessionLocal()
    db_item = db.query(TodoItem).filter(TodoItem.id == todo_id).first()
    if not db_item:
        raise HTTPException(status_code=404, detail="Item not found")
    if item.title is not None:
        db_item.title = item.title
    if item.completed is not None:
        db_item.completed = item.completed
    db.commit()
    db.refresh(db_item)
    return {"id": db_item.id, "title": db_item.title, "completed": db_item.completed}

# 删除待办事项
@app.delete("/todos/{todo_id}")
def delete_todo(todo_id: int):
    db = SessionLocal()
    db_item = db.query(TodoItem).filter(TodoItem.id == todo_id).first()
    if not db_item:
        raise HTTPException(status_code=404, detail="Item not found")
    db.delete(db_item)
    db.commit()
    return {"message": "Item deleted"}

启动 FastAPI 服务

运行以下命令启动服务:

uvicorn main:app --reload

服务默认运行在 http://127.0.0.1:8000,你可以通过访问 /docs 查看 API 文档。

2. Flutter 前端部分(main.dart)

接下来,我们编写 Flutter 应用来与后端交互。所有代码将写在一个 main.dart 文件中。

安装依赖

在 Flutter 项目的 pubspec.yaml 文件中添加以下依赖:

dependencies:
  flutter:
    sdk: flutter
  http: ^0.13.5
  provider: ^6.0.5

运行 flutter pub get 安装依赖。

编写 Flutter 代码

以下是完整的 main.dart 文件代码:

import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Todo App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: TodoListScreen(),
    );
  }
}

class TodoListScreen extends StatefulWidget {
  
  _TodoListScreenState createState() => _TodoListScreenState();
}

class _TodoListScreenState extends State<TodoListScreen> {
  List<dynamic> _todos = [];
  final TextEditingController _textEditingController = TextEditingController();

  
  void initState() {
    super.initState();
    _fetchTodos();
  }

  Future<void> _fetchTodos() async {
    try {
      final response = await http.get(Uri.parse('http://127.0.0.1:8000/todos/'));
      if (response.statusCode == 200) {
        setState(() {
          _todos = json.decode(response.body);
        });
      } else {
        throw Exception('Failed to load todos');
      }
    } catch (e) {
      print('Error fetching todos: $e');
    }
  }

  Future<void> _createTodo(String title) async {
    try {
      final response = await http.post(
        Uri.parse('http://127.0.0.1:8000/todos/'),
        headers: {'Content-Type': 'application/json'},
        body: json.encode({'title': title}),
      );
      if (response.statusCode == 200) {
        _fetchTodos();
      } else {
        throw Exception('Failed to create todo');
      }
    } catch (e) {
      print('Error creating todo: $e');
    }
  }

  Future<void> _updateTodo(int id, {String? title, bool? completed}) async {
    try {
      final response = await http.put(
        Uri.parse('http://127.0.0.1:8000/todos/$id'),
        headers: {'Content-Type': 'application/json'},
        body: json.encode({
          'title': title,
          'completed': completed,
        }),
      );
      if (response.statusCode == 200) {
        _fetchTodos();
      } else {
        throw Exception('Failed to update todo');
      }
    } catch (e) {
      print('Error updating todo: $e');
    }
  }

  Future<void> _deleteTodo(int id) async {
    try {
      final response = await http.delete(Uri.parse('http://127.0.0.1:8000/todos/$id'));
      if (response.statusCode == 200) {
        _fetchTodos();
      } else {
        throw Exception('Failed to delete todo');
      }
    } catch (e) {
      print('Error deleting todo: $e');
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Todo List'),
      ),
      body: Column(
        children: [
          Expanded(
            child: ListView.builder(
              itemCount: _todos.length,
              itemBuilder: (context, index) {
                final todo = _todos[index];
                return ListTile(
                  title: Text(todo['title']),
                  trailing: Checkbox(
                    value: todo['completed'],
                    onChanged: (bool? value) {
                      _updateTodo(todo['id'], completed: value);
                    },
                  ),
                  onLongPress: () {
                    _deleteTodo(todo['id']);
                  },
                );
              },
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: _textEditingController,
                    decoration: InputDecoration(
                      hintText: 'Enter a new todo',
                    ),
                  ),
                ),
                IconButton(
                  icon: Icon(Icons.add),
                  onPressed: () {
                    final title = _textEditingController.text;
                    if (title.isNotEmpty) {
                      _createTodo(title);
                      _textEditingController.clear();
                    }
                  },
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

3. 运行和测试

启动 FastAPI 后端服务:

uvicorn main:app --reload

运行 Flutter 应用:
在 Flutter 项目目录下运行:

flutter run

测试功能:

  • 在 Flutter 应用中添加待办事项,查看是否成功保存到后端。
  • 勾选或取消勾选待办事项,查看是否更新状态。
  • 长按待办事项删除,查看是否从后端删除。

http://www.niftyadmin.cn/n/5865181.html

相关文章

Node.js 入门 原型链污染

前言 node.js是后端的语言 我们知道的apache 服务器 才能支撑一些代码 但是 node.js是一个独立的环境&#xff08;node.js本身就是一个平台&#xff09;这个就是为什么很多小程序和app是直接使用它的 nodejs的特点 &#xff1a; 1、单线程 &#xff1a;一次node 只能执行…

DSP芯片C6678的SRIO及其中断跳转的配置

C6678SRIO读写测试门铃中断跳转测试 SRIO简述代码前言SRIO配置原始代码1.使能电源2.初始化SRIO回环修改 3.SRIO测试 Doorbell门铃中断1.初始化中断函数2.中断向量表建立3.中断向量表的链接 本博客基于创龙“678ZH产品线”的SRIO代码&#xff0c;部分参考于网友们的博客&#xf…

angular表格排序分页

说明: 使用angular实现表格&#xff0c;包括数据源&#xff0c;排序&#xff0c;分页 效果图&#xff1a; step1:C:\Users\Administrator\WebstormProjects\untitled4\src\app\setting\setting.component.ts import { Component, ViewChild, AfterViewInit } from angular/c…

一文讲解Redis中的数据一致性问题

一文讲解Redis中的数据一致性问题 在技术派实战项目中&#xff0c;我们采用的是先写 MySQL&#xff0c;再删除 Redis 的方式来保证缓存和数据库的数据一致性。 我举例说明一下。 对于第一次查询&#xff0c;请求 B 查询到的缓存数据是 10&#xff0c;但 MySQL 被请求 A 更新为…

WebXR教学 03 项目1 旋转彩色方块

一、项目结构 webgl-cube/ ├── index.html ├── main.js ├── package.json └── vite.config.js二、详细实现步骤 初始化项目 npm init -y npm install three vite --save-devindex.html <!DOCTYPE html> <html lang"en"> <head><…

Debain12.9安装大模型微调基础环境

Debain12.9安装大模型微调基础环境 硬件信息安装基础组件安装CUDA安装NVCC 硬件信息 操作系统&#xff1a;Debain 12.9/Ubuntu 24.04 CPU&#xff1a;i7-10750H 内存&#xff1a;32G 显卡&#xff1a;GTX 1650&#xff08;4G&#xff09; 硬盘&#xff1a;SSD&#xff08;1T&a…

基于同轴聚类点云去重的铆钉高度测量

基于同轴聚类点云去重的铆钉高度测量 一、引言二、解决方案三、实验效果四、实验部分代码 一、引言 本实验起源于工业检测领域一个普遍存在的需求——精确测量铆钉相对于料盘&#xff08;即基准平面&#xff09;的高度。 需求&#xff1a;如何准确测定铆钉至料盘表面的高度&am…

C++对象模型之C++额外成本

1.介绍 C与C最大的区别&#xff0c;无疑在于面向对象&#xff0c;面向对象编程给C带来了强大的特性和灵活性。但同时也带来了一定的运行时和编译时的开销。下面介绍C对象模型的额外成本及其来源。 2.C的额外成本 &#xff08;1&#xff09;虚函数和动态多态的成本 虚函数表&am…