Dandy Now!
  • [Flutter] "Do it! 플러터 앱 프로그래밍" - Dart 기초 문법 | 특징, async/await, JSON, Stream
    2022년 02월 10일 00시 55분 41초에 업로드 된 글입니다.
    작성자: DandyNow
    728x90
    반응형

    Dart 기초 문법 요약정리

    "조준수. (2021). Do it! 플러터 앱 프로그래밍. 이지스퍼블리싱", 이 책에서 Dart 문법이 차지하는 비중은 적지만 서버와의 통신 부분(비동기 처리, JSON, 스트림 통신)을 꽤 많이 할애하고 있다. 이 점이 좋아 보인다!

     

    Do it! 플러터 앱 프로그래밍

    플러터 기본 & 고급 위젯은 물론오픈 API와 파이어베이스를 이용한 앱 개발부터 배포까지!플러터 SDK 2.x 버전을 반영한 개정판!이 책은 플러터의 기초부터 고급 활용법까지 다루어 다양한 영역에

    book.naver.com

     


     

    다트 언어의 9가지 특징

    1. main() 함수로 시작
    2. 어디에서나 변수 선언 및 사용 가능
    3. 모든 변수가 객체(Object 클래스 상속)
    4. 자료형이 엄격하나 유연한 var, dynamic 자료형도 존재
    5. 제네릭 타입 이용 가능
    6. 접근 제한자 없으나 외부 노출을 막고 싶다면 함수, 변수 명 앞에 언더스코어(_)로 가능
    7. 변수, 함수 명은 언더스코어, 문자열로 시작, 숫자로 시작하면 안 됨
    8. 삼항 연산자 사용 가능
    9. Null safety 지원

     

    var, dynamic 자료형의 차이점

    두 자료형은 자료형 추론(type inference)에 해당한다. 자료형 추론은 변수를 선언하면 변수에 저장되는 값의 유형에 따라 자료형이 정해지는 것을 말한다.

    구분 자료형 내용
    자료형 추론 var 최초 할당되는 변수의 자료형으로 고정되어 다른 자료형으로 변경 불가
    dynamic 할당되는 변수의 자료형에 따라 계속적으로 자료형 변경 가능

     

    Null safety

    프로그램 실행 중 널 예외가 발생하면 프로그램이 중지된다. 이를 코드 단계에서 구분하여 작성할 수 있도록 한다. 자료형 뒤에 ?를 붙이면 null을 허용한다. 식 뒤에 !를 붙이면 null 이 아님을 직접 표시한다.

    int? couldReturnNullButDoesnt() => null; // null을 넣을 수 있다.
    
    void main() {
      List<int?> listThatCouldHoldNulls = [2, null, 4]; // List 요소 값으로 null 사용 가능하다.
      List<int>? nullList; // List 자체가 null일 수 있다.
      int b = listThatCouldHoldNulls.first!; // !는 null이 아님을 직접 표시한다.
    
      print('b is $b.'); // b is 2.
    }

     


     

    async / await

    async를 이용해 비동기 처리를 구현한다. 비동기란 언제 끝날지 모르는 작업을 기다리지 않고 다음 작업을 처리하게 하는 것이다. await을 비동기 함수 내 언제 끝날지 모르는 작업 앞에 붙이면 그 작업이 끝나기 전까지 다음 작업을 처리하지 않고 기다리게 한다. 비동기 처리의 브레이크 같은 인상이다!

    async 비동기 처리 예제

    async 비동기 처리 적용으로 인해 getVersionNameR 함수와 getVersionNameQ 함수를 처리하는 동안에 print 함수가 먼저 처리 완료되었다.

    void main() async {
      getVersionNameR().then((value) => {print(value)});  // (2등)
      getVersionNameQ().then((value) => {print(value)});  // (3등)
      print('end process'); // (1등)
    }
    
    // [출력결과]
    // end process
    // Android R
    // Android Q
    
    // Future, async, await의 위치를 잘 봐두자!
    // Future(값이 여러 개면 Stream), async가 붙으면 비동기로 만들겠다는 의미이다.
    // await은 결과를 반환할 때까지 다음 코드를 실행하지 않고 기다린다.
    Future<String> getVersionNameQ() async {
      var versionName = await lookUpVersionNameQ();
      return versionName;
    }
    
    Future<String> getVersionNameR() async {
      var versionName = await lookUpVersionNameR();
      return versionName;
    }
    
    String lookUpVersionNameQ() {
      return 'Android Q';
    }
    String lookUpVersionNameR() {
      return 'Android R';
    }

     

    await 예제 1

    async 비동기 처리 적용되었지만 getVersionNameR 함수에 await이 붙어있어 다음 차순으로 넘어가지 않고 처리가 끝나기를 기다린다. getVersionNameR 함수 처리가 완료되어야 비로소 getVersionNameQ로 넘어간다. 비동기 처리로 인해 getVersionNameQ 함수가 처리되는 동안 마지막 차순인 print 함수가 먼저 처리 완료되어 출력된다. getVersionNameQ는 가장 마지막에 처리 완료된다.

    void main() async {
      await getVersionNameR().then((value) => {print(value)});  // (1등)
      getVersionNameQ().then((value) => {print(value)});  // (3등)
      print('end process'); // (2등)
    }
    
    // [출력결과]
    // Android R
    // end process
    // Android Q
    
    Future<String> getVersionNameQ() async {
      var versionName = await lookUpVersionNameQ();
      return versionName;
    }
    
    Future<String> getVersionNameR() async {
      var versionName = await lookUpVersionNameR();
      return versionName;
    }
    
    String lookUpVersionNameQ() {
      return 'Android Q';
    }
    String lookUpVersionNameR() {
      return 'Android R';
    }

     

    await 예제 2

    async 비동기 처리 적용으로 getVersionNameR 함수가 처리되는 동안 getVersionNameQ 함수를 처리한다. getVersionNameQ 함수에는 await이 붙었기 때문에 처리가 완료되기 전까지 다음 차순으로 넘어가지 않는다. 그 사이 getVersionNameR 함수가 처리 완료되고, getVersionNameQ 함수도 완료된다. 비로소 기다리고 있던 print 함수가 처리된다.

    void main() async {
      getVersionNameR().then((value) => {print(value)});  // (1등)
      await getVersionNameQ().then((value) => {print(value)});  // (2등)
      print('end process'); // (3등)
    }
    
    // [출력결과]
    // Android R
    // Android Q
    // end process
    
    
    Future<String> getVersionNameQ() async {
      var versionName = await lookUpVersionNameQ();
      return versionName;
    }
    
    Future<String> getVersionNameR() async {
      var versionName = await lookUpVersionNameR();
      return versionName;
    }
    
    String lookUpVersionNameQ() {
      return 'Android Q';
    }
    String lookUpVersionNameR() {
      return 'Android R';
    }

     

    then

    비동기 함수가 데이터를 성공적으로 반환하면 호출하는 쪽에서 then() 함수로 처리할 수 있다. then() 함수 처리를 하지 않으면 데이터 반환이 완료되지 않은 상태로 진행된다. 그 결과 받아온 데이터가 없는 상태(Instance of 'Future<String>')가 출력되는 것이다. 이외에 error() 함수도 있는데 예외 처리에 이용할 수 있다.

    void main() async {
      getVersionNameR().then((value) => {print(value)}); // 2등)
      print(getVersionNameR()); // (1등) 
    }
    
    // [출력결과]
    // Instance of 'Future<String>'
    // Android R
    
    Future<String> getVersionNameR() async {
      var versionName = await lookUpVersionNameR();
      return versionName;
    }
    
    String lookUpVersionNameR() {
      return 'Android R';
    }

     


     

    JSON

    서버와 데이터를 주고받을 때 가장 많이 쓰이는 형식이다.

    jsonDecode / jsonIncode

    서버로 데이터를 보낼 때는 JSON 형태로 변환해야 하고, 받아서 활용할 때는 Map 형태로 변환해야 한다.

    import 'dart:convert';
    
    void main() {
      // jsonDncode는 JSON 형식을 Map 형식으로 변환한다.
      var jsonString = '''[{"score":40}, {"score":80}]''';
      var scores = jsonDecode(jsonString);
      print(scores); // [{score: 40}, {score: 80}]
      print(scores[0].runtimeType); // _InternalLinkedHashMap<String, dynamic>
    
      // jsonEncode는 Map 형식을 JSON 형식으로 변환한다.
      var sendScores = [
        {'score': 40},
        {'score': 40},
        {'score': 100}
      ];
      var jsonText = jsonEncode(scores);
      print(jsonText); // [{"score":40},{"score":80}]
      print(jsonText[0].runtimeType); // String
    }

     


     

    Stream

    통신 환경이 불안정할 때, 특정 API 호출이 늦어질 때 등의 상황에서도 순서를 보장받아야 할 때 사용한다. 스트림은 서버의 데이터가 변경되면 자동으로 화면에 반영되도록 할 때 유용하게 사용할 수 있다.

    async 이용

    // import 'dart:async'; // 주석처리 해도 정상 작동한다(본문에는 주석처리 되지 않았다).
    
    Future<int> sumStream(Stream<int> stream) async {
      var sum = 0;
      await for (var value in stream) {
        print('sumStream : $value');
        sum += value;
      }
      return sum;
    }
    
    // async*는 yield를 이용해 지속적으로 데이터를 전달하겠다는 의미이다(Stream, async*, yield가 한 세트).
    // 여래개의 데이터를 받기 위해 Future 대신 Stream이 사용되었다.
    Stream<int> countStream(int to) async* {
      for (int i = 1; i <= to; i++) {
        print('countStream : $i');
        yield i;
      }
    }
    
    main() async {
      var stream = countStream(10);
      var sum = await sumStream(stream);
      print(sum);
    }
    
    // [출력 결과]
    // countStream : 1
    // sumStream : 1
    // countStream : 2
    // sumStream : 2
    // countStream : 3
    // sumStream : 3
    // countStream : 4
    // sumStream : 4
    // countStream : 5
    // sumStream : 5
    // 15

     

    then 이용

    main() {
      var stream = Stream.fromIterable([1, 2, 3, 4, 5]);
      stream.first.then((value) => print('first: $value')); // 1
    
      var stream2 = Stream.fromIterable([1, 2, 3, 4, 5]);
      stream2.last.then((value) => print('last: $value')); // 5
      
      // 한번 사용한 변수는 재사용시 오류가 발생한다.
      // stream2.last.then((value) => print('last: $value')); // Bad state: Stream has already been listened to.
    
      var stream3 = Stream.fromIterable([1, 2, 3, 4, 5]);
      stream3.isEmpty.then((value) => print('isEmpty: $value')); // false
    
      var stream4 = Stream.fromIterable([1, 2, 3, 4, 5]);
      stream4.length.then((value) => print('length: $value')); // 5
    }

     

    728x90
    반응형
    댓글