Flutter

11-1. BLoC의 구조 (상세)

모리선생 2023. 4. 10. 23:00
728x90

목표

BLoC의 구조 및 기능에 대해서 더 상세하게 설명할 수 있다. 해당 설명글을 통해 BLoC의 내용을 도표로 그려 설명할 수 있을 정도의 이해력을 갖춘다.


지난 시간에 BLoC을 사용하는 방법에 대해서 설명을 해보았다. 그런데 생각하고 보니, BLoC에 대한 구조 및 기능들에 대해서 상세하게 다루지 않아 처음 보는 사람의 입장에서는 이해하기 힘든 부분도 있을 것이라 생각이 든다. 그래서 BLoC의 구조에 대해서 조금 더 쉬운 설명으로 알아보고 BLoC과 freezed를 함께 사용하는 방법을 알아보겠다.

 

BLoC의 사용

일단 구조를 알기전에 BLoC이라는 것이 왜 유용한 걸까? 우리가 일반적으로 생각하는 Flutter App의 경우에는 Material App내 Scaffold에서 여러 Widget을 아래에 가지고 있으며, 이 중 특정 계산 혹은 기능을 하는 Widget을 가지고 있다. 자, 그런데 일반적으로 Material App의 지배하에서 setState() 함수를 써주면 각 위젯에 넣고자 하는 수치가 반영된다. 이처럼 말이다.

그런데 생각해보자. 만약 이 구조가 너무나도 방대해진다면 모든 수치를 일일이 setState()로 입력할 수는 없다. 물론 할 수 있지만 변경이 필요하지 않은 부분까지 모두 build가 되어서 UI를 다시 그려야 한다. 이는 전반적으로 앱이 느려지는 등 필요하지 않은 리소스를 많이 사용하는 결과를 낳게 된다. 그런데, 일반적인 앱의 경우 네트워크를 통해 데이터를 받는 기능까지 가지고 있다. 이러한 경우에는 앱이 매우 느려질 가능성이 크다. 자 그럼 비즈니스로직과 UI를 구분하기 위해서 Google에서 만든 BLoC은 어떻게 이 문제를 해결했을까?

 

그럼 일일이 build하도록 하는 것이 아니라 UI가 build가 되어야 하는 부분의 Logic은 BLoC을 해줌으로써 (쉽게 말하자면, 쓱, 피해서 그 부분만 변경하고 관리 함으로써) 코드를 관리할 수 있다. 그렇다면 변경이 필요하지 않은 부분의 경우에는 UI를 새롭게 build해야하는 등의 리소스를 쓰지 않아도 된다. 

 

BLoC에서 알아야할 개념

1. Event. Event라고 하는 것은 UI 또는 사용자로부터 발생하는 동작을 나타내는 객체이다. 쉽게 말하면, 사용자가 버튼을 클릭하거나 텍스트 필드를 편집하는 동작들이다.

2. Stream. Stream은 비동기 이벤트 스트림을 나타내는 객체이다. 이벤트 생성하고 수신하는데 적절한 응답을 생성할시 사용된다.

3. Sink. Stream으로 이벤트를 보내는 방법이며, Sink를 사용하여 이벤트를 생성하고 Stream으로 보낸다.

4. State. UI의 상태를 나타내는 객체로 BLoC에서 상태는 일반적으로 변경가능한 객체가 아니며, 새로운 상태를 반환하는 방식으로 업데이트된다. 이를 통해 상태 추적와 UI 업데이트 등이 가능하다. 

5. BlocProvider. BLoC을 전역으로 공유하는 역할을 하며, 트리 내 모든 하위 위젯에서 BLoC을 사용하도록 도와준다.

6. BlocBuilder. 상태 변경에 따라 UI를 업데이트 하기 위함이며, 스트림의 수신과 방출 이벤트를 통해 UI 업데이트를 한다.

7. Transformation. Stream의 데이터를 변환하는 방법이며, 이를 처리하여 새로운 이벤트를 생성한다.

8. AsyncSnapshot: BLoC에서 Stream에서 방출된 데이터를 나타내는 객체이다.

 

BLoC의 개념들을 사용한 간단한 예제 작성

