Flutter串接restful api
创建新专案
架构:
package
使用JSONPlaceholder测试:
JSONPlaceholder
使用quicktype将json档案转承我们要的形式:
quicktype
测试使用posts:
建立Model
在models里新增post_model:
import 'dart:convert';class PostModel { PostModel({ this.userId, this.id, this.title, this.body, }); int userId; int id; String title; String body; factory PostModel.fromJson(String str) => PostModel.fromMap(json.decode(str)); String toJson() => json.encode(toMap()); factory PostModel.fromMap(Map<String, dynamic> json) => PostModel( userId: json["userId"] == null ? null : json["userId"], id: json["id"] == null ? null : json["id"], title: json["title"] == null ? null : json["title"], body: json["body"] == null ? null : json["body"], ); Map<String, dynamic> toMap() => { "userId": userId == null ? null : userId, "id": id == null ? null : id, "title": title == null ? null : title, "body": body == null ? null : body, };}
稍微修改(with null-safety):
import 'dart:convert';class PostModel { PostModel({ this.userId, this.id, this.title, this.body, }); int? userId; int? id; String? title; String? body; factory PostModel.fromJson(String str) => PostModel.fromMap(json.decode(str)); String toJson() => json.encode(toMap()); factory PostModel.fromMap(Map<String, dynamic> json) => PostModel( userId: json["userId"] ?? json["userId"], id: json["id"] ?? json["id"], title: json["title"] ?? json["title"], body: json["body"] ?? json["body"], ); Map<String, dynamic> toMap() => { "userId": userId ?? userId, "id": id ?? id, "title": title ?? title, "body": body ?? body, };}
models.dart:
export './post_model.dart';
建立service
在services里新增post_service:
import 'dart:convert';import 'dart:developer';import 'package:flutter_restful/core/models/models.dart';import 'package:http/http.dart' as http;class PostService { Future<PostModel?> findById(String id) async { PostModel post; try { var headers = {'content-type': 'application/json'}; var url = Uri.parse('https://jsonplaceholder.typicode.com/posts/$id'); final response = await http.get(url, headers: headers); if (response.statusCode == 200) { post = PostModel.fromJson(response.body); return post; } else { log('请求失败'); } } catch (err) { log('post fetch and set catch error', error: err); } } Future<List<PostModel>> findAll() async { try { var headers = {'content-type': 'application/json'}; var url = Uri.parse('https://jsonplaceholder.typicode.com/posts'); final response = await http.get(url, headers: headers); final extractedData = json.decode(response.body); final List<PostModel> loadedposts = []; for (var data in extractedData) { loadedposts.add(PostModel( id: data['id'], userId: data['userId'], title: data['title'], body: data['body'], )); } return loadedposts; } catch (err) { log('posts fetch and set catch error', error: err); return []; } } Future<PostModel> create(PostModel post, [String token = '']) async { try { var headers = { 'content-type': 'application/json', 'Authorization': token, }; var url = Uri.parse('https://jsonplaceholder.typicode.com/posts'); final response = await http.post( url, headers: headers, body: json.encode({ 'id': post.id, 'userId': post.userId, 'title': post.title, 'body': post.body, }), ); log(response.body); return PostModel.fromJson(response.body); } catch (err) { log('Add error, ', error: err); throw Exception(err); } } Future<PostModel> delete(String postId, [String token = '']) async { try { var headers = { 'content-type': 'application/json', 'Authorization': token, }; var url = Uri.parse('https://jsonplaceholder.typicode.com/posts/$postId'); final response = await http.delete( url, headers: headers, ); log(response.body); if (response.statusCode == 200) { return PostModel.fromJson(response.body); } else { throw Exception('Failed to delete post'); } } catch (err) { log('Delete error, ', error: err); throw Exception(err); } } Future<PostModel> edit(String postId, PostModel post, [String token = '']) async { try { var headers = { 'content-type': 'application/json; charset=UTF-8', 'Authorization': token, }; var url = Uri.parse('https://jsonplaceholder.typicode.com/posts/$postId'); final response = await http.put( url, headers: headers, body: json.encode({ 'id': post.id, 'userId': post.userId, 'title': post.title, 'body': post.body, }), // body: post.toJson() ); log(response.body); if (response.statusCode == 200) { return PostModel.fromJson(response.body); } else { throw Exception('Failed to edit post'); } } catch (err) { log('Edit error, ', error: err); throw Exception(err); } }}
services.dart:
export './post_service.dart';
UI
home_screen.dart:
import 'package:flutter/material.dart';import 'package:flutter_restful/core/models/models.dart';import 'package:flutter_restful/core/services/post_service.dart';import 'package:flutter_restful/ui/post_detail_screen.dart';class HomeScreen extends StatefulWidget { const HomeScreen({Key? key}) : super(key: key); @override _HomeScreenState createState() => _HomeScreenState();}class _HomeScreenState extends State<HomeScreen> { final _formKey = GlobalKey<FormState>(); final TextEditingController _titleController = TextEditingController(); final TextEditingController _contentController = TextEditingController(); PostService post = PostService(); PostModel model = PostModel(); List<PostModel> _posts = []; @override void initState() { // TODO: implement initState super.initState(); post.findAll().then((value) { setState(() { _posts = value; }); }); } @override Widget build(BuildContext context) { return Scaffold( body: ListView.builder( itemCount: _posts.length, itemBuilder: (context, index) { PostModel _post = _posts[index]; return ListTile( title: Text(_post.title ?? ''), dense: true, onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => PostDetailScreen( post: _post, ))); }, trailing: Wrap( spacing: 12, // space between two icons children: <Widget>[ IconButton( onPressed: () { post.delete(index.toString()); setState(() { _posts.removeAt(index); }); }, icon: const Icon(Icons.delete), ), IconButton( onPressed: () { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( content: Stack( clipBehavior: Clip.none, children: <Widget>[ Positioned( right: -40.0, top: -40.0, child: InkResponse( onTap: () { Navigator.of(context).pop(); }, child: const CircleAvatar( child: Icon(Icons.close), backgroundColor: Colors.red, ), ), ), Form( key: _formKey, child: Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ const Text('Edit Post'), Padding( padding: const EdgeInsets.all(8.0), child: TextFormField( decoration: const InputDecoration( labelText: "title", ), initialValue: _posts[index].title, onSaved: (value) { model.title = value; }, ), ), Padding( padding: const EdgeInsets.all(8.0), child: TextFormField( decoration: const InputDecoration( labelText: "content", ), initialValue: _posts[index].body, onSaved: (value) { model.body = value; }, ), ), Padding( padding: const EdgeInsets.all(8.0), child: ElevatedButton( child: const Text("Edit"), onPressed: () { if (_formKey.currentState! .validate()) { _formKey.currentState!.save(); model.userId = 1; model.id = _posts[index].id; post .edit( _posts[index] .id .toString(), model) .then( (value) { setState(() { _posts[index] = value; }); }, ); Navigator.of(context).pop(); } }, ), ) ], ), ), ], ), ); }); }, icon: const Icon(Icons.edit), ), ], ), ); }, ), floatingActionButton: FloatingActionButton( child: const Icon(Icons.add), onPressed: () { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( content: Stack( clipBehavior: Clip.none, children: <Widget>[ Positioned( right: -40.0, top: -40.0, child: InkResponse( onTap: () { Navigator.of(context).pop(); }, child: const CircleAvatar( child: Icon(Icons.close), backgroundColor: Colors.red, ), ), ), Form( key: _formKey, child: Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ const Text('Add Post'), Padding( padding: const EdgeInsets.all(8.0), child: TextFormField( controller: _titleController, decoration: const InputDecoration( labelText: "title", ), onSaved: (value) { model.title = value; }, ), ), Padding( padding: const EdgeInsets.all(8.0), child: TextFormField( controller: _contentController, decoration: const InputDecoration( labelText: "content", ), onSaved: (value) { model.body = value; }, ), ), Padding( padding: const EdgeInsets.all(8.0), child: ElevatedButton( child: const Text("Add"), onPressed: () { if (_formKey.currentState!.validate()) { _formKey.currentState!.save(); model.userId = 1; post.create(model).then( (value) { setState(() { _posts.add(value); }); }, ); Navigator.of(context).pop(); } }, ), ) ], ), ), ], ), ); }); }, ), ); }}
post_detail_screen.dart:
import 'package:flutter/material.dart';import 'package:flutter_restful/core/models/models.dart';class PostDetailScreen extends StatefulWidget { final PostModel post; const PostDetailScreen({ Key? key, required this.post, }) : super(key: key); @override _PostDetailScreenState createState() => _PostDetailScreenState();}class _PostDetailScreenState extends State<PostDetailScreen> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.post.title ?? ''), ), body: Column( children: [ Text(widget.post.body ?? ''), ], ), ); }}
main.dart:
import 'package:flutter/material.dart';import 'ui/home_screen.dart';void main() { runApp(const MyApp());}class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: const HomeScreen(), ); }}
Test
欢迎大家来我的Blog看:
1.Blog: 文章连结