Study Record

[Flutter] StreamBuilder 본문

Flutter/widget

[Flutter] StreamBuilder

초코초코초코 2023. 2. 19. 01:20
728x90

✍ StreamBuilder Widget

FutureBuilder 와 비슷하게 stream 과 상호작용하며 값을 리턴 받을 때마다 다른 위젯들을 보여줘야 할 때 유용한 위젯이다.

StreamBuilder<T>({   
  Key? key,   
  T? initialData,   
  Stream? stream,  
  required Widget Function(BuildContext, AsyncSnapshot<T> ) builder,
})

stream 인자에 stream 을 설정하면 값(데이터 타입: T)을 리턴 받을 때마다 builder 함수가 호출되면서 AsyncSnapshot<T> 에 리턴 받은 값, 상태를 보고 판단하여 상황에 맞게 위젯들을 보여줄 수 있다. initialData가 null 이 아니면 초기 데이터 값이 null 이 아닌 initialData값으로 설정된다.

 

 

예시)

import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';

void main() => runApp(MaterialApp(home: HomeScreen()));

class HomeScreen extends StatelessWidget {
  HomeScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      body: _MyStatefulWidget(),
    );
  }
}

class _MyStatefulWidget extends StatefulWidget {
  _MyStatefulWidget({Key? key}) : super(key: key);

  @override
  State<_MyStatefulWidget> createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<_MyStatefulWidget> {
  TextStyle textStyle = TextStyle(
    color: Colors.black,
    fontSize: 15.0
  );

  Stream<int> _calculate() async* {
    for(int i=1; i<4; i++){
      yield i;

      await Future.delayed(Duration(seconds: 2));
    }
  }

  @override
  Widget build(BuildContext context) {
    return DefaultTextStyle(
      style: Theme.of(context).textTheme.displayMedium!,
      textAlign: TextAlign.center,
      child: StreamBuilder<int>(
        stream: _calculate(),
        builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
          print("connectionState : ${snapshot.connectionState} data : ${snapshot.data}");
          return Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              Text("snapshot state : ${snapshot.connectionState}", style: textStyle,),
              Text("data : ${snapshot.data}", style: textStyle,),
              Text("error : ${snapshot.error}", style: textStyle,),
              ElevatedButton(
                onPressed: () {
                  setState(() {});
                },
                child: Text("setsTate()"),
              )
            ],
          );
        },
      ),
    );
  }
}

로그 기록

snapshot 은 작업 상태(snapshot.connectionState)와 반환값(snapshot.data) 정보를 알 수 있는데, connectionState는 4가지 상태를 가질 수 있다.

  • ConnectionState.none : initalData만 있는 상태
  • ConnectionState.waiting : future(비동기 작업)이 시작 단계에 있는 상태로 snapshot.data은 initialData인 상태이다.
  • ConnectionState.active : 비동기 작업의 반환값(snapshot.data)이 null 이 아니지만 언제든 바뀔 수 있는 상태이다. StreamBuilder 에서만 사용된다.
  • ConnectionState.done : 비동기 작업이 완료된 시점이며 반환값(snapshot.data)이 null 이 아니다.

 

또한, 비동기 작업 중 오류가 있다면 snapshot.hasError 값이 true 이며 초기 데이터(initialData)가 null 일 경우 비동기 작업의 반환값이 아직 없으면 snapshot.hasData 는 false을 리턴한다.

 

 

 

+ setState() 와 StreamBuilder

StatefulWiget 에서 setState() 를 하면 build 함수가 다시 실행되면서 StreamBuilder도 다시 만들어진다. 이 때, stream 을 함수로 설정하면 비동기 작업도 다시 실행된다. 이는 위의 예시에서 setState() 버튼을 눌렀을 때 다시 비동기 작업을 시작하고 이에 따른 UI도 변경된 모습까지 확인할 수 있다. 

 

stream 을 변수로 설정하면 setState()를 해도 비동기 작업을 반복하지 않는다.

import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';

void main() => runApp(MaterialApp(home: HomeScreen()));

class HomeScreen extends StatelessWidget {
  HomeScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      body: _MyStatefulWidget(),
    );
  }
}

class _MyStatefulWidget extends StatefulWidget {
  _MyStatefulWidget({Key? key}) : super(key: key);

  @override
  State<_MyStatefulWidget> createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<_MyStatefulWidget> {
  TextStyle textStyle = TextStyle(
    color: Colors.black,
    fontSize: 15.0
  );

  final Stream<int> _bids = (() {
    late final StreamController<int> controller;
    controller = StreamController<int>(
      onListen: () async {
        await Future<void>.delayed(const Duration(seconds: 1));
        controller.add(1);
        await Future<void>.delayed(const Duration(seconds: 1));
        controller.add(2);
        await controller.close();
      },
    );
    return controller.stream;
  })();

  @override
  Widget build(BuildContext context) {
    return DefaultTextStyle(
      style: Theme.of(context).textTheme.displayMedium!,
      textAlign: TextAlign.center,
      child: StreamBuilder<int>(
        stream: _bids,
        builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
          print("connectionState : ${snapshot.connectionState} data : ${snapshot.data}");
          return Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              Text("snapshot state : ${snapshot.connectionState}", style: textStyle,),
              Text("data : ${snapshot.data}", style: textStyle,),
              Text("error : ${snapshot.error}", style: textStyle,),
              ElevatedButton(
                onPressed: () {
                  setState(() {});
                },
                child: Text("setsTate()"),
              )
            ],
          );
        },
      ),
    );
  }
}

 

 

 

StreamBuilder class - widgets library - Dart API

Widget that builds itself based on the latest snapshot of interaction with a Stream. Widget rebuilding is scheduled by each interaction, using State.setState, but is otherwise decoupled from the timing of the stream. The builder is called at the discretion

api.flutter.dev

 

728x90