flutter项目初始化

flutter项目初始化

0、flutter 官方插件网址

flutter官方链接
数据模型转换

注意:使用 flutter 的时候一定要把数据放在父组件中(主要是指Dio数据请求),往子组件传展示

1、flutter项目环境配置

1-1、下载 flutter sdk 地址
1-2、flutter sdk 配置环境变量
1-3、在pub.dev下载插件配置镜像快速拉取

 // 前面环境变量名字:后面是环境变量的值
 FLUTTER_STORAGE_BASE_URL: https://storage.flutter-io.cn
 PUB_HOSTED_URL: https://pub.flutter-io.cn

详细见 gitee地址

1-4、插件配置(vscode)
dart 、 flutter、Comment Translate、Awesome Flutter Snippets
1-5、 flutter doctor全部正确之后,创建好的项目还是报错 ,考虑是 gradle 下载太慢,可以 提前下载好 , 然后找到 android/gradle/wrapper/gradle-wrapper.properties 文件修改里面的 distributionUrl=file:///D:/BaiduNetdiskDownload/gradle-7.4-all.zip

android sdk 配置环境变量

在这里插入图片描述
环境变量配置完之后可能还会报错
在这里插入图片描述
上面的报错需要这样配置
在这里插入图片描述
继续报错
在这里插入图片描述
上面的报错有可能是java jdk版本8没有对应上
在这里插入图片描述

走提示的代码即可

2、flutter数据本地存储(shared_preferences)

1、dependencies:
shared_preferences: ^2.2.2

2、utils 包里面创建文件:
shared_preferences.dart

import 'dart:convert';

import 'package:shared_preferences/shared_preferences.dart';

class JSpUtil {
   
    JSpUtil._internal();
    factory JSpUtil() => _instance;
    static final JSpUtil _instance = JSpUtil._internal();


    static late SharedPreferences _preferences;
    static Future<JSpUtil> getInstance() async {
   
        _preferences = await SharedPreferences.getInstance();
        return _instance;
    }


    /// 通用设置持久化数据
    static setLocalStorage<T>(String key, T value) {
   
        String type = value.runtimeType.toString();

        switch (type) {
   
            case "String":
                _preferences.setString(key, value as String);
                break;
            case "int":
                _preferences.setInt(key, value as int);
                break;
            case "bool":
                _preferences.setBool(key, value as bool);
                break;
            case "double":
                _preferences.setDouble(key, value as double);
                break;
            case "List<String>":
                _preferences.setStringList(key, value as List<String>);
                break;
        }
    }

    /// 获取持久化数据
    static dynamic getLocalStorage<T>(String key) {
   
        dynamic value = _preferences.get(key);
        if (value.runtimeType.toString() == "String") {
   
            if (_isJson(value)) {
   
                return json.decode(value);
            }
        }
        return value;
    }



    /// 获取持久化数据中所有存入的key
    static Set<String> getKeys() {
   
        return _preferences.getKeys();
    }

    /// 获取持久化数据中是否包含某个key
    static bool containsKey(String key) {
   
        return _preferences.containsKey(key);
    }

    /// 删除持久化数据中某个key
    static Future<bool> remove(String key) async {
   
        return await _preferences.remove(key);
    }

    /// 清除所有持久化数据
    static Future<bool> clear() async {
   
        return await _preferences.clear();
    }

    /// 重新加载所有数据,仅重载运行时
    static Future<void> reload() async {
   
        return await _preferences.reload();
    }

    /// 判断是否是json字符串
    static _isJson(String value) {
   
        try {
   
            const JsonDecoder().convert(value);
            return true;
        } catch(e) {
   
            return false;
        }
    }   
}

3、入口函数加上两句话,不然报错

void main() async {
   
    // 下面两句是持久化数据用的
    WidgetsFlutterBinding.ensureInitialized();
    await JSpUtil.getInstance();
    
    runApp(const ApplicationApp());
} 

4、使用

// 1、取值
JSpUtil.getLocalStorage('token');

// 2、存值
JSpUtil.setLocalStorage("token", "需要存的值");

3、Dio 封装使用详解

1、安装 Dio

dio: ^4.0.0

2、封装 Dio

// 在 utils 包里面创建 http 目录
// 然后创建四个文件


// 1、 http_request.dart 核心文件
import 'package:dio/dio.dart';
import 'package:hook_up_rant/utils/http/http_interceptor.dart';
import 'package:hook_up_rant/utils/http/http_options.dart';

// http 请求单例类
class HttpRequest {
   
