Flutter创建

1.安装开发环境

1.到官网下载Flutter的SDK包下载地址

2.将包解压到自己定义的目录

3.在环境变量里添加包的地址

4.在终端测试是否安装成功

注意:验证安装是否成功时,会自动下载相关依赖内容,但要配置镜像,否则很慢

复制代码
// 配置镜像
set PUB_HOSTED_URL=https://pub.flutter-io.cn
set FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn

// 检查安装
flutter doctor

5.下载开发工具

我这里用的是vs code,安装插件:

  • Dart:支持Dart语言
  • Flutter:支持Flutter语言
  • Flutter Widget Snippets:提供快捷键方法

2.创建项目

1.创建项目,具体还有一些其他命令,可参考官网

复制代码
flutter create lian_flutter

2.初始化项目结构

  • 在lib目录里创建相关目录

    api:接口
    assets:静态资源
    components:公共组件
    contants:常量文件
    page:页面
    routes:路由
    stores:全局状态
    utils:工具类

3.启动项目

  • 在main.dart目录里启动项目

  • 使用命令符启动

    flutter run : 默认启动,会让指定启动设备
    flutter run -d edge:指定IE浏览器启动

3.封装路由

1.在main.dart里包裹路由部件

复制代码
Future<void> main(List<String> args) async {
  runApp(getRootwidget());
}

2.封装路由部件,将编写的部件引入

复制代码
Widget getRootwidget() {
  final RouteGuard _routeGuard = RouteGuard();
  
  return MaterialApp(
    initialRoute: "/", // 默认主页
    navigatorObservers: [_routeGuard],// 全局路由
    routes: getRootRoutes(),
  );
}

Map<String, Widget Function(BuildContext)> getRootRoutes() {
  return {
    "/": (contex) => MainPage(), 
    "/login": (contex) => LoginPage(),
    "/detail":(contex)=> DetailPage()
  };
}

3.封装路由守卫,根据需求修改内容

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

class RouteGuard extends NavigatorObserver {
  // ========== 类似 Vue 的 beforeEach ==========
  @override
  void didPush(Route route, Route? previousRoute) {
    super.didPush(route, previousRoute);
    _handleRouteChange(route, 'push');
  }

  @override
  void didPop(Route route, Route? previousRoute) {
    super.didPop(route, previousRoute);
    print('离开: ${route.settings.name}');
  }

  @override
  void didRemove(Route route, Route? previousRoute) {
    super.didRemove(route, previousRoute);
    print('移除: ${route.settings.name}');
  }

  @override
  void didReplace({Route? newRoute, Route? oldRoute}) {
    super.didReplace(newRoute: newRoute, oldRoute: oldRoute);
    print('替换: ${oldRoute?.settings.name} -> ${newRoute?.settings.name}');
  }

  // ========== 路由变化处理 ==========
  void _handleRouteChange(Route route, String action) {
    final routeName = route.settings.name ?? 'unknown';
    print('[$action] 进入路由: $routeName');

    // 权限验证
    _checkAuth(route);
  }

  // ========== 权限验证 ==========
  void _checkAuth(Route route) {
    final routeName = route.settings.name;
    
    // 需要登录才能访问的路由列表
    final authRoutes = ['/cart',"/my"];
    
    if (authRoutes.contains(routeName)) {
      // 检查是否登录(假设用共享偏好或状态管理)
      final isLoggedIn = false; // 从状态管理获取
      
      if (!isLoggedIn) {
        print('未登录,跳转到登录页');
        // 这里不能直接跳转,需要通过 Navigator 的上下文
        // 实际使用中需要获取 context
      }
    }
  }
}

