Flutter学习 - 组件通信与网络请求Dio

目录

组件通信

[组件通信 - 父传子(构造函数传参数)](#组件通信 - 父传子(构造函数传参数))

步骤:

小案例:

[组件通信 - 子传父(回调函数)](#组件通信 - 子传父(回调函数))

[网络请求 - Dio插件使用](#网络请求 - Dio插件使用)

[网络请求案例 - 1. 封装Dio工具](#网络请求案例 - 1. 封装Dio工具)

[网络请求案例 - 2. 初始化获取数据](#网络请求案例 - 2. 初始化获取数据)

[网络请求案例 - 3. 解决web端跨域问题](#网络请求案例 - 3. 解决web端跨域问题)

[网络请求案例 - 4. 父传子实现](#网络请求案例 - 4. 父传子实现)

路由管理:


组件通信

组件通信 - 父传子(构造函数传参数)

|-----------------|-----------|----------|
| 通信方式 | 方向 | 适用场景 |
| 构造函数传递 | 父 => 子 | 简单的数据传递 |
| 回调函数 | 子 => 父 | 子组件通知父组件 |
| InheritedWidget | 祖先 => 后代 | 跨层级数据共享 |
| Provider | 任意组件间 | 状态管理推荐方案 |
| EventBus | 任意组件间 | 全局事件通信 |
| Bloc/Riverpod | 任意组件间 | 复杂状态管理 |

步骤:
  1. 子组件定义接收属性
  2. 子组件在构造函数中接收参数
  3. 父组件传递属性给子组件
  4. 有状态组件在'对外的类'接受属性,'对内的类'通过widget对象获取对应属性

子组件定义接收属性需要使用final关键字 - 因为属性由父组件决定,子组件不能随意更改

子组件属性如果没有初始值,需要在构造函数中使用required来接收属性

Dart 复制代码
import 'package:flutter/material.dart';

void main(List<String> args) {
  runApp(MainPage());
}

// 父组件
class MainPage extends StatelessWidget {
  const MainPage({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Container(
          alignment: Alignment.center,
          child: Column(
            children: [
              Text("父组件", style: TextStyle(color: Colors.blue, fontSize: 30)),
              Child(message: "张三"),
              ChildActive(message: "李四")
            ],
          ),
        ),
      ),
    );
  }
}

// 无状态组件的子组件
class Child extends StatelessWidget {
 final  String? message; // 子组件定义属性(使用final)
 // 构造函数中接收参数
  const Child({super.key,this.message});

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text("无状态子组件 - ${message}", style: TextStyle(color: Colors.red, fontSize: 18)),
    );
  }
}

// 有状态组件的子组件
class ChildActive extends StatefulWidget { // 对外接收
  final String message; // 用final定义
  const ChildActive({super.key,required this.message});

  @override
  State<ChildActive> createState() => _ChildActiveState();
}

class _ChildActiveState extends State<ChildActive> { // 对内接收
  @override
  Widget build(BuildContext context) {
    return Container(
      // 对内使用widget对象接收
      child: Text("有状态的子组件 -- ${widget.message}", style: TextStyle(color: Colors.red, fontSize: 18)),
    );
  }
}
小案例:
Dart 复制代码
import 'package:flutter/material.dart';

void main(List<String> args) {
  runApp(MainPage());
}

// 父组件
class MainPage extends StatefulWidget {
  const MainPage({super.key});

  @override
  State<MainPage> createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
  List<String> _list = ['鱼香肉丝', '宫保鸡丁', '麻婆豆腐', '酸辣土豆丝', '酱排骨'];

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: GridView.count(
            padding: EdgeInsets.all(10),
            crossAxisCount: 2,
            mainAxisSpacing: 10,
            crossAxisSpacing: 10,
            children: List.generate(_list.length, (index) {
              return Child(foodName: _list[index],); // 返回整个子组件
            })),
      ),
    );
  }
}

// 子组件
class Child extends StatefulWidget {
  final String foodName;
  const Child({super.key,required this.foodName});

  @override
  State<Child> createState() => _ChildState();
}

class _ChildState extends State<Child> {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.blue,
      alignment: Alignment.center,
      child: Text(widget.foodName,style: TextStyle(color:Colors.white,fontSize: 20),),
    );
  }
}
组件通信 - 子传父(回调函数)

步骤:

  1. 父组件传一个函数给子组件

  2. 子组件调用该函数

  3. 父组件通过回调函数获取参数

需求:

点击子组件删除父组件的菜品数据并更新列表

Dart 复制代码
import 'package:flutter/material.dart';

void main(List<String> args) {
  runApp(MainPage());
}

// 父组件
class MainPage extends StatefulWidget {
  const MainPage({super.key});

