자바스크립트가 비활성화 되어있습니다.
자바스크립트가 활성화 되어야 콘텐츠가 깨지지 않고 보이게 됩니다.
자바스크립트를 사용할수 있도록 옵션을 변경해 주세요.
- willbsoon

본문 바로가기
Mobile/Flutter

[flutter] Bloc Usage - official document

by willbsoon 2022. 7. 19.

1. 예시를 보자

이제껏 이론적인것만 봐왔다면 이제는 예시를 통해서 좀더 직관적인 사용법을 익혀보자.

사용법과 함께본다면 조금 더 이해하는데 도움을 얻을 수 있을 것이다.

 

 

이전 포스팅을 보고싶다면

 

2022.07.19 - [Mobile/Flutter] - [flutter] Bloc 이란? - official document

2022.07.19 - [Mobile/Flutter] - [flutter] flutter_bloc 이란?(Bloc Widgets) - official document

2022.07.19 - [Mobile/Flutter] - [flutter] Bloc Usage - official document

 

 

2. Usage (링크)

 

 

 

 

 

 

 1) BlocProvider와 BlocBuilder 사용 예시

3개의 파일로 나눠 사용법을 알아보자.

- counter_bloc.dart

counter에 대한 event를 담고 있는 bloc을 생성함.(상태를 변경 시키는 비즈니스 로직을 담고 있음.)

abstract class CounterEvent {}
class CounterIncrementPressed extends CounterEvent {}
class CounterDecrementPressed extends CounterEvent {}

class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc() : super(0) {
    on<CounterIncrementPressed>((event, emit) => emit(state + 1));
    on<CounterDecrementPressed>((event, emit) => emit(state - 1));
  }
}

 

-main.dart

provider를 통해 view에 bloc을 전달하고 있음.

void main() => runApp(CounterApp());

class CounterApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: BlocProvider(
        create: (_) => CounterBloc(),
        child: CounterPage(),
      ),
    );
  }
}

 

-couter_page.dart

ui만을 담당하는 코드를 만들고 BlocBuilder를 통해 state가 변경되면 업데이트를 시켜줌. 또 버튼이 눌렸을때 Bloc을 검색해 이벤트를 실행시키는 코드.

class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Counter')),
      body: BlocBuilder<CounterBloc, int>(
        builder: (context, count) {
          return Center(
            child: Text(
              '$count',
              style: TextStyle(fontSize: 24.0),
            ),
          );
        },
      ),
      floatingActionButton: Column(
        crossAxisAlignment: CrossAxisAlignment.end,
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          Padding(
            padding: EdgeInsets.symmetric(vertical: 5.0),
            child: FloatingActionButton(
              child: Icon(Icons.add),
              onPressed: () => context.read<CounterBloc>().add(CounterIncrementPressed()),
            ),
          ),
          Padding(
            padding: EdgeInsets.symmetric(vertical: 5.0),
            child: FloatingActionButton(
              child: Icon(Icons.remove),
              onPressed: () => context.read<CounterBloc>().add(CounterDecrementPressed()),
            ),
          ),
        ],
      ),
    );
  }
}

이 시점에서 프레젠테이션 계층을 비즈니스 논리 계층에서 성공적으로 분리했다. CounterPage 위젯은 사용자가 버튼을 탭할 때 어떤 일이 발생하는지 전혀 알지 못한다. 위젯은 단순히 CounterBloc에 사용자가 증가 또는 감소 버튼을 눌렀음을 알려준다.

 

 

 2) RepositoryProvider 사용 예시

- weather_repository.dart

class WeatherRepository {
  WeatherRepository({
    MetaWeatherApiClient? weatherApiClient
  }) : _weatherApiClient = weatherApiClient ?? MetaWeatherApiClient();

  final MetaWeatherApiClient _weatherApiClient;

  Future<Weather> getWeather(String city) async {
    final location = await _weatherApiClient.locationSearch(city);
    final woeid = location.woeid;
    final weather = await _weatherApiClient.getWeather(woeid);
    return Weather(
      temperature: weather.theTemp,
      location: location.title,
      condition: weather.weatherStateAbbr.toCondition,
    );
  }
}

앱에는 WeatherRepository에 대한 명시적인 종속성이 있으므로 생성자를 통해 인스턴스를 주입한다. 이를 통해 빌드 플레이버 또는 환경에 따라 WeatherRepository의 다른 인스턴스를 주입할 수 있다.

 

-main.dart

import 'package:flutter/material.dart';
import 'package:flutter_weather/app.dart';
import 'package:weather_repository/weather_repository.dart';

