前言

最近比较忙,没时间更新,今天更新新一期Flutter学习笔记。本篇讲述Flutter是如何进行网络请求的,毕竟没有网络请求就只能看见自己手机本地的数据了,无法查看别人的。以后教程采用Android Studio编写,感觉Vscode不如Android Studio好用。

Android Studio安装教程可查看这篇:

【2025 最新】下载安装配置最新版 Android Studio 最详细教程(带图展示)_android studio安装教程-CSDN博客

一、项目初始化与代码管理

1.1 仓库初始化

本教程采用AtomGit的代码托管网站进行托管。首先进入此网站,点击创建项目,链接在下方。

幻影(6540542857dbf456ecbe217f) - AtomGit | GitCodehttps://atomgit.com/Deng666

起好自己的项目名称,设置公开项目或者私有项目。README文件可以后续再添加,点击创建项目即可。

1.2 配置访问令牌

AtomGit不同于Github、Gitee等网站,AtomGit需要先配置访问令牌才可以进行代码的上传。

记得先复制下来自己的自己的访问令牌,保存好。因为只会展示这一次,后续无法找回,只能新建。

1.3 使用Android Studio创建项目

1.3.1 前置准备

在创建项目之前大家需要先下载好两个插件:Dart和Flutter

如何查看并下载?

鼠标悬浮在框住的这个地方并且点击该图标并且选择Settings,选中Plugins。Marketplace是插件市场,Installed是已经下载的。下载插件选择Marketplace

1.3.2 创建项目

创建好项目会出现一个New flutter Project的选项,点击该选项。然后会弹出让你选择SDK路径,这个SDK需要选择已经适配了鸿蒙的SDK。

这里起项目名,项目名尽量见名知意。选择需要开发的平台,我这里就全部勾选了。点击Create即可创建项目完成。(这里没有鸿蒙项目是正常的,我们创建完在终端创建即可。)

打开终端后,先输入"flutter --version"查看是否是适配了鸿蒙的Flutter SDK,如果不是需要切换至鸿蒙的SDK

在终端输入"flutter create --platform ohos ."创建鸿蒙的项目

1.3.3 项目分包

选中lib目录选择New再选择Directory,该操作为新建文件夹。

新建以下的目录,每个目录的作用如图所示:

1.3.4 使用Git命令托管代码

git init 作用:初始化仓库

git add . 作用:提交本地仓库到暂存区

git commit -m "初始化仓库" 作用:" "的内容可以改,说明本次提交代码完成了什么任务。

git remote add origin master <远程仓库地址> 作用:添加远程仓库。(<远程仓库地址>改成自己实际代码仓库,<>是不需要写的,这里这是为了方便读者看)

git push -u origin master 作用:推送主分支到远程

git log 作用:查看推送代码的日志

复制好自己仓库的地址,然后粘贴到终端,再复制令牌,先执行配置仓库密钥的git命令。

git remote set-url origin https://Deng666:wKRPhLaASqWJUs-Bag2h7U8B@gitlab.com:8080/MyProject.git

最后执行推送仓库的命令

git push -u origin master

  • 用户名Deng666

  • 令牌wKRPhLaASqWJUs-Bag2h7U8B

  • 地址格式https://用户名:令牌@仓库地址

可以看到项目已经推送成功。

二、使用Dio实现网络请求

由于我后续打算做一个商城的项目,这里的代码我就不使用git上传了,而且我打算在下篇能顺便演示git的回撤操作。在pubspec.yaml添加dio的库,dio的库为:^5.5.0+1。

2.1 简单总结

dio: ^5.5.0+1:都会安装最新的5.5.x版本,5.5.0+1约等于^5.5.0
provider: ^6.1.2:安装6.1.2或者更新的6.x版本,但别装7.0

2.2 接口说明

本篇先使用网上找的一个比较简单的接口进行接口的请求示例。后续再全部更换成商城的。

这里使用的是猫咪图片请求接口

基本说明:

接口地址:https://api.thecatapi.com/v1/images/search
返回格式:json
请求方式:get
请求示例:https://api.thecatapi.com/v1/images/search?limit=1

请求参数说明:

名称 类型 必填 说明
limit int 选填 要返回的图片数
page int 选填 页码
order int 选填 按照上传日期排序
has_breeds int 选填 是否包含猫品种信息
breed_ids string 选填 品种的ID
category_ids string 选填 品种的ID
sub_id int 选填 过滤具有上传时使用的值的图像sub_id

返回参数说明:

名称 类型 说明
url string 图片链接
width int 宽度
height int 图片高度

JSON返回示例:

[{
	"id": "MTgxNTAxOA",
	"url": "https://cdn2.thecatapi.com/images/MTgxNTAxOA.jpg",
	"width": 560,
	"height": 443
}]

2.3 使用Dio进行网络请求并渲染UI

