flutter_bloc 使用解析

flutter_bloc 使用将从下图的三个维度说明

初始化代码

来看下这三个生成的 bloc 文件:main_bloc,main_event,main_state

main_bloc:这里就是咱们主要写逻辑的页面了mapEventToState 方法只有一个参数,后面自动带了一个逗号,格式化代码就分三行了,建议删掉逗号,格式化代码。

class MainBloc extends Bloc<MainEvent, MainState> {
  MainBloc() : super(MainInitial());

  @override
  Stream<MainState> mapEventToState(
    MainEvent event,
  ) async* {
    // TODO: implement mapEventToState
  }
}

main_event:这里是执行的各类事件,有点类似 fish_redux 的 action 层

@immutable
abstract class MainEvent {}

main_state:状态数据放在这里保存,中转

@immutable
abstract class MainState {}

class MainInitial extends MainState {}

实现

主入口

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MainPage(),
    );
  }
}

说明

这里对于简单的页面,state 的使用抽象状态继承实现的方式,未免有点麻烦,这里我进行一点小改动,state 的实现类别有很多,官网写 demo 也有不用抽象类,直接 class,类似实体类的方式开搞的。

相关代码的注释写的比较多,大家可以着重看看

main_bloc
state 变量是框架内部定义的,会默认保存上一次同步的 MainSate 对象的值

class MainBloc extends Bloc<MainEvent, MainState> {
  MainBloc() : super(MainState(selectedIndex: 0, isExtended: false));

  @override
  Stream<MainState> mapEventToState(MainEvent event) async* {
    ///main_view中添加的事件,会在此处回调,此处处理完数据,将数据yield,BlocBuilder就会刷新组件
    if (event is SwitchTabEvent) {
      ///获取到event事件传递过来的值,咱们拿到这值塞进MainState中
      ///直接在state上改变内部的值,然后yield,只能触发一次BlocBuilder,它内部会比较上次MainState对象,如果相同,就不build
      yield MainState()
        ..selectedIndex = event.selectedIndex
        ..isExtended = state.isExtended;
    } else if (event is IsExtendEvent) {
      yield MainState()
        ..selectedIndex = state.selectedIndex
        ..isExtended = !state.isExtended;
    }
  }
}

全局 Bloc

说明

什么是全局 Bloc?

BlocProvider 介绍里面有这样的形容:BlocProvider should be used to create new blocs which will be made available to the rest of the subtree(BlocProvider 应该被用于创建新的 Bloc,这些 Bloc 将可用于其子树)

这样的话,我们只需要在主入口地方使用 BlocProvider 创建 Bloc,就能使用全局的 XxxBloc 了,这里的全局 XxxBloc,state 状态都会被保存的,除非关闭 app,否则 state 里面的数据都不会被还原!

注意:在主入口创建的 XxxBloc,在主入口处创建了一次,在其它页面均不需要再次创建,在任何页面只需要使用 BlocBuilder,便可以定点刷新及其获取全局 XxxBloc 的 state 数据

使用场景

全局的主题色,字体样式和大小等等全局配置更改;这种情况,在需要全局属性的地方,使用 BlocBuilder 对应的全局 XxxBloc 泛型去刷新数据就行了

跨页面去调用事件,既然是全局的 XxxBloc,这就说明,我们可以在任何页面,使用 BlocProvider.of(context)调用全局 XxxBloc 中事件,这就起到了一种跨页面调用事件的效果

使用全局 Bloc 做跨页面事件时,应该明白,当你关闭 Bloc 对应的页面,对应全局 Bloc 中的并不会被回收,下次进入页面,页面的数据还是上次退出页面修改的数据,这里应该使用 StatefulWidget,在 initState 生命周期处,初始化数据;或者在 dispose 生命周期处,还原数据源

思考下:全局 Bloc 对象存在周期是在整个 App 存活周期,必然不能创建过多的全局 Bloc,跨页面传递事件使用全局 Bloc 应当只能做折中方案

06/22/2022 08:54 上午 posted in  Flutter

对于Flutter中BLoC架构的几个实例代码

flutter_bloc 使用将从下图的三个维度说明

MultiBlocProvider的使用


class HomeWidget extends StatelessWidget {
  const HomeWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(providers: [
      BlocProvider<HomeBloc>(
        create: (context) => HomeBloc(),
      ),
      BlocProvider<HomeTreatmentCubit>(
        create: (context) => HomeTreatmentCubit(),
      ),
      BlocProvider<HomeOralInspectionCubit>(
        create: (context) => HomeOralInspectionCubit(),
      ),
      BlocProvider<HomeSmileSolutionCubit>(
        create: (context) => HomeSmileSolutionCubit(),
      ),
    ], child: const HomePage());
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
  @override
  void initState() {
    super.initState();
  }

