开篇寄语
这一篇文章主要是学习flutter中的Networking相关,也就是各种如打开网址,输入网址打开,获取数据,发送数据,绑定api等等,伯衡君通过一些实例,确实掌握了其中不少实用技巧,分享给大家。
前情提要
- 《Flutter制作自己的第一个全平台APP学习之Widgets篇》
- 《Flutter制作自己的第一个全平台APP学习之Layouts篇》
- 《Flutter制作自己的第一个全平台APP学习之Navigation篇》
- 《Flutter制作自己的第一个全平台APP学习之Popup篇》
- 《Flutter制作自己的第一个全平台APP学习之多媒体篇》
内容详情
以下内容大多是更改lib下的main.dart文件内容,删掉里面的内容,复制粘贴代码,开启调试就可以出现了。
Webview
- 不通过浏览器打开浏览一个网页,这个在flutter中可以使用url_launcher这个支撑库,安装和使用可以参看这个地址:
- 安装完成后,试举一例:
import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', home: _SizedBoxExample(), ); } } class _SizedBoxExample extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Flutter Demo'), ), body: Center( child: ElevatedButton( onPressed: _launchURL, child: Text('Open Google'), ), ), ); } } _launchURL() async { final Uri uri = Uri( scheme: 'https', path: 'www.google.com', queryParameters: {'name': 'google dot com', 'about': 'Flutter Dart'}, ); const url = "https://www.google.com/"; if (await canLaunch(url)) { await launch(uri.toString()); } else { print('Could not launch $url'); } }
生成效果如下图所示:
fetching data
- 通过Api,接收数据,这个需要先安装一个名为http的flutter支持库,安装和使用例子,可以参看这个链接:
- 试举例如下:
import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; Future<Album> fetchAlbum() async { final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts/1')); // Appropriate action depending upon the // server response if (response.statusCode == 200) { return Album.fromJson(json.decode(response.body)); } else { throw Exception('Failed to load album'); } } class Album { final int userId; final int id; final String title; final String body; Album( {required this.userId, required this.id, required this.title, required this.body}); factory Album.fromJson(Map<String, dynamic> json) { return Album( userId: json['userId'], id: json['id'], title: json['title'], body: json['body'], ); } } void main() => runApp(MyApp()); class MyApp extends StatefulWidget { MyApp({Key? key}) : super(key: key); @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { late Future<Album> futureAlbum; @override void initState() { super.initState(); futureAlbum = fetchAlbum(); } @override Widget build(BuildContext context) { return MaterialApp( title: 'Fetching Data', theme: ThemeData( primarySwatch: Colors.blue, ), home: Scaffold( appBar: AppBar( title: Text('Flutter Demo'), ), body: Center( child: FutureBuilder<Album>( future: futureAlbum, builder: (context, snapshot) { if (snapshot.hasData) { return Text(snapshot.data!.body); } else if (snapshot.hasError) { return Text("${snapshot.error}"); } return CircularProgressIndicator(); }, ), ), ), ); } }
生成效果如下图所示:
sending data
- 通过Api,传递数据,这个需要先安装一个名为http的flutter支持库,安装和使用例子,可以参看这个链接:
- 试举例如下:
import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; Future<Album> createAlbum(String title) async { final response = await http.post( Uri.parse('https://jsonplaceholder.typicode.com/albums'), headers: <String, String>{ 'Content-Type': 'application/json; charset=UTF-8', }, body: jsonEncode(<String, String>{ 'title': title, }), ); if (response.statusCode == 201) { // If the server did return a 201 CREATED response, // then parse the JSON. return Album.fromJson(jsonDecode(response.body)); } else { // If the server did not return a 201 CREATED response, // then throw an exception. throw Exception('Failed to create album.'); } } class Album { final int id; final String title; Album({required this.id, required this.title}); factory Album.fromJson(Map<String, dynamic> json) { return Album( id: json['id'], title: json['title'], ); } } void main() { runApp(MyApp()); } class MyApp extends StatefulWidget { MyApp({Key? key}) : super(key: key); @override _MyAppState createState() { return _MyAppState(); } } class _MyAppState extends State<MyApp> { final TextEditingController _controller = TextEditingController(); Future<Album>? _futureAlbum; @override Widget build(BuildContext context) { return MaterialApp( title: 'Create Data Example', theme: ThemeData( primarySwatch: Colors.blue, ), home: Scaffold( appBar: AppBar( title: Text('Create Data Example'), ), body: Container( alignment: Alignment.center, padding: const EdgeInsets.all(8.0), child: (_futureAlbum == null) ? buildColumn() : buildFutureBuilder(), ), ), ); } Column buildColumn() { return Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ TextField( controller: _controller, decoration: InputDecoration(hintText: 'Enter Title'), ), ElevatedButton( onPressed: () { setState(() { _futureAlbum = createAlbum(_controller.text); }); }, child: Text('Create Data'), ), ], ); } FutureBuilder<Album> buildFutureBuilder() { return FutureBuilder<Album>( future: _futureAlbum, builder: (context, snapshot) { if (snapshot.hasData) { return Text(snapshot.data!.title); } else if (snapshot.hasError) { return Text('${snapshot.error}'); } return CircularProgressIndicator(); }, ); } }
生成效果如下图所示:
接下来结合一个实例,即利用Google Books Api来制作一个图书查询的app,完整代码如下:
import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; const url = 'https://www.googleapis.com/books/v1/volumes?q=harry+potter+inauthor:rowling'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Book Finder', theme: ThemeData( primarySwatch: Colors.blue, ), home: BookFinderPage(), ); } } class BookFinderPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Book Finder'), leading: Icon(Icons.book), ), body: FutureBuilder( future: _fetchPotterBooks(), builder: (context, AsyncSnapshot<List<Book>> snapshot) { if (snapshot.connectionState == ConnectionState.done) { if (snapshot.hasError) { return Center(child: Text('Error: ${snapshot.error}')); } else { return ListView( children: snapshot.data!.map((b) => BookTile(b)).toList()); } } else { return Center(child: CircularProgressIndicator()); } }), ); } } class BookTile extends StatelessWidget { final Book book; BookTile(this.book); @override Widget build(BuildContext context) { return ListTile( leading: CircleAvatar( backgroundImage: NetworkImage(book.thumbnailUrl.replaceAll("http:", "https:")), ), title: Text(book.title), subtitle: Text(book.author), onTap: () => _navigateToDetailsPage(book, context), ); } } Future<List<Book>> _fetchPotterBooks() async { final res = await http.get(Uri.parse(url)); if (res.statusCode == 200) { return _parseBookJson(res.body); } else { throw Exception('Error: ${res.statusCode}'); } } List<Book> _parseBookJson(String jsonStr) { final jsonMap = json.decode(jsonStr); final jsonList = (jsonMap['items'] as List); return jsonList .map((jsonBook) => Book( title: jsonBook['volumeInfo']['title'], author: (jsonBook['volumeInfo']['authors'] as List).join(', '), thumbnailUrl: jsonBook['volumeInfo']['imageLinks'] ['smallThumbnail'], )) .toList(); } class Book { final String title; final String author; final String thumbnailUrl; Book({required this.title, required this.author, required this.thumbnailUrl}) // ignore: unnecessary_null_comparison : assert(title != null), // ignore: unnecessary_null_comparison assert(author != null); } void _navigateToDetailsPage(Book book, BuildContext context) { Navigator.of(context).push(MaterialPageRoute( builder: (context) => BookDetailsPage(book), )); } class BookDetailsPage extends StatelessWidget { final Book book; BookDetailsPage(this.book); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(book.title)), body: Padding( padding: const EdgeInsets.all(15.0), child: BookDetails(book), ), ); } } class BookDetails extends StatelessWidget { final Book book; BookDetails(this.book); @override Widget build(BuildContext context) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ Image.network(book.thumbnailUrl), SizedBox(height: 10.0), Text(book.title), Padding( padding: const EdgeInsets.only(top: 10.0), child: Text(book.author, style: TextStyle(fontWeight: FontWeight.w700)), ), ], ), ); } }
生成效果如下图所示:
可能在运行的时候,会出现图片无法显示的问题,提示错误如下:
Flutter web can't load network image from another domain
只需要在terminal输入以下内容运行即可:
flutter build web --release --web-renderer html
下拉加载数据
- 在制作App中,下拉加载数据,这种情况很常见,所以这个需要掌握,试举一例:
import 'package:flutter/material.dart'; import 'dart:convert'; // import the http package import 'package:http/http.dart' as http; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( // Hide the debug banner debugShowCheckedModeBanner: false, title: 'Flutter Demo', home: HomePage(), ); } } class HomePage extends StatefulWidget { @override _HomePageState createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { // The initial todos List _todos = []; // Call this when the user pull down the screen Future<void> _loadData() async { final url = 'https://jsonplaceholder.typicode.com/todos'; try { final http.Response response = await http.get(Uri.parse(url)); final _loadedTodos = json.decode(response.body); setState(() { _todos = _loadedTodos; }); } catch (err) { throw err; } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Flutter Demo'), ), body: RefreshIndicator( // trigger the _loadData function when the user pulls down onRefresh: _loadData, // Render the todos child: ListView.builder( itemCount: _todos.length, itemBuilder: (BuildContext ctx, index) { return Card( margin: EdgeInsets.symmetric(horizontal: 15, vertical: 8), child: ListTile( // Render each todo leading: Text(_todos[index]['id'].toString()), title: Text(_todos[index]["title"]), trailing: _todos[index]["completed"] ? Icon( Icons.check_circle, color: Colors.blue, ) : Icon( Icons.circle, color: Colors.yellow, ), ), ); })), ); } }
生成效果如下图所示:
下载图片
- 这个功能也常常用到,可以参看以下代码:
import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; // if you don't add universal_html to your dependencies you should // write import 'dart:html' as html; instead import 'package:universal_html/html.dart' as html; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', debugShowCheckedModeBanner: false, theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(), ); } } class MyHomePage extends StatefulWidget { @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { final imageUrls = <String>[ 'https://images.pexels.com/photos/208745/pexels-photo-208745.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940', 'https://images.pexels.com/photos/1470707/pexels-photo-1470707.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940', 'https://images.pexels.com/photos/2671089/pexels-photo-2671089.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940', 'https://images.pexels.com/photos/2670273/pexels-photo-2670273.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940', ]; @override Widget build(BuildContext context) { return Scaffold( body: GridView.count( crossAxisCount: 2, children: imageUrls .map( (imageUrl) => ImageCard(imageUrl: imageUrl), ) .toList(), ), ); } } class ImageCard extends StatefulWidget { @override _ImageCardState createState() => _ImageCardState(); final String imageUrl; ImageCard({ required this.imageUrl, }); } class _ImageCardState extends State<ImageCard> { Future<void> downloadImage(String imageUrl) async { try { // first we make a request to the url like you did // in the android and ios version final http.Response r = await http.get( Uri.parse(imageUrl), ); // we get the bytes from the body final data = r.bodyBytes; // and encode them to base64 final base64data = base64Encode(data); // then we create and AnchorElement with the html package final a = html.AnchorElement(href: 'data:image/jpeg;base64,$base64data'); // set the name of the file we want the image to get // downloaded to a.download = 'download.jpg'; // and we click the AnchorElement which downloads the image a.click(); // finally we remove the AnchorElement a.remove(); } catch (e) { print(e); } } @override Widget build(BuildContext context) { return InkWell( onTap: () => downloadImage(widget.imageUrl), child: Card( child: Image.network( widget.imageUrl, fit: BoxFit.cover, ), ), ); } }
生成效果如下图所示:
- 我的微信
- 微信扫一扫加好友
- 我的微信公众号
- 扫描关注公众号