2.3.1 猫咪图片数据模型

文件路径:lib/viewmodels/cat_model.dart

作用:定义API返回的数据结构

cat_model.dart代码

class CatModel {
  final String id;
  final String url;
  final int width;
  final int height;

  CatModel({
    required this.id,
    required this.url,
    required this.width,
    required this.height,
  });

  factory CatModel.fromJson(Map<String, dynamic> json) {
    return CatModel(
      id: json['id'] ?? '',
      url: json['url'] ?? '',
      width: json['width'] ?? 0,
      height: json['height'] ?? 0,
    );
  }

  Map<String, dynamic> toJson() => {
    'id': id,
    'url': url,
    'width': width,
    'height': height,
  };
}

2.3.2 猫咪API服务

文件路径:lib/api/cat_api.dart

作用:封装具体的网络请求方法

cat_api.dart代码

import 'package:qing_mall/utils/http_util.dart';
import 'package:qing_mall/viewmodels/cat_model.dart';
import 'package:qing_mall/contents/api_constants.dart';

class CatApi {
  static final HttpUtil _http = HttpUtil();

  // 获取猫咪图片列表(基础方法)
  static Future<List<CatModel>> getCatImages({
    int limit = 1,  // 默认只获取1张
    int? page,
    String? order,
    bool? hasBreeds,
    String? breedIds,
    String? categoryIds,
    int? subId,
  }) async {
    try {
      final Map<String, dynamic> queryParams = {
        'limit': limit,
      };

      // 添加可选参数
      if (page != null) queryParams['page'] = page;
      if (order != null) queryParams['order'] = order;
      if (hasBreeds != null) queryParams['has_breeds'] = hasBreeds ? 1 : 0;
      if (breedIds != null) queryParams['breed_ids'] = breedIds;
      if (categoryIds != null) queryParams['category_ids'] = categoryIds;
      if (subId != null) queryParams['sub_id'] = subId;

      final data = await _http.get(
        '${ApiConstants.catBaseUrl}${ApiConstants.catImagesEndpoint}',
        queryParams: queryParams,
      );

      if (data is List) {
        return data.map((json) => CatModel.fromJson(json)).toList();
      } else {
        throw Exception('返回数据格式错误');
      }
    } catch (e) {
      rethrow;
    }
  }

  // 获取单张猫咪图片(简化方法)
  static Future<CatModel> getSingleCat() async {
    final cats = await getCatImages(limit: 1); // ✅ 调用上面定义的方法
    return cats.first;
  }

  // 可选:获取多张猫咪图片
  static Future<List<CatModel>> getMultipleCats(int count) async {
    return await getCatImages(limit: count);
  }
}

2.3.3 猫咪页面调用

文件:lib/pages/Cats/index.dart
作用:界面调用API并显示数据

index.dart代码

import 'package:flutter/material.dart';
import 'package:qing_mall/api/cat_api.dart';
import 'package:qing_mall/viewmodels/cat_model.dart';
import 'package:qing_mall/components/cat_card.dart';

class CatsPage extends StatefulWidget {
  const CatsPage({super.key});

  @override
  State<CatsPage> createState() => _CatsPageState();
}

class _CatsPageState extends State<CatsPage> {
  CatModel? _cat; // ✅ 只保存一张图片
  bool _isLoading = false;
  String? _error;

  @override
  void initState() {
    super.initState();
    _fetchCat(); // ✅ 只获取一张
  }

