GGbond : Flutter 中的异步同步接口操作与实际项目中的操作

在 Flutter 中,开发者经常需要处理一些耗时的 I/O 操作,例如网络请求、数据库读写、文件操作等。这些操作不能在主线程中执行,否则会导致 UI 卡顿。为了解决这个问题,Flutter 提供了一套异步编程的机制,让开发者可以用同步的代码结构实现异步的操作。

在 Dart 语言中,异步编程主要依赖于两个核心概念:Future 和 async/await。Future 是一个表示异步操作结果的对象,它可以是未完成的、返回结果值或者返回异常。Future 可以通过 then、catchError 和 whenComplete 等方法来处理数据回调和异常回调。async/await 是两个用于简化异步 API 操作的关键字,它们可以让开发者用同步的方式写出异步的代码,提高代码的可读性和简洁性。

下面我们来看一个具体的例子,如何在 Flutter 3.0 中对接口进行异步同步操作。假设我们有一个接口,它可以根据用户输入的关键词返回一些相关的文章列表。我们想要在 Flutter 应用中调用这个接口,并将结果显示在一个 ListView 中。我们可以按照以下步骤来实现:

  1. 定义一个 Article 类,用来表示文章的基本信息,例如标题、作者、摘要等。
  2. 定义一个 fetchArticles 方法,用来发起网络请求,并返回一个 Future<List> 类型的对象。
  3. 在 StatefulWidget 的 State 类中定义一个 List 类型的变量 _articles,用来存储接口返回的文章列表。
  4. 在 initState 方法中调用 fetchArticles 方法,并使用 async/await 来等待结果,并将结果赋值给 _articles 变量。
  5. 在 build 方法中根据 _articles 变量来构建 ListView,并使用 FutureBuilder 来处理加载中和加载失败的情况。

代码示例:

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

// 定义 Article 类
class Article {
  final String title;
  final String author;
  final String summary;

  Article({required this.title, required this.author, required this.summary});

  // 将 JSON 数据转换为 Article 对象
  factory Article.fromJson(Map<String, dynamic> json) {
    return Article(
      title: json['title'],
      author: json['author'],
      summary: json['summary'],
    );
  }
}

// 定义 fetchArticles 方法
Future<List<Article>> fetchArticles(String keyword) async {
  // 拼接接口地址
  final url = 'https://example.com/api/articles?keyword=$keyword';
  // 发起网络请求
  final response = await http.get(Uri.parse(url));
  // 判断响应状态码
  if (response.statusCode == 200) {
    // 解析响应数据
    final data = jsonDecode(response.body) as List
    // 将数据转换为 Article 列表
    final articles = data.map((item) => Article.fromJson(item)).toList();
    // 返回结果
    return articles;
  } else {
    // 抛出异常
    throw Exception('Failed to load articles');
  }
}

// 定义 StatefulWidget 类
class ArticleListPage extends StatefulWidget {
  final String keyword;

  ArticleListPage({required this.keyword});

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

// 定义 State 类
class _ArticleListPageState extends State<ArticleListPage> {
  // 定义 _articles 变量
  late List<Article> _articles;

  @override
  void initState() {
    super.initState();
    // 调用 fetchArticles 方法,并使用 async/await 来等待结果
    fetchArticles(widget.keyword).then((articles) {
      // 将结果赋值给 _articles 变量
      setState(() {
        _articles = articles;
      });
    }).catchError((error) {
      // 处理异常
      print(error);
    });
  }

  @override
  Widget build(BuildContext context) {
    // 使用 FutureBuilder 来处理加载中和加载失败的情况
    return FutureBuilder<List<Article>>(
      future: fetchArticles(widget.keyword),
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          // 根据 _articles 变量来构建 ListView
          return ListView.builder(
            itemCount: _articles.length,
            itemBuilder: (context, index) {
              final article = _articles[index];
              return ListTile(
                title: Text(article.title),
                subtitle: Text(article.author),
                trailing: Text(article.summary),
              );
            },
          );
        } else if (snapshot.hasError) {
          // 显示错误信息
          return Center(
            child: Text('Error: ${snapshot.error}'),
          );
        } else {
          // 显示加载中的指示器
          return Center(
            child: CircularProgressIndicator(),
          );
        }
      },
    );
  }
}

这样,我们就完成了一个简单的 Flutter 应用,可以对接口进行异步同步操作,并将结果显示在 UI 上。当然,这只是一个基本的示例,实际开发中可能会遇到更复杂的场景和需求,需要根据具体情况进行调整和优化。