    // 工厂构造方法
    factory HttpRequest() => _instance;
    // 初始化一个单例实例
    static final HttpRequest _instance = HttpRequest._internal();
    // dio 实例
    Dio? dio;
    // 内部构造方法
    HttpRequest._internal() {
   
        if (dio == null) {
   
            // BaseOptions、Options、RequestOptions 都可以配置参数,优先级别依次递增,且可以根据优先级别覆盖参数
            BaseOptions baseOptions = BaseOptions(
                baseUrl: HttpOptions.BASE_URL,
                connectTimeout: HttpOptions.CONNECT_TIMEOUT,
                receiveTimeout: HttpOptions.RECEIVE_TIMEOUT,
                headers: {
   },
            );
            // 没有实例 则创建之
            dio = Dio(baseOptions);
            // 添加拦截器
            dio!.interceptors.add(HttpInterceptor());

            // 打印日志
            // dio!.interceptors.add(LogInterceptor(responseBody: true));
        }
    }

    /// 初始化公共属性 如果需要覆盖原配置项 就调用它
    ///
    /// [baseUrl] 地址前缀
    /// [connectTimeout] 连接超时赶时间
    /// [receiveTimeout] 接收超时赶时间
    /// [headers] 请求头
    /// [interceptors] 基础拦截器
    // void init({ String? baseUrl, int? connectTimeout, int? receiveTimeout, Map<String, dynamic>? headers, List<Interceptor>? interceptors }) {
   
    //     print("许潇111 --- $baseUrl  --   $connectTimeout  ---   $receiveTimeout  --- $headers  --  $interceptors");
    //     dio!.options.baseUrl = baseUrl ?? "";
    //     dio!.options.connectTimeout = connectTimeout ?? 10000;
    //     dio!.options.receiveTimeout = receiveTimeout ?? 10000;
    //     dio!.options.headers = headers;
    //     if (interceptors != null && interceptors.isNotEmpty) {
   
    //         dio!.interceptors.addAll(interceptors);
    //     }
    // }

    /// 设置请求头
    void setHeaders(Map<String, dynamic> headers) {
   
        dio!.options.headers.addAll(headers);
    }

    final CancelToken _cancelToken = CancelToken();
    /*
    * 取消请求
    *
    * 同一个cancel token 可以用于多个请求
    * 当一个cancel token取消时,所有使用该cancel token的请求都会被取消。
    * 所以参数可选
    */
    void cancelRequests({
   CancelToken? token}) {
   
        token ?? _cancelToken.cancel("cancelled");
    }

    /// GET
    Future get(String path, {
   Map<String, dynamic>? params, Options? options, CancelToken? cancelToken, ProgressCallback? onReceiveProgress}) async {
   
        

        Response response = await dio!.get(
            path,
            queryParameters: params,
            options: options,
            cancelToken: cancelToken ?? _cancelToken,
            onReceiveProgress: onReceiveProgress
        );
        return response.data;
    }

    /// POST
    Future post(String path, {
    Map<String, dynamic>? params, Options? options, CancelToken? cancelToken, ProgressCallback? onSendProgress, ProgressCallback? onReceiveProgress}) async {
   
        
        Response response = await dio!.post(
            path,
            data: params,
            options: options,
            cancelToken: cancelToken ?? _cancelToken,
            onSendProgress: onSendProgress,
            onReceiveProgress: onReceiveProgress
        );
        return response.data;
    }

    /// 文件上传
    Future postFormData(String path, {
    Map<String, dynamic>? params, Options? options, CancelToken? cancelToken, ProgressCallback? onSendProgress, ProgressCallback? onReceiveProgress}) async {
   
        var option = Options(
            // 上传文件设置请求头
            contentType: "multipart/form-data",
        );
        Response response = await dio!.post(
            path,
            data: params,
            options: option,
            cancelToken: cancelToken ?? _cancelToken,
            onSendProgress: onSendProgress,
            onReceiveProgress: onReceiveProgress
        );
        return response.data;
    }
}


// 2、http_options.dart
// 请求配置
class HttpOptions {
   
    // 连接服务器超时时间,单位是毫秒 20S 响应超时
    static const int CONNECT_TIMEOUT = 20000;
    // 接收超时时间,单位是毫秒 20S 响应超时
    static const int RECEIVE_TIMEOUT = 20000;
    // 地址前缀
    // static const String BASE_URL = 'http://dev.duuchin.com';
    static const String BASE_URL = 'http://192.168.1.114:8888';
}


