Flutter 如何接入 GraphQL

本文将带着大家创建一个以 GraphQL 为后端的 Flutter 项目。

项目依赖

创建项目

创建一个全新的 Flutter 项目 tinylearn_client:

$ flutter create tinylearn_client

使用代码编辑器打开项目:

点击文件 lib/main.dart,清理代码,删除注释,将其改为:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Container(),
    );
  }
}

运行后得到:

接入 GraphQL

安装依赖库 graphql_flutter:

pubspec.yaml:

dependencies:
  flutter:
    sdk: flutter
+ graphql_flutter: ^3.0.1

运行:

$ flutter pub get

安装完毕后,我们先回顾上一集 《10 分钟搭建一个 GraphQL 后端项目》 的内容:

这个页面是我们在上集中提到的 “Postman”。 图片中间部分就是服务器返回的 JSON。我们先来解析这些 JSON。

大家可以用自己喜欢的方式解析 JSON,这里我使用在线工具 quicktype 生成模型,详情可参见 《Flutter 如何使用在线转码工具将 JSON 转为 Model》

将解析代码存放到新文件 lib/PostsData.dart 中:

// To parse this JSON data, do
//
//     final postsData = postsDataFromJson(jsonString);

import 'dart:convert';

class PostsData {
    final List<Post> posts;

    PostsData({
        this.posts,
    });

    factory PostsData.fromJson(String str) => PostsData.fromMap(json.decode(str));

    String toJson() => json.encode(toMap());

    factory PostsData.fromMap(Map<String, dynamic> json) => PostsData(
        posts: json["posts"] == null ? null : List<Post>.from(json["posts"].map((x) => Post.fromMap(x))),
    );

    Map<String, dynamic> toMap() => {
        "posts": posts == null ? null : List<dynamic>.from(posts.map((x) => x.toMap())),
    };
}

class Post {
    final String id;
    final int created;
    final String content;

    Post({
        this.id,
        this.created,
        this.content,
    });

    factory Post.fromJson(String str) => Post.fromMap(json.decode(str));

    String toJson() => json.encode(toMap());

    factory Post.fromMap(Map<String, dynamic> json) => Post(
        id: json["id"] == null ? null : json["id"],
        created: json["created"] == null ? null : json["created"],
        content: json["content"] == null ? null : json["content"],
    );

    Map<String, dynamic> toMap() => {
        "id": id == null ? null : id,
        "created": created == null ? null : created,
        "content": content == null ? null : content,
    };
}

打开 lib/main.dart 文件,加入 GraphQL 网络请求相关代码:

import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:tinylearn_client/PostsData.dart';

...

final _uri = 'http://10.0.0.10:4444/';  // 这里 IP 为服务器局域网 IP 地址,端口为 4444,大家需要根据网络环境自己配置

class _MyHomePageState extends State<MyHomePage> {

  Future<PostsData> _postsData;

  @override
  void initState() {
    
    final client = GraphQLClient(
      cache: InMemoryCache(),
      link: HttpLink(uri: _uri)
    );

    _postsData = _createPostsData(client);
    super.initState();
  }

  Future<PostsData> _createPostsData(GraphQLClient client) async {
        
    final result = await client.query(QueryOptions(
      documentNode: gql(r'''
        query {
          posts {
            id
            created
            content
          }
        }
      '''),
    ));
    if (result.hasException) throw result.exception;
    return PostsData.fromMap(result.data);
  }

  ...

}

首先,配置一个 GraphQLClient, 通过这个 client 我们可以向 GraphQL 服务器发送请求。

final _uri = 'http://10.0.0.10:4444/'; 

class _MyHomePageState extends State<MyHomePage> {

  ...

  @override
  void initState() {
    
    final client = GraphQLClient(
      cache: InMemoryCache(),
      link: HttpLink(uri: _uri)
    );

    ...
  }

  ...
}

注意,这里 final _uri = 'http://10.0.0.10:4444/';GraphQL 服务器的地址。我们使用上一篇文章《10 分钟搭建一个 GraphQL 后端项目》 中写好的后台,就需要将它配置成后台的局域网 IP 加端口号:http://${backendLanIP}:4444/

backendLanIP 可能是 192.169.0.1010.0.0.10, 这个需要自己查询。

我们定义一个方法 Future<PostsData> _createPostsData(GraphQLClient client),它通过 clientGraphQL 服务器请求 PostsData 数据:

...


class _MyHomePageState extends State<MyHomePage> {

  Future<PostsData> _postsData;

  @override
  void initState() {
    
    final client = ...;

    _postsData = _createPostsData(client);
    super.initState();
  }

  Future<PostsData> _createPostsData(GraphQLClient client) async {
        
    final result = await client.query(QueryOptions(
      documentNode: gql(r'''
        query {
          posts {
            id
            created
            content
          }
        }
      '''),
    ));
    if (result.hasException) throw result.exception;
    return PostsData.fromMap(result.data);
  }

