2023.5.14.(일)

베리어 프리 공모전, <베프> 를 준비하고 있습니다. 기획은 마친 상태이고... 이제 정말 개발을 시작하여야 합니다. flutter 를 사용해서 개발을 할거고, flutter 를 쓰려면 dart 라는 언어를 알아야 하더군요.

팀원 모두 개발 경험 없음… 절망적인 상황이지만, 아이디어가 있고 피그마로 도안을 뚜렷하게 그려놓았으므로 열심히 공부하며 개발하면 안될 것 없으리라 생각됩니다.

개발 기간이 한 달이 안되기 때문에 공부할 시간이 많이 없어서, 하루 날 잡고 노마드 코더의 Dart 무료 강의를 전부 들었습니다. 다행히도 Dart 는 크게 어렵지는 않았어요.

Dart 문법에 대해 정리해 보도록 하겠습니다.

VARIABLES

Var

void main() {
    var name = 'jun';
    name = 'hey';
}

var 키워드는 name 의 타입을 알아서 추론해 줍니다. 물론 name 을 처음에 String 으로 선언했다면 이후에 값은 변경할 때도 String 타입으로 값을 할당해 주어야 합니다.

Dynamic

void main() {
    dynamic name;
    name = 12;
    name = 'jun';
}

dynamic 키워드는 name 의 타입을 자유자재로 변환해서 쓸 수 있습니다. 위에서의 var 에서 name 을 처음에 ‘jun’ 으로 할당해 주지 않고 var name; 으로만 선언해 주면 var 은 dynamic 으로 추론되어 name 의 타입을 자유자재로 사용할 수 있겠죠.

void main() {
    dynamic name;
    if (name is String) {

    }
}

if (name is String) {} 처럼 name 이 String 이라는 것이 명확해 졌다면 String 에 관련된 여러 메서드들을 사용할 수 있게 됩니다.

Nullable Variables

void main() {
    String name = 'jun';
    name = null; 
    if (nico != null) {
        nico.isNotEmpty;
    }
}

위의 코드는 name 에 null 을 할당하려고 할 때 오류가 발생합니다. 그러면, name 이 null 값이 될 수도 있음을 표시해야 합니다. 바로 아래처럼요.

void main() {
    String? name = 'jun';
    name = null; 
    if (nico != null) {
        nico.isNotEmpty;
    }
}

이를 이렇게 쓸 수도 있습니다:

void main() {
    String name = 'jun';
    name = null; 
    name?.isNotEmpty;
}

Final Variables

void main() {
    final name = 'jun';
}

final 키워드를 붙이면 더이상 name 값을 변경할 수 없음을 의미합니다. name 을 다른 값으로 바꾸려는 순간 컴파일 에러가 발생하죠.

void main() {
    late final String name; 
    name = 'nico';
}

Late Variables

late 키워드는 나중에 변수를 할당하겠다는 뜻입니다. 만약 name 을 선언만 해두고 추후에 값을 할당하지 않는다면 컴파일 에러가 발생합니다.

void main() {
   const API = 1212112;
}

Constant Variables

const 키워드는 컴파일 타임에 변경될 수 없는 값들을 할당할 수 있습니다. final 키워드는 사용자로부터 값을 입력 받든, 외부 API 로부터 값을 가지고 오든 값을 받은 후에 나중에 이 값을 변경할 수 없지만 const 키워드가 붙으면 컴파일 시에 이미 값을 컴퓨터가 알아야 합니다. 1221212, ‘jun’, … 처럼 말이죠.

void main() {
    List<int> numbers = [1,2,3,4];
    numbers.add(5);
    print(numbers.first); 
    print(numbers.last); 
    var numbers2 = [4,3,2,1];
}

DATA TYPES

Lists

리스트 는 위와 같이 사용할 수 있습니다.

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

위와 같은 문법을 collection if 라고 합니다.

String Interpolation

void main() {
    var name = 'jun';
    var greeting = "Hello everyone, my name is $name, nice to meet you!!";
    print(greeting); 
}

문자열 안에 $를 넣고 이 옆에 변수명을 써주어 변수 값이 문자열 안에 들어갈 수 있도록 합니다. 이것이 String Interpolation 입니다.

void main() {
    var age = 19;
    var greeting = "Hello everyone, my name is ${age + 2}, nice to meet you!!";
    print(greeting); 
}

문자열 안에서 숫자를 계산하여 넣으려면 대괄호 {} 를 넣어주면 됩니다.

Collection For

void main() {
    var oldFriends = ['jun', 'sun'];
    var newFriends = [
        'hi',
        'this',
        'is',
        'cool',
        for (var friend in oldFriends) "loving $friend",
    ];
}

위와 같은 문법을 collection for 라고 합니다.

Maps

void main() {
    var player = {
        'name': 'jun',
        'age': 21,
        'superpower': true,
    };
    Map<int, bool> player2 = {
        1: true,
        2: false,
        3: true;
    };
}