// 3、http_interceptor.dart 拦截器
import 'dart:convert';

import 'package:dio/dio.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:hook_up_rant/store/index.dart';
import 'package:hook_up_rant/utils/http/http_exception.dart';
import 'package:hook_up_rant/utils/showLoading.dart';

// 自定义拦截器
class HttpInterceptor extends Interceptor {
   
    // 请求拦截
    
    void onRequest(RequestOptions options,RequestInterceptorHandler handler) {
   
        super.onRequest(options, handler);

        EasyLoading.show(status: "加载中...");

        // 从登录的持久换缓存中取出来
        String token = StoreIndex.token.value; 
        if(token != "null" && token != "") {
   
            options.headers["token"] = token;
        }
    }

    // 响应拦截
    
    Future onResponse( Response response, ResponseInterceptorHandler handler) async {
   
        var res = json.decode(response.toString());
        // success不为 true 表示响应不成功,后台报错,就不 super 往父级传了
        if(!res['success']) {
   
            EasyLoadings.showToast(res['resultMessage']);
            return;
        }

        EasyLoading.dismiss();

        super.onResponse(response, handler);
    }

    // 异常拦截
    
    Future onError( DioError err, ErrorInterceptorHandler handler) async {
   
        // 覆盖异常为自定义的异常类
        HttpException httpException = HttpException.create(err);
        err.error = httpException;

        EasyLoading.dismiss();

        super.onError(err, handler);
    }
}

// 4、http_exception.dart 异常处理文件
import 'package:dio/dio.dart';
import 'package:hook_up_rant/utils/showLoading.dart';

// 自定义 http 异常
class HttpException implements Exception {
   
    final int code;
    final String msg;

    HttpException({
   this.code = 500, this.msg = '未知异常,请联系管理员'});

    
    String toString() {
   
        return "HttpError [$code]: $msg";
    }

    factory HttpException.create(DioError error) {
   
        // dio 异常
        switch (error.type) {
   
            case DioErrorType.cancel:
                EasyLoadings.showError("请求取消");
                return HttpException(code: -1, msg: '请求取消');
            case DioErrorType.connectTimeout:
            EasyLoadings.showError("连接超时");
                return HttpException(code: -1, msg: '连接超时');
            case DioErrorType.sendTimeout:
                EasyLoadings.showError("请求超时");
                return HttpException(code: -1, msg: '请求超时');
            case DioErrorType.receiveTimeout:
                EasyLoadings.showError("响应超时");
                return HttpException(code: -1, msg: '响应超时');
            case DioErrorType.response:
                // 服务器异常
                int statusCode = error.response!.statusCode ?? 0;
                switch (statusCode) {
   
                case 400:
                    EasyLoadings.showError("请求语法错误");
                    return HttpException(code: statusCode, msg: '请求语法错误');
                case 401:
                    EasyLoadings.showError("没有权限");
                    return HttpException(code: statusCode, msg: '没有权限');
                case 403:
                    EasyLoadings.showError("服务器拒绝执行");
                    return HttpException(code: statusCode, msg: '服务器拒绝执行');
                case 404:
                    EasyLoadings.showError("无法连接服务器");
                    return HttpException(code: statusCode, msg: '无法连接服务器');
                case 500:
                    EasyLoadings.showError("服务器内部错误");
                    return HttpException(code: statusCode, msg: '服务器内部错误');
                case 502:
                    EasyLoadings.showError("无效的请求");
                    return HttpException(code: statusCode, msg: '无效的请求');
                case 503:
                    EasyLoadings.showError("服务器挂了");
                    return HttpException(code: statusCode, msg: '服务器挂了');
                case 505:
                    EasyLoadings.showError("不支持HTTP协议请求");
                    return HttpException(code: statusCode, msg: '不支持HTTP协议请求');
                default:
                    EasyLoadings.showError("服务器异常");
                    return HttpException(
                        code: statusCode,
                        msg: error.response?.statusMessage ?? "服务器异常",
                    );
                }
            default:
                EasyLoadings.showError("服务器异常");
                return HttpException(code: 500, msg: error.message);
        }
    }
}

3、创建Api类,index.dart

import 'package:hook_up_rant/utils/http/http_request.dart';

class Api {
   

    // 注册
    static Future register(params) async {
   
        return await HttpRequest().post("/hookUpRant/register", params: params);
    }

    // 登录
    static Future login(params) async {
   
        return await HttpRequest().post("/hookUpRant/login", params: params);
    }