4.封装Dio请求

  • Dio依赖是Flutter提供的http请求

  • 安装依赖

    flutter pub add dio

  • 封装请求

    // 需要在main.dart里进行初始化缓存和Http
    import 'package:dio/dio.dart';
    import 'package:get/get.dart';
    import 'package:lian_flutter/page/Login/index.dart';
    import 'package:lian_flutter/stores/TokenManager.dart';
    import 'package:shared_preferences/shared_preferences.dart';

    class Http {
    static final Dio _dio = Dio();

    复制代码
    // 统一错误处理
    static String _handleError(DioException error) {
      switch (error.type) {
        case DioExceptionType.connectionTimeout:
          return '网络连接超时,请检查网络';
        case DioExceptionType.receiveTimeout:
          return '服务器响应超时,请稍后重试';
        case DioExceptionType.sendTimeout:
          return '数据发送超时,请检查网络';
        case DioExceptionType.connectionError:
          return '网络连接失败,请检查网络设置';
        case DioExceptionType.cancel:
          return '请求已取消';
        case DioExceptionType.badResponse:
          final statusCode = error.response?.statusCode;
          switch (statusCode) {
            case 400:
              return '请求参数错误';
            case 401:
              return '未授权,请重新登录';
            case 403:
              return '没有权限访问';
            case 404:
              return '接口不存在';
            case 500:
              return '服务器内部错误';
            default:
              return '服务器错误 ($statusCode)';
          }
        default:
          return error.message ?? '网络请求失败';
      }
    }
    
    // 初始化配置
    static void init() {
      _dio.options.baseUrl = 'https://meikou-api.itheima.net'; // 配置基地址
      _dio.options.connectTimeout = const Duration(seconds: 10); // 设置请求超时时间
      _dio.options.receiveTimeout = const Duration(seconds: 10); // 设置响应超时时间
    
      /**
       * 不设置 contentType,Dio 默认会根据 data 自动判断
       * application/json	JSON 格式	{"name":"张三","age":18}	✅ 最常用,RESTful API
       * application/x-www-form-urlencoded	表单格式	name=张三&age=18	传统表单提交
       * multipart/form-data	文件上传格式	二进制数据	上传图片/文件
       */
      // _dio.options.contentType = "application/x-www-form-urlencoded";
    
      // 拦截器
      _dio.interceptors.add(
        InterceptorsWrapper(
          // 请求拦截
          onRequest: (request, handler) {
            // 获取token
            final token = tokenManager.getToken();
            if (token.isNotEmpty) {
              request.headers['Authorization'] = 'Bearer $token';
            }
            print('请求: ${request.method} ${request.path}');
    
            // 下一步
            handler.next(request);
          },
    
          // 响应拦截
          onResponse: (response, handler) {
            print('响应: ${response.statusCode}');
            final data = response.data;
            if (data is Map && data.containsKey('code')) {
              if (data['code'] == "1") {
                // 具体看后端如何定义成功code和数据类型
                response.data = data['result'];
              } else {
                // 业务错误抛出异常
                throw DioException(
                  error: data['msg'] ?? '业务错误',
                  requestOptions: response.requestOptions,
                );
              }
            }
            handler.next(response);
          },
    
          // 错误拦截
          onError: (error, handler) {
            // 获取错误内容
            final errorMsg = _handleError(error);
            print('错误: ${error.message}');
    
            // 特殊处理,401自动跳转登录
            if (error.response?.statusCode == 401) {
              // 清除token,跳转登录页
              tokenManager.removeToken();
              // 这里用你的路由跳转方式
              Get.to(() => LoginPage());
            }
    
            // 构造新错误
            final handledError = DioException(
              error: errorMsg,
              requestOptions: error.requestOptions,
              response: error.response,
              type: error.type,
            );
    
            handler.next(handledError);
          },
        ),
      );
    }
    
    // 封装get方法
    static Future get(String url, {Map<String, dynamic>? params}) async {
      try {
        final response = await _dio.get(url, queryParameters: params);
        return response.data;
      } on DioException {
        // 直接抛出,因为已经在 onError 中处理过了
        rethrow;
      } catch (e) {
        // 其他未知错误
        throw '未知错误: $e';
      }
    }
    
    // 封装post方法
    static Future post(String url, {Map<String, dynamic>? data}) async {
      try {
        final response = await _dio.post(url, data: data);
        return response.data;
      } on DioException {
        rethrow;
      } catch (e) {
        throw '未知错误: $e';
      }
    }

    }

  • 在main.dart里初始化

    Future main(List args) async {
    // 初始化Flutter引擎,否则下面内容报错
    WidgetsFlutterBinding.ensureInitialized();

    复制代码
    // 初始化Dio
    Http.init();
    
    runApp(getRootwidget());

    }

  • 定义接口方法

    import 'package:lian_flutter/api/Http.dart';
    import 'package:lian_flutter/viewmodels/Category.dart';
    import 'package:lian_flutter/viewmodels/user.dart';

    // 调用方法不同
    // Api.getCarouselAPI()调用,是静态方法
    class Api {
    // 获取轮播图
    static Future<List> getCarouselAPI() async {
    final result = await Http.get('/home/banner');

    复制代码
      // ✅ 将 List<dynamic> 转换为 List<BannerItem>
      if (result is List) {
        return BannerItem.fromList(result);
      }
    
      return [];
    }

    }

    // 登录,可以直接引入,然后调用loginAPI()
    Future loginAPI(Map<String, dynamic> data) async {
    return UserInfo.fromJSON(await Http.post('/login', data: data));
    }

    // 获取用户信息
    Future getUserInfoAPI() async {
    return UserInfo.fromJSON(await Http.get("/member/profile"));
    }

  • 使用接口

    调用class里的接口
    void _getBannderList() async {
    _bannerItem = await Api.getCarouselAPI();
    setState(() {});
    }

    调用方法api
    _initUser() async {
    await tokenManager.init();
    var token = tokenManager.getToken();
    if (token.isNotEmpty) {
    final res = await getUserInfoAPI();
    // 登录成功后更新共享数据
    _userController.updateUserInfo(res);
    }
    }