实际项目

实际项目中,我们则经常采用各种库的形式完成flutter的接口交互:

  • 使用 Dio 库来发起网络请求。Dio 是一个强大的 Dart Http 客户端,支持拦截器、全局配置、FormData、请求取消、文件下载、超时等功能。使用 Dio 可以让我们更方便地管理和处理网络请求。
  • 使用 Provider 库来实现状态管理。Provider 是一个简单而优雅的状态管理库,可以让我们在应用中共享和更新数据,避免不必要的 UI 重建。使用 Provider 可以让我们更好地组织和维护应用的状态。
  • 使用 Flutter Hooks 库来简化 StatefulWidget 的写法。Flutter Hooks 是一个基于 Hook 的 Flutter 库,可以让我们在无状态组件中使用动画、计时器、流等功能,减少模板代码和重复代码。使用 Flutter Hooks 可以让我们更高效地编写 Flutter 组件。

下面是一个使用了 Dio、Provider 和 Flutter Hooks 的改进版本的代码示例:

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:provider/provider.dart';
import 'package:dio/dio.dart';

// 定义 Article 类
class Article {
  final String title;
  final String author;
  final String summary;

  Article({required this.title, required this.author, required this.summary});

  // 将 JSON 数据转换为 Article 对象
  factory Article.fromJson(Map<String, dynamic> json) {
    return Article(
      title: json['title'],
      author: json['author'],
      summary: json['summary'],
    );
  }
}

// 定义 ArticleProvider 类,用来管理文章列表的状态
class ArticleProvider extends ChangeNotifier {
  // 定义 _articles 变量
  List<Article> _articles = [];

  // 定义 articles getter 方法
  List<Article> get articles => _articles;

  // 定义 fetchArticles 方法,用 Dio 发起网络请求,并通知监听者
  Future<void> fetchArticles(String keyword) async {
    // 拼接接口地址
    final url = 'https://example.com/api/articles?keyword=$keyword';
    // 创建 Dio 实例
    final dio = Dio();
    // 发起网络请求
    final response = await dio.get(url);
    // 判断响应状态码
    if (response.statusCode == 200) {
      // 解析响应数据
      final data = response.data as List;
      // 将数据转换为 Article 列表
      final articles = data.map((item) => Article.fromJson(item)).toList();
      // 将结果赋值给 _articles 变量
      _articles = articles;
      // 通知监听者
      notifyListeners();
    } else {
      // 抛出异常
      throw Exception('Failed to load articles');
    }
  }
}

// 定义 HookWidget 类
class ArticleListPage extends HookWidget {
  final String keyword;

  ArticleListPage({required this.keyword});

  @override
  Widget build(BuildContext context) {
    // 使用 useEffect 来在组件初始化时调用 fetchArticles 方法
    useEffect(() {
      // 获取 ArticleProvider 实例
      final articleProvider = context.read<ArticleProvider>();
      // 调用 fetchArticles 方法
      articleProvider.fetchArticles(keyword);
      // 返回一个空的销毁函数
      return () {};
    }, const []);

    // 使用 useProvider 来获取文章列表的状态
    final articles = useProvider<ArticleProvider>((provider) => provider.articles);

    // 使用 FutureBuilder 来处理加载中和加载失败的情况
    return FutureBuilder<List<Article>>(
      future: context.read<ArticleProvider>().fetchArticles(keyword),
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          // 根据 articles 变量来构建 ListView
          return ListView.builder(
            itemCount: articles.length,
            itemBuilder: (context, index) {
              final article = articles[index];
              return ListTile(
                title: Text(article.title),
                subtitle: Text(article.author),
                trailing: Text(article.summary),
              );
            },
          );
        } else if (snapshot.hasError) {
          // 显示错误信息
          return Center(
            child: Text('Error: ${snapshot.error}'),
          );
        } else {
          // 显示加载中的指示器
          return Center(
            child: CircularProgressIndicator(),
          );
        }
      },
    );
  }
}

这样,我们就完成了一个使用了 Dio、Provider 和 Flutter Hooks 的 接口请求,可以对接口进行异步同步操作,并将结果显示在 UI 上。这些库可以让我们的代码更加简洁、高效和可维护。当然,这也只是一个基本的示例,实际开发中可能会遇到更复杂的场景和需求,需要根据具体情况进行调整和优化。