  ...

}

请求的参数来自于 “Postmain” 左侧的配置。

下一步,我们来绘制页面,页面将会消费这个请求,并将结果以列表的形式展示出来:

  ...

  Future<PostsData> _createPostsData(GraphQLClient client) async { ... }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: FutureBuilder<PostsData>(
          future: _postsData,
          builder: (context, snapshot) {
            if (snapshot.hasData) {
              final List<Post> posts = snapshot.data.posts;
              return _buildListView(posts);
            }
            if (snapshot.hasError) {
              return Text('${snapshot.error}', textAlign: TextAlign.center);
            }
            return CircularProgressIndicator();
          },
        ),
      ),
    );
  }

  ListView _buildListView(List<Post> posts) {
    return ListView.separated(
      itemCount: posts.length,
      itemBuilder: (context, index) {
        final post = posts[index];
        return _postListTile(post);
      },
      separatorBuilder: (context, index) => Divider(),
    );
  }

  ListTile _postListTile(Post post) {
    return ListTile(
      title: Text(post.content),
      subtitle: Text(post.created.time.toString()),
    );
  }

...

最后,加入一个拓展方法将服务器返回的 int 时间戳转为 DateTime 时间类型。

...

class MyHomePage extends StatefulWidget {
  ...
}

extension Time on int {

  DateTime get time => DateTime.fromMillisecondsSinceEpoch(this);
}

运行:

大功告成!

未完待续

这次我们打通了 GraphQL 的前后端,并演示了一些基本功能。后面,我们会先介绍 GraphQL 的设计理念,以及它的优势与劣势。然后,将这些理念投入到实战中,制作一个功能完善的前后端产品。

大家有什么想法,欢迎给我留言。

源码:

代码地址

lib/main.dart:

import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:tinylearn_client/PostsData.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

final _uri = 'http://10.0.0.105:4444/';

class _MyHomePageState extends State<MyHomePage> {

  Future<PostsData> _postsData;

  @override
  void initState() {

    final client = GraphQLClient(
      cache: InMemoryCache(),
      link: HttpLink(uri: _uri)
    );

    _postsData = _createPostsData(client);
    super.initState();
  }

  Future<PostsData> _createPostsData(GraphQLClient client) async {
        
    final result = await client.query(QueryOptions(
      documentNode: gql(r'''
        query {
          posts {
            id
            created
            content
          }
        }
      '''),
    ));
    if (result.hasException) throw result.exception;
    return PostsData.fromMap(result.data);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: FutureBuilder<PostsData>(
          future: _postsData,
          builder: (context, snapshot) {
            if (snapshot.hasData) {
              final List<Post> posts = snapshot.data.posts;
              return _buildListView(posts);
            }
            if (snapshot.hasError) {
              return Text('${snapshot.error}', textAlign: TextAlign.center);
            }
            return CircularProgressIndicator();
          },
        ),
      ),
    );
  }

  ListView _buildListView(List<Post> posts) {
    return ListView.separated(
      itemCount: posts.length,
      itemBuilder: (context, index) {
        final post = posts[index];
        return _postListTile(post);
      },
      separatorBuilder: (context, index) => Divider(),
    );
  }

  ListTile _postListTile(Post post) {
    return ListTile(
      title: Text(post.content),
      subtitle: Text(post.created.time.toString()),
    );
  }
}

extension Time on int {

  DateTime get time => DateTime.fromMillisecondsSinceEpoch(this);
}

lib/PostData.dart:

// To parse this JSON data, do
//
//     final postsData = postsDataFromJson(jsonString);

import 'dart:convert';

class PostsData {
    final List<Post> posts;

    PostsData({
        this.posts,
    });

    factory PostsData.fromJson(String str) => PostsData.fromMap(json.decode(str));

    String toJson() => json.encode(toMap());

    factory PostsData.fromMap(Map<String, dynamic> json) => PostsData(
        posts: json["posts"] == null ? null : List<Post>.from(json["posts"].map((x) => Post.fromMap(x))),
    );

    Map<String, dynamic> toMap() => {
        "posts": posts == null ? null : List<dynamic>.from(posts.map((x) => x.toMap())),
    };
}

class Post {
    final String id;
    final int created;
    final String content;

    Post({
        this.id,
        this.created,
        this.content,
    });

    factory Post.fromJson(String str) => Post.fromMap(json.decode(str));

    String toJson() => json.encode(toMap());

    factory Post.fromMap(Map<String, dynamic> json) => Post(
        id: json["id"] == null ? null : json["id"],
        created: json["created"] == null ? null : json["created"],
        content: json["content"] == null ? null : json["content"],
    );

    Map<String, dynamic> toMap() => {
        "id": id == null ? null : id,
        "created": created == null ? null : created,
        "content": content == null ? null : content,
    };
}

参考

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。