• 使用 Flutter&&Hive&&Bloc 写一个待办小demo


     

    Github 地址:GITHUB

    没图说个锤子,先上效果图:

    最简陋那个是测试直接通过hive读数据

    开发环境:

    老规矩先上环境:

    • Flutter SDK ---->2.5.3 & Dart sdk 2.14.4
    • flutter_bloc ^7.0.0
    • Jdk 1.8

    插件依赖

    创建项目 并依赖下方插件

      flutter_slidable: ^1.0.0
      hive: ^2.0.4
      hive_flutter: ^1.1.0
      uuid: ^3.0.5
      material_design_icons_flutter: ^5.0.6295
      flutter_material_color_picker: ^1.1.0+2
      google_fonts: ^2.1.0
      animated_text_kit: ^4.2.1
      intl: ^0.17.0
      bloc: ^7.0.0
      equatable: ^2.0.0
      flutter_bloc: ^7.0.0
      meta: ^1.3.0

    在项目根目录创建 packages的路径,如下图

    创建项目工具包

    新建两个包选择项目根目录的packages位置(如图):

     选择packagey因为我们只是在这边写一些方法,如果创建插件项目就太大并不合适

    两个包名分别为:

    todos_repository

    todo_repository_simple

    todo_repository的编写

    todo_repository 包添加 依赖

      hive: ^2.0.4
      hive_flutter: ^1.1.0

    创建一个 todo_modle 的dart文件写入如下代码

    在Teminal中cd 到 todo_repository 的目录

    执行 这行代码,生成.g文件。也就是 todo_model 的适配器,可以自己手写

    flutter packages pub run build_runner build

    如果报错尝试执行这条

    flutter packages pub run build_runner build --delete-conflicting-outputs

    如果还是报错那就直接copy这里的代码

    todo_model:

    // GENERATED CODE - DO NOT MODIFY BY HAND
    
    part of 'todo_model.dart';
    
    // **************************************************************************
    // TypeAdapterGenerator
    // **************************************************************************
    
    class TodoCategoryAdapter extends TypeAdapter<TodoCategory> {
      @override
      final int typeId = 1;
    
      @override
      TodoCategory read(BinaryReader reader) {
        switch (reader.readByte()) {
          case 0:
            return TodoCategory.personal;
          case 1:
            return TodoCategory.work;
          case 2:
            return TodoCategory.shopping;
          default:
            return TodoCategory.work;
        }
      }
    
      @override
      void write(BinaryWriter writer, TodoCategory obj) {
        switch (obj) {
          case TodoCategory.personal:
            writer.writeByte(0);
            break;
          case TodoCategory.work:
            writer.writeByte(1);
            break;
          case TodoCategory.shopping:
            writer.writeByte(2);
            break;
        }
      }
    
      @override
      int get hashCode => typeId.hashCode;
    
      @override
      bool operator ==(Object other) =>
          identical(this, other) ||
          other is TodoCategoryAdapter &&
              runtimeType == other.runtimeType &&
              typeId == other.typeId;
    }
    
    class MyColorAdapter extends TypeAdapter<MyColor> {
      @override
      final int typeId = 2;
    
      @override
      MyColor read(BinaryReader reader) {
        switch (reader.readByte()) {
          case 0:
            return MyColor.red;
          case 1:
            return MyColor.orange;
          case 2:
            return MyColor.teal;
          case 3:
            return MyColor.pink;
          case 4:
            return MyColor.blueGrey;
          case 5:
            return MyColor.blue;
          case 6:
            return MyColor.purple;
          default:
            return MyColor.red;
        }
      }
    
      @override
      void write(BinaryWriter writer, MyColor obj) {
        switch (obj) {
          case MyColor.red:
            writer.writeByte(0);
            break;
          case MyColor.orange:
            writer.writeByte(1);
            break;
          case MyColor.teal:
            writer.writeByte(2);
            break;
          case MyColor.pink:
            writer.writeByte(3);
            break;
          case MyColor.blueGrey:
            writer.writeByte(4);
            break;
          case MyColor.blue:
            writer.writeByte(5);
            break;
          case MyColor.purple:
            writer.writeByte(6);
            break;
        }
      }
    
      @override
      int get hashCode => typeId.hashCode;
    
      @override
      bool operator ==(Object other) =>
          identical(this, other) ||
          other is MyColorAdapter &&
              runtimeType == other.runtimeType &&
              typeId == other.typeId;
    }
    
    class TodoModelAdapter extends TypeAdapter<TodoModel> {
      @override
      final int typeId = 0;
    
      @override
      TodoModel read(BinaryReader reader) {
        final numOfFields = reader.readByte();
        final fields = <int, dynamic>{
          for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
        };
        return TodoModel(
          content: fields[0] as String,
          done: fields[1] as bool,
          time: fields[2] as DateTime,
          category: fields[3] as TodoCategory,
          color: fields[4] as MyColor,
        );
      }
    
      @override
      void write(BinaryWriter writer, TodoModel obj) {
        writer
          ..writeByte(5)
          ..writeByte(0)
          ..write(obj.content)
          ..writeByte(1)
          ..write(obj.done)
          ..writeByte(2)
          ..write(obj.time)
          ..writeByte(3)
          ..write(obj.category)
          ..writeByte(4)
          ..write(obj.color);
      }
    
      @override
      int get hashCode => typeId.hashCode;
    
      @override
      bool operator ==(Object other) =>
          identical(this, other) ||
          other is TodoModelAdapter &&
              runtimeType == other.runtimeType &&
              typeId == other.typeId;
    }

    todo_model.g.dart:

    // GENERATED CODE - DO NOT MODIFY BY HAND
    
    part of 'todo_model.dart';
    
    // **************************************************************************
    // TypeAdapterGenerator
    // **************************************************************************
    
    class TodoCategoryAdapter extends TypeAdapter<TodoCategory> {
      @override
      final int typeId = 1;
    
      @override
      TodoCategory read(BinaryReader reader) {
        switch (reader.readByte()) {
          case 0:
            return TodoCategory.personal;
          case 1:
            return TodoCategory.work;
          case 2:
            return TodoCategory.shopping;
          default:
            return TodoCategory.work;
        }
      }
    
      @override
      void write(BinaryWriter writer, TodoCategory obj) {
        switch (obj) {
          case TodoCategory.personal:
            writer.writeByte(0);
            break;
          case TodoCategory.work:
            writer.writeByte(1);
            break;
          case TodoCategory.shopping:
            writer.writeByte(2);
            break;
        }
      }
    
      @override
      int get hashCode => typeId.hashCode;
    
      @override
      bool operator ==(Object other) =>
          identical(this, other) ||
          other is TodoCategoryAdapter &&
              runtimeType == other.runtimeType &&
              typeId == other.typeId;
    }
    
    class MyColorAdapter extends TypeAdapter<MyColor> {
      @override
      final int typeId = 2;
    
      @override
      MyColor read(BinaryReader reader) {
        switch (reader.readByte()) {
          case 0:
            return MyColor.red;
          case 1:
            return MyColor.orange;
          case 2:
            return MyColor.teal;
          case 3:
            return MyColor.pink;
          case 4:
            return MyColor.blueGrey;
          case 5:
            return MyColor.blue;
          case 6:
            return MyColor.purple;
          default:
            return MyColor.red;
        }
      }
    
      @override
      void write(BinaryWriter writer, MyColor obj) {
        switch (obj) {
          case MyColor.red:
            writer.writeByte(0);
            break;
          case MyColor.orange:
            writer.writeByte(1);
            break;
          case MyColor.teal:
            writer.writeByte(2);
            break;
          case MyColor.pink:
            writer.writeByte(3);
            break;
          case MyColor.blueGrey:
            writer.writeByte(4);
            break;
          case MyColor.blue:
            writer.writeByte(5);
            break;
          case MyColor.purple:
            writer.writeByte(6);
            break;
        }
      }
    
      @override
      int get hashCode => typeId.hashCode;
    
      @override
      bool operator ==(Object other) =>
          identical(this, other) ||
          other is MyColorAdapter &&
              runtimeType == other.runtimeType &&
              typeId == other.typeId;
    }
    
    class TodoModelAdapter extends TypeAdapter<TodoModel> {
      @override
      final int typeId = 0;
    
      @override
      TodoModel read(BinaryReader reader) {
        final numOfFields = reader.readByte();
        final fields = <int, dynamic>{
          for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
        };
        return TodoModel(
          content: fields[0] as String,
          done: fields[1] as bool,
          time: fields[2] as DateTime,
          category: fields[3] as TodoCategory,
          color: fields[4] as MyColor,
        );
      }
    
      @override
      void write(BinaryWriter writer, TodoModel obj) {
        writer
          ..writeByte(5)
          ..writeByte(0)
          ..write(obj.content)
          ..writeByte(1)
          ..write(obj.done)
          ..writeByte(2)
          ..write(obj.time)
          ..writeByte(3)
          ..write(obj.category)
          ..writeByte(4)
          ..write(obj.color);
      }
    
      @override
      int get hashCode => typeId.hashCode;
    
      @override
      bool operator ==(Object other) =>
          identical(this, other) ||
          other is TodoModelAdapter &&
              runtimeType == other.runtimeType &&
              typeId == other.typeId;
    }
    View Code

    创建两个抽象方法,分别是

    TodoRepository
    abstract class TodoRepository {
    
      /// 加载待办事项
      Future<List<TodoModel>> loadTodos();
      /// 保存待办事项
      Future saveTodos(List<TodoModel> todos);
    }

    ReactiveTodosRepository 

    abstract class ReactiveTodosRepository {
    
      /// 添加新的待办
      Future<void> addNewTodo(TodoModel todo);
      /// 删除待办事项
      Future<void> deleteTodo(List<String> idList);
      /// 获取全部待办事项
      Stream<List<TodoModel>> todos();
      /// 更新待办事项
      Future<void> updateTodo(TodoModel todo);
    }

    熟悉java的都知道,这和java项目中定义的接口是一样的,应该都能看懂 

    todos_repository_simple 

    todos_repository的代码基本上就这样,接下来看看 todo_repository_simple

    在todo_repository_simple 我们要做的事情是请求网络数据或者本地数据,并将数据返回到页面给到用户观看的一个方法

    首先还是添加插件

      hive: ^2.0.4
      hive_flutter: ^1.1.0
      todos_repository:
        path: ../todos_repository

    hive插件我们已经分别添加了三次,因为项目里不能像安卓那样直接通过根项目拿到依赖,所以只能重新再依赖一次

    而 todos_repository则是我们刚刚写的插件,我们需要在这边去实现它的方法,所以就得把它依赖进来

    好,那接下来创建一个本地存储的方法

    FileStorage代码:

    class FileStorage {
      final String tag;
      final Future<Directory> Function() getDirectory;
    
      const FileStorage(
        this.tag,
        this.getDirectory,
      );
    
      Future<List<TodoModel>> loadTodos() async {
        final todoBox = await Hive.openBox<TodoModel>("todos");
        List<TodoModel> todos = [];
        todos.addAll(todoBox.values.map((e) => e).toList());
        return todos;
      }
    
      Future saveTodos(List<TodoModel> todos) async {
        final settingsBox = await Hive.openBox<bool>('settings');
        final todosBox = await Hive.openBox<TodoModel>('todos');
        await todosBox.addAll(todos);
        await settingsBox.put('initialized', true);
      }
    
      Future saveTodo(TodoModel todoModel) async {
        if (todoModel.isInBox) {
          final key = todoModel.key;
          Hive.box<TodoModel>('todos').put(key, todoModel);
        } else {
          await Hive.box<TodoModel>('todos').add(todoModel);
        }
      }
    
    
    }

    在这里实现两个方法,其中 loadTodos 是通过 hive中读取用户的全部待办事项,当然,当前 hive box 还没初始化,如果现在直接调用肯定是会报错的,等会会在根项目中进行初始化

    saveTodos 保存全部待办事项

    创建一个 WebClient

    这个方法理应上是获取网络的数据,不过目前没有搭后台,所以就先写个模拟数据用用

    WebClient:

    class WebClient {
      final Duration delay;
    
      const WebClient([this.delay = const Duration(milliseconds: 3000)]);
    
      Future<List<TodoModel>> fetchTodos() async {
        return Future.delayed(
            delay,
            () => [
              TodoModel(
                  category: TodoCategory.personal,
                  color: MyColor.purple,
                  content: '去散步',
                  done: false,
                  time: DateTime.now().subtract(const Duration(days: 1))),
              TodoModel(
                  category: TodoCategory.shopping,
                  color: MyColor.orange,
                  content: '去工作',
                  done: false,
                  time: DateTime.now().subtract(const Duration(days: 2))),
              TodoModel(
                  category: TodoCategory.work,
                  color: MyColor.blueGrey,
                  content: '去运动',
                  done: true,
                  time: DateTime.now().subtract(const Duration(days: 3)))
                ]);
      }
    
      Future<bool> postTodos(List<TodoModel> todos) async {
        return Future.value(true);
      }
    }

    可以看到上面设置一个Duration的东西,用来模拟请求网络的一个延迟效果 

    fetchTodos方法中则是模拟获取数据的
    postTodos 是往服务器推待办数据,还是因为后台没写,搁置先,后面有心情在继续更

    创建TodosRepositoryFlutter

    这个方法是继承todo_repository的方法,并通过上面写的两个方法获得数据,代码如下

    // Copyright 2018 The Flutter Architecture Sample Authors. All rights reserved.
    // Use of this source code is governed by the MIT license that can be found
    // in the LICENSE file.
    
    import 'dart:async';
    import 'dart:core';
    
    import 'package:meta/meta.dart';
    import 'package:todos_repository/todo_repository_core.dart';
    import 'file_storage.dart';
    import 'web_client.dart';
    
    class TodosRepositoryFlutter implements TodoRepository {
      final FileStorage fileStorage;
      final WebClient webClient;
    
      const TodosRepositoryFlutter({
        required this.fileStorage,
        this.webClient = const WebClient(),
      });
    
      ///首先从文件存储中加载待办事项,如果不存在则通过web端去加载
      @override
      Future<List<TodoModel>> loadTodos() async {
        try {
          final todos  = await fileStorage.loadTodos();
          if(todos.isEmpty){
            final todos = await webClient.fetchTodos();
            fileStorage.saveTodos(todos);
            return todos;
          }
          return todos;
        } catch (e) {
          final todos = await webClient.fetchTodos();
          fileStorage.saveTodos(todos);
          return todos;
        }
      }
    
      //将TODO持久化到本地磁盘和web
      @override
      Future saveTodos(List<TodoModel> todos) {
        return Future.wait<dynamic>([
          fileStorage.saveTodos(todos),
          webClient.postTodos(todos),
        ]);
      }
    
      @override
      Future saveTodo(TodoModel todoModel) {
        return Future.wait<dynamic>([
          fileStorage.saveTodo(todoModel)
        ]);
      }
    }

    其实 saveTodo 并不应该写在这里,这个方法最开始想要达到得结果是读取全部数据和添加全部数据,

    而像添加新的待办事项和删除等操作是应该写另一个地方,但是这里出于偷懒得原因,直接就写这里了,后续完善后会进行修改

    那todo_repository_simple的代码也就暂时告一段落,接下来就是根项目的编写了

    回到 flutter_bloc_hive_todo中

    首先还是依赖包,依赖我们刚刚写的两个包

      todo_repository_simple:
        path: packages/todo_repository_simple
      todos_repository:
        path: packages/todos_repository

    在lib中创建两个个文件夹 分别是 blocs 和view

    先来看看bloc。在AndroidStudio中安装bloc插件

    然后右键创建文件的时候就会出现,方便快速创建初始代码

     

    BLOC

    在使用bloc前,我们需要了解到Bloc是个什么玩意,emmm网上的文章很多,说得都非常详细,我就不班门弄斧了,只说说我的理解和感受

    Bloc 给我的感觉就是

    View 通过 event 去调用bloc的方法 bloc在通过修改state 去修改界面显示的信息,互不干扰,这倒是和java的mvp模式有些异曲同工之处

    好了,来看看state的代码

    abstract class TodosState extends Equatable {
      const TodosState();
    
      @override
      List<Object> get props => [];
    }
    
    /// 待办事项加载中
    class TodosLoadInProgress extends TodosState {}
    
    /// 待办事项加载完成
    class TodosLoadSuccess extends TodosState {
      /// 待办事项集合
      final List<TodoModel> todos;
    
      const TodosLoadSuccess({this.todos = const []});
    
      @override
      List<Object> get props => [todos];
    
      @override
      String toString() => 'TodosLoadSuccess { todos: $todos }';
    }
    
    /// 待办事项加载失败
    class TodosLoadFailure extends TodosState {}
    
    /// 添加待办失败
    class TodoAddFailure extends TodosState{}

    可以看到除了加载成功的状态里有写方法,其他三个均不用理会,因为它们嫩不会返回数据给到界面,我们仅需知道它是什么状态即可

    再来看看 event

    abstract class TodosEvent extends Equatable {
      const TodosEvent();
    
      @override
      List<Object> get props => [];
    }
    
    /// 待办事项加载中
    class TodosLoaded extends TodosEvent {}
    
    ///添加一个待办事项
    class TodoAdded extends TodosEvent{
      final TodoModel todo;
      const TodoAdded(this.todo);
    
      @override
      List<Object> get props => [todo];
    
      @override
      String toString() => 'TodoAdded { todo: $todo }';
    }
    
    /// 更新一个待办事项
    class TodoUpdated extends TodosEvent{
      final TodoModel todo;
      const TodoUpdated(this.todo);
    
      @override
      List<Object> get props => [todo];
    
      @override
      String toString() => 'TodoAdded { todo: $todo }';
    
    }
    
    /// 删除一个待办事项
    class TodoDeleted extends TodosEvent{
      final TodoModel todo;
      const TodoDeleted(this.todo);
    
      @override
      List<Object> get props => [todo];
    
      @override
      String toString() => 'TodoDeleted { todo: $todo }';
    }
    
    /// 清空全部待办事项
    class ClearCompleted extends TodosEvent {}
    
    /// 选中全部待办事项
    class ToggleAll extends TodosEvent {}

    另外两个方法没想好怎么做,暂时不写,删除添加等代码其实都一样,都是传入一个 待办对象,应该没什么好说的

    所以。直接来看 bloc

    bloc:

    class TodosBloc extends Bloc<TodosEvent, TodosState> {
      /// 待办事项方法
      final TodosRepositoryFlutter todosRepository;
    
      TodosBloc(this.todosRepository) : super(TodosLoadInProgress()) {
        on<TodosEvent>((event, emit) {
          // TODO: implement event handler
        });
        on<TodosLoaded>(_onLoaded);
        on<TodoAdded>(_onAddTodo);
      }
    
      /// 加载待办事项
      void _onLoaded(TodosEvent event, Emitter<TodosState> emit) async {
        try {
          /// 获得初始默认待办事项
          final todos = await todosRepository.loadTodos();
          emit(TodosLoadSuccess(
            todos: todos,
          ));
        } catch (e) {
          emit(TodosLoadFailure());
        }
      }
    
      /// 添加待办事项
      void _onAddTodo(TodoAdded event, Emitter<TodosState> emit) async {
        try {
          if (state is TodosLoadSuccess) {
            emit(TodosLoadSuccess(
                todos: List.from((state as TodosLoadSuccess).todos)
                  ..add(event.todo)));
            _saveTodo(event.todo);
          }
        } catch (e) {
          emit(TodoAddFailure());
        }
      }
    
      /// 删除待办事项
      void _onDelTodo(TodosEvent event, Emitter<TodosState> emit) async {}
    
      /// 修改待办事项
      void _onUpdateTodo(TodosEvent event, Emitter<TodosState> emit) async {}
    
      /// 将todo事项持久化
      Future _saveTodo(TodoModel todo) {
        return todosRepository.saveTodo(todo);
      }
    }

    如果有用过bloc的都会发现 没有  mapEventToState  方法,这个方法在以前是必备的,所有的event都是通过它来判断,但是在7.0后,官方已经逐渐弃用  mapEventToState 取而代之的是 on的这种写法,mapEventToState 将会在flutter_bloc 8.0版本彻底移除,所以这也是我写这篇文章的原因

    仔细看这行代码,我们在super中定义默认的bloc状态是加载中,你也可以写其他状态,但是 这个状态必须是继承你创建的state,而不能直接继承Equatable

    TodosBloc(this.todosRepository) : super(TodosLoadInProgress())

    这个 TodosRepositoryFlutter 则是我们刚刚写的插件,我们通过它来获得待办数据

      final TodosRepositoryFlutter todosRepository;

    在 _onLoaded 我们调用它的 loadTodos 方法,这个方法我们刚才的做法是如果没有本地数据就会获取网络数据,当然只是模拟请求后台

    而后通过 emit 去修改state 状态,并将待办数据传给 成功的状态的 待办列表,如果出现异常则返回 TodosLoadFailure


    在添加待办事项中,我们是是直接将添加的待办数据添加到 state中,然后才会进行网络很本地存储

    View

    接下来在view中创建这三个文件

    因为我们是显示state中的数据,所以为了查看hive中是否存储成功了,所以就创建一个test的界面测试

    那先看最简单的的test,很简单,就是监听hive中有没有数据变化,有变化就修改界面

    class TestPage extends StatefulWidget {
      const TestPage({Key? key}) : super(key: key);
    
      @override
      _TestPageState createState() => _TestPageState();
    }
    
    class _TestPageState extends State<TestPage> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(),
          body: ValueListenableBuilder<Box<TodoModel>>(
              valueListenable: Hive.box<TodoModel>('todos').listenable(),
              builder: (context, box, _) {
                final todos = box.values.toList();
                return ListView.builder(itemBuilder: (context,index){
                  return Text(todos[index].content!);
                },itemCount: todos.length,);
              }),
        );
      }
    }

    todo_editor_dialog是弹窗添加待办的一个弹出界面

    代码如下:

    import 'package:flutter/material.dart';
    import 'package:flutter_bloc/flutter_bloc.dart';
    import 'package:flutter_bloc_hive_todo/blocs/todos/todos_bloc.dart';
    import 'package:flutter_material_color_picker/flutter_material_color_picker.dart';
    import 'package:hive/hive.dart';
    import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
    import 'package:todos_repository/todo_repository_core.dart';
    
    
    class TodoEditorDialog extends StatefulWidget {
      const TodoEditorDialog({Key? key,required this.todo}):super(key: key);
      final TodoModel todo;
    
      @override
      _TodoEditorDialogState createState() => _TodoEditorDialogState();
    }
    
    class _TodoEditorDialogState extends State<TodoEditorDialog> {
      final TodoModel _todo = TodoModel();
    
      @override
      void initState() {
        super.initState();
        _todo.content = widget.todo.content;
        _todo.category = widget.todo.category;
        _todo.time = widget.todo.time;
        _todo.color = widget.todo.color;
        _todo.done = widget.todo.done;
      }
    
      @override
      Widget build(BuildContext context) {
        return Dialog(
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
          child: Padding(
            padding: const EdgeInsets.all(16.0),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: [
                      const Align(
                        alignment: Alignment.centerLeft,
                        child: Text(
                          '添加新待办',
                          style: TextStyle(fontSize: 20),
                        ),
                      ),
                      if (widget.todo.isInBox)
                        FloatingActionButton(
                            heroTag: 'remove_todo_button',
                            child: const Icon(MdiIcons.delete),
                            backgroundColor: Colors.red,
                            foregroundColor: Colors.white,
                            mini: true,
                            onPressed: () => _deleteHandle(context)),
                      FloatingActionButton(
                          heroTag: 'add_todo_button',
                          child: const Icon(MdiIcons.contentSave),
                          backgroundColor: Colors.green,
                          foregroundColor: Colors.white,
                          onPressed: () => _saveHandle(context)),
                    ],
                  ),
                ),
                const SizedBox(height: 10),
                TextField(
                  controller: TextEditingController(text: _todo.content),
                  autofocus: true,
                  cursorColor: Colors.deepOrange,
                  onChanged: (value) {
                    _todo.content = value;
                  },
                  onEditingComplete: () async {
                    // todo : save()
                  },
                  decoration: const InputDecoration(
                      focusedBorder: OutlineInputBorder(
                          borderSide: BorderSide(color: Colors.deepOrange)),
                      prefixIcon: Icon(
                        MdiIcons.rocketLaunch,
                        color: Colors.deepOrange,
                      ),
                      hintText: 'do...'),
                ),
                SingleChildScrollView(
                  scrollDirection: Axis.horizontal,
                  child: MaterialColorPicker(
                      shrinkWrap: true,
                      allowShades: false,
                      circleSize: 32,
                      colors: const [
                        Colors.red,
                        Colors.orange,
                        Colors.teal,
                        Colors.pink,
                        Colors.blueGrey,
                        Colors.blue,
                        Colors.purple,
                      ],
                      onMainColorChange: (color) {
                        _todo.color = color!.toMyColor();
                      },
                      selectedColor: _todo.color!.toColor()),
                ),
                CategoryPicker(
                  initialCategory: _todo.category ?? TodoCategory.personal,
                  categoryOnChanged: (category) {
                    _todo.category = category;
                  },
                ),
              ],
            ),
          ),
        );
      }
    
      Future<void> _saveHandle(BuildContext context) async {
        _todo.time = DateTime.now();
        BlocProvider.of<TodosBloc>(context).add(
          TodoAdded(_todo),
        );
        //
        // if (widget.todo.isInBox) {
        //   final key = widget.todo.key;
        //   Hive.box<TodoModel>('todos').put(key, _todo);
        // } else {
        //   await Hive.box<TodoModel>('todos').add(_todo);
        // }
        //
        Navigator.pop(context);
      }
    
      Future<void> _deleteHandle(BuildContext context) async {
        if (widget.todo.isInBox) await widget.todo.delete();
        Navigator.pop(context);
      }
    }
    
    class CategoryPicker extends StatefulWidget {
      const CategoryPicker({Key? key, required this.categoryOnChanged, required this.initialCategory})
          : super(key: key);
      final ValueChanged<TodoCategory> categoryOnChanged;
      final TodoCategory initialCategory;
    
      @override
      _CategoryPickerState createState() => _CategoryPickerState();
    }
    
    class _CategoryPickerState extends State<CategoryPicker> {
      var _isSelected = List.generate(3, (index) => false);
    
      @override
      void initState() {
        super.initState();
        final index = TodoCategory.values.indexOf(widget.initialCategory);
        _isSelected[index] = true;
      }
    
      @override
      Widget build(BuildContext context) {
        return ToggleButtons(
            fillColor: Colors.white.withOpacity(0.1),
            onPressed: (index) {
              setState(() {
                _isSelected = _isSelected.map((e) => e = false).toList();
                _isSelected[index] = true;
              });
              final category = TodoCategory.values[index];
              widget.categoryOnChanged(category);
            },
            children:  const [
              _ToggleButtonContainer(
                  icon: Icon(Icons.person), color: Colors.red, label: '暂定'),
              _ToggleButtonContainer(
                  icon: Icon(Icons.work), color: Colors.blue, label: '工作'),
              _ToggleButtonContainer(
                  icon: Icon(MdiIcons.shopping),
                  color: Colors.yellow,
                  label: '购物')
            ],
            isSelected: _isSelected);
      }
    }
    
    class _ToggleButtonContainer extends StatelessWidget {
      const _ToggleButtonContainer({Key? key, required this.color, required this.icon, required this.label})
          : super(key: key);
      final Color color;
      final Icon icon;
      final String label;
    
      @override
      Widget build(BuildContext context) {
        return Container(
          padding: const EdgeInsets.symmetric(horizontal: 8),
          child: Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              IconTheme(data: IconThemeData(color: color), child: icon),
              const SizedBox( 4),
              Text(
                label,
                style: TextStyle(color: color),
              ),
            ],
          ),
        );
      }
    }

    todo_item.dart则是每一个待办的子项

    todo_item:

    import 'package:flutter/material.dart';
    import 'package:todos_repository/todo_repository_core.dart';
    
    class TodoItem extends StatelessWidget {
      final DismissDirectionCallback onDismissed;
      final GestureTapCallback onTap;
      final ValueChanged<bool> onCheckboxChanged;
      final TodoModel todo;
    
      const TodoItem({
        Key? key,
        required this.onDismissed,
        required this.onTap,
        required this.onCheckboxChanged,
        required this.todo,
      }) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Dismissible(
          onDismissed: onDismissed,
          key: Key("__TodoItem__"),
          child: ListTile(
            onTap: onTap,
            leading: Checkbox(
              value: todo.done, onChanged: (bool? value) {  },
              // onChanged: onCheckboxChanged,
            ),
            title: Hero(
              tag: '${todo.category}__heroTag',
              child: Container(
                 MediaQuery.of(context).size.width,
                child: Text(
                  todo.content??"",
                  style: Theme.of(context).textTheme.headline6,
                ),
              ),
            ),
            subtitle: Text(
              "${todo.time}",
              maxLines: 1,
              overflow: TextOverflow.ellipsis,
              style: Theme.of(context).textTheme.subtitle1,
            ),
          ),
        );
      }
    }

    todo_home就是todo显示的界面了,需要调用todo_item,所以先贴出item的代码

    import 'package:flutter/material.dart';
    import 'package:flutter_bloc/flutter_bloc.dart';
    import 'package:flutter_bloc_hive_todo/blocs/todos/todos.dart';
    import 'package:flutter_bloc_hive_todo/view/test.dart';
    import 'package:flutter_bloc_hive_todo/view/todo_editor_dialog.dart';
    import 'package:flutter_slidable/flutter_slidable.dart';
    import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
    import 'package:todos_repository/todo_repository_core.dart';
    
    class TodoHome extends StatefulWidget {
      const TodoHome({Key? key}) : super(key: key);
    
      @override
      _TodoHomeState createState() => _TodoHomeState();
    }
    
    class _TodoHomeState extends State<TodoHome> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text("待办事项"),
            actions: [
              IconButton(
                  onPressed: () {
                    //路由跳转  固定写法  PageA 为目标页面类名
                    Navigator.of(context)
                        .push(MaterialPageRoute(builder: (context) => TestPage()));
                  },
                  icon: const Icon(Icons.add))
            ],
          ),
          floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
          floatingActionButton: FloatingActionButton(
            foregroundColor: Colors.white,
            backgroundColor: Colors.red,
            heroTag: 'add_todo_button',
            onPressed: () => showTodoEditorDialog(context),
            child: const Icon(MdiIcons.rocketLaunch),
          ),
          body: BlocBuilder<TodosBloc, TodosState>(
            builder: (context, state) {
              if (state is TodosLoadInProgress) {
                return const Text("加载中");
              } else if (state is TodosLoadSuccess) {
                return CustomScrollView(
                  slivers: [
                    SliverList(
                        delegate: SliverChildListDelegate([
                      const SizedBox(height: 10),
                      ...state.todos.map(
                        (todo) => InkWell(
                          onTap: () {
                            showTodoEditorDialog(context, todo: todo);
                          },
                          child: Slidable(
                            startActionPane: ActionPane(
                              motion: const ScrollMotion(),
                              dismissible: DismissiblePane(onDismissed: () {}),
                              children: [
                                SlidableAction(
                                  onPressed: (BuildContext context) {},
                                  flex: 2,
                                  backgroundColor: const Color(0xFFFE4A49),
                                  foregroundColor: Colors.white,
                                  icon: Icons.delete,
                                  label: '删除',
                                ),
                              ],
                            ),
                            endActionPane: ActionPane(
                              motion: const ScrollMotion(),
                              children: [
                                SlidableAction(
                                  flex: 2,
                                  onPressed: (BuildContext context) async {},
                                  backgroundColor: const Color(0xFF7BC043),
                                  foregroundColor: Colors.white,
                                  icon: Icons.radio_button_unchecked,
                                  label: '完成',
                                ),
                              ],
                            ),
                            child: Card(
                              elevation: 0,
                              child: SizedBox(
                                height: 60,
                                child: Row(
                                  children: [
                                    Padding(
                                      padding: const EdgeInsets.symmetric(
                                          horizontal: 8.0),
                                      child: todo.done!
                                          ? Stack(
                                              alignment: Alignment.center,
                                              children: const [
                                                Icon(
                                                  MdiIcons.check,
                                                  color: Colors.amberAccent,
                                                  size: 18,
                                                ),
                                                Icon(MdiIcons.circleOutline,
                                                    color: Colors.red),
                                              ],
                                            )
                                          : const Icon(MdiIcons.circleOutline,
                                              color: Colors.cyan),
                                    ),
                                    const SizedBox( 10),
                                    Expanded(
                                      child: Column(
                                        mainAxisSize: MainAxisSize.min,
                                        crossAxisAlignment:
                                            CrossAxisAlignment.start,
                                        children: [
                                          Opacity(
                                            opacity: todo.done! ? 0.4 : 1,
                                            child: Text(
                                              todo.content ?? "",
                                              style: TextStyle(
                                                  fontSize: 17,
                                                  fontWeight: todo.done!
                                                      ? FontWeight.w100
                                                      : FontWeight.normal,
                                                  decoration:
                                                      TextDecoration.lineThrough),
                                            ),
                                          ),
                                          const SizedBox(height: 6),
                                          Opacity(
                                            opacity: 0.4,
                                            child: Row(
                                              children: const [
                                                Icon(
                                                  Icons.date_range,
                                                  size: 12,
                                                ),
                                                SizedBox( 4),
                                                Text("'MM-dd-yyyy HH:mm'",
                                                    style: TextStyle(
                                                      fontSize: 12,
                                                    ))
                                              ],
                                            ),
                                          )
                                        ],
                                      ),
                                    ),
                                    Padding(
                                      padding: const EdgeInsets.all(8.0) +
                                          const EdgeInsets.symmetric(horizontal: 8),
                                      child: const Icon(Icons.person,
                                          color: Colors.red),
                                    )
                                  ],
                                ),
                              ),
                            ),
                          ),
                        ),
                      )
                    ].toList()))
                  ],
                );
              }
              return const Text("加载错误");
            },
          ),
        );
      }
    }
    
    void showTodoEditorDialog(BuildContext context, {TodoModel? todo}) {
      final _todo = todo ??
          TodoModel(
              color: MyColor.red,
              category: TodoCategory.personal,
              content: '',
              time: DateTime.now(),
              done: false);
    
      Navigator.push(
          context,
          PageRouteBuilder(
              fullscreenDialog: true,
              opaque: false,
              barrierDismissible: true,
              transitionsBuilder: (context, animation, secondaryAnimation, child) {
                if (animation.status == AnimationStatus.reverse) {
                  return SlideTransition(
                      position: Tween<Offset>(
                        begin: const Offset(0, 1.0),
                        end: Offset.zero,
                      ).animate(animation),
                      child: child);
                } else {
                  return SlideTransition(
                      position: Tween<Offset>(
                        begin: const Offset(0, 1.0),
                        end: Offset.zero,
                      ).animate(animation),
                      child: child);
                }
              },
              pageBuilder: (context, _, __) => TodoEditorDialog(todo: _todo)));
    }

    好了最后一个main就完事了

    Main:

    import 'package:flutter/material.dart';
    import 'package:flutter_bloc/flutter_bloc.dart';
    import 'package:flutter_bloc_hive_todo/view/todo_home.dart';
    import 'package:hive/hive.dart';
    import 'package:hive_flutter/hive_flutter.dart';
    import 'package:path_provider/path_provider.dart';
    import 'package:todo_repository_simple/todo_repository_simple.dart';
    import 'package:todos_repository/todo_repository_core.dart';
    
    import 'blocs/todos/todos_bloc.dart';
    
    void main() async{
      await _hiveSetup();
      runApp(
        BlocProvider(
          create: (context) {
            return TodosBloc(
              const TodosRepositoryFlutter(
                fileStorage: FileStorage(
                  '__flutter_bloc_app__',
                  getApplicationDocumentsDirectory,
                ),
              ),
            )..add(TodosLoaded());
          },
          child: App(),
        ),
      );
    }
    
    class App extends StatelessWidget {
      const App({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return const MaterialApp(title: "待办事项", home: TodoHome());
      }
    }
    
    Future<void> _hiveSetup() async {
      await Hive.initFlutter();
    
      Hive.registerAdapter(TodoCategoryAdapter());
      Hive.registerAdapter(MyColorAdapter());
      Hive.registerAdapter(TodoModelAdapter());
      await Hive.openBox<TodoModel>('todos');
    
    }

    嗯,先这样,乏了,看心情更新吧

  • 相关阅读:
    PHP使用http_build_query()构造URL字符串的方法
    php将一个二维数组按照某个字段值合并成一维数组,如果有重复则将重复的合并成二维数组
    资金管理
    偏爱粉色,我的儿子会不会娘娘腔?
    中文期刊有哪些?
    超声胎儿图像分割
    加州wonders教材扫盲
    美国小学1-5年级教学大纲
    A股回归牛市?
    深入研究股票涨停
  • 原文地址:https://www.cnblogs.com/inthecloud/p/flutter_bloc_hive_todo.html
Copyright © 2020-2023  润新知