본문으로 바로가기
  1. Home
  2. Flutter/Widget
  3. Flutter FutureBuilder , StreamBuilder 문서

Flutter FutureBuilder , StreamBuilder 문서

· 댓글개 · Dev_Whale

이 글에서는 FutureBuilder를 약속과 같다 하고 StreamBuilder 비동기 Iterator과 같다고 말한다.
FutureBuilder는 HTTP를 사용하거나 하나의 response를 가져올 때 사용한다고 한다.

StreamBuilder는 수시로 바뀌는 값에 사용을 하게 된다. 글의 마지막 말에 Future는 값이 변한 걸 알지 못하지고 1회성 이다. 대신에 Stream을 사용하면 가능하다. 그래서 직접 확인해보려고 한다.


공통

  • 비동기적 데이터를 받아올 때 사용하기 적절하다.
  • 4가지의 ConnectionState가 존재한다.
    • ConnectionState.none - null 일 때 initialData, defaultValue가 사용된다.
    • ConnectionState.active - null 아니지만 값을 받아오진 않았을 때
    • ConnectionState.waiting - 데이터가 오고 있으며 곧 결과를 얻을 수 있을 때
    • ConnectionState.done - 데이터가 도착하였을 때

FutureBuilder

  • 일회성 데이터에 사용하기 적합하다.

StreamBuilder

  • 수시로 변하는 데이터에 사용하기 적합하다.
  • async 가 아닌 async* 를 사용한다.
  • return 이 아닌 yield를 사용한다.

pubspec.yaml (22/4/30 기준)

  cupertino_icons: ^1.0.4
  http: ^0.13.4
  web_socket_channel: ^2.2.0
  cloud_firestore: ^3.1.13
  firebase_core: ^1.15.0

FutureBuilder 실습

fetchFirstUser() - 1번째 유저의 데이터를 3초 후에 futureUser에 리턴한다.

fetchSecondUser() - 2번째 유저의 데이터를 3초 후에 futureUser에 리턴한다.

그 후 floatingActionButton을 눌렀을 때 fetchSecondUser() 메서드가 3초 후에 futureUser에 두 번째 유저의 데이터를 리턴한다. 결과적으로 화면에는 아무런 변화가 없었다. 하지만 값은 변했다.

 

왼쪽 gif는 setState가 없고 오른쪽 gif는 setState가 있다.

import 'package:flutter/material.dart';
import 'package:future_app/FutureBuilder/User/user.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;

class UserPage extends StatefulWidget {
  @override
  _UserPageState createState() => _UserPageState();
}

class _UserPageState extends State<UserPage> {
  Future<User> futureUser;

  @override
  void initState() {
    super.initState();
    futureUser = fetchFirstUser();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Http Demo'),
        centerTitle: true,
      ),
      body: Center(
        child: FutureBuilder(
          future: futureUser,
          builder: (context, AsyncSnapshot<User> snapshot) {
            if (snapshot.hasError) {
              return Text('${snapshot.error}');
            } else if (snapshot.hasData) {
              return Column(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text('name : ${snapshot.data.name}'),
                  Text('userName : ${snapshot.data.userName}'),
                  Text('email : ${snapshot.data.email}'),
                  Text('phone : ${snapshot.data.phone}'),
                ],
              );
            }
            return CircularProgressIndicator();
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.next_plan),
        onPressed: () async {
          futureUser = fetchSecondUser(); // setState 만 추가해서 확인가능
        },
      ),
    );
  }

  Future<User> fetchFirstUser() async {
    await Future.delayed(Duration(seconds: 3));
    final response = await http.get(Uri.parse("https://jsonplaceholder.typicode.com/users/1"));

    if (response.statusCode == 200) {
      return User.fromJson(json.decode(response.body));
    } else {
      return null;
    }
  }

  Future<User> fetchSecondUser() async {
    await Future.delayed(Duration(seconds: 3));
    final response = await http.get(Uri.parse("https://jsonplaceholder.typicode.com/users/2"));

    if (response.statusCode == 200) {
      print(response.body);
      return User.fromJson(json.decode(response.body));
    } else {
      return null;
    }
  }
}

