본문 바로가기

Programmer Jinyo/Dart & Flutter

Dart 튜토리얼 강의 (from 노마드 코더)


투명한 기부를 하고싶다면 이 링크로 와보세요! 🥰 (클릭!)

바이낸스(₿) 수수료 평생 20% 할인받는 링크로 가입하기! 🔥 (클릭!)

https://nomadcoders.co/dart-for-beginners

 

Dart 시작하기 – 노마드 코더 Nomad Coders

Flutter 앱 개발을 위한 Dart 배우기

nomadcoders.co

위 링크의 강의를 들으며 내용을 정리하는 포스트이다.

 

Dart는 OOP 언어이다.

Dart는 UI에 최적화 되어있으며, 모든 플랫폼에서 빠르다고 한다.

Dart는 Dart webDart Native 두개의 컴파일러가 있고, 그래서 한 코드로 여러 플랫폼에서 잘 돌아가게 만들어 줄 수 있다.

 

Dart 는 Just In Time 컴파일러가 있으면서, Ahead Of Time 컴파일러이다. 개발 중에는 기본적으로 VM위에서 돌아간다. 그래서 JIT를 만족한다.

 

또한 null safety하다.

 

시작

프로그래밍을 시작 해 보자.

나는 VS Code 기반으로 개발환경을 세팅했다.

https://medium.com/@hj.veronica.shim/%ED%94%8C%EB%9F%AC%ED%84%B0-flutter-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-1-%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95-1131711dd651

 

플러터(Flutter) 시작하기 — (1) 개발 환경 구축

이 글은 Sebastian Eschweiler의 Getting Started With Flutter — (1) Setting Up The Development Environment를 번역/의역한 글입니다. 오역이 있거나, 수정해야 할 부분이 있다면…

medium.com

 

그리고 아래와 같이 코드를 입력하고 실행 해 본다.

void main(){
  print('hello world');
}

결과

변수

dart에서 변수를 만드는 한가지 방법은 var 키워드를 쓰는 것이다.

var 사용 시에 자동으로 타입 추론은 되지만, 한번 정해진 변수의 타입을 다시 바꿀수는 없다.

또 다른 한가지 방법은 명시적으로 타입을 적어주는 것이다.

 

관습적으로 함수나 메소드 내부에 지역 변수를 사용할 때에는 var 을 사용한다.

그리고 class에서 변수나 property를 사용할 때는 타입을 지정 해 준다.

 

여러 타입을 가지는 변수도 있다.

dynamic 이라는 키워드이다.

이는 var 변수 선언 시에 아무런 타입 지정을 하지 않거나, dynamic 타입으로 지정하면 된다.

타입 확인은 아래와 같이 할 수 있다.

void main() {
  var abc;
  if (abc is String){}
}

 

Null Safety

Null safety가 보장되지 않은 상태에서 프로그램이 Null 값을 참조하면, 런타임 에러가 뜬다.

기본적으로 Null값을 허용하지 않고, 허용하고 싶다면 Type 뒤에 ?를 붙이면 된다.

?를 통해 해당 변수에 값이 존재하는지 확인해줄 수 있다.

또한, ! 를 통해 null이 아닌 타입으로 새로 캐스팅 해 줄 수 있다.

String a = 'a'; // Null safety
String? b = 'b'; // Null available
if(b != null) {
	b.length; // error 방지 가능
}
b?.length; // 컴파일러에게 b가 null이 아닐 경우에만 이후 함수를 실행하게 함.
b!.length; // 컴파일러에게 not null임을 확신시켜줌.

 

final

한번 정의된 변수를 수정할 수 없게 바꿔주려면, var 대신에 final 키워드를 쓰면 된다.

final String name = 'nico';
// 혹은
final name = 'nico';

js의 const와 비슷한 것.

 

late

late은 final이나 var 앞에 둘 수 있는 수식어이다. late은 초기 데이터 없이 변수를 선언할 수 있게 해 준다.

late final String name;
// do something here ( ex, API로 부터 뭔가를 받아온다던지 )
name = 'nico';

late은 컴파일러가 값이 선언되기 전에는 사용하지 못하게 막아준다. (null safety같은 것)

API 등에서 data fetching할 때 유용하다.

 