    // 根据token获取用户信息
    static Future userInfo() async {
   
        return await HttpRequest().get("/hookUpRant/userInfoByUserName");
    }
}

4、组件中使用

// UserInfo.fromJson(res['result']) 是通过网站 https://app.quicktype.io/ 快速生成的,将后台返回的json数据转成dart数据模型
_initData() async {
   
	// 走上一步创建的接口
	var res = await Api.userInfo({
   name: '张三'});
    setState(() {
   
        userInfo = UserInfo.fromJson(res['result']);
    });
}

5、文件上传(没有使用上面的统一封装)

/**
*  文件上传(单个文件)
*/
  // 封装FormData
  import 'package:dio/dio.dart' as form_data_name;  // FormData 和其他的报名重复, 所以重新名了一个名字叫 form_data_name
  form_data_name.FormData formData = form_data_name.FormData.fromMap({
   
     // 其它的key value 值
     "key": "value",
     // 后端接的 key 是 file,   其中 File.path 是imagePicker插件获取到的文件路径 File 对象的 path 属性
     'file': await form_data_name.MultipartFile.fromFile(File.path, filename: "image1.jpg"),
  });
  // 开始上传
 try{
   
    var result = await Dio().post("${
     HttpOptions.BASE_URL}/hookUpRant/uploadPicture", data: formData,  onSendProgress: (int progress, int total) {
   
                                                                                                                                            print("当前进度是 $progress 总进度是 $total");
                                                                                                                                        });
	   final resultMap = json.decode(result.toString());
	   if(resultMap['success']) {
   
	       UploadResult uploadResult = UploadResult.fromJson(resultMap['result']);
	       print(uploadResult.filepath);
	   }else{
   
	       EasyLoadings.showError("上传失败");
	   }
 }catch(err) {
   
	   EasyLoadings.showError("服务器异常");
 }
/**
* 多文件上传
*/
final formData = FormData.fromMap({
   
   'files': [
	    MultipartFile.fromFileSync('path/to/upload1.txt', filename: 'upload1.txt'),
	    await MultipartFile.fromFile('path/to/upload2.txt', filename: 'upload2.txt'),
    ],
});

6、文件下载

 //当前进度进度百分比  当前进度/总进度 从0-1
 double currentProgress =0.0;
 ///下载文件的网络路径
 String apkUrl ="https://www.baidu1.com/test.apk";
 ///使用dio 下载文件
 
 /// 申请写文件权限
 bool isPermiss =  await checkPermissFunction();  // 获取权限放在见下面
 if(isPermiss) {
   
     ///手机储存目录
     String savePath = await getPhoneLocalPath();
     String appName = "test.apk";  // 下载的文件名字

     ///创建DIO
     Dio dio = new Dio();

     ///参数一 文件的网络储存URL
     ///参数二 下载的本地目录文件
     ///参数三 下载监听
     Response response = await dio.download(
         apkUrl, 
         "$savePath$appName",
         onReceiveProgress: (received, total) {
   
             if (total != -1) {
   
                 ///当前下载的百分比例
                 print((received / total * 100).toStringAsFixed(0) + "%");
                 // CircularProgressIndicator(value: currentProgress,) 进度 0-1
                 currentProgress = received / total;
                 setState(() {
   });
             }
         }
     );

 }else{
   
     ///提示用户请同意权限申请
 }

文件下载需要用到手里目录的访权限


Android权限目前分为三种:正常权限、危险权限、特殊权限

正常权限 直接在AndroidManifest中配置即可获得的权限。大部分权限都归于此。 危险权限,Android 6.0之后将部分权限定义于此。 危险权限不仅需要需要在AndroidManifest中配置,还需要在使用前check是否真正拥有权限,以动态申请。
在ios中,使用xcode打开本目录

选中Xcode 工程中的 info.plist文件,右键选择Open As - Source Code,将权限配置的代码copy到里面即可,键值对中的内容可按项目需求相应修改。

ios的权限设置