이렇게 setState를 넣어주면 값이 3초 후에 rebuild 되면서 값이 변하는 걸 확인할 수 있다. 이렇게 사용하면 Futurebuilder 용도를 잘못 사용하는 것 같았지만 어쩌다 한 번은 사용할 때가 있지 않을까 생각한다.

마지막으로 Streambuilder에서 확인하러 가볼 것이다.


StreamBuilder 실습

StreamBuilder에서는 setState 없이 StreamBuilder 위젯 부분만 rebuild 되는 것을 확인할 수 있었다.

import 'package:flutter/material.dart';
import 'package:web_socket_channel/io.dart';
import 'package:future_app/FutureBuilder/User/user.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;

class StreamPage extends StatefulWidget {
  @override
  _StreamPageState createState() => _StreamPageState();
}

class _StreamPageState extends State<StreamPage> {
  bool _running = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('StreamBuilder Page'),
        centerTitle: true,
      ),
      body: Column(
        children: [
          Card(
            margin: EdgeInsets.all(10),
            child: Column(
              children: [
                Center(child: Text('시간 StreamBuilder Example')),
                SizedBox(height: 5),
                StreamBuilder(
                  stream: clock(),
                  builder: (context, snapshot) {
                    if (snapshot.connectionState == ConnectionState.waiting) {
                      return CircularProgressIndicator();
                    }
                    return Text(snapshot.data, style: TextStyle(fontSize: 40));
                  },
                ),
                SizedBox(
                  height: 5,
                ),
                Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    ElevatedButton(
                        onPressed: () {
                          setState(() => _running = true);
                        },
                        child: Text('시작')),
                    ElevatedButton(onPressed: () => _running = false, child: Text('멈춤')),
                  ],
                ),
              ],
            ),
          ),
          StreamBuilder(
            stream: fetchFirstUser(),
            builder: (context, AsyncSnapshot<User> snapshot) {
              if (snapshot.connectionState == ConnectionState.waiting) {
                return Text('대기중');
              } else {
                return Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text('name : ${snapshot.data.name}'),
                    Text('userName : ${snapshot.data.userName}'),
                    Text('email : ${snapshot.data.email}'),
                    Text('phone : ${snapshot.data.phone}'),
                  ],
                );
              }
            },
          )
        ],
      ),
    );
  }

  Stream<String> clock() async* {
    while (_running) {
      await Future.delayed(Duration(milliseconds: 500));
      DateTime _now = DateTime.now();
      yield "${_now.hour} : ${_now.minute} : ${_now.second}";
    }
  }

  Stream<User> fetchFirstUser() async* {
    for (int i = 1; i <= 10; i++) {
      await Future.delayed(Duration(seconds: 2));
      final response = await http.get(Uri.parse("https://jsonplaceholder.typicode.com/users/$i"));

      if (response.statusCode == 200) {
        yield User.fromJson(json.decode(response.body));
      } else {
        yield null;
      }
    }
  }
}

StreamBuilder(
  stream: FirebaseFirestore.instance.collection('hello').doc('world').snapshots(),
  builder: (context, snapshot) {
    switch (snapshot.connectionState) {
      case ConnectionState.done:
        return Text('연결상태 : done', style: TextStyle(fontSize: 20));
        break;
      case ConnectionState.none:
        return Text('연결상태 : none', style: TextStyle(fontSize: 20));
        break;
      case ConnectionState.waiting:
        return Text('연결상태 : waiting', style: TextStyle(fontSize: 20));
        break;
      case ConnectionState.active:
        if (snapshot.data.exists) {
          return Text('연결상태 : active \n값: ${snapshot.data['name']}', style: TextStyle(fontSize: 20));
        } else {
          return Text('연결상태 : active + 데이터 없음', style: TextStyle(fontSize: 20));
        }
        break;
      default:
        return Text('default');
        break;
    }
  },
)

Source Code

 

SeongWoo-97/blog_upload_project

Contribute to SeongWoo-97/blog_upload_project development by creating an account on GitHub.

github.com

최근 글
Dev_Whale의 Flutter 블로그
추천하는 글
Dev_Whale의 Flutter 블로그
💬 댓글 개
이모티콘창 닫기
울음
안녕
감사해요
당황
피폐

이모티콘을 클릭하면 댓글창에 입력됩니다.