void main() {
  runApp(WeatherApp(weatherRepository: WeatherRepository()));
}

앱에는 저장소가 하나만 있으므로 RepositoryProvider.value를 통해 위젯 트리에 삽입한다. 리포지토리가 두 개 이상인 경우 MultiRepositoryProvider를 사용하여 하위 트리에 여러 repository 인스턴스를 제공할 수 있다.

 

 

-app.dart

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

class WeatherApp extends StatelessWidget {
  const WeatherApp({Key? key, required WeatherRepository weatherRepository})
      : _weatherRepository = weatherRepository,
        super(key: key);

  final WeatherRepository _weatherRepository;

  @override
  Widget build(BuildContext context) {
    return RepositoryProvider.value(
      value: _weatherRepository,
      child: BlocProvider(
        create: (_) => ThemeCubit(),
        child: WeatherAppView(),
      ),
    );
  }
}

대부분의 경우 루트 앱 위젯은 RepositoryProvider를 통해 하위 트리에 하나 이상의 리포지토리를 노출한다.

 

-weather_page.dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_weather/weather/weather.dart';
import 'package:weather_repository/weather_repository.dart';

class WeatherPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => WeatherCubit(context.read<WeatherRepository>()),
      child: WeatherView(),
    );
  }
}

이제 bloc 을 인스턴스화할 때 context.read를 통해 저장소 인스턴스에 액세스하고 생성자를 통해 저장소를 블록에 주입할 수 있다.

전체 예시는 여기에서 확인할 수 있다.

 

 

 

 

 

 

 

 

 

 3) 확장 메서드(Extension Methods)

package:flutter_bloc은 package:provider의 확장인 ReadContext, WatchContext 및 SelectContext를 export 한다.

 

Context.read

context.read<T>()는 T 유형의 가장 가까운 조상 인스턴스를 찾고 기능적으로 BlocProvider.of<T>(context)와 동일합니다. context.read는 onPressed 콜백 내에 이벤트를 추가하기 위해 블록 인스턴스를 검색하는 데 가장 일반적으로 사용됩니다.

onPressed() {
  context.read<CounterBloc>().add(CounterIncrementPressed()),
}

 

Context.watch

context.read<T>()와 마찬가지로 context.watch<T>()는 T 유형의 가장 가까운 조상 인스턴스를 제공하지만 인스턴스의 변경 사항도 수신합니다. BlocProvider.of<T>(context, listen: true)와 기능적으로 동일합니다.

제공된 T 유형의 개체가 변경되면 context.watch가 다시 빌드를 트리거합니다.

Widget build(BuildContext context) {
  return MaterialApp(
    home: Scaffold(
      body: BlocBuilder<MyBloc, MyState>(
        builder: (context, state) {
          // Whenever the state changes, only the Text is rebuilt.
          return Text(state.value);
        },
      ),
    ),
  );
}


//Alternatively, use a Builder to scope rebuilds.
@override
Widget build(BuildContext context) {
  return MaterialApp(
    home: Scaffold(
      body: Builder(
        builder: (context) {
          // Whenever the state changes, only the Text is rebuilt.
          final state = context.watch<MyBloc>().state;
          return Text(state.value);
        },
      ),
    ),
  );
}

 

Context.select

context.watch<T>()와 마찬가지로 context.select<T, R>(R function(T value))는 T 유형의 가장 가까운 조상 인스턴스를 제공하고 T에서 변경 사항을 수신합니다. context.watch와 달리 context.select 상태의 더 작은 부분에서 변경 사항을 수신할 수 있습니다.

위는 ProfileBloc 상태의 속성 이름이 변경될 때만 위젯을 다시 빌드합니다.

Widget build(BuildContext context) {
  return MaterialApp(
    home: Scaffold(
      body: BlocSelector<ProfileBloc, ProfileState, String>(
        selector: (state) => state.name,
        builder: (context, name) {
          // Whenever the state.name changes, only the Text is rebuilt.
          return Text(name);
        },
      ),
    ),
  );
}

// Alternatively, use a Builder to scope rebuilds.

@override
Widget build(BuildContext context) {
  return MaterialApp(
    home: Scaffold(
      body: Builder(
        builder: (context) {
          // Whenever state.name changes, only the Text is rebuilt.
          final name = context.select((ProfileBloc bloc) => bloc.state.name);
          return Text(name);
        },
      ),
    ),
  );
}

 

 

 

 

3. 결론

 

 

 

 

 

 

 

 

 

 

 

댓글