Map<key, value> 는 위와 같이 만들어 줍니다.

Sets

void main() {
    Set<int> numbers = {1,2,3,4};
    numbers.add(1); 
    numbers.add(1); 
    numbers.add(1); 
    print(numbers); 
}

Set 에는 모든 값들이 유니크 합니다. 아무리 1을 계속 추가해 주어도 막상 print 하면 1은 오직 한 개뿐이죠.

FUNCTIONS

Defining a Function

String sayHello(String potato) {
    return "Hello $potato nice to meet you!";
}

void main() 
{
    print(sayHello('jun')); 
}

Name Parameters

String sayHello(String potato) => "Hello $potato nice to meet you!";
num plus(num a, num b) => a + b; 

void main() 
{
    print(sayHello('jun')); 
}

함수는 위와 같이 만들 수 있습니다.

String sayHello({String name, int age, String country}) {
    return "Hello $name, you are $age, and you come from $country";
}

void main() {
    print(sayHello('jun', 21, 'south korea'));
}

이 함수는 positional 함수입니다.

String sayHello({String name, int age, String country}) {
    return "Hello $name, you are $age, and you come from $country";
}

void main() {
    print(sayHello(
        age: 12,
        country: 'cuba',
        name: 'jun',
    ));
}

하지만 순서가 헷갈릴 수도 있으므로 위처럼 named parameter 를 쓸 수도 있습니다. 위와 같이 파라미터를 넘기면, 컴파일 에러가 납니다. name, age, country 가 null 일 가능성이 있기 때문입니다. 따라서 우리는 디폴트 값들을 아래처럼 넣어주거나

String sayHello({String name = 'anon', int age = 99, String country = 'wakanda'}) {
    return "Hello $name, you are $age, and you come from $country";
}

void main() {
    print(sayHello(
        age: 12,
        country: 'cuba',
        name: 'jun',
    ));
}

아래처럼 required 키워드를 각 파라미터 앞에 붙여 주어 애초에 각 파라미터에 값이 들어가지 않으면 컴파일이 되지 않도록 방지할 수 있습니다.

String sayHello({required String name = 'anon', required int age = 99, required String country = 'wakanda'}) {
    return "Hello $name, you are $age, and you come from $country";
}

void main() {
    print(sayHello(
        age: 12,
        country: 'cuba',
        name: 'jun',
    ));
}

Optional Positional Parameters

String sayHello(
    String name, 
    int age = 99, 
    [String? country = 'wakanda']) => "Hello $name, you are $age, and you come from $country";


void main() {
    var results = sayHello('jun', 21); 
}

하나의 파라미터에 대한 값을 써주고 싶지 않을 때는, 위와 같이 []를 써주면 됩니다.

QQ Operator

String capitalizeName(String? name) =>
    name != null ? name.toUpperCase() : 'ANON';

void main() {
    capitalizeName('jun'); 
    capitalizeName(null); 
}

위 함수에서 name 이 null 이 아니라면 name.toUpperCase() 를 name 에 할당할 테고, null 이라면 name 에 ‘ANON’ 을 할당합니다.

이를 더 짧게 만들면

String capitalizeName(String? name) =>
    name.toUpperCase() ?? 'ANON';

void main() {
    capitalizeName('jun'); 
    capitalizeName(null); 
}

좌항이 null 이 아니라면 name.toUpeerCase() 를 그대로 반환하고, 좌항이 null 이라면 ‘ANON’ 을 반환합니다.

void main() {
    String? name;
    name ??= 'jun';
    name ??= 'another';
}

만약 name 이 null 이라면 name 에 ‘jun’ 을 할당합니다. name 이 현재 null 이므로 name 에는 ‘jun’ 이 할당 됩니다. 하지만 name 에 ‘jun’ 이 할당된 이상, name 은 더이상 null 이 아니므로 ‘another’ 은 할당되지 않습니다.

List<int> reverseListOfNumbers(List<int> list) {
    var reversed = list.reversed; 
    return reversed.toList(); 
}

void main() {
    reverseListOfNumbers(list); 
}

Typedef

위의 함수를 아래와 같이 typedef 키워드를 써서 바꿀 수 있습니다.

typedef ListOfInts = List<int>;

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

void main() {
    reverseListOfNumbers(list); 
}
typedef UserInfo = Map<String, String>;

String sayHi(UserInfo userInfo) {
    return "Hi ${userInfo['name']}";
}

void main() {
    sayHi({'name': 'jun'}); 
}

CLASSES

Dart Class

class Player {
    final String name = 'jun';
    int xp = 1500; 

    void sayHello() {
        var name = '121';
        print("Hi my name is $name"); 
        print("My name is ${this.name}"); 
    }
}

void main()
{
    var player = Player(); 
    print(player.name); 
    player.name = 'haha'; 
    print(player.name);

    player.sayHello(); 
}