다음에서 그럼 방금 설명한 부분을 가지고 어떻게 비즈니스 로직을 분리하여 코드의 재사용성을 높이는지 코드를 작성하며 설명을 해보도록 하자. 우리가 항상 시작하는건 Todo 앱이다 (항상 이렇게 쓰는것이 설명이 편하다.)

 

(1). flutter bloc 라이브러리 추가 @ pubspec.yaml

dependencies:
  flutter_bloc: ^8.0.1

 

(2) bloc 생성. Bloc은 상태, 이벤트 그리고 상태 변화 처리기를 사용하여 작성하여 Todo 리스트의 저장 및 업데이트를 위한 TodoBloc을 만든다. @TodoBloc.dart

import 'package:flutter_bloc/flutter_bloc.dart';

/// TodoBloc에서 이벤트 타입을 정의한다.
enum TodoEvent { add, delete }

/// TodoBloc 클래스 생성, TodoBloc 초기 상태 리스트로 설정
class TodoBloc extends Bloc<TodoEvent, List<String>> {
  TodoBloc() : super([]);

/// mapEventToState는 이벤트를 받아 상태 변화시키는 동작 정의
  @override
  Stream<List<String>> mapEventToState(TodoEvent event) async* {
    switch (event) {
      case TodoEvent.add:
      /// 새로운 할 일 항목을 추가하고 상태를 업데이트 한다.
        yield state..add('New Todo');
        break;
      case TodoEvent.delete:
      /// 마지막 할 일 항목을 제거하고 상태를 업데이트 한다.
        yield state..removeLast();
        break;
    }
  }
}

 

(3) UI 작성. 

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
  /// MaterialApp 앱의 루트 위젯
    return MaterialApp(
      title: 'Todo App',
      /// UI 레이아웃 정의
      home: Scaffold(
        appBar: AppBar(title: Text('Todo App')),
        /// TodoBloc 인스턴스의 생성 및 앱의 위젯 트리 추가
        body: BlocProvider(
        /// TodoBloc() 클래스를 호출한다.
          create: (_) => TodoBloc(),
          /// TodoList를 표시하는 위젯
          child: TodoList(),
        ),
      ),
    );
  }
}

class TodoList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
      /// TodoList를 화면에 꽉 차게 만듦
        Expanded(
        /// TodoBloc의 현재 상태를 표시 하고 리스트 형태로 표시
          child: BlocBuilder<TodoBloc, List<String>>(
            builder: (context, state) {
              return ListView.builder(
                itemCount: state.length,
                itemBuilder: (context, index) {
                return ListTile(
                title: Text(state[index]),
              );
            },
          );
        },
      ),
    ),
    Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: [
        FloatingActionButton(
          onPressed: () =>
              context.read<TodoBloc>().add(TodoEvent.add),
          tooltip: 'Add Todo',
          child: Icon(Icons.add),
        ),
        FloatingActionButton(
          onPressed: () =>
              context.read<TodoBloc>().add(TodoEvent.delete),
          tooltip: 'Delete Todo',
          child: Icon(Icons.delete),
        ),
      ],
    ),
  ],
);

이 코드에서 Bloc은 결국 애플리케이션의 데이터와 상태를 처리하고 있으며, Event와 State 클래스를 사용하여 작동을 하고 있습니다. TodoBloc이라는 Bloc에서 Todolist를 관리하고 있으며, TodoEvent는 할 일 항목을 추가하거나 제거하는 이벤트를 처리하고 있고 TodoState는 Todo 리스트의 현재 상태를 나타내고 있습니다.

 

Bloc은 중요한 관리 도구이기 때문에 애플리케이션을 더욱 효율적이고 유지보수가 용이하도록 할 수 있습니다. 개념 및 사용방법을 이해하는데 시간이 걸리므로 더 많은 이해와 실습이 필요합니다.

 

 

참고

1. 개발하는 남자, BLoC 패턴이 무엇이고, 왜 사용하는걸까? (https://sudarlife.tistory.com/entry/Bloc-패턴이-무엇이고-왜-사용하는-것일까)

 

728x90

'Flutter' 카테고리의 다른 글

[Practice] Todolist 만들기2  (0) 2023.04.13
11-2. BLoC과 freezed  (0) 2023.04.11
11. BLoC  (0) 2023.04.09
10-1. freezed의 다른 기능들  (0) 2023.04.09
10. freezed  (0) 2023.04.09