5.封装全局属性GetX和缓存shared_preferences

1.安装依赖

复制代码
// 安装GetX
flutter pub add get

// 安装缓存
flutter pub add shared_preferences

2.Getx封装

  • 封装通用数据方法类UserController

    class UserController extends GetxController {
    var user = UserInfo.fromJSON({}).obs;// user对象监听,要给初始值结尾加.obs
    // 获取值要到user.value里取值

    复制代码
    // 更新数据对象
    updateUserInfo(UserInfo newUser){
      user.value = newUser;
    }
    
    // 清空数据
    logout(){
      user.value = UserInfo.fromJSON({});
    }

    }

  • 在main.dart里初始化GetX

    Future main(List args) async {
    // 初始化Flutter引擎,否则下面内容报错
    WidgetsFlutterBinding.ensureInitialized();

    复制代码
    // 初始化GetX
    Get.put(UserController());
    
    runApp(getRootwidget());

    }

  • 存放全局数据

    复制代码
      // 1.定义find实列
      final UserController _userController = Get.find();
      
      // 2.调用定义的更新方法,将最新数据存入
      _userController.updateUserInfo(res);
  • 使用全局数据

    复制代码
      // 1.定义find实列
      final UserController _userController = Get.find();
      
      // 获取共享数据内容
      _userController.user.value.id

3.封装缓存数据shared_preferences

  • 封装缓存数据类TokenManager

    class TokenManager {
    late SharedPreferences _prefs;
    String _token = '';

    复制代码
    // 初始化token
    Future<void> init() async {
      // 1.获取持久化实例,里面有一系列方法
      _prefs = await SharedPreferences.getInstance();
      // 2.设置token
      _token = _prefs.getString("set_token") ?? '';
    }
    
    // 设置token
    Future<void> setToken(String val) async {
      _prefs.setString("set_token", val);
      _token = val;
    }
    
    // 获取token,要用同步方法,否则会报异常,所有在初始化里调用并赋值
    String getToken() {
      return _token;
    }
    
    // 删除token
    Future<void> removeToken() async {
      _prefs.remove("set_token");// 删除磁盘缓存
      _token = '';// 删除内存缓存
    }

    }

    final tokenManager = TokenManager();// 实例化

  • 在main.dart里初始化缓存

    Future main(List args) async {
    // 初始化Flutter引擎,否则下面内容报错
    WidgetsFlutterBinding.ensureInitialized();

    复制代码
    // 初始化持久化
    await tokenManager.init();
    
    runApp(getRootwidget());

    }

  • 设置缓存数据,调用缓存方法

    tokenManager.setToken(res.token);

  • 使用缓存数据

    tokenManager.getToken()

相关推荐
Privasa-隐私实验室44 分钟前
跨端技术选型|Flutter搭建隐私加密App,安卓与iOS双端差异适配实战
android·笔记·安全·flutter·ios·隐私安全·aes-256
Flutter OH11 小时前
Flutter OH 外接纹理问题定位指南
flutter
未来猫咪花15 小时前
Everything is ViewModel:让状态管理回到对象世界
android·flutter·ios
Flutter OH16 小时前
Flutter OH 日志抓取与过滤指南
flutter
Flutter OH16 小时前
Flutter OH 内存与 GPU 问题定位指南
flutter
Flutter OH17 小时前
Flutter OH 多设备适配问题定位指南
flutter
律宏阔18 小时前
Flutter Hooks 与 flutter_map_animations 冲突:第二次地图动画报错的解决方案
flutter
Flutter OH1 天前
Flutter OH 卡死冻屏问题定位指南
flutter·命令模式
2501_916007471 天前
Flutter与游戏App加固避坑指南:如何为混合开发与高交互应用选对安全方案?
安全·flutter·游戏·ios·小程序·uni-app·iphone