Never give up

Flutter - Bloc todo example 본문

Flutter

Flutter - Bloc todo example

대기만성 개발자 2021. 2. 28. 23:07
반응형

보통 상태관리 예제로 카운터 예제를 많이 하는데

 

다른 곳에 적용하기가 조금 어려운 분들을 위해 간단한 todo list를 만들어봤습니다

 

구조는 저번과 동일하며 데이터타입(State부분)과 이벤트 처리만 조금 변경되니

 

예제를 보면 충분히 이해될거라 생각됩니다

 

State

class ListState {
  List<ListItem> list = [];

  ListState(this.list);

  ListState update(List<ListItem> list) => ListState(list);
}

ListItem

class ListItem {
  String title = '', desc = '';
  DateTime date = DateTime.now();
  int isChecked = 0;

  ListItem({this.title, this.desc});

  void toggleCheck() {
    if (getIsChecked) {
      isChecked = 0;
    } else {
      isChecked = 1;
    }
  }

  bool get getIsChecked => (isChecked == 1);

  String get getDate => DateFormat('yyyy.MM.dd').format(date);
}

Event

abstract class ListEvent{}

class AddEvent extends ListEvent{
  final ListItem item;

  AddEvent(this.item);
}

class DeleteEvent extends ListEvent{
  final int index;

  DeleteEvent(this.index);
}

class UpdateEvent extends ListEvent{
  final int index;
  final ListItem item;

  UpdateEvent(this.index, this.item);
}

class ToggleEvent extends ListEvent{
  final int index;

  ToggleEvent(this.index);
}

bloc

class ListBloc extends Bloc<ListEvent, ListState> {
  ListBloc() : super(ListState([]));

  @override
  Stream<ListState> mapEventToState(ListEvent event) async* {
    if (event is AddEvent) {
      yield* _add(event.item);
    } else if (event is DeleteEvent) {
      yield* _delete(event.index);
    } else if (event is UpdateEvent) {
      yield* _update(index: event.index, item: event.item);
    } else if (event is ToggleEvent) {
      yield* _toggle(event.index);
    }
  }

  Stream<ListState> _add(ListItem item) async* {
    List<ListItem> list = state.list;
    list.add(item);

    yield state.update(list);
  }

  Stream<ListState> _delete(int index) async* {
    List<ListItem> list = state.list;
    list.removeAt(index);

    yield state.update(list);
  }

  Stream<ListState> _update({int index, ListItem item}) async* {
    List<ListItem> list = state.list;
    list[index] = item;

    yield state.update(list);
  }

  Stream<ListState> _toggle(int index) async* {
    List<ListItem> list = state.list;
    list[index].toggleCheck();

    yield state.update(list);
  }
}

Main

class BlocListExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider<ListBloc>(create: (_) => ListBloc(), child: BlocMain());
  }
}

class BlocMain extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Bloc list example')),
      body: BlocBuilder<ListBloc, ListState>(builder: (_, state) {
        return ListView.builder(
            padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
            itemCount: state.list.length,
            itemBuilder: (_, index) {
              var item = state.list[index];
              return Card(
                  elevation: 2,
                  shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(12)),
                  child: ExpansionTile(
                    leading: Checkbox(
                        value: item.getIsChecked,
                        onChanged: (value) =>
                            context.read<ListBloc>().add(ToggleEvent(index))),
                    title: Text(item.title),
                    children: [
                      ListTile(
                          title: Text(item.desc), trailing: Text(item.getDate)),
                      Row(mainAxisAlignment: MainAxisAlignment.end, children: [
                        FlatButton(
                            onPressed: () => context
                                .read<ListBloc>()
                                .add(DeleteEvent(index)),
                            child: Text('Delete')),
                        FlatButton(
                            onPressed: () => _dialog(
                                context: context, isEdit: true, index: index),
                            child: Text('Edit')),
                      ])
                    ],
                  ));
            });
      }),
      floatingActionButton: FloatingActionButton(
          child: Icon(Icons.edit), onPressed: () => _dialog(context: context)),
    );
  }

  void _dialog({BuildContext context, bool isEdit = false, int index}) {
    String title = '', desc = '';
    showDialog(
        context: context,
        builder: (_) => AlertDialog(
              title: Text(isEdit ? 'Edit' : 'Add'),
              content: Column(mainAxisSize: MainAxisSize.min, children: [
                Divider(
                  color: Colors.grey,
                ),
                Row(children: [
                  Text('Title : '),
                  Expanded(
                    child: TextField(
                        maxLines: 1,
                        decoration:
                            InputDecoration(border: UnderlineInputBorder()),
                        onChanged: (value) {
                          title = value;
                        }),
                  )
                ]),
                Padding(
                  padding: const EdgeInsets.only(top: 8),
                  child: TextField(
                    maxLines: 3,
                    decoration: InputDecoration(border: OutlineInputBorder()),
                    onChanged: (value) {
                      desc = value;
                    },
                  ),
                )
              ]),
              actions: [
                FlatButton(
                    onPressed: () => Navigator.pop(context),
                    child: Text('Cancel')),
                FlatButton(
                    onPressed: () {
                      if (isEdit) {
                        context.read<ListBloc>().add(UpdateEvent(
                            index, ListItem(title: title, desc: desc)));
                      } else {
                        context
                            .read<ListBloc>()
                            .add(AddEvent(ListItem(title: title, desc: desc)));
                      }
                      Navigator.pop(context);
                    },
                    child: Text('Confirm')),
              ],
            ));
  }
}

 

먼저 State에 List를 선언해주고 이전과 동일하게 update를 통해 상태를 업데이트 해주고

 

이벤트에서는 해당 이벤트에 필요한 파라미터를 넣어줍니다

(예를들어 removeAt(index)인경우 index값이 필요하므로 int값을 주면 됩니다)

 

bloc에서는 event별로 메소드를 나눠주고 state의 list를 넣어주는데 크게 다른점은 없습니다

 

한줄로 정리해보면 "event에 따라 list가 변하게 되고 변화된 list를 stream을 통해 보내줍니다"

 

메인에서는 blocprovider안에서 발생하는 이벤트들 add, delete, edit, toggle를

 

context.read.add를 통해 event를 주고 blocbuilder안에서 event에 따라 state값이 변하게됩니다

반응형
Comments