  // _onRefresh 下拉刷新回调
  Future _onRefresh() async {
    Log.d("HomePage execu refresh");
    //HomeBloc homeBloc = BlocProvider.of<HomeBloc>(context);
    //结束刷新
    return Future.value(true);
  }

  @override
  Widget build(BuildContext context) {
    ScrollController scrollController = ScrollController();

    EdgeInsets paddings = MediaQuery.of(context).padding;
    return Scaffold(
      appBar: AppBar(
        backgroundColor: ColorT.appBarBackground,
        leading: Container(),
        title: const Text(
          "首页",
          style: TextStyle(
              fontSize: 18,
              color: ColorT.appBarTitle,
              fontWeight: FontWeight.bold),
        ),
        elevation: 0,
      ),
      body: SafeArea(
        top: false,
        bottom: true,
        left: true,
        right: false,
        child: Container(
          color: ColorT.primaryBackground,
          margin: const EdgeInsets.fromLTRB(0, 0, 0, 0),
          padding: EdgeInsets.fromLTRB(10, 10, 10, paddings.bottom),
          child: RefreshIndicator(
            onRefresh: _onRefresh,
            displacement: 40,
            child: ListView(
              controller: scrollController,
              padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
              shrinkWrap: true,
              children: const <Widget>[
                HomeTreatmentWidget(),
                HomeOralInspectionWidget(),
                HomeSmileSolutionWidget()
              ],
            ),
          ),
        ),
      ),
    );
  }
}

06/22/2022 08:49 上午 posted in  Flutter

Flutter项目高德地图后台持续定位功能的实现(iOS)

首先高德本身就支持后台持续定位:实例文档.对于Flutter项目高德也提供了框架支持:文档

pubspec.yaml如下:

dependencies:
  flutter:
    sdk: flutter
  # 权限相关
  permission_handler: ^5.1.0+2
  # 定位功能
  amap_location_fluttify: ^0.20.0

实现逻辑我们以iOS项目为例:

iOS项目工程(ios/Runner)配置:

添加定位权限申请配置

<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>申请Always权限以便应用在前台和后台(suspend 或 terminated)都可以获取到更新的位置数据</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>需要您的同意才能始终访问位置</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>需要您的同意,才能在使用期间访问位置</string>

以上权限会根据iOS 系统版本的不同有所不同

后台任务(Background Modes)模式配置

<key>UIBackgroundModes</key>
<array>
	<string>location</string>
	<string>remote-notification</string>
</array>

选择Location updates选项

Flutter项目实例

对于Flutter中的使用方法,具体实例如下:

  1. 首先要在main函数中进行高德地图组件的注册
  2. 视图中在调用定位之前必须进行权限申请
  3. 开启后台任务功能
  4. 执行持续定位

代码 main.dart:

import 'package:amap_location_fluttify/amap_location_fluttify.dart';

void main() {
  runApp(const MyApp());
  # 注册高德地图组件
  AmapLocation.instance.init(iosKey: 'xxxxxx');
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: LocationPage(),
    );
  }
}

location_page.dart:

import 'package:amap_location_fluttify/amap_location_fluttify.dart';
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:permission_handler/permission_handler.dart';

class LocationPage extends StatefulWidget {
  LocationPage({Key? key}) : super(key: key);

  _LocationPageState createState() => _LocationPageState();
}

class _LocationPageState extends State<LocationPage> {
  //获取数据
  // Map<String, Object> _locationResult;
  String _latitude = ""; //纬度
  String _longitude = ""; //经度

  @override
  void initState() {
    super.initState();
    /// 动态申请定位权限
    requestPermission();
  }

  @override
  void dispose() {
    super.dispose();
  }

  /// 动态申请定位权限
  void requestPermission() async {
    // 申请权限
    bool hasLocationPermission = await requestLocationPermission();
    if (hasLocationPermission) {
      print("定位权限申请通过");
    } else {
      print("定位权限申请不通过");
    }
  }

