이 글에서는 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
'Flutter > Widget' 카테고리의 다른 글
Flutter bottomNavigationBar (상태유지) 문서 (0) | 2022.05.03 |
---|---|
Flutter Stepper widget 문서 (0) | 2022.05.02 |
Flutter ListTile 프로퍼티 문서 (0) | 2021.05.22 |
Flutter ExpansionPanel 문서 (0) | 2021.05.14 |
Flutter Navigator(push, pop, replace) 문서 (0) | 2021.05.12 |