flutter

bloc timer

paulaner80 2021. 9. 30. 11:39
반응형

플러그 인을 통해서 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