  @override
  State<MainPage> createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
  List<String> _list = ['鱼香肉丝', '宫保鸡丁', '麻婆豆腐', '酸辣土豆丝', '酱排骨'];

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: GridView.count(
            padding: EdgeInsets.all(10),
            crossAxisCount: 2,
            mainAxisSpacing: 10,
            crossAxisSpacing: 10,
            children: List.generate(_list.length, (index) {
              return Child(foodName: _list[index],index: index, delFood: (int index) {
                print("父接收$index");
                _list.removeAt(index);
                setState(() {});
              }); // 返回整个子组件
            })),
      ),
    );
  }
}

// 子组件
class Child extends StatefulWidget {
  final String foodName;
  final int index;
  final Function(int index) delFood; // 声明函数属性
  const Child({super.key,required this.foodName,required this.index,required this.delFood});

  @override
  State<Child> createState() => _ChildState();
}

class _ChildState extends State<Child> {
  @override
  Widget build(BuildContext context) {
    return Stack(
      alignment: Alignment.topRight,
      children: [
        Container(
            color: Colors.blue,
            alignment: Alignment.center,
            child: Text(widget.foodName,style: TextStyle(color:Colors.white,fontSize: 20),),
        ),
        IconButton(color:Colors.red,onPressed: (){
          print("删除菜单${widget.index}");
          widget.delFood(widget.index);
        },icon: Icon(Icons.delete))
      ],
    );
  }
}

网络请求 - Dio插件使用

安装dio:flutter pub add dio

基本使用:

Dio( ).get( 地址 ).then( ).catchError( )

复制代码
import 'package:dio/dio.dart';

void main(List<String> args) {
  Dio().get("https://geek.itheima.net/v1_0/channels").then((res) {
    print(res);
  }).catchError((error) {});
}

一般情况下,在初始化状态initState获取页面数据

网络请求案例 - 1. 封装Dio工具
  1. 创建工具类
  2. 构造函数2中设置基础地址和超时时间
  3. 添加各类拦截器
  4. 封装统一请求方法
  5. 请求频道数据进行循环渲染解决web端跨域问题
  6. 实现UI渲染绘制
Dart 复制代码
import 'package:dio/dio.dart';

void main(List<String> args) {

}

// 封装一个工具类
class DioUtils {
  final Dio _dio = Dio(); // 内部Dio对象
  DioUtils(){
    // 基本操作
    // 配置基础地址和超时时间
    // _dio.options.baseUrl =  "https://geek.itheima.net/v1_0/";
    // _dio.options.connectTimeout = Duration(seconds: 10); // 连接超时
    // _dio.options.sendTimeout = Duration(seconds: 10); // 发送超时
    // _dio.options.receiveTimeout = Duration(seconds: 10); // 接收超时
    // 简 写 ..连续赋值的写法
    _dio.options..baseUrl =  "https://geek.itheima.net/v1_0/"
        ..connectTimeout = Duration(seconds: 10)
        ..sendTimeout = Duration(seconds: 10)
        ..receiveTimeout = Duration(seconds: 10);

    // 拦截器
    _addInterceptor(); // 添加注册拦截器
  }
  void _addInterceptor() {
    _dio.interceptors.add(InterceptorsWrapper(
      // 请求拦截器
      onRequest: (context,handler) {
        // handler.next(requestOptions) // 放过请求
        // handler.reject(error)  拦截请求
        handler.next(context);
      },
     // 响应拦截器
      onResponse: (context,handler){
        // handler.next(response);
        // http 状态码 2xx 成功 3xx 4xx 5xx
        if(context.statusCode! >= 200 && context.statusCode! < 300){
          handler.next(context); //放过
          return;
        }
        // 说明异常
        handler.reject(DioException(requestOptions: context.requestOptions));
      },
      //   错误拦截器
      onError: (context,handler){
        handler.reject(context); // 直接抛出异常
      }
    ));
  }
  // 向外暴露一个get方法
  get(String url,{Map<String,dynamic>? params}){
    return _dio.get(url,queryParameters: params);
  }
}
网络请求案例 - 2. 初始化获取数据
网络请求案例 - 3. 解决web端跨域问题

默认情况下,flutter运行web端加载网络资源会报跨域提示错误

网络请求案例 - 4. 父传子实现
Dart 复制代码
import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

void main(List<String> args) {
  runApp(MainPage());
}
class MainPage extends StatefulWidget {
  const MainPage({super.key});

