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. 결론
'Mobile > Flutter' 카테고리의 다른 글
flutter ci&cd, firebase flavor (0) | 2022.09.07 |
---|---|
Flutter android - BuildConfig 사용 (0) | 2022.07.23 |
Flutter flavors 설정 (0) | 2022.07.23 |
[flutter] flutter_bloc 이란?(Bloc Widgets) - official document (0) | 2022.07.19 |
[flutter] Bloc 이란? - official document (0) | 2022.07.19 |
댓글