24 분 소요

1. 네트워크 프로그래밍

1) JSON 파싱하기

(1) JSON 데이터 디코딩과 인코딩하기

  • dart:convert 패키지를 이용하여 JSON → Map 형식으로 변경
  • Map 형식으로 변환
      Map<String, dynamic> map = jsonDecode(jsonStr);
    	
      setState(() {
          result = "decode : id: ${map['id']}, title: ${map['title']}, completed: ${map['completed']}"
      });
    
  • List 형식으로 변환([{}, {}]형식일 시)
      List list = jsonDecode(jsonStr);
    
  • Json인코딩
      result = "encode : ${jsonEncode(map)}"
      result = "encode : ${jsonEncode(list)}"
    

(2) 모델 클래스로 JSON 데이터 이용하기

  • 클래스에 변수명과 각 타입이 지정된 채로 받음
      class Todo {
          int id;
          String title;
          bool completed;
          // 생성자
          Todo(this.id, this.title, this.completed);
    
          // 생성자2
          Todo.fromJson(Map<String, dynamic> json)
              : id = json['id'], title = josn['title'], completed = json['completed'];
    	
          Map<string, dynamic> toJson() => {
              'id': id,
              'title': title,
              'completed': completed
          };
      }
    
      Map<String, dynamic> map = jsonDecode(jsonStr);
      Todo todo = Todo.fromJson(map);
    

✨코드 예시

import 'dart:convert';
import 'package:flutter/material.dart';
  
void main() {
  runApp(MyApp());
}
  
class MyApp extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return MyAppState();
  }
}

class Todo {
  int id;
  String title;
  bool completed;
  
  Todo(this.id, this.title, this.completed);
  
  Todo.fromJson(Map<String, dynamic> json)
      : id = json['id'],
        title = json['title'],
        completed = json['completed'];

  Map<String, dynamic> toJson() =>
      {'id': id, 'title': title, 'completed': completed};
}

class MyAppState extends State<MyApp> {
  String jsonStr = '{"id": 1, "title": "HELLO", "completed": false}';
  Todo? todo;
  String result = '';
  
  onPressDecode() {
    Map<String, dynamic> map = jsonDecode(jsonStr);
    todo = Todo.fromJson(map);
    setState(() {
      result =
          "decode : id: ${todo?.id}, title: ${todo?.title}, completed: ${todo?.completed}";
    });
  }
  
  onPressEncode() {
    setState(() {
      result = "encode : ${jsonEncode(todo)}";
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
            appBar: AppBar(title: Text('Test')),
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text('$result'),
                  ElevatedButton(
                      onPressed: onPressDecode, child: Text('Decode')),
                  ElevatedButton(
                      onPressed: onPressEncode, child: Text('Encode')),
                ],
              ),
            )));
  }
}

(3) JSON 데이터 자동 매핑하기 - json_serializable

  • pubspec.yaml
      dependencies:
          json_annotation: ^4.5.0
      dev_dependencies:
          build_runner: ^2.1.11
          json_serializable: ^6.2.0
    
  • 패키지 임포트
      import 'package:json_annotation/json_annotation.dart';
      import 'test_json_serializable.g.dart';
    
  • 모델 클래스 파일
      @JsonSerializable()
      class Todo {
          @JsonKey(name: "id")
          int todoId;
          String title;
          bool completed;
          Todo(this.todoId, this.title, this.completed);
          factory Todo.fromJson(Map<String, dynamic> json) => _$TodoFromJson(json);
          Map<String, dynamic> toJson() => _$TodoToJson(json);
      }
    
  • terminal 입력
      flutter pub run build_runner build
    
  • 중첩 클래스 매핑(Location 클래스)
      @JsonSerializable()
      class Location {
          String latitude;
          String longitude;
    	
          Location(this.latitude, this.longitude);
    	
          factory Location.fromJson(Map<String, dynamic> json) => _$LocationFromJson(json);
          Map<String, dynamic> tojson() => _$LocationToJson(this);
      }
    
  • 중첩 클래스 매핑(Todo 클래스)
      @JsonSerializable
      class Todo {
          @JsonKey(name: "id")
          int todoId;
          String title;
          bool completed;
          Location location;  // 중첩 클래스
    	
          Todo(this.todoId, this.title, this.completed, this.location);
          factory Todo.fromJson(Map<String, dynamic> json) => _$TodoFromJson(json);
          Map<String, dynamic> toJson() => _$TodoToJson(this);
      }
    
  • Todo 객체 출력하기
      Map<String, dynamic> map = jsonDecode(jsonStr);
      todo = Todo.fromJson(map);
      print(todo?.toJson());
    
  • Location 객체에 담긴 값 출력하기
      @JsonSerializable(explicitToJson: true)
      class Todo{
          Location location; // 중첩 클래스
      }
    