const

Dart의 const는 Js, Ts 등에서 사용하는 const와는 다르다. js, ts의 const는 dart의 final과 비슷하다.

dart의 const는 컴파일 타임에서의 const를 만들어 준다.

const data = 'name'; // compile time const
const APIData = fetchAPI(); // not compiletime const. final을 써야 맞음.

(니꼬쌤은 app을 app store에 보내기 전에 알고있는 값이라고 설명하신다.)

 

Data Types

이미 우리가 아는 타입들은 String, bool, int, double 등이 있다.

이 모든 자료형(뿐만 아니라 대부분의 자료형)이 object로 이루어져 있다.

 

List

자동으로 만들어주고 싶으면 아래와 같이 쓰면 된다.

var numbers = [1,2,3,]; //or
List<int> numbers = [1,2,3,4,];

//List의 끝은 꼭 , 로 마무리 해 주자. 그러면 자동으로 여러 라인으로 포매팅 된다.

collection if

collection if로는 List를 만들 수 있는데, 아래와 같은 것을 할 수 있다.

var giveMeFive = true;
var numbers = [
	1,
    2,
    3,
    4,
    if(giveMeFive) 5,
];

String interpolation(문자열 보간법)

String interpolation은 text에 변수를 추가하는 방법이다.

var name = 'nico';
var greeting = 'hello my name is $nico';

$ 사인 뒤에 변수 이름을 적어주면 된다.

계산이 필요한 경우에는 ${} 를 쓰면 된다.

 

collection for

var oldFriends = ['nico', 'lynn'];
var newFriends = ['lewins',
	for(var friend in oldFriends) "<3 $friend",
    ];

파이썬의 list comprehention과 비슷하다.

 

Maps

JS TS의 Object, Python의 Dictionary같은 것이다.

var player = {
	'name': 'name1',
    'xp' : 19,
    'superpower' : false,
}

위 자료형은 Map<String, Object> 이다. (Object는 any 같은 것으로 취급하면 된다. Dart는 기본적으로 oop이므로.)

혹은 Map<int, bool> 이런 식으로 새로운 타입을 지정해줄 수도 있다.

 

Sets

파이썬의 Set과 비슷하다. 중괄호를 이용하여 작성 해 주면 된다.

var numbers = {1,2,3,4};

모든 원소는 유일하다.

 

Function

function에 대해서 알아보자.

void sayHello(String name) {
	print("hello $name nice to meet you!");
}

void : 아무것도 리턴하지 않는다는 뜻.

바로 return 하는 경우에는 Arrow function도 가능하다.

String sayHello(String name) => "hello $name nice to meet you!";

 

Dart의 function은 named parameter 개념을 사용할 수 있다.

// 일반적인 함수 선언 방법
void sayHello(String name, int age,) {
	print("hello $name is $age years old");
}
// named argument 사용 방법
// null-safety 때문에 기본 값을 지정 해 주거나,  required 표시를 해 주면 된다.
void sayHello({required String name, int age = 99}) {
	print("hello $name is $age years old");
}

void main() {
	// named argument 사용할 때 할 수 있는 것.
    // 함수의 정의를 보고 이름 자체에 해당하는 값을 순서 관계없이 넘길 수 있어서 좋다.
	sayHello(
        age:12,
        name:'steve'
    );
}

 

optional positional parameter

positional parameter은 순서에 맞춰서 입력해야 하는 파라미터이다. (제일 기본적인 방법)

String hello(String name, int age, [String? country = 'cuba']) => 'Hello $name, you re $age years old'

void main() {
	sayHello('nico', 12);
    // [] 문법 + ?를 통해 null일 수 있다는 것을 표시.
}

필수가 아닌 argument의 경우 []?를 활용하여 만들 수도 있다.

 

QQ operatior (??, ??= operatior)

내 이름을 대문자로 리턴하는 함수를 만든다고 하자.

String capitalizeName(String? name) {
    if(name!=null) return name.toUpperCase();
    else return 'ANON';
    // 혹은
    return name != null ? name.toUpperCase() : 'ANON';
    // 혹은
    // left가 null이면 right을 리턴하는 ?? 오퍼레이터를 호출할 수 있다.
    return name?.toUpperCase() ?? 'ANON'
}
void main() {
    capitalizeName('nico');
	capitalizeName('null');
}

