꿈꾸는 시스템 디자이너

Flutter 강좌2 - 가변 인자 사용법 | @required와 assert 사용법 본문

Development/Flutter

Flutter 강좌2 - 가변 인자 사용법 | @required와 assert 사용법

독행소년 2019. 7. 25. 15:00

Flutter 강좌 시즌2 목록 : https://here4you.tistory.com/149

 

 

이번 강좌에서는 가변 인자를 사용하는 방법에 대해서 알아본다.

 

기존의 함수 호출방법은 다음과 같았다.

import 'package:flutter/material.dart';

void main() {
  String my_name = 'Park';
  int my_age = 39;
  String my_location = "Daejeon";

  Class_A class_A = Class_A(my_name, my_age);
  class_A.printInfo();
}

class Class_A {
  String name;
  int age;

  Class_A(String name, int age) {
    this.name = name;
    this.age = age;
  }

  void printInfo() {
    print('<Class_A> name: $name, age: $age');
  }
}

메인함수에서 Class_A의 생성자를 호출할 때, 생성자의 아규먼트 타입과 순서에 맞게 파라미터를 설정해야했다. 프로그램을 실행하면 다음과 같이 출력된다.

이 방식의 단점은 넘기고자 하는 파라미터 형태의 수만큼 메소드(생성자)를 오버로딩해야한다는 점이다.

 

우선 생성자를 좀 더 간결하게 구현하는 방법을 알아보기 위해 다음 코드를 살펴보자.

void main() {
  String my_name = 'Park';
  int my_age = 39;
  String my_location = "Daejeon";

  Class_B class_B = Class_B(age: my_age, name: my_name);
  class_B.printInfo();
}

class Class_B {
  String name;
  int age;

  Class_B({this.name, this.age});

  void printInfo() {
    print('<Class_B> name: $name, age: $age');
  }
}

Class_B의 생성자를 보면 생성자의 구현부분은 없고, 아규먼트 설정부분에서 줄괄호 안에 this 키워드를 통해 바로 필드값을 채우고 있다.

아규먼트를 받는 코드와 그 아규먼트를 필드에 채우는 코드를 모두 구현해야했던 기존 방식에 비해 코드가 짧아진다.

생성자를 호출할 때에도 장점이 있다. 아규먼트의 순서에 맞게 파라미터를 설정하지 않아도 된다. 첫번째 코드에서는 name과 age 순으로 아규먼트를 수신하다록 생성자를 설정하고, 생성자 호출시에도 그 순서에 맞게 파라미터를 입력해야했으나 이 코드에서는 순서는 무관하고 대신 필드명과 함께 파라미터값을 대입한다.

이 방식을 이용해서 가변인자도 처리할 수 있다.

void main() {
  String my_name = 'Park';
  int my_age = 39;
  String my_location = "Daejeon";

  Class_C class_c1 = Class_C(name: my_name, age: my_age, location: my_location);
  class_c1.printInfo();
  Class_C class_c2 = Class_C(age: my_age, location: my_location);
  class_c2.printInfo();
  Class_C class_c3 = Class_C(name: my_name);
  class_c3.printInfo();
}

class Class_C {
  String name;
  int age;
  String location;

  Class_C({this.name, this.age, this.location});

  void printInfo() {
    print('<Class_C> name: $name, age: $age, location: $location');
  }
}

Class_C는 필드 name, age, location을 가진다. 그리고 생성자에서 중괄호와 this 키워드를 이용해 아규먼트 값을 바로 필드로 채우게 된다.

메인함수에서는 Class_C의 인스턴스를 세개 만들어서 서로 다른 형태의 파라미터를 전달하고 있다.

첫번째 생성자 호출에서는 3개의 파리미터를 모두 전달했지만 두번째와 세번째에서는 일부만을 전달하고 있다.

실행결과는 다음과 같다. 전달하지 않은 파라미터에 해당하는 필드값은 null 값을 갖게되지만 생성자 하나로 세 형태의 파라미터를 전달할 수 있는 것을 확인할 수 있다.

 

그럼 기본의 방식과 가변인자 방식을 혼용하거나 특정 아규먼트는 꼭 채우도록 할 수는 없을까? 있다!!!

void main() {
  String my_name = 'Park';
  int my_age = 39;
  String my_location = "Daejeon";

  Class_D class_d1 = Class_D(my_name, age: my_age, location: my_location);
  class_d1.printInfo();
  Class_D class_d2 = Class_D(my_name, age: my_age);
  class_d2.printInfo();
}

class Class_D {
  String name;
  int age;
  String location;

  Class_D(String name, {this.age, @required this.location})
      : assert(location != null) {
    this.name = name;
  }

  void printInfo() {
    print('<Class_C> name: $name, age: $age, location: $location');
  }
}

Class_D의 생성자는 1개의 String 타입의 아규먼트를 기본 방식대로 수신하고, 필드 age와 location에 해당하는 아규먼트를 가변인자로 수신할 수 있다.

메인함수에서 생성자를 호출하는 부분을 보면 my_name값은 기존 방식대로 파라미터로 채우고, 나머지 두개의 파라미터는 가변인자 방식으로 혼용하여 채우고 있다. 이는 name에 해당하는 아규먼트는 호출시 파라미터를 꼭 채우도록 강제하는 방법으로 이용할 수 있다.