✨코드 예시

  • pubspec.yaml
      dependencies:
          json_annotation: ^4.5.0
      dev_dependencies:
          build_runner: ^2.1.11
          json_serializable: ^6.2.0
    
  • test.dart
      import 'dart:convert';
      import 'package:flutter/material.dart';
      import 'package:json_annotation/json_annotation.dart';
    	  
      part 'test.g.dart';
    	
      void main() {
        runApp(MyApp());
      }
    	  
      class MyApp extends StatefulWidget {
        @override
        State<StatefulWidget> createState() {
          return MyAppState();
        }
      }
    	
      @JsonSerializable()
      class Location {
        String latitude;
        String longitude;
    	  
        Location(this.latitude, this.longitude);
        factory Location.fromJson(Map<String, dynamic> json) =>
            _$LocationFromJson(json);
        Map<String, dynamic> toJson() => _$LocationToJson(this);
      }
    	  
      @JsonSerializable(explicitToJson: true)
      class Todo {
        @JsonKey(name: "id")
        int todoId;
        String title;
        bool completed;
    	  
        Location location;
        Todo(this.todoId, this.title, this.completed, this.location);
        factory Todo.fromJson(Map<String, dynamic> json) => _$TodoFromJson(json);
        Map<String, dynamic> toJson() => _$TodoToJson(this);
      }
    	
      class MyAppState extends State<MyApp> {
        String jsonStr = '{"id" : 1, "title" : "HELLO", "completed" : false, "location" : {"latitude":"37.5", "longitude":"127.1"}}';
        Todo? todo;
        String result = '';
    	  
        onPressDecode() {
          Map<String, dynamic> map = jsonDecode(jsonStr);
          todo = Todo.fromJson(map);
          print(todo?.toJson());
          setState(() {
            result = "decode : ${todo?.toJson()}";
          });
        }
    	
        onPressEncode() {
          setState(() {
            result = "encode : ${jsonEncode(todo)}";
          });
        }
    	
        @override
        Widget build(BuildContext context) {
          return Materia력
      ```bash
      flutter pub run build_runner build
    
  • test.g.dart(생성된 파일)
      part of 'test.dart'; 
    	
      Location _$LocationFromJson(Map<String, dynamic> json) => Location(
            json['latitude'] as String,
            json['longitude'] as String,
          );
    	
      Map<String, dynamic> _$LocationToJson(Location instance) => <String, dynamic>{
            'latitude': instance.latitude,
            'longitude': instance.longitude,
          };
    	  
      Todo _$TodoFromJson(Map<String, dynamic> json) => Todo(
            json['id'] as int,
            json['title'] as String,
            json['completed'] as bool,
            Location.fromJson(json['location'] as Map<String, dynamic>),
          );
    	  
      Map<String, dynamic> _$TodoToJson(Todo instance) => <String, dynamic>{
            'id': instance.todoId,
            'title': instance.title,
            'completed': instance.completed,
            'location': instance.location.toJson(),
          };
    

2) http 패키지 이용하기

  • pubspec.yaml
      dependencies:
          http: ^0.13.4
    
  • 패키지 임포트
      import 'package:http/http.dart' as http;
    
  • 서버에 요청하기
      http.Response response =
          await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts/1'));
    
  • 서버에서 전달한 데이터 얻기
      if (response.statusCode == 200) {
          String result = response.body;
      }
    
  • 헤더 이용하기
      Map<String, String> headers = {
          "content-type" : "application/json",
          "accept" : "application/json"
      };
      http.Response response =
          await http.get(Uri.parse('https://jsonplaceholder.typicode.com/post/1'),
          headers: headers);
    
  • POST 방식으로 요청
      http.Response response =
      await http.post(Uri.parse('https://jsonplaceholder.typicode.com/posts'),
                     body : {'title' : 'hello', 'body' : 'world', 'userId' : 1});
    
  • 반복해서 요청하기
      var client = http.Client();
      try {
          http.Response response =
              await client.post(Uri.parse('https://jsonplaceholder.typicode.com/posts'),
                  body: {'title' : 'hello', 'body' : 'world', 'userId' : '1'});
    	
          if (response.statusCode == 200 || response.statusCode == 201) {
              response = await client.get(
                  Uri.parse('https://jsonplaceholder.typicode.com/posts/1')
              );
              print('response: ${response.body}');
          } else {
              print('error......');
          }
      } finally {
          client.close();
      }
    

✨코드 예시

  • pubspec.yaml
      dependencies:
          http: ^0.13.4	
    
  • test.dart
      import 'package:flutter/material.dart';
      import 'package:http/http.dart' as http;
    	  
      void main() {
        runApp(MyApp());
      }
    	
      class MyApp extends StatefulWidget {
        @override
        State<StatefulWidget> createState() {
          return MyAppState();
        }
      }
    	
      class MyAppState extends State<MyApp> {
        String result = '';
    	  
        onPressGet() async {
          Map<String, String> headers = {
            "content-type": "application/json",
            "accept": "application/json"
          };
          http.Response response = await http.get(
              Uri.parse('https://jsonplaceholder.typicode.com/posts/1'),
              headers: headers);
          if (response.statusCode == 200) {
            setState(() {
              result = response.body;
            });
          } else {
            print('error......');
          }
        }
    	  
        onPressPost() async {
          try {
            http.Response response = await http.post(
                Uri.parse('https://jsonplaceholder.typicode.com/posts'),
                body: {'title': 'hello', 'body': 'world', 'userId': '1'});
            print('statusCode : ${response.statusCode}');
            if (response.statusCode == 200 || response.statusCode == 201) {
              setState(() {
                result = response.body;
              });
            } else {
              print('error......');
            }
          } catch (e) {
            print('error ... $e');
          }
        }
    	  
        onPressClient() async {
          var client = http.Client();
          try {
            http.Response response = await client.post(
                Uri.parse('https://jsonplaceholder.typicode.com/posts'),
                body: {'title': 'hello', 'body': 'world', 'userId': '1'});
    	  
            if (response.statusCode == 200 || response.statusCode == 201) {
              response = await client
                  .get(Uri.parse('https://jsonplaceholder.typicode.com/posts/1'));
              setState(() {
                result = response.body;
              });
            } else {
              print('error......');
            }
          } finally {
            client.close();
          }
        }
    	  
        @override
        Widget build(BuildContext context) {
          return MaterialApp(
              home: Scaffold(
                  appBar: AppBar(
                    title: Text('Test'),
                  ),
                  body: Center(
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Text('$result'),
                        ElevatedButton(onPressed: onPressGet, child: Text('GET')),
                        ElevatedButton(onPressed: onPressPost, child: Text('POST')),
                        ElevatedButton(
                            onPressed: onPressClient, child: Text('Client')),
                      ],
                    ),
                  )));
        }
      }
    

3) dio 패키지 이용하기

  • pubspec.yaml
      dependencies:
          dio: ^4.0.6
    
  • GET 방식으로 요청
      try {
          var response = await Dio().get('https://reqres.in/api/users?page=2');
          if (response.statusCode == 200) {
              String result = response.data.toString();
              print("result... $result");
          }
      } catch (e) {
          print(e);
      }
    
  • queryParameters 매개변수로 데이터 전달
      var response = await.Dio().get('https://reqres.in/api/users', queryParameters: {'page':2});
    
  • POST방식으로 요청
      var response = await Dio().post(
          'https://reqres.in/api/users',
          data: {
              'name': 'kkang',
              'job': 'instructor'
          }
      );
    

(1) request() 함수로 요청

  • request() 함수로 요청
      var response = await Dio().request(
          'https://reqres.in/api/users',
          data: {
              'name': 'kkang',
              'job': 'instructor'
          },
          options: Options(method: 'POST')
      )
    

(2) BaseOptions로 Dio 속성 지정하기

  • BaseOptions로 Dio 속성 지정하기
      var dio = Dio(BaseOptions(
          baseUrl: 'https://reqres.in/api/',
          connectTimeout: 5000,
          receiveTimeout: 5000,
          headers: {
              HttpHeaders.contentTypeHeader: 'application/json',
              HttpHeaders.acceptHeader: 'application/json'
          }
      ))
    

(3) 동시 요청하기

  • 동시 요청하기
      List<Response<dynamic>> response =
          await Future.wait([dio.get('https://reqres.in/api/users?page=1'),
          dio.get('https://reqres.in/api/users?page=2')]);
    	
      response.forEach((element) {
          if (element.statusCode == 200) {
              String result = element.data.toString();
              print('result... $result');
      }});
    

(4) 파일 전송하기 - MultifileUpload

  • 파일 전송하기
      MultipartFile.fromFile('./test.txt', filename:'upload.txt')
    
  • 파일 데이터를 지정해서 전송하기
      MultipartFile multipartFile = new MultipartFile.fromBytes(
          imageData, // 파일 데이터
          filename: 'load_image',
          contentType: MediaType('image', 'jpg')
      );
    
  • FormData 객체로 파일 전송하기
      var formData = FormData.fromMap({
          'name':'kkang',
          'file': await MultipartFile.fromFile('./test.txt', filename:'upload.txt')
      });
      var response = await dio.post('/info', data: formData);
    

(5) 요청이나 응답 가로채기 - Interceptor

  • 인터셉터 작성하기
      class MyInterceptor extends Interceptor {
          @override
          void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
              print('request... ${options.method}, ${options.path}');
              print('request data : ${options.data}');
              super.onRequest(options, handler); // 서버 요청
          }
    	
          @override
          void onResponse(Response response, ResponseInterceptorHandler handler) {
              print('response... ${response.statusCode}, ${response.requestOptions.path}');
              print('response data : ${response.data}');
              super.onResponse(response, handler);  // 결괏값 반환
          }
    		
          @override
          void onError(DioError err, ErrorInterceptorHandler handler) {
              super.onError(err, handler);
              print('error... ${err.response?.statusCode}, ${err.requestOptions.path}');
          }
      }
    
  • dio에 인터셉터 추가하기
      var dio = Dio();
      dio.interceptors.add(MyInterceptor());
      await dio.post(
          'https://reqres.in/api/users',
          data: {
              'name': 'kkang',
              'job': 'instructor'
          }
      )
    
  • InterceptiorsWrapper 이용하기
      dio.interceptors.add(InterceptorsWrapper(
          onRequest: (options, handler) {
              print('request... ${options.method}, ${options.path}');
              print('request data : ${options.data}');
              handler.next(options);  // 서버 요청
          },
          onResponse: (response, handler) {
              print('response... ${response.statusCode}, ${response.requestOptions.path}');
              print('request data : ${response.data}');
              handler.next(response);  // 결괏값 반환
          }
      ));
    
  • 서버 대신 응답하기
      onRequest: (options, handler) {
          print('request... ${options.method}, ${options.path}');
          print('request data : ${options.data}');
          // handler.next(options);  // 서버 요청
          handler.resolve(Response(requestOptions: options, data: {'hello':'world'}));
      }
    
  • 3초 후 요청하기
      onRequest: (options, handler) {
          dio.lock();
          handler.next(options);
          Timer(Duration(seconds: 3), () {
              dio.unlock();
          });
      }
    

✨코드 예시

  • pubspec.yaml
      dependencies:
          dio: ^4.0.6
    
  • test.dart
      import 'dart:async';
      import 'dart:io';
      import 'package:flutter/material.dart';
      import 'package:dio/dio.dart';
      import 'package:flutter_lab/ch13/customscrollview_sliverappbar.dart';
    	  
      void main() {
        runApp(MyApp());
      }
    	
      class MyApp extends StatefulWidget {
        @override
        State<StatefulWidget> createState() {
          return MyAppState();
        }
      } 
    	
      class MyAppState extends State<MyApp> {
        String result = '';
    	  
        dioTest() async {
          try {
            var dio = Dio(BaseOptions(
                baseUrl: 'https://reqres.in/api/',
                connectTimeout: 5000,
                receiveTimeout: 5000,
                headers: {
                  HttpHeaders.contentTypeHeader: 'application/json',
                  HttpHeaders.acceptHeader: 'application/json'
                }));
    	  
            List<Response<dynamic>> response = await Future.wait([
              dio.get('https://reqres.in/api/users?page=1'),
              dio.get('https://reqres.in/api/users?page=2')
            ]);
            response.forEach((element) {
              if (element.statusCode == 200) {
                setState(() {
                  result = element.data.toString();
                });
              }
            });
          } catch (e) {
            print(e);
          }
        }
    	  
        @override
        Widget build(BuildContext context) {
          return MaterialApp(
              home: Scaffold(
                  appBar: AppBar(
                    title: Text('Test'),
                  ),
                  body: Center(
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Text('$result'),
                        ElevatedButton(
                            onPressed: dioTest, child: Text('Get Server Data'))
                      ],
                    ),
                  )));
        }
      }
    

2. 퓨처와 스트림으로 비동기 프로그래밍

1) 퓨처와 퓨처 빌더

(1) 퓨처 - Future

  • 비동기 프로그래밍을 지원
    • 비동그 프로그래밍 : 시간이 오래 걸리는 작업을 실행한 후 끝날 때까지 기다리지 않고 다음 작업을 실행
  • 동기 프로그래밍
      void sum() {
          var sum = 0;
          Stopatch stopwatch = Stopwatch();
          stopwatch.start();
    	
          for (int i = 0; i < 500000000; i++) {
              sum += i;
          }
          stopwatch.stop();
          print('${stopwatch.elapsed}, result: $sum')
      }
    	
      void onPress() {
          print('onPress top...');
          sum();
          print('onPress bottom...');
      }
    
  • 비동기 처리
      Future<int> sum() {
          return Future<int>(() { // 미래의 데이터를 담을 상자 반
              var sum = 0;
              Stopwatch stopwatch = Stopwatch();
              stopwatch.start();
              for (int i = 0; i < 500000000; i++) {
                  sum += i;
              }
              stopwatch.stop();
              print('${stopwatch.elapsed}, result: $sum');
              return sum;  // 실제 데이터를 상자에 담기
          })
      }
    	
      void onPress() {
          print('onPress top...');
          sum();
          print('onPress bottom...')
      }
    

(2) 퓨처 빌더 - FutureBuilder

  • FutureBuilder의 생성자 : 결과 대기 후 화면 출력(자체 화면 보유X)
      const Future Builder<t>(
          { Key? key,
          Future<T>? future,
          T? initialData,
          required AsyncWidgetBuilder<T> builder
          }
      )
    
  • AsyncWidgetBuilder 정의
      AsyncWidgetBuilder<T> = Widget Function(
          BuildContext context,
          AsyncSnapshot<T> snapshot
      )
    
  • 퓨처 데이터 출력
      body: Center(
          child : FutureBuilder(
              future : calFun(),
              builder : (context, snapshot) {
                  if (snapshot.hasData) {
                      return Text('${snapshot.data}');
                  }
                  return CircularProgressIndicator();
      }))
    

✨코드 예시

import 'dart:async';
import 'package:flutter/material.dart';
  
void main() {
  runApp(MyApp());
}
  
class MyApp extends StatelessWidget {
  Future<int> sum() {
    return Future<int>(() {
      var sum = 0;
      for (int i = 0; i < 500000000; i++) {
        sum += i;
      }
      return sum;
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
            appBar: AppBar(title: Text('Test')),
            body: FutureBuilder(
                future: sum(),
                builder: (context, snapshot) {
                  if (snapshot.hasData) {
                    return Center(
                      child: Text('${snapshot.data}',
                          style: TextStyle(color: Colors.black, fontSize: 30)),
                    );
                  }
                  return Center(
                      child: Text('waiting',
                          style: TextStyle(color: Colors.black, fontSize: 30)));
                })));
  }
}

2) await와 async

  • Future에 담은 데이터 가져오기
      void onPress() {
          print('onPress top...');
          Future<int> future = sum();
          print('onPress future: $future');
          print('onPress bottom...');
      }
    

(1) then() 함수 사용하기

  • then() 함수로 Future에 담은 데이터 가져오기
          print('onPress top...');
          Future<int> future = sum();
          future.then((value) => print('onPress then... $value'));
          future.catchError((error) => print('onPress catchError... $error'));
          print('onPress bottom...');
    
  • 시간이 오래 걸리는 함수 2개
      Future<int> funA() {
          return Future.delayed(Duration(seconds: 3), () {
              return 10;
          });
      }
      Future<int> funB(int arg) {
          return Future.delayed(Duration(second: 2), () {
              return arg * arg
          });
      }
    
  • then() 함수 중첩
      Future<int> calFun() {
          return funA().then((aResult) {
              return funB(aResult);
          }).then((bResult) {
              return bResult;
          });
      }
    

(2) await와 async 사용하기

  • await, async로 처리
    • async : 비동기식 메써드 설정
    • await는 Future의 객체가 완료될 때까지 기다림, 해당 객체의 값을 반환
  • ✨코드 예시
      Future<int> calFun() async {
          int aResult = await funA();
          int bResult = await funB(aResult);
          return bResult;
      }
    

✨코드 예시

import 'dart:async';
import 'package:flutter/material.dart';
  
void main() {
  runApp(MyApp());
}
  
class MyApp extends StatelessWidget {
  Future<int> funA() {
    return Future.delayed(Duration(seconds: 3), () {
      return 10;
    });
  }
  
  Future<int> funB(int arg) {
    return Future.delayed(Duration(seconds: 2), () {
      return arg * arg;
    });
  }
  
  Future<int> calFun() async {
    int aResult = await funA();
    int bResult = await funB(aResult);
    return bResult;
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
            appBar: AppBar(title: Text('Test')),
            body: Center(
                child: FutureBuilder(
              future: calFun(),
              builder: (context, snapshot) {
                if (snapshot.hasData) {
                  return Center(
                      child: Text('${snapshot.data}',
                          style: TextStyle(color: Colors.black, fontSize: 30)));
                }
                return Center(
                    child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    SizedBox(
                      width: 100,
                      height: 100,
                      child: CircularProgressIndicator(),
                    ),
                    Text('waiting...',
                        style: TextStyle(color: Colors.black, fontSize: 20))
                  ],
                ));
              },
            ))));
  }
}

3) 스트림과 스트림 빌더

(1) 스트림 - Stream

  • Stream : (미래에) 반복해서 발생하는 데이터
  • 데이터 한 번 반환
      Future<int> futureFun() async {
          return await Future.delayed(Duration(seconds: 1), () {
              return 10;
          });
      }
    	
      void onPress() async {
          await futureFun().then((value) => print("result: $value"));
      }
    
  • 데이터 5번 반환
      Stream<int> streamFun() async* {
          for (int i = 1; i <= 5; i++) {
              await Future.delayed(Duration(seconds: 1));
              yield i;
          }
      }
    	
      void onPress() async {
          await for (int value in streamFun()) {
              print("value : $value");
          }
      }
    
  • listen() 함수로 반환값 여러 번 받기
      void onPress() {
          streamFun().listen((value) {
              print('value : $value');
          });
      }
    

(2) 스트림을 만드는 여러 가지 방법

  • Iterable 타입 데이터 만들기 - fromIterable()
      var stream = Stream.fromIterable([1, 2, 3]);
      stream.listen((value) {
          print("value : $value");
      })
    
  • Future 타입 데이터 만들기 - fromFuture()
      Future<int> futureFun() {
          return Future.delayed(Duration(seconds: 3), () {
              return 10;
          });
      }
    	
      test4() {
          var stream = Stream.fromFuture(futureFun());
          stream.listen((value) {
              print("value : $value");
          });
      }
    
  • 주기 지정하기 - periodic()
      int calFun(int x) {
          return x*x;
      }
    	
      test1() async {
          Duration duration = Duration(seconds: 2);
          Stream<int> stream = Stream<int>.periodic(duration, calFun);
          await for (int value in stream) {
              print('value : $value');
          }
      }
    
  • 횟수 지정하기 - take()
      int calFun(int x) {
          return x*x;
      }
    	
      test1() async {
          Duration duration = Duration(seconds: 2);
          Stream<int> stream = Stream<int>.periodic(duration, calFun); // 2초에 한 번씩 데이터 발생
          stream = stream.take(3);
          await for (int value in stream) {
              print('value : $value');
          }
      }
    
  • 조건 지정하기 - takeWhile()
      int calFun(int x) {
          return x * x;
      }
    	
      test1() async {
          Duration duration = Duration(seconds: 2);
          Stream<int> stream = Stream<int>.periodic(duration, calFun);
          // 조건 설정
          stream = stream.takeWhile((value) {
              return value < 20;
          });
          await for (int value in stream) {
              print('value : $value');
          }
      }
    
  • 생략 지정하기 - skip()
      int calFun(int x) {
          return x * x;
      }
    	
      test1() async {
          Duration duration = Duration(seconds: 2);
          Stream<int> stream = Stream<int>.periodic(duration, calFun);
          // stream = stream.take(3);
          stream = stream.takeWhile((value) {
              return value < 20;
          });
          // 생략 지정 하기
          stream = stream.skip(2);
          await for (int value in stream) {
              print('value : $value');
          }
      }
    
  • 생략 조건 지정하기 - skipWhile()
      int calFun(int x) {
          return x * x;
      }
    	
      test1() async {
          Duration duration = Duration(seconds: 2);
          Stream<int> stream = Stream<int>.periodic(duration, calFun);
          stream = stream.skipWhile((value) {
              return value < 50;
          });
          await for (int value in stream) {
              print('value : $value');
          }
      }
    
  • List 타입으로 만들기 - toList()
      int calFun(int x) {
          return x * x;
      }
    	
      test2() async {
          Duration duration = Duration(seconds: 2);
          Stream<int> stream = Stream<int>.periodic(duration, calFun);
          stream = stream.take(3);
          Future<List<int>> future = stream.toList();  // list타입으로 만들기
          future.then((list) {
              list.forEach((value) {
                  print('value : $value');
              });
          });
      }
    

(3) 스트림 빌더 - StreamBuilder

  • 스트림 빌더 사용하기
      body: Center(
          child: StreamBuilder(
              stream: test(),
              builder: (BuildContext context, AsyncSnapshot<int> snapshot {
                  if (snapshot.hasData) {
                      return Text('data : ${snapshot.data}');
                  }
                  return CircularProgressIndicator();
              })
          )
      )
    
  • AsyncSnapshot의 connectionState 속성
    • ConnectionState.waiting : 데이터 발생을 기다리는 상태
    • ConnectionState.active : 데이터가 발생하고 있으며 아직 끝나지 않은 상태
    • ConnectionState.done : 데이터 발생이 끝난 상태
  • 연결 상태 파악하기
    Center(
      child: StreamBuilder(
          stream: test(),
          builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
              if (snapshot.connectionState == ConnectionState.done) {
                  return Text(
                      'Completed',
                      style: TextStyle(
                          fontSize: 30.0
                      ),
                  );
              } else if (snapshot.connectionState == ConnectionState.waiting) {
                  return Text(
                      'Waiting For Stream',
                      style : TextStyle(
                          fontSize: 30.0
                      )
                  );
              }
              return Text(
                  'data : ${snapshot.data}',
                  style : TextStyle(
                      fontSize: 30.0
                  )
              );
          }
      )
    );
    

✨코드 예시

import 'dart:async';
import 'package:flutter/material.dart';
  
void main() {
  runApp(MyApp());
}
 
class MyApp extends StatelessWidget {
  int calFun(int x) {
    return x * x;
  }
  
  Stream<int> test() {
    Duration duration = Duration(seconds: 3);
    Stream<int> stream = Stream<int>.periodic(duration, calFun);
    return stream.take(5);
  }
  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
            appBar: AppBar(title: Text('Test')),
            body: Center(
                child: StreamBuilder(
                    stream: test(),
                    builder:
                        (BuildContext context, AsyncSnapshot<int> snapshot) {
                      if (snapshot.connectionState == ConnectionState.done) {
                        return Center(
                            child: Text('Completed',
                                style: TextStyle(fontSize: 30.0)));
                      } else if (snapshot.connectionState ==
                          ConnectionState.waiting) {
                        return Center(
                          child: Column(
                              mainAxisAlignment: MainAxisAlignment.center,
                              children: [
                                SizedBox(
                                    width: 100,
                                    height: 100,
                                    child: CircularProgressIndicator()),
                                Text('waiting...',
                                    style: TextStyle(fontSize: 20))
                              ]),
                        );
                      }
                      return Center(
                          child: Text(
                        'data : ${snapshot.data}',
                        style: TextStyle(fontSize: 30.0),
                      ));
                    }))));
  }
}

4) 스트림 구독, 제어기, 변환기

(1) 스트림 구독자 - StreamSubscription

  • 스트림 데이터 얻기
      var stream = Stream.fromIterable([1, 2, 3]);
      stream.listen((value) {
          print("value : $value");
      });
    
  • onError와 onDone 함수
      var stream = Stream.fromIterable([1,2,3]);
      stream.listen((value) {
          print('value : $value');
      },
      onError: (error) {
          print('error : $error');
      },
      onDone: () {
          print('stream done...');
      });
    
  • onError와 onDone 따로 정의하기
      var stream = Stream.fromIterable([1, 2, 3]);
      StreamSubscription subscription = stream.listen(null);
    	
      subscription.onData((data) {
          print('value : $data');
      });
      subscription.onError((error) {
          print('value : $data');
      });
      subscription.onDone(() {
          print('stream done...');
      });
    

(2) 스트림 제어기 - StreamController

  • 스트림 제어기
      var controller = StreamController();
    	
      var stream1 = Stream.fromIterable([1,2,3]);
      var stream2 = Stream.fromIterable(['A','B','C']);
    	
      stream1.listen((value) {
          contoller.add(value);
      });
      stream2.listen((value) {
          contoller.add(value);
      });
    	
      controller.stream.listen((value) {
          print('$value');
      });
    
  • 스트림에 다른 데이터 추가
      controller.stream.listen((value) {
          print('$value');
      });
    	
      controller.add(100);
      controller.add(200);
    
  • 두 번째 가져오기 실패
     var stream1 = Stream.fromIterable([1, 2, 3]);
     stream1.listen((value) {print('listen1 : $value');}); // 정상
     stream1.listen((value) {print('listen2 : $value');}); // 오류
    
  • 방송용 스트림 제어기 만들기
      var controller = StreamController.broadcast();
      controller.stream.listen((value) {print('listen1 : $value');});
      controller.stream.listen((value) {print('listen2 : $value');});
      controller.add(100);
      controller.add(200);
    

(3) 스트림 변환기 - StreamTransformer

  • 스트림 변환기
      var stream = Stream.fromIterable([1, 2, 3]);
    	
      StreamTransformer<int, dynamic> transformer =
          StreamTransformer.fromHandlers(handleData: (value, sink) {
          print('in transformer... $value');
      });
    
  • sink 매개변수 이용하기
      var stream = Stream.fromIterable([1, 2, 3]);
    	
      StreamTransformer<int, dynamic> transformer = StreamTransformer.fromHandlers(handleData: (value, sink) {
          print('in transformer... $value');
      });
    	
      stream.transform(transformer).listen((value) {
          print('in listen... $value');
      });
    

✨코드 예시

import 'dart:async';
import 'dart:html';
import 'package:flutter/material.dart';
  
void main() {
  runApp(MyApp());
}
  
class MyApp extends StatelessWidget {
  subscriptionTest() {
    var stream = Stream.fromIterable([1, 2, 3]);
    StreamSubscription subscription = stream.listen(null);
    subscription.onData((data) {
      print('value : $data');
    });
    subscription.onError((error) {
      print('error : $error');
    });
    subscription.onDone(() {
      print('stream done...');
    });
  }

  controllerTest() {
    var controller = StreamController();
  
    var stream1 = Stream.fromIterable([1, 2, 3]);
    var stream2 = Stream.fromIterable(['A', 'B', 'C']);
  
    stream1.listen((value) {
      controller.add(value);
    });
    stream2.listen((value) {
      controller.add(value);
    });

    controller.stream.listen((value) {
      print('$value');
    });
  }
  
  transformerTest() {
    var stream = Stream.fromIterable([1, 2, 3]);
  
    StreamTransformer<int, dynamic> transformer =
        StreamTransformer.fromHandlers(handleData: (value, sink) {
      print('in transformer... $value');
      sink.add(value * value);
    });
  
    stream.transform(transformer).listen((value) {
      print('in listen... $value');
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
            appBar: AppBar(
              title: Text('Test'),
            ),
            body: Center(
                child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                    child: Text('subscription'), onPressed: subscriptionTest),
                ElevatedButton(
                    child: Text('controller'), onPressed: controllerTest),
                ElevatedButton(
                    child: Text('transformer'), onPressed: transformerTest),
              ],
            ))));
  }
}

댓글남기기