플러그 인을 통해서 timer bloc 생성합니다.
1. Ticker 생성
Ticker는 어플리케이션의 데이터 소스가 될 것입니다. 구독하고 반응할 수 있는 틱 스트림을 노출합니다.
class Ticker{
const Ticker();
Stream<int> tick({required int ticks}){
return Stream.periodic(Duration(seconds: 1), (x)=>ticks-x-1).take(ticks);
}
}
BLOC
2. TimerState
Equatable 을 상속합니다.
그래야 같은 state로 인한 rebuild를 막을 수 있습니다.
TimerInitial, TimerRunPause, TimerRunInProgress, TimerRunComplete 구현합니다.
part of 'timer_bloc.dart';
abstract class TimerState extends Equatable {
final int duration;
const TimerState(this.duration);
@override
List<Object> get props => [duration];
}
class TimerInitial extends TimerState {
const TimerInitial(int duration):super(duration);
@override
String toString() =>"TimerInitial {duration : $duration}";
}
class TimerRunPause extends TimerState{
const TimerRunPause(int duration):super(duration);
@override
String toString() =>"TimerRunPause {duration : $duration}";
}
class TimerRunProgress extends TimerState{
const TimerRunProgress(int duration):super(duration);
@override
String toString() =>"TimerRunProgress {duration:$duration}";
}
class TimerComplete extends TimerState{
const TimerComplete():super(0);
}
3. TimerEvent
@immutable
abstract class TimerEvent extends Equatable{
const TimerEvent();
@override
List<Object?> get props => [];
}
class TimerStart extends TimerEvent{
final int duration;
const TimerStart({required this.duration});
}
class TimerPaused extends TimerEvent {
const TimerPaused();
}
class TimerResumed extends TimerEvent {
const TimerResumed();
}
class TimerReset extends TimerEvent {
const TimerReset();
}
class TimerTicked extends TimerEvent {
final int duration;
const TimerTicked({required this.duration});
@override
List<Object?> get props => [duration];
}
4. TimerBloc
기본 으로 생성된 코드 입니다.
class TimerBloc extends Bloc<TimerEvent, TimerState> {
TimerBloc() : super(TimerInitial()) {
on<TimerEvent>((event, emit) {
// TODO: implement event handler
});
}
}
여기에 초기값 TimerInitial(_duration) 과 Ticker를 설정해줍니다.
class TimerBloc extends Bloc<TimerEvent, TimerState> {
final Ticker _ticker;
static const int _duration = 60; // 추가
StreamSubscription<int>? _tickerSubscription; //추가
TimerBloc({required Ticker ticker})
:_ticker = ticker, //추가
super(const TimerInitial(_duration)) {// 추가
on<TimerEvent>((event, emit) {
// TODO: implement event handler
});
}
}
TimerStarted 이벤트 핸들러 구현
class TimerBloc extends Bloc<TimerEvent, TimerState> {
final Ticker _ticker;
static const int _duration = 60;
StreamSubscription<int>? _tickerSubscription;
TimerBloc({required Ticker ticker})
:_ticker = ticker,
super(const TimerInitial(_duration)) {
on<TimerStarted>(_onStarted); //변경
}
//추가
//TimerBloc가 종료될 때 _tickerSubscription을 종료하기 위해서
@override
Future<void> close() {
_tickerSubscription?.cancel();
return super.close();
}
//추가
//이벤트 핸들러 구현
//[add]새로운 이벤트가 트리거 되었다고 알림
void _onStarted(TimerStarted event, Emitter<TimerState> emit){
emit(TimerRunProgress(event.duration));
_tickerSubscription?.cancel();
_tickerSubscription = _ticker
.tick(ticks: event.duration)
.listen((duration)=>add(TimerTicked(duration: duration)));
}
}
TimeTickec 이벤트핸들러구현
class TimerBloc extends Bloc<TimerEvent, TimerState> {
...생략
on<TimerTicked>(_onTicked); //추가
...생략
void _onTicked(TimerTicked event, Emitter<TimerState> emit){
emit(
event.duration>0
? TimerRunProgress(event.duration)
: const TimerRunComplete(),
);
}
...생략
TimerPaused 이벤트핸들러 구현
class TimerBloc extends Bloc<TimerEvent, TimerState> {
...생략
on<TimerStarted>(_onStarted);
on<TimerPaused>(_onPaused); //추가
on<TimerTicked>(_onTicked);
...생략
void _onPaused(TimerPaused event, Emitter<TimerState> emit){
if(state is TimerRunProgress){
_tickerSubscription?.pause();
emit(TimerRunPause(state.duration));
}
}
...생략
TimerResumed 이벤트 핸들러 구현
class TimerBloc extends Bloc<TimerEvent, TimerState> {
...생략
on<TimerStarted>(_onStarted);
on<TimerPaused>(_onPaused);
on<TimerResumed>(_onResumed);//추가
on<TimerTicked>(_onTicked);
...생략
void _onResumed(TimerResumed event, Emitter<TimerState> emit){
if(state is TimerRunPause){
_tickerSubscription?.resume();
emit(TimerRunProgress(state.duration));
}
}
...생략
5. UI
5-1. timer_page.dart
만들 윗젯 목록입니다.
class TimerPage extends StatelessWidget {
const TimerPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container();
}
}
class TimerView extends StatelessWidget {
const TimerView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container();
}
}
class TimerText extends StatelessWidget {
const TimerText({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container();
}
}
class Actions extends StatelessWidget {
const Actions({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container();
}
}
class Background extends StatelessWidget {
const Background({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container();
}
}
5-1-1. TimerPage
BlocProvider를 정의해줍니다.
class TimerPage extends StatelessWidget {
const TimerPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create:(_)=>TimerBloc(ticker: Ticker())
);
}
}
5-1-2. TimerView
기본 형태를 잡아줍니다.
class TimerView extends StatelessWidget {
const TimerView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter Timer')),
body: Stack(
children: [
const Background(),
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: const <Widget>[
Padding(
padding: EdgeInsets.symmetric(vertical: 100.0),
child: Center(child: TimerText()),
),
Actions(),
],
),
],
),
);
}
}
5-1-3 TimerText
block를 통해 duration을 가져옵니다.
소비만 하는 경우는 context.select를 하면되는 것 같습니다.
context.read<TimerBloc>().
context.select((TimerBloc bloc) => bloc.state.duration) 이렇게 해도 되는 것 같습니다.
또는
final duration2 = context.read<TimerBloc>().state.duration; 이런식도 가능합니다.
class TimerText extends StatelessWidget {
const TimerText({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final duration = context.select((TimerBloc bloc) => bloc.state.duration);
final minutesStr = ((duration / 60) % 60).floor().toString().padLeft(2, '0');
final secondsStr = (duration % 60).floor().toString().padLeft(2, '0');
return Text(
'$minutesStr:$secondsStr',
style: Theme.of(context).textTheme.headline1,
);
}
}
5-1-4 Actions
BlocBuilder 를 사용합다.
이벤트를 발생시킬 때는 context.select를 사용하면 되는 것 같습니다.
context.read<TimerBloc>().add(TimerStarted(duration: state.duration)),
context.read<TimerBloc>().add(TimerReset()),
context.read<TimerBloc>().add(TimerPaused()),
class Actions extends StatelessWidget {
const Actions({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocBuilder<TimerBloc, TimerState>(
buildWhen: (prev, state) => prev.runtimeType != state.runtimeType,
builder: (context, state) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
if (state is TimerInitial) ...[
FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: () => context
.read<TimerBloc>()
.add(TimerStarted(duration: state.duration)),
),
],
if (state is TimerRunInProgress) ...[
FloatingActionButton(
child: Icon(Icons.pause),
onPressed: () => context.read<TimerBloc>().add(TimerPaused()),
),
FloatingActionButton(
child: Icon(Icons.replay),
onPressed: () => context.read<TimerBloc>().add(TimerReset()),
),
],
if (state is TimerRunPause) ...[
FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: () => context.read<TimerBloc>().add(TimerResumed()),
),
FloatingActionButton(
child: Icon(Icons.replay),
onPressed: () => context.read<TimerBloc>().add(TimerReset()),
),
],
if (state is TimerRunComplete) ...[
FloatingActionButton(
child: Icon(Icons.replay),
onPressed: () => context.read<TimerBloc>().add(TimerReset()),
),
]
],
);
},
);
}
}
5-1-5 Background
class Background extends StatelessWidget {
const Background({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.blue.shade50,
Colors.blue.shade500,
],
),
),
);
}
}
@immutable 설명
https://levelup.gitconnected.com/flutter-dart-immutable-objects-and-values-5e321c4c654e
@immutable 어노테이션은 클래스의 모든 필드가 final 이어야함을 나타냅니다.
context.select((TimerBloc bloc) => bloc.state.duration)
TimerBloc를 찾아서 반화함.
참고
https://bloclibrary.dev/#/fluttertimertutorial?id=timerbloc
'flutter' 카테고리의 다른 글
dart generator (0) | 2021.09.09 |
---|---|
dart future then (0) | 2021.04.09 |
Provider 예제 (0) | 2021.04.08 |
dart functor (0) | 2020.12.28 |
flutter - firebase 로그인, 로그아웃 (0) | 2020.12.21 |