Flutter

[Flutter] 비동기 프로그래밍 (Future, Stream, async, async*, yeild, yeild*)

Hoo_Dev 2022. 12. 22. 17:37

동기와 비동기의 차이

동기

  • '직렬적'으로 작동하는 방식

비동기 -

  • '병렬적'으로 작동하는 방식이다.
  • 비동기란 특정 코드가 끝날때 까지 코드의 실행을 멈추지 않고 다음 코드를 먼저 실행하는 것

Future와 Stream의 차이( FutureBuilder(), StreamBuilder()** )**

FutureBuilder()

  • 일회성 데이터에 사용하기 적합 (이미지 가져오기, 파일 가져오기 등)
  • 데이터의 캐싱이 가장 큰 장점
  • setState가 없이 자동으로 화면에 변화를 FutureBuilder가 적용해줄 수 있다.
  • connectionState*가 바뀔때마다 builder함수가 새로 불린다.
    • ConnectionState의 상태 4가지
      • ConnectionState.none - null 일 때 initialData, defaultValue가 사용된다.
      • ConnectionState.active - null 아니지만 값을 받아오진 않았을 때
      • ConnectionState.waiting - 데이터가 오고 있으며 곧 결과를 얻을 수 있을 때
      • ConnectionState.done - 데이터가 도착하였을 때

StreamBuilder()

  • 수시로 변하는 데이터에 사용하기 적합하다. (지도, 음악 등)
  • StreamBuilder 또한 데이터 캐싱이 됨.
  • async 가 아닌 async* 를 사용한다.
  • return 이 아닌 yield를 사용한다.

FutureBuilder()의 간단 예제

import 'dart:math';

import 'package:flutter/material.dart';

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

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  @override
  Widget build(BuildContext context) {
    final textStyle = TextStyle(
      fontSize: 16.0,
    );

    return Scaffold(
        body: Padding(
          padding: const EdgeInsets.all(8.0),
          // 화면에 나온다 -> connectionState가 바뀔때마다 builder함수가 새로 불린다.
          // setState가 없이 자동으로 화면에 변화를 FutureBuilder가 적용해줄 수 있다
          // connectionState가 waiting -> done 으로 바뀜을 알 수 있음
          child: FutureBuilder(
            future: getNumber(),
            builder: (context, snapshot) {

              if(snapshot.hasData) {
                // 데이터가 있을 때 위젯 렌더링
              }

              if(snapshot.hasError) {
                // 에러가 났을 때 위젯 렌더링
              }

              // 로딩중일때 위젯 렌더링

              // 한번도 데이터를 저장하지 않았을 때(not hasData) 로딩바를 보여주는게 좋다.
              // if (!snapshot.hasData) {
              //   return Center(
              //     child: CircularProgressIndicator(),
              //   );
              // }

              return Column(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  Text(
                    'FutureBuilder',
                    style: textStyle.copyWith(
                      fontWeight: FontWeight.w700,
                      fontSize: 20.0,
                    ),
                  ),
                  Text(
                    'ConState:${snapshot.connectionState}',
                    style: textStyle,
                  ),
                  Text(
                    'Data : ${snapshot.data}',
                    style: textStyle,
                  ),
                  Text(
                    'Error : ${snapshot.error}',
                    style: textStyle,
                  ),
                  // setState 버튼을 누르면 해당 클래스의 빌더가 다시 실행되면서 Future도 재실행 됨.
                  // Data가 null이 안들어 가는 이유? -> FutureBuilder의 캐싱 때문(기존 데이터가 유지된 상태에서 재빌드 )
                  ElevatedButton(
                      onPressed: () {
                        setState(() {});
                      },
                      child: Text('setState'))
                ],
              );
            },
          ),
        ));
  }

  Future<int> getNumber() async {
    await Future.delayed(Duration(seconds: 3));

    final random = Random();

    // 에러를 던지면 data는 null로 돌아가고 error에는 error가 나옴
    throw Exception('에러가 발생했습니다.');

    return random.nextInt(100);
  }
}

FutureBuilder()의 간단 예제

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

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

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  @override
  Widget build(BuildContext context) {
    final textStyle = TextStyle(
      fontSize: 16.0,
    );

    return Scaffold(
        body: Padding(
          padding: const EdgeInsets.all(8.0),
          child: StreamBuilder<int>(
            stream: streamNumbers(),
            builder: (context, AsyncSnapshot<int> snapshot) {

              return Column(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  Text(
                    'StreamBuilder',
                    style: textStyle.copyWith(
                      fontWeight: FontWeight.w700,
                      fontSize: 20.0,
                    ),
                  ),
                  Text(
                    'ConState:${snapshot.connectionState}',
                    style: textStyle,
                  ),
                  Text(
                    'Data : ${snapshot.data}',
                    style: textStyle,
                  ),
                  Text(
                    'Error : ${snapshot.error}',
                    style: textStyle,
                  ),

                  ElevatedButton(
                      onPressed: () {
                        setState(() {});
                      },
                      child: Text('setState'))
                ],
              );
            },
          ),
        ));
  }

  Future<int> getNumber() async {
    await Future.delayed(Duration(seconds: 3));

    final random = Random();
    return random.nextInt(100);
  }

  Stream<int> streamNumbers() async* {
    for(int i = 0; i < 10; i ++) {
      if (i == 5){
        throw Exception('i = 5');
      }
       await Future.delayed(Duration(seconds: 1));
      yield i;
    }
  }
}