  @override
  State<MainPage> createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    // 发起网络请求
    __getChannels();
  }
  List<Map<String,dynamic>> _list = []; // 用来接收数据

  void __getChannels() async{
    DioUtils dioUtil = DioUtils();
    Response<dynamic> result =  await dioUtil.get("channels");
    Map<String,dynamic> res = result.data as Map<String,dynamic>;
    List data = res['data']['channels'] as List;
    // cast 强制转换列表项的类型
   _list = data.cast<Map<String,dynamic>>() as List<Map<String,dynamic>>;
   setState(() {}); // 执行方法,UI才会更新
   // channels是一个后端支持前端跨域访问的接口

  }
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar:AppBar(title: Text("频道管理")),
        body: GridView.extent(maxCrossAxisExtent: 140,
        padding: EdgeInsets.all(10),
        mainAxisSpacing: 10,
        crossAxisSpacing: 10,
        childAspectRatio: 3,
        children: List.generate(_list.length, (index){
          return ChannelItem(item:_list[index],index: index,delItem: (int index){
            _list.removeAt(index);
            setState(() {});
          });
        }),
        )
      ),
    );
  }
}

// 用来绘制每个频道的UI内容
class ChannelItem extends StatelessWidget {
  final Map<String,dynamic> item;
  final int index;
  final Function(int index) delItem;
  const ChannelItem({super.key,required this.item,required this.index, required this.delItem});

  @override
  Widget build(BuildContext context) {
    return Stack(
      alignment: Alignment.topRight,
      children: [
          Container(
          color: Colors.blue,
          alignment: Alignment.center,
          child: Text(
            item['name'] ?? "空",
            style: TextStyle(color:Colors.white,fontSize: 14),
          ),
        ),
        IconButton(icon: Icon(Icons.delete),color: Colors.red,onPressed: (){
          delItem(index);
        })
      ],
    );
  }
}

// 封装一个工具类
class DioUtils {
  final Dio _dio = Dio(); // 内部Dio对象
  DioUtils(){
    // 基本操作
    // 配置基础地址和超时时间
    // _dio.options.baseUrl =  "https://geek.itheima.net/v1_0/";
    // _dio.options.connectTimeout = Duration(seconds: 10); // 连接超时
    // _dio.options.sendTimeout = Duration(seconds: 10); // 发送超时
    // _dio.options.receiveTimeout = Duration(seconds: 10); // 接收超时
    // 简 写 ..连续赋值的写法
    _dio.options..baseUrl =  "https://geek.itheima.net/v1_0/"
        ..connectTimeout = Duration(seconds: 10)
        ..sendTimeout = Duration(seconds: 10)
        ..receiveTimeout = Duration(seconds: 10);

    // 拦截器
    _addInterceptor(); // 添加注册拦截器
  }
  void _addInterceptor() {
    _dio.interceptors.add(InterceptorsWrapper(
      // 请求拦截器
      onRequest: (context,handler) {
        // handler.next(requestOptions) // 放过请求
        // handler.reject(error)  拦截请求
        handler.next(context);
      },
     // 响应拦截器
      onResponse: (context,handler){
        // handler.next(response);
        // http 状态码 2xx 成功 3xx 4xx 5xx
        if(context.statusCode! >= 200 && context.statusCode! < 300){
          handler.next(context); //放过
          return;
        }
        // 说明异常
        handler.reject(DioException(requestOptions: context.requestOptions));
      },
      //   错误拦截器
      onError: (context,handler){
        handler.reject(context); // 直接抛出异常
      }
    ));
  }
  // 向外暴露一个get方法
 Future<Response<dynamic>> get(String url,{Map<String,dynamic>? params}){
    return _dio.get(url,queryParameters: params);
  }
}
相关推荐
摘星编程2 小时前
React Native for OpenHarmony 实战:Swiper 滑动组件详解
javascript·react native·react.js
workflower2 小时前
软件需求规约的质量属性
java·开发语言·数据库·测试用例·需求分析·结对编程
鸣弦artha2 小时前
Flutter框架跨平台鸿蒙开发——Build流程深度解析
开发语言·javascript·flutter
情缘晓梦.2 小时前
C++ 内存管理
开发语言·jvm·c++
黄晓琪2 小时前
Java AQS底层原理:面试深度解析(附实战避坑)
java·开发语言·面试
姓蔡小朋友2 小时前
Java 定时器
java·开发语言
百锦再3 小时前
python之路并不一马平川:带你踩坑Pandas
开发语言·python·pandas·pip·requests·tools·mircro
灏瀚星空3 小时前
基于 Python 与 GitHub,打造个人专属本地化思维导图工具全流程方案(上)
开发语言·人工智能·经验分享·笔记·python·个人开发·visual studio
是Dream呀3 小时前
Python从0到100(一百):基于Transformer的时序数据建模与实现详解
开发语言·python·transformer