가변인자 중에 꼭 채워야하는 파라미터로 강제 설정하는 방식도 존재한다. 생성자를 다시 살펴보면 아규먼트 location 선언 앞에 @required라는 어노테이션이 추가되어 있다. 어노테이션이 붙은 아규먼트는 꼭 채워져야 한다는 의미를 가진다. 실제로 안드로이드 스튜디오에서 생성자를 호출하는 코드를 작성할 때 다음과 같은 형태로 코드가 자동 생성된다. name과 location 값은 생략되어선 안되니 해당 템플릿 코드가 자동으로 생성되는 것이다.

 

하지만 @required 어노테이션이 설정된 가변인자도 개발자가 파라미터를 입력하지 않으면 null로 채워지고 프로그램은 정상동작 된다. 즉 어노테이션만으로는 강제할 수 없다는 것이다. 가변인자를 강제로 입력하도록 하기 위해서는 어노테이션외에 추가로 assert 설정을 해주어야 한다. 위 코드에 기술된 것처럼 아규먼트 location은 null이 되어선 안된다는 조거은 assert함수를 통해 선언한다. assert 설정까지 완료한 후 소스코드를 실행하면 다음과 같은 에러를 확인할 수 있다. location 값이 아규먼트로 입력되지 않아 null이므로 exception이 발생한 것이다. 에러가 발생하는 생성자 호출부분에 location 파라미터를 채워주면 프로그램이 정상실행된다.

I/flutter ( 8311): <Class_C> name: Park, age: 39, location: Daejeon
E/flutter ( 8311): [ERROR:flutter/lib/ui/ui_dart_state.cc(148)] Unhandled Exception: 'package:variable_argument/main.dart': Failed assertion: line 70 pos 16: 'location != null': is not true.
E/flutter ( 8311): #0      _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:40:39)
E/flutter ( 8311): #1      _AssertionError._throwNew (dart:core-patch/errors_patch.dart:36:5)
E/flutter ( 8311): #2      new Class_D (package:variable_argument/main.dart:70:16)
E/flutter ( 8311): #3      main (package:variable_argument/main.dart:23:22)
E/flutter ( 8311): #4      _runMainZoned.<anonymous closure>.<anonymous closure> (dart:ui/hooks.dart:216:25)
E/flutter ( 8311): #5      _rootRun (dart:async/zone.dart:1124:13)
E/flutter ( 8311): #6      _CustomZone.run (dart:async/zone.dart:1021:19)
E/flutter ( 8311): #7      _runZoned (dart:async/zone.dart:1516:10)
E/flutter ( 8311): #8      runZoned (dart:async/zone.dart:1500:12)
E/flutter ( 8311): #9      _runMainZoned.<anonymous closure> (dart:ui/hooks.dart:208:5)
E/flutter ( 8311): #10     _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:301:19)
E/flutter ( 8311): #11     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:172:12)
E/flutter ( 8311): 

 

전체 소스코드

import 'package:flutter/material.dart';

void main() {
  String my_name = 'Park';
  int my_age = 39;
  String my_location = "Daejeon";

  Class_A class_A = Class_A(my_name, my_age);
  class_A.printInfo();

  Class_B class_B = Class_B(age: my_age, name: my_name);
  class_B.printInfo();

  Class_C class_c1 = Class_C(name: my_name, age: my_age, location: my_location);
  class_c1.printInfo();
  Class_C class_c2 = Class_C(age: my_age, location: my_location);
  class_c2.printInfo();
  Class_C class_c3 = Class_C(name: my_name);
  class_c3.printInfo();

  Class_D class_d1 = Class_D(my_name, age: my_age, location: my_location);
  class_d1.printInfo();
  Class_D class_d2 = Class_D(my_name, location: my_location);
  class_d2.printInfo();
}

class Class_A {
  String name;
  int age;

  Class_A(String name, int age) {
    this.name = name;
    this.age = age;
  }

  void printInfo() {
    print('<Class_A> name: $name, age: $age');
  }
}

class Class_B {
  String name;
  int age;

  Class_B({this.name, this.age});

  void printInfo() {
    print('<Class_B> name: $name, age: $age');
  }
}

class Class_C {
  String name;
  int age;
  String location;

  Class_C({this.name, this.age, this.location});

  void printInfo() {
    print('<Class_C> name: $name, age: $age, location: $location');
  }
}

class Class_D {
  String name;
  int age;
  String location;

  Class_D(String name, {this.age, @required this.location})
      : assert(location != null) {
    this.name = name;
  }

  void printInfo() {
    print('<Class_C> name: $name, age: $age, location: $location');
  }
}

 

실행결과

Performing hot restart...
Syncing files to device Android SDK built for x86...
Restarted application in 1,283ms.
I/flutter ( 8311): <Class_A> name: Park, age: 39
I/flutter ( 8311): <Class_B> name: Park, age: 39
I/flutter ( 8311): <Class_C> name: Park, age: 39, location: Daejeon
I/flutter ( 8311): <Class_C> name: null, age: 39, location: Daejeon
I/flutter ( 8311): <Class_C> name: Park, age: null, location: null
I/flutter ( 8311): <Class_C> name: Park, age: 39, location: Daejeon
I/flutter ( 8311): <Class_C> name: Park, age: null, location: Daejeon

 


Flutter Code Examples 강좌를 추천합니다.

  • 제 블로그에서 Flutter Code Examples 프로젝트를 시작합니다.
  • Flutter의 다양한 예제를 소스코드와 실행화면으로 제공합니다.
  • 또한 모든 예제는 Flutter Code Examples 앱을 통해 테스트 가능합니다.

Flutter Code Examples 강좌로 메뉴로 이동

Flutter Code Examples 강좌 목록 페이지로 이동

Flutter Code Examples 앱 설치 | Google Play Store로 이동

 

Flutter Code Examples - Google Play 앱

Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.

play.google.com

Comments