이렇게 ??를 쓸 수 있고, ??=의 경우에는 아래와 같이 쓰면 된다.

    String? name;
    // if name is null, 해당 변수 안에 값을 넣어라.
    name ??= 'nico';
    name = null;
    name ??= 'anon';

 

Typedef

자료형이 헷갈릴 떄 도움이 될 alias 를 만드는 법이다.

예시로, List를뒤집는 함수를 만들어 보자.

typedef ListOfInts = List<int>;

ListOfInts reverseListOfNumbers(ListOfInts list) {
    var reversed = list.reversed;
    return reversed.toList();
}

void main() {
    // typedef... 꼭 써야할까?
}

 

Class

플러터의 모든것은 class 이다.

class Player {
  //타입을 꼭 명시 해 주자.
  String name = 'nico';
  // 더이상 수정할 수 없게 만들기.
  final String kind = 'human';
  int xp = 1500;
  void sayHello() {
    // this를 class 내부에서 사용하지 않는 것이 권장됨.
    print("Hi my name is $name");
    // print("Hi my name is ${this.name}"); 도 동작함.
  }
}

void main() {
  var player = Player();
  print(player.name);
  player.name = 'something';
  print(player.name);
  player.sayHello();
}

자세한 설명은 comment로 달아 놓았다.

 

constructors

아래와 같이 무식하게 만드는 원시적 방법이 있다.

class Player {
  // 생성자를 통해 값을 대입하고 싶을 경우 late 키워드를 쓰자.
  late final String name;
  late int xp;

  Player(String name, int xp) {
    this.name = name;
    this.xp = xp;
  }

  void sayHello() {
    // this를 class 내부에서 사용하지 않는 것이 권장됨.
    print("Hi my name is $name");
    // print("Hi my name is ${this.name}"); 도 동작함.
  }
}

void main() {
  var player = Player('nico', 14);
  player.sayHello();
  var player2 = Player('lynn', 14);
  player2.sayHello();
}

이를 아래와 같이 수정할 수 있다.

class Player {
  final String name;
  int xp;
  // positional constructor
  Player(this.name, this.xp);

  void sayHello() {
    // this를 class 내부에서 사용하지 않는 것이 권장됨.
    print("Hi my name is $name");
    // print("Hi my name is ${this.name}"); 도 동작함.
  }
}

named constructor parameter  적용 가능

class Player {
  // 생성자를 통해 값을 대입하고 싶을 경우 late 키워드를 쓰자.
  final String name;
  int xp;
  String team;
  int age;

  // 초기값을 주거나, required 적용.
  Player(
      {required this.name,
      required this.xp,
      required this.team,
      required this.age});

  void sayHello() {
    print("Hi my name is $name");
  }
}

void main() {
  var player = Player(name: 'nico', xp: 140, team: 'red', age: 12);
  player.sayHello();
}

named constructor

위의 것과는 다른 개념이다.

constructor에 이름을 붙이고 초기화 하는 다양한 방법을 제공하며, positional과 named parameter 전부 제공한다.

class Player {
  // 생성자를 통해 값을 대입하고 싶을 경우 late 키워드를 쓰자.
  final String name;
  int xp;
  String team;
  int age;

  // 초기값을 주거나, required 적용.
  Player(
      {required this.name,
      required this.xp,
      required this.team,
      required this.age});
  Player.createBluePlayer({required String name, required int age})
      : this.age = age,
        this.name = name,
        this.team = 'blue',
        this.xp = 0;
  Player.createRedPlayer(String name, int age)
      : this.age = age,
        this.name = name,
        this.team = 'blue',
        this.xp = 0;

  void sayHello() {
    print("Hi my name is $name");
  }
}

void main() {
  var player = Player(name: 'nico', xp: 140, team: 'red', age: 12);
  player.sayHello();
  var player2 = Player.createBluePlayer(name: 'nico', age: 12);
  var player3 = Player.createRedPlayer('nico', 12);
}

 

fromJson을 하고을 경우 아래와 같이 응용할 수 있다.

class Player {
  final String name;
  int xp;
  String team;