// ios > Runner >Info.plist
<!-- 相册 --> 
<key>NSPhotoLibraryUsageDescription</key> 
<string>需要您的同意,APP才能访问相册</string> 
<!-- 相机 --> 
<key>NSCameraUsageDescription</key> 
<string>需要您的同意,APP才能访问相机</string> 
<!-- 麦克风 --> 
<key>NSMicrophoneUsageDescription</key> 
<string>需要您的同意,APP才能访问麦克风</string> 
<!-- 位置 --> 
<key>NSLocationUsageDescription</key> 
<string>需要您的同意, APP才能访问位置</string> 
<!-- 在使用期间访问位置 --> 
<key>NSLocationWhenInUseUsageDescription</key> 
<string>App需要您的同意, APP才能在使用期间访问位置</string> 
<!-- 始终访问位置 --> 
<key>NSLocationAlwaysUsageDescription</key> 
<string>App需要您的同意, APP才能始终访问位置</string> 
<!-- 日历 --> 
<key>NSCalendarsUsageDescription</key> 
<string>App需要您的同意, APP才能访问日历</string> 
<!-- 提醒事项 --> 
<key>NSRemindersUsageDescription</key> 
<string>需要您的同意, APP才能访问提醒事项</string> 
<!-- 运动与健身 --> 
<key>NSMotionUsageDescription</key> 
<string>需要您的同意, APP才能访问运动与健身</string> 
<!-- 健康更新 --> 
<key>NSHealthUpdateUsageDescription</key> 
<string>需要您的同意, APP才能访问健康更新 </string> 
<!-- 健康分享 --> 
<key>NSHealthShareUsageDescription</key> 
<string>需要您的同意, APP才能访问健康分享</string> 
<!-- 蓝牙 --> 
<key>NSBluetoothPeripheralUsageDescription</key> 
<string>需要您的同意, APP才能访问蓝牙</string> 
<!-- 媒体资料库 --> 
<key>NSAppleMusicUsageDescription</key> 
<string>需要您的同意, APP才能访问媒体资料库</string>

安卓设置权限

//在这里使用的是 permission_handler 插件来申请权限的
//flutter sdk 3.16.9
permission_handler: ^10.2.0

// 申请权限代码如下
Future<bool> _checkPermission() async {
   
     // 先对所在平台进行判断
     if (Theme.of(context).platform == TargetPlatform.android) {
   
         PermissionStatus permission = await Permission.storage.request();
         if (permission != PermissionStatus.granted) {
     // 没有获得权限
             openAppSettings();  // 固定api,调起授权窗口让用户授权
         } else {
   
             return true;
         }
     } else {
   
         return true;
     }
     return false;
 }
// 获取路径,用于存储文件
Future<String> _findLocalPath() async {
   
    // ignore: use_build_context_synchronously
    final directory = Theme.of(context).platform == TargetPlatform.android ? await getExternalStorageDirectory() : await getApplicationSupportDirectory();
    return directory!.path;
}

// 可以自己在上面获取的路径上在创建新的目录
Future<String> _getStoragePath() async {
   
     // 获取存储路径
     var localPath = '${
     await _findLocalPath()}/Download';

     final savedDir = Directory(localPath);
     // 判断下载路径是否存在
     bool hasExisted = await savedDir.exists();
     // 不存在就新建路径
     if (!hasExisted) {
   
         savedDir.create();
     }
     return localPath;
 }

到此,走上面第六步下载文件即可

4、EasyLoading的使用

1、 安装 flutter_easyloading: ^3.0.5

2、在 MaterialApp 配置

MaterialApp(
   builder: EasyLoading.init(),
);

3、使用

// 显示指示器
 EasyLoading.show(
   status: '加载中', // 要显示的文字
 );
 //  延时2秒
 Future.delayed(const Duration(seconds: 2), () {
   
   //  关闭指示器
   EasyLoading.dismiss();
 });

在这里插入图片描述

4、其它的使用方式

// 1、showToast
EasyLoading.showToast("这是一个Toast");

// 2、showInfo
EasyLoading.showInfo("这是一个Info");

// 3、showError
EasyLoading.showError("这是一个Error");

// 4、showSuccess
 EasyLoading.showSuccess("这是一个success");

5、修改样式

// 设置 EasyLoading 样式,由于 EasyLoading 是全局的, 所以在什么地方修改下面的样式都生效
void main() async {
   
    // 设置 EasyLoading 样式
    EasyLoading.instance
   		 ..indicatorType = EasyLoadingIndicatorType.circle   // 加载进度的样式( 只适用于 EasyLoading.show() )
    	 ..userInteractions = true                           // 是否使用单例
    	 ..maskType = EasyLoadingMaskType.black              // 遮罩层
    	 ..dismissOnTap = false;
    	 
    runApp(const ApplicationApp());
}

详细其它的样式修改可查看 : https://pub-web.flutter-io.cn/packages/flutter_easyloading

5、缓存 keep-alive使用(除了Tabbar,其它的页面、组件都可以使用)

在utils包里面创建 keep-alive.dart 文件

import 'package:flutter/material.dart';

class 
  • 20
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值