클래스는 위와 같이 만들 수 있습니다. 만약 메서드 안에 같은 이름의 변수가 없다면, this.name 이 아닌 name 으로 써도 Player 의 name 인 줄로 압니다.

Constructors

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

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

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

void main() {
    var player = Player('jun', 1500);
    player.sayHello(); 
    var player2 = Player('sun', 2000);
    player2.sayHello(); 
}

위와 같이 constructor 를 만들어 줍니다.

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

    Player(this.name, this.xp); 

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

void main() {
    var player = Player('jun', 1500);
    player.sayHello(); 
    var player2 = Player('sun', 2000);
    player2.sayHello(); 
}

아니면 이렇게 더 짧게 만들 수 있습니다.

Named Constructor Parameters

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

    Player( {
        required this.name,
        required this.xp,
        required this.team,
        required this.age,
    });
}

void main() {
    var player = Player(
        name: 'jun',
        xp: 1200,
        team: 'blue',
        age: 21,
    );
    var player2 = Player(
        name: 'sun',
        xp: 2500,
        team: 'red',
        age: 12,
    );
}

Named Constructors

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

    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 = 1500;

    Player.createRedPlayer(String name, int age) :
        this.age = age,
        this.name = name,
        this.team = 'red',
        this.xp = 2000; 

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

void main() {
    var player = Player.createBluePlayer(
        name: 'jun',
        age: 21,
    );
    var player2 = Player.createRedPlayer('jun', 21); 
}

Cascade Notation

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

    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 = 1500;

    Player.createRedPlayer(String name, int age) :
        this.age = age,
        this.name = name,
        this.team = 'red',
        this.xp = 2000; 

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

void main() {
    var jun = Player(name: 'jun', xp: 1500, team: 'red');
        ..name = 'hyojeong'
        ..xp = 120000
        ..team = 'blue';
        ..sayHello(); 
}

클래스의 각 값에 .. 를 이용하여 접근할 수 있습니다.

void main() {
    var jun = Player(name: 'jun', xp: 1500, team: 'red');
    var hyojeong = jun
        ..name = 'hyojeong'
        ..xp = 120000
        ..team = 'blue';
        ..sayHello(); 
}

Enum

enum Team {red, blue}
enum XPLevel {beginner, medium, pro}

class Player {
    final String name; 
    XPLevel xp; 
    Team team;
    int age;

    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 = Team.blue,
         this.xp = XPLevel.medium;

    Player.createRedPlayer(String name, int age) :
        this.age = age,
        this.name = name,
        this.team = Team.red,
        this.xp = XPLevel.pro; 

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

void main() {
    var jun = Player(name: 'jun', xp: XPLevel.medium, team: Team.red);
        ..name = 'hyojeong'
        ..xp = XPLevel.pro
        ..team = Team.blue;
        ..sayHello(); 
}

enum 은 위와 같이 사용할 수 있습니다.

Abstract Classes

abstract class Human {
    void walk(); 
}

enum Team {red, blue}
enum XPLevel {beginner, medium, pro}

class Player extends Human {
    final String name; 
    XPLevel xp; 
    Team team;
    int age;

    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 = Team.blue,
         this.xp = XPLevel.medium;

    Player.createRedPlayer(String name, int age) :
        this.age = age,
        this.name = name,
        this.team = Team.red,
        this.xp = XPLevel.pro; 

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

    void walk() {
        print("Player is walking"); 
    }
}

class Coach extends Human {
    void walk() {
        print("Coach is walking"); 
    }
}

void main() {
    var jun = Player(name: 'jun', xp: XPLevel.medium, team: Team.red);
        ..name = 'hyojeong'
        ..xp = XPLevel.pro
        ..team = Team.blue;
        ..sayHello(); 
}

위와 같이 Coach 와 Player 가 walk 메소드를 구현하도록 강제할 수 있습니다. Human 은 여기서 abstract class 로써, Coach 와 Player 에서 extends 하여 강제할 수 있습니다.

Inheritance

class Human {
    final String name; 
    Human(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("Hi I'm $age years old"); 
    }
}

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

extends 키워드로 상속의 관계를 나타낼 수 있습니다. super 키워드는 부모 클래스를 의미하고요.

Mixins

enum Team {blue, red}

mixin Strong {
    final double strengthLevel = 1500.99;
}

mixin QuickRunner {
    void runQuick() {
        print("ruuuuun!"); 
    }
}

class Tall {
    final double height = 1.99;
}

class Player with Strong, QuickRunner {
    final Team team; 

    Player({
        required this.team,
    });
}

class Horse with Strong, QuickRunner {}

class Kid with Strong {}

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

mixin은 단순히 mixin 내부의 프로퍼티와 메소드를 가져옵니다. 상속의 관계는 아닙니다.

Dart 기초 문법을 훑어 보았습니다. 바로 flutter 프레임워크 공부 + 개발 들어가 봅시다. 한달 동안은 flutter 앱개발에만 몰입해야 겠어요. 시간이 너무 촉박하다 보니… 열심히 달려가 봅시다.