  Player({required this.name, required this.xp, required this.team});
  Player.fromJson(Map<String, dynamic> playerJson)
      : name = playerJson['name'],
        xp = playerJson['xp'],
        team = playerJson['team'];

  void sayHello() {
    print("Hi my name is $name");
  }
}

void main() {
  var playerJsonData = [
    {'name': 'nico', 'xp': 10, 'team': 'red'}
  ];
  playerJsonData.forEach((element) {
    Player.fromJson(element);
  });
}

Cascade Notation

class Player {
  // 생성자를 통해 값을 대입하고 싶을 경우 late 키워드를 쓰자.
  String name;
  int xp;
  String team;

  // 초기값을 주거나, required 적용.
  Player({required this.name, required this.xp, required this.team});
}

void main() {
  var nico = Player(name: 'nico', xp: 1200, team: 'red');
  nico.name = 'las';
  nico.xp = 120000;
  nico.team = 'blue';
  // 같은 효과
  var nico2 = Player(name: 'nico', xp: 1200, team: 'red')
    ..name = 'las'
    ..xp = 120000
    ..team = 'blue';

  // 이런것도 가능.
  var nico3 = nico2
    ..name = 'las'
    ..team = 'red';
}

Enum

일반적인 enum 기능이다.

// red가 문자열인지 숫자인지 등 타입 생각 안해도 된다.
enum Team { red, blue }

class Player {
  // 생성자를 통해 값을 대입하고 싶을 경우 late 키워드를 쓰자.
  String name;
  int xp;
  Team team;

  // 초기값을 주거나, required 적용.
  Player({required this.name, required this.xp, required this.team});
}

void main() {
  var nico = Player(name: 'nico', xp: 1200, team: Team.red);
  nico.name = 'las';
  nico.xp = 120000;
  nico.team = Team.blue;
  // 같은 효과
  var nico2 = Player(name: 'nico', xp: 1200, team: Team.red)
    ..name = 'las'
    ..xp = 120000
    ..team = Team.blue;

  // 이런것도 가능.
  var nico3 = nico2
    ..name = 'las'
    ..team = Team.red;
}

Abstract Classes

다른 클래스들이 구현해야 하는 메소드 등을 적어놓은 blue print같은 것이라고 생각하면 된다.

abstract class Human {
  void walk();
}

// red가 문자열인지 숫자인지 등 타입 생각 안해도 된다.
enum Team { red, blue }

class Player extends Human {
  // 생성자를 통해 값을 대입하고 싶을 경우 late 키워드를 쓰자.
  String name;
  int xp;
  Team team;

  // 초기값을 주거나, required 적용.
  Player({required this.name, required this.xp, required this.team});

  @override
  void walk() {
    print('Im walking');
  }
}

Inheritance

상속, 오버라이드 등에 대한 문법 정리이다.

class Human {
  final String name;
  Human({required this.name});
  void sayHello() {
    print("Hi my name is $name");
  }
}

enum Team { blue, red }

class Player extends Human {
  final Team team;
  Player({required this.team, required String name}) : super(name: name);
  @override
  void sayHello() {
    super.sayHello();
    print("I'm $team team");
  }
}

void main() {
  var player = Player(team: Team.red, name: 'nico');
  player.sayHello();
}

Mixins

믹스인은 플러터에서 자주 사용된다. 믹스인은 생성자가 없는 class인 상태에서 사용한다.

우리는 믹스인을 class에 프로퍼티 등을 추가할 때 사용한다.

extend와는 다른데, extend한 class는 자식 class의 부모가 되는 것이고, super 키워드를 통해서 부모에 접근이 가능하며ㅡ 부모 클래스의 인스턴스가 된다.

Mixin은 내부의 프로퍼티와 메소드만 가져온다. Mixin의 조건은 생성자가 없는 class여야만 한다.

class Strong {
  final double strLevel = 1500;
}

class QuickRunner {
  void runQuick() {
    print("run");
  }
}

class Tall {
  final double height = 1.99;
}

enum Team { blue, red }

class Player with Strong, QuickRunner {
  final Team team;
  Player({
    required this.team,
    required String name,
  });
}

void main() {
  var player = Player(team: Team.red, name: 'nico');
}