CUBIT를 사용한 BLOC 패턴
Bloc.observer를 BlocObserver를 상속한 class로 초기화해서 onChange를 override해서 상태가 변할때마다 print 할것이고 navigation push 도 해볼 것 입니다.
pubspec.yaml
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.0
bloc: ^6.1.0 //추가
flutter_bloc: ^6.1.0 //추가
counter_observer.dart
import 'package:bloc/bloc.dart';
class CounterObserver extends BlocObserver {
@override
void onChange(Cubit cubit, Change change) {
print('CounterObserver onChange()');
print('${cubit.runtimeType} $change');
super.onChange(cubit, change);
}
}
상태가 변할때마다 onChange됩니다. 큐빗의 타입과 현재값과 변할 값을 print합니다.
main.dart
import 'package:flutter/material.dart';
import 'package:bloc/bloc.dart';
import 'counter/view/app.dart';
import 'counter/observer/counter_observer.dart';
void main() {
Bloc.observer = CounterObserver();
runApp(const CounterApp());
}
Bloc.observer로 CounterObserver를 초기화해줍니다.
app.dart
import 'package:flutter/material.dart';
import 'counter/view/counter_page.dart';
class CounterApp extends MaterialApp{
const CounterApp({Key key}) : super(key: key, home: const CounterPage1());
}
MaterialApp을 하나로 두고 CounterPage1를 home에 넣습니다. 추가할 위젯이 필요없어서 MaterialApp만 상속 받았습니다.
counter_first_cubit.dart
import 'package:bloc/bloc.dart';
class CounterFirstCubit extends Cubit<int>{
CounterFirstCubit() : super(0);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
}
counter_second_subit.dart
import 'package:bloc/bloc.dart';
class CounterSecondCubit extends Cubit<int>{
CounterSecondCubit() : super(0);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
}
counter_first_page.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_bloc_sample/counter/cubit/counter_first_cubit.dart';
import 'counter_first_view.dart';
class CounterFirstPage extends StatelessWidget {
const CounterFirstPage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
print('CounterFirstPage build()');
return BlocProvider(
create: (_) => CounterFirstCubit(),
child: CounterFirstView(),
);
}
}
counter_first_view.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_bloc_sample/counter/cubit/counter_first_cubit.dart';
import 'package:flutter_bloc_sample/counter/view/counter_second/counter_second_page.dart';
class CounterFirstView extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('CounterFirstView build()');
final textTheme = Theme.of(context).textTheme;
return Scaffold(
appBar: AppBar(title: const Text('CounterFirstView')),
body: Center(
child: BlocBuilder<CounterFirstCubit, int>(
builder: (context, state) {
return Text('$state', style: textTheme.headline2);
},
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
FloatingActionButton(
heroTag: "btn1",
child: const Icon(Icons.add),
onPressed: () => context.read<CounterFirstCubit>().increment(),
),
const SizedBox(height: 8),
FloatingActionButton(
heroTag: "btn2",
child: const Icon(Icons.remove),
onPressed: () => context.read<CounterFirstCubit>().decrement(),
),
const SizedBox(height: 8),
FloatingActionButton(
heroTag: "btn3",
child: const Icon(Icons.navigate_next_rounded),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => CounterSecondPage()),
),
),
],
),
);
}
}
first의 맨 하단 floatingActionButton 에는 navigator push route를 정해줬습니다.
counter_second_page.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_bloc_sample/counter/cubit/counter_second_cubit.dart';
import 'counter_second_view.dart';
class CounterSecondPage extends StatelessWidget {
const CounterSecondPage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
print('CounterSecondPage build()');
return BlocProvider(
create: (_) => CounterSecondCubit(),
child: CounterSecondView(),
);
}
}
counter_second_view.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_bloc_sample/counter/cubit/counter_second_cubit.dart';
class CounterSecondView extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('CounterSecondView build()');
final textTheme = Theme.of(context).textTheme;
return Scaffold(
appBar: AppBar(title: const Text('CounterSecondView')),
body: Center(
child: BlocBuilder<CounterSecondCubit, int>(
builder: (context, state) {
return Text('$state', style: textTheme.headline2);
},
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
FloatingActionButton(
heroTag: "btn1",
child: const Icon(Icons.add),
onPressed: () => context.read<CounterSecondCubit>().increment(),
),
const SizedBox(height: 8),
FloatingActionButton(
heroTag: "btn2",
child: const Icon(Icons.remove),
onPressed: () => context.read<CounterSecondCubit>().decrement(),
),
],
),
);
}
}
+ 또는 - 클릭하였을때 onChange 함수가 실행된다.
navigation push 하였을때는 Cubit과 연관없어서 onChange가 실행되지않는다.
networking 하면 repository 같은 패키지를 만들어서 구분을 두는것이 좋겠다.
매우 깔끔히 구분이되어서 좋기는 하지만 너무 많은 구분으로 인해서 개발자가 피로하고 높은 복잡성은 문제점으로 보인다.
cubit 말고도 rxdart를 이용하면서 streambuilder를 사용하는 패턴도 있다.
종류가 매우 다양한데 단점으로 다가오는것 같다. 어느게 좋고 언제 사용해야할 적기인지 구분하기 힘들다.
현재는 provider 패턴을 사용하는것도 좋아보인다.
provider 패턴은 중규모프로젝트, bloc 패턴은 대규모 프로젝트에서 사용한다고 한다.