  Future<void> _fetchCat() async {
    setState(() {
      _isLoading = true;
      _error = null;
    });

    try {
      final cat = await CatApi.getSingleCat(); // ✅ 调用获取单张的方法
      setState(() {
        _cat = cat;
      });
    } catch (e) {
      setState(() {
        _error = e.toString();
      });
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('随机猫咪 🐱'),
        actions: [
          IconButton(
            icon: const Icon(Icons.refresh),
            onPressed: _fetchCat, // ✅ 刷新时也只获取一张
          ),
        ],
      ),
      body: _buildBody(),
    );
  }

  Widget _buildBody() {
    if (_isLoading) {
      return const Center(child: CircularProgressIndicator());
    }

    if (_error != null) {
      return Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(Icons.error_outline, size: 64, color: Colors.red),
            const SizedBox(height: 16),
            Text('加载失败: $_error'),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: _fetchCat,
              child: const Text('重新获取'),
            ),
          ],
        ),
      );
    }

    if (_cat == null) {
      return const Center(child: Text('暂无猫咪图片'));
    }

    // ✅ 只显示一张图片,居中显示
    return SingleChildScrollView(
      child: Center(
        child: Padding(
          padding: const EdgeInsets.all(20),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              CatCard(cat: _cat!),
              const SizedBox(height: 20),
              // 添加一些操作按钮
              Wrap(
                spacing: 12,
                children: [
                  ElevatedButton.icon(
                    onPressed: _fetchCat,
                    icon: const Icon(Icons.refresh),
                    label: const Text('换一张'),
                  ),
                  OutlinedButton.icon(
                    onPressed: () {
                      // 可以添加保存或分享功能
                    },
                    icon: const Icon(Icons.download),
                    label: const Text('保存'),
                  ),
                ],
              ),
              const SizedBox(height: 20),
              // 显示详细信息
              Container(
                padding: const EdgeInsets.all(16),
                decoration: BoxDecoration(
                  color: Colors.grey[50],
                  borderRadius: BorderRadius.circular(12),
                ),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text(
                      '图片信息',
                      style: TextStyle(
                        fontSize: 16,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    const SizedBox(height: 8),
                    Text('ID: ${_cat!.id}'),
                    Text('宽度: ${_cat!.width} 像素'),
                    Text('高度: ${_cat!.height} 像素'),
                    Text('URL: ${_cat!.url}'),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

2.3.4 猫咪卡片组件

文件:lib/components/cat_card.dart
作用:专门显示猫咪图片的UI组件

cat_card.dart代码

import 'package:flutter/material.dart';
import 'package:qing_mall/viewmodels/cat_model.dart';

class CatCard extends StatelessWidget {
  final CatModel cat;

  const CatCard({
    super.key,
    required this.cat,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(12),
      ),
      elevation: 2,
      child: Column(
        children: [
          // 猫咪图片
          ClipRRect(
            borderRadius: const BorderRadius.vertical(
              top: Radius.circular(12),
            ),
            child: SizedBox(
              width: double.infinity,
              height: 200,
              child: Image.network(
                cat.url,
                fit: BoxFit.cover,
                loadingBuilder: (context, child, loadingProgress) {
                  if (loadingProgress == null) return child;
                  return Center(
                    child: CircularProgressIndicator(
                      value: loadingProgress.expectedTotalBytes != null
                          ? loadingProgress.cumulativeBytesLoaded /
                          loadingProgress.expectedTotalBytes!
                          : null,
                    ),
                  );
                },
                errorBuilder: (context, error, stackTrace) {
                  return Container(
                    color: Colors.grey[100],
                    child: const Center(
                      child: Icon(
                        Icons.image_not_supported,
                        size: 50,
                        color: Colors.grey,
                      ),
                    ),
                  );
                },
              ),
            ),
          ),
          // 猫咪信息
          Padding(
            padding: const EdgeInsets.all(12),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  '尺寸: ${cat.width} × ${cat.height}',
                  style: const TextStyle(
                    fontSize: 14,
                    fontWeight: FontWeight.w500,
                  ),
                ),
                const SizedBox(height: 4),
                Text(
                  'ID: ${cat.id}', // ✅ 修复:直接显示完整ID,不截取
                  style: TextStyle(
                    fontSize: 12,
                    color: Colors.grey[600],
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

2.3.5 路由配置文件

文件:lib/routes/index.dart
作用:管理页面之间的跳转关系

index.dart代码

import 'package:flutter/material.dart';
import 'package:qing_mall/pages/Login/index.dart';
import 'package:qing_mall/pages/Main/index.dart';
import 'package:qing_mall/pages/Cats/index.dart';

// 管理路由
import 'package:flutter/material.dart';
import 'package:qing_mall/pages/Login/index.dart';
import 'package:qing_mall/pages/Main/index.dart';
import 'package:qing_mall/pages/Cats/index.dart';

// 返回App根组件
Widget getRootWidget() {
  return MaterialApp(
    // 命名路由
    initialRoute: '/', // 默认首页
    routes: getRootRoutes(),
  );
}

// 返回该App的路由配置
Map<String, Widget Function(BuildContext)> getRootRoutes() {
  return {
    '/': (context) => MainPage(), // 主页路由
    '/login': (context) => LoginPage(), // 登录路由
    '/cats': (context) => CatsPage(), // 猫咪图库路由
  };
}

2.3.6 猫咪API常量配置

文件:lib/contents/api_constants.dart
作用:集中管理API配置,避免硬编码,便于统一维护。

api_constants.dart代码

class ApiConstants {
  // 猫咪API
  static const String catBaseUrl = 'https://api.thecatapi.com/v1';
  static const String catImagesEndpoint = '/images/search';

  // 默认配置
  static const int defaultPageSize = 10;
  static const int defaultTimeout = 15; // 秒
}

2.3.7 封装Dio网络请求

文件:lib/utils/http_util.dart
作用:Dio网络请求工具类,封装单例和错误处理

http_util.dart代码

import 'package:dio/dio.dart';

class HttpUtil {
  static final HttpUtil _instance = HttpUtil._internal();
  late Dio _dio;

  factory HttpUtil() => _instance;

  HttpUtil._internal() {
    _dio = Dio(BaseOptions(
      connectTimeout: const Duration(seconds: 15),
      receiveTimeout: const Duration(seconds: 15),
    ));
  }

  Dio get dio => _dio;

  // 通用GET请求
  Future<dynamic> get(String endpoint, {Map<String, dynamic>? queryParams}) async {
    try {
      final response = await _dio.get(endpoint, queryParameters: queryParams);
      return response.data;
    } on DioException catch (e) {
      throw _handleError(e);
    }
  }

  String _handleError(DioException e) {
    switch (e.type) {
      case DioExceptionType.connectionTimeout:
      case DioExceptionType.sendTimeout:
      case DioExceptionType.receiveTimeout:
        return '网络连接超时,请检查网络';
      case DioExceptionType.badResponse:
        return '服务器错误: ${e.response?.statusCode}';
      case DioExceptionType.cancel:
        return '请求已取消';
      default:
        return '网络错误: ${e.message}';
    }
  }
}

2.3.8 状态管理

文件:lib/stores/cat_store.dart

作用:负责获取、管理和更新猫咪图片数据

cat_store.dart代码

import 'package:flutter/material.dart';
import 'package:qing_mall/api/cat_api.dart';
import 'package:qing_mall/viewmodels/cat_model.dart';

class CatStore extends ChangeNotifier {
  List<CatModel> _cats = [];
  bool _isLoading = false;
  String? _error;
  int _currentPage = 1;

  List<CatModel> get cats => _cats;
  bool get isLoading => _isLoading;
  String? get error => _error;
  int get currentPage => _currentPage;

  // 获取猫咪图片
  Future<void> fetchCats({
    int limit = 10,
    bool hasBreeds = false,
    bool loadMore = false,
  }) async {
    if (!loadMore) {
      _currentPage = 1;
    }

    _isLoading = true;
    _error = null;
    notifyListeners();

    try {
      final newCats = await CatApi.getCatImages(
        limit: limit,
        page: _currentPage,
        hasBreeds: hasBreeds,
      );

      if (loadMore) {
        _cats.addAll(newCats);
      } else {
        _cats = newCats;
      }

      _currentPage++;
    } catch (e) {
      _error = e.toString();
    } finally {
      _isLoading = false;
      notifyListeners();
    }
  }

  // 刷新数据
  Future<void> refresh() async {
    await fetchCats(limit: _cats.length);
  }

  // 清空数据
  void clear() {
    _cats = [];
    _error = null;
    _currentPage = 1;
    notifyListeners();
  }
}

2.3.9 App入口主页

文件:lib/pages/Main/index.dart

作用:App的入口主页,用户从这里选择进入猫咪图库

index.dart代码

import 'package:flutter/material.dart';

class MainPage extends StatelessWidget {
  const MainPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Qing Mall'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              '欢迎使用 Qing Mall',
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 40),
            // 猫咪图库入口
            SizedBox(
              width: 200,
              child: ElevatedButton.icon(
                onPressed: () {
                  Navigator.pushNamed(context, '/cats');
                },
                icon: const Icon(Icons.pets),
                label: const Text('猫咪图库'),
                style: ElevatedButton.styleFrom(
                  padding: const EdgeInsets.symmetric(vertical: 16),
                ),
              ),
            ),
            const SizedBox(height: 16),
            // 登录入口
            SizedBox(
              width: 200,
              child: OutlinedButton(
                onPressed: () {
                  Navigator.pushNamed(context, '/login');
                },
                child: const Text('用户登录'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

2.3.10 登录页面

文件:lib/pages/Login/index.dart

作用:登录页面的空壳子,有页面结构但需要填充具体登录功能

index.dart代码

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class LoginPage extends StatefulWidget {
  const LoginPage({super.key});

  @override
  State<LoginPage> createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('登录'),),
      body: Center(child: Text('登录页面'),),
    );
  }
}

2.3.11 主程序入口文件

文件:lib/main.dart
作用:应用启动入口,初始化

main.dart代码

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:qing_mall/routes/index.dart';
import 'package:qing_mall/stores/cat_store.dart';

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => CatStore()),
        // 可以添加更多Provider
      ],
      child: getRootWidget(),
    ),
  );
}

2.4 运行项目

2.4.1 鸿蒙端

打开ohos的项目并且点击右上角启动项目即可。点击猫咪图库,即可看到可爱的小猫~

2.4.2 安卓端

选择任意一个安卓模拟器,打开后会显示出来。这里已经显示出来了Pixel 6(mobile),点击绿色三角形进行运行即可。程序运行完点击猫咪图库,即可看到小猫咪。

三、结语

本篇文章到此结束,感谢各位阅读。如果本篇文章对你有帮助,那就请点个赞吧。后续计划打算做一个商城项目,目前仍在学习,希望能早日做出并且发布在CSDN上帮助大家。

最后,欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