  ///  申请定位权限  授予定位权限返回true, 否则返回false
  Future<bool> requestLocationPermission() async {
    //获取当前的权限

    var status = await Permission.locationAlways.status;
    if (status == PermissionStatus.granted) {
      //已经授权
      return true;
    } else {
      //未授权则发起一次申请
      status = await Permission.location.request();
      if (status == PermissionStatus.granted) {
        return true;
      } else {
        return false;
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("地理定位演示"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            //  latitude: 36.570091461155336, longitude: 109.5080830206976
            //
            Text("纬度:${this._latitude}"),
            Text("经度:${this._longitude}"),
            SizedBox(height: 20),
            ElevatedButton(
              child: Text('开始定位'),
              onPressed: () {
                this._startTheLocation();
              },
            ),
          ],
        ),
      ),
    );
  }

  Future _startTheLocation() async {
    if (await Permission.location.request().isGranted) {
    
        # 开启后台持续定位功能
      await AmapLocation.instance.enableBackgroundLocation(
        10,
        BackgroundNotification(
          contentTitle: 'contentTitle',
          channelId: 'channelId',
          contentText: 'contentText',
          channelName: 'channelName',
        ),
      );
      
      # 监听持续定位
      AmapLocation.instance.listenLocation().listen((location) {
        setState(() {
          _latitude = location.latLng.latitude.toString();
          _longitude = location.latLng.longitude.toString();
          print("监听定位: {$_latitude, $_longitude}");
        });
      });
    } else {
      openAppSettings();
    }
  }

}

总结

关于后台持续定位对于高德来说核心函数只有2个:

开启后台任务

AmapLocation.instance.enableBackgroundLocation(id, notification)

执行持续定位:

AmapLocation.instance.listenLocation().listen((location) {
    // do someting
});
06/10/2022 13:10 下午 posted in  Flutter

Flutter开发的一些知识点记录1

Error: Cannot run with sound null safety, because the following dependencies

don't support null safety:

flutter build ios --no-sound-null-safety

Flutter 升级到指定版本——版本升级与回退

相关命令:

查看版本: flutter --version
检查环境:flutter doctor
查看渠道:flutter channel
切换渠道(stable, beta, dev, master):flutter channel stable
升级到最新版本:flutter upgrade 
升级到指定版本:flutter upgrade v2.2.3
回退到指定版本:flutter downgrade v2.0.3

也可通过git回退版本:

  1. 进入 flutter github 找到要回退的版本


  1. cd进入到存放flutter sdk目录,运行回退指令 git reset --hard [commit_id]
    例如-> git reset --hard 4d7946a68d26794349189cf21b3f68cc6fe61dcb
  2. 查看flutter版本
    查看版本-> flutter doctor 或者 flutter --version
06/09/2022 16:26 下午 posted in  Flutter

vscode如何使⽤模拟器运⾏flutter项⽬

在vscode中,快捷键ctrl+shift+P,在顶部搜索框中输⼊ Flutter:Latrl+shift+P,在顶部搜索框中输⼊ Flutter:Launch Emulator,
它会弹出你设备上的虚拟机或者真机设备,你可以选择任意设备运⾏查看你的项⽬界⾯
使⽤ios模拟器⽰例:
1:xcode下载好之后,在终端执⾏以下命令打开模拟器

open -a Simulator 
06/04/2022 14:49 下午 posted in  Flutter

创建flutter项目命令

打开终端,cd 项目目录,然后输入以下命令

1、创建Flutter项目

flutter create 项目名字
flutter create --org com.example 项目名字
flutter create -i <objc或者swift> -a <kotlin或者java> 项目名字
flutter create -i <objc或者swift> -a <kotlin或者java> --org com.example 项目名字
flutter create --sample widgets.SliverFillRemaining.1  wigsfr1
flutter create --sample widgets.Navigator.1  wigsfr1
flutter create --sample widgets.SliverFillRemaining.2  wigsfr1
flutter create --sample widgets.SliverFillRemaining.3  wigsfr1
flutter create --sample widgets.SliverFillRemaining.4  wigsfr1

--org表示指定bundleId或者包名
-i 和 -a 表示设置语言(iOS默认是swift,android默认是kottlin)
--sample表示创建示例文档

2、创建Flutter组件包
如果要在项目的某个目录下创建一个模块,需要先进入这个目录

flutter create -t module --org com.example 组件名字
module表示要创建的是一个组件而不是完整的app

3、创建插件包

flutter create --template=plugin  --org com.example --platforms android,ios 插件名字
flutter create -i objc -a java  --template=plugin  --org com.example --platforms android,ios 插件名字

--template=plugin表示创建的是跟原生有交互的插件
--platforms表示指定平台

4、创建Dart包
flutter create --template=package 插件名字
--template=package表示创建的是纯dart语言的插件
注意:--template=package,等号两边不能有空格,纯Dart库是不会自动创建example项目的,但可以在库文件夹里自己创建一个example项目 然后在pubspec.yaml通过路径引用

06/04/2022 14:45 下午 posted in  Flutter

Flutter_bloc

flutter_bloc 是一个bloc第三方库,这个库很方便的让你集成bloc模式,这个库结合了RXDart,先了解一下bloc 的模式吧

Read more   02/09/2020 22:09 下午 posted in  Flutter

Dart语法简介

Read more   12/24/2019 11:11 上午 posted in  Flutter

[Flutter]Image图片缓存

源码分析

Read more   12/18/2019 10:12 上午 posted in  Flutter