// 머터리얼 디자인 import
// 기본 위젯 제공
import 'package:flutter/material.dart';
// 플러터 프로젝트 실행하는 함수
void main() {
runApp(
// 머터리얼 디자인 위젯
MaterialApp(
// Scaffold 위젯
home: Scaffold(
// Text 위젯
body: Text(
'Hello World', // 마지막 매개변수 끝에 콤마
),
),
),
);
}
MaterialApp : 머터리얼 디자인 기반의 위젯을 사용
Scaffold 위젯 : 화면 전체를 차지. 레이아웃을 도와주고 UI 관련 특수 기능 제공(알림과 같은 스낵바 실행, 화면의 위에 앱바를 추가, 아래에 탭바를 추가 등)
※ MaterialApp 과 Scaffold 위젯을 추가하는 것이 기본 설정.
Text 위젯 : 글자를 화면에 출력하기 위해 사용.
실행
에뮬레이터를 선택하고 실행 버튼을 클릭한다.
에뮬레이터에서 결과 화면 확인.
- 화면 좌측 상단에 Hello World 글자가 보인다.(자세히 봐야 보인다. --;;)
$ flutter create hello_world2
....
All done!
You can find general documentation for Flutter at: https://docs.flutter.dev/
Detailed API documentation is available at: https://api.flutter.dev/
If you prefer video documentation, consider: https://www.youtube.com/c/flutterdev
In order to run your application, type:
$ cd hello_world2
$ flutter run
Your application code is in hello_world2\lib\main.dart.
Flutter는 고성능, 고품질의 iOS, Android, 웹(tech preview) 앱을 단일 코드 베이스로 개발할 수 있는 모바일 앱 SDK입니다.
스크롤 동작, 글씨, 아이콘과 같이 플랫폼 별로 달라지는 부분들을 아울러서 서로 다른 플랫폼에서도 자연스럽게 동작하는 고성능의 앱을 개발할 수 있게 하는 것이 Flutter의 목표입니다.
Flutter의 장점 ● 높은 생산성 ○ 단일 코드베이스로 iOS와 Android 개발할 수 있습니다. ○ 모던하고 표현적인 언어 그리고 선언적 접근법을 통해 단일 OS에서 더 적은 코드로 더 많은 것을 할 수 있습니다. ○쉽게 프로토타입을 제작하고 반복할 수 있습니다. √ 앱 실행 중에 코드를 바꾸고 리로드하여 개발을 할 수 있습니다. ( hot reload) √ 앱이 중단된 지점에서 문제를 수정하고 디버깅을 이어나갈 수 있습니다.
●아름답고, 고도로 커스터마이징된 UX를 만들 수 있습니다. ○Flutter의 자체 프레임워크를 사용하여 머티리얼 디자인과 쿠퍼티노 (iOS) 스타일의 풍부한 위젯들을 만들 수 있습니다. ○OEM 위젯의 제한없이 맞춤형의 아름다운 브랜드 주도 디자인을 실현할 수 있습니다.
핵심 원리
Flutter는 현대적인 react-style 프레임워크, 2D 렌더링 엔진, 바로 이용 가능한 위젯들, 그리고 개발 툴들을 포함합니다.
이러한 구성 요소들을 통해 앱을 디자인, 개발, 테스트 그리고 디버깅할 수 있습니다. 모든 것은 몇가지 핵심 원리들을 중심으로 구성됩니다.
모든 것은 위젯입니다
위젯은 Flutter 앱 UI의 기본 단위입니다. 모든 위젯은 UI의 불변 선언입니다.
뷰, 뷰 컨트롤러, 레이아웃 그리고 기타 다른 속성들을 분리하는 다른 프레임워크들과 다르게, Flutter는 일관적이고 통일된 오브젝트 모델을 갖고 있는데, 그것이 바로 위젯입니다.
위젯은 다음의 것들을 정의할 수 있습니다:
● 구조적인 요소 (예: 버튼이나 메뉴)
● 스타일적인 요소 (예: 폰트나 색상)
● 레이아웃 요소 (예: 패딩)
● 기타 등등…
위젯은 구성을 기반으로 계층 구조를 형성합니다.
각 위젯은 내부에 중첩되고 부모의 속성들을 상속받습니다.
별도 “application” 오브젝트가 없는 대신 최상위 위젯이 그 역할을 하게 됩니다.
프레임워크에게 위젯을 계층 구조 상 다른 위젯으로 교체하게 함으로써, 사용자 상호작용과 같은 이벤트를 구현할 수 있습니다. 프레임워크는 새로운 위젯과 기존 위젯을 비교하고 효을적으로 UI를 업데이트하게 됩니다.
구성 > 상속
위젯은 종종 강력한 효과를 내기 위해 단일 목적의 여러 작은 위젯들로 구성됩니다.
예를 들어, 일반적으로 사용되는 Container 위젯은 painting, positioning, sizing과 같은 레이아웃 관련 위젯들로 구성됩니다.
비동기 : 요청을 하고 나서 응답을 받지 않아도 다음 코드를 진행. 추후에 응답이 오면 처리.
Future
- Future 클래스 : 미래에 제너릭으로 값을 받아옴.
// 일정 시간 후 콜백 함수 실행.
// Future.delayed
void main() {
print("실행 시작");
Future.delayed(Duration(seconds: 3), (){
print('3초 후 실행합니다.');
});
print("실행 완료?");
}
// 실행 결과(비동기로 동작)
실행 시작
실행 완료?
3초 후 실행합니다.
async, await
- 코드를 순서대로 실행시키기 위해 사용
void main() {
addNumbers(1, 1);
}
// async 키워드는 함수 매개변수 정의와 바디 사이에 입력.
void addNumbers(int number1, int number2) async {
print('$number1 + $number2 계산 시작!');
// await는 대기하고 싶은 비동기 함수 앞에 입력.
await Future.delayed(Duration(seconds: 3), (){
print('$number1 + $number2 = ${number1 + number2}');
});
print('$number1 + $number2 코드 실행 끝');
}
// 실행 결과
1 + 1 계산 시작!
1 + 1 = 2
1 + 1 코드 실행 끝
비동기적으로 동작하는 지 확인
void main() {
addNumbers(1, 1);
addNumbers(2, 2);
}
Future<void> addNumbers(int number1, int number2) async {
print('$number1 + $number2 계산 시작!');
await Future.delayed(Duration(seconds: 3), (){
print('$number1 + $number2 = ${number1 + number2}');
});
print('$number1 + $number2 코드 실행 끝');
}
// 실행 결과
1 + 1 계산 시작!
2 + 2 계산 시작!
1 + 1 = 2
1 + 1 코드 실행 끝
2 + 2 = 4
2 + 2 코드 실행 끝
두 함수를 순차적으로 실행하고 싶다면...
// main 함수에 async, await 사용.
void main() async {
await addNumbers(1, 1);
await addNumbers(2, 2);
}
Future<void> addNumbers(int number1, int number2) async {
print('$number1 + $number2 계산 시작!');
await Future.delayed(Duration(seconds: 3), (){
print('$number1 + $number2 = ${number1 + number2}');
});
print('$number1 + $number2 코드 실행 끝');
}
// 실행 결과
1 + 1 계산 시작!
1 + 1 = 2
1 + 1 코드 실행 끝
2 + 2 계산 시작!
2 + 2 = 4
2 + 2 코드 실행 끝
결과 값을 비동기로 리턴 받고 싶다면...
void main() async {
final result = await addNumbers(1, 1);
print('결과값 $result'); // 일반 함수와 동일하게 반환값을 받을 수 있음
final result2 = await addNumbers(2, 2);
print('결과값 $result2');
}
Future<int> addNumbers(int number1, int number2) async {
print('$number1 + $number2 계산 시작!');
await Future.delayed(Duration(seconds: 3), (){
print('$number1 + $number2 = ${number1 + number2}');
});
print('$number1 + $number2 코드 실행 끝');
return number1 + number2;
}
//
1 + 1 계산 시작!
1 + 1 = 2
1 + 1 코드 실행 끝
결과값 2
2 + 2 계산 시작!
2 + 2 = 4
2 + 2 코드 실행 끝
결과값 4
Stream
- Future 는 결과값을 한번 리턴.
- 지속적으로 리턴받고 싶을 때는 Stream 을 사용.
- Stream 은 한번 Listen 하면 지속적으로 값을 받아온다.
import 'dart:async';
void main() {
final controller = StreamController(); // StreamController 선언
final stream = controller.stream; // Stream 가져오기
// Stream에 listen() 함수를 실행하면 값이 주입될 때마다 콜백 함수를 실행
final streamListener1 = stream.listen((val) {
print(val);
});
// Stream에 값을 주입할 때는 sink.add() 함수를 실행
controller.sink.add(1);
controller.sink.add(2);
controller.sink.add(3);
controller.sink.add(4);
}
// 실행 결과
1
2
3
4
Broadcasting
- Stream 을 여러번 Listen 하기
import 'dart:async';
void main() {
final controller = StreamController();
// 여러 번 listen할 수 있는 Broadcast Stream 객체 생성
final stream = controller.stream.asBroadcastStream();
// listen() 함수
final streamListener1 = stream.listen((val) {
print('listening 1');
print(val);
});
// listen() 함수 추갸.
final streamListener2 = stream.listen((val) {
print('listening 2');
print(val);
});
// add()를 실행할 때마다 listen()하는 모든 콜백 함수에 값이 주입
controller.sink.add(1);
controller.sink.add(2);
controller.sink.add(3);
}
// 실행 결과.
listening 1
1
listening 2
1
listening 1
2
listening 2
2
listening 1
3
listening 2
3
함수로 Stream 반환하기
- 이건 잘 모르겠음. --;;
import 'dart:async';
// Stream을 반환하는 함수는 async*로 선언합니다.
Stream<String> calculate() async* {
for (int i = 0; i < 5; i++) {
// StreamController의 add()처럼 yield 키워드를 이용해서 값 반환
yield 'i = $i';
await Future.delayed(Duration(seconds: 1));
}
}
void playStream() {
// StreamController와 마찬가지로 listen() 함수로 콜백 함수 입력
calculate().listen((val) {
print(val);
});
}
void main() {
playStream();
}
// 실행 결과(1초에 한줄씩 출력)
i = 0
i = 1
i = 2
i = 3
i = 4
// class 정의.
class Person {
// class 종속 변수
String name = '이름';
// class 종속 함수 = 메소드
// 내부 속성을 사용할 때 this 키워드 사용.
void sayName() {
print('My name is ${this.name}');
}
}
인스턴스 생성
void main() {
// 인스턴스 생성
Person person = Person();
// 메소드 실행.
person.sayName();
}
생성자
class Person {
// 생성자에서 입력 받는 변수들은 일반적으로 final 키워드 사용.
final String name;
// 생성자 선언. class 와 같은 이름.
// 매개변수 지정
Person(String name) : this.name = name;
// this 를 사용할 경우.
// 해당되는 변수에 자동을 매개변수가 저장.
Person(this.name);
void sayName() {
print('My name is ${this.name}');
}
}
void main() {
Persion person = Person('홍길동');
}
네임드 생성자
class Person {
final String name;
final int age;
// 1개 이상의 변수를 저장할때는 , 기호로 연결.
Person(String name, int age)
: this.name = name,
this.age = age;
// 네임드 생성자
Person.fromMap(Map<String, dynamic> map)
: this.name = map['name'],
this.age = map['age'];
void sayName() {
print('My name is ${this.name}');
}
}
void main() {
Persion person = Person('홍길동', 20);
Persion hong = Person.fromMap({
'name' : '홍길동',
'age' : 20,
});
}
private 변수
class Person {
// '_' 로 시작하면 private 변수 선언.
String _name;
Person(this._name);
void sayName() {
print('My name is ${this._name}');
}
}
void main() {
Persion person = Person('홍길동');
print(person._name); // 같은 파일에서는 에러가 발생하지 않지만 다른 파일에서 사용시 에러 발생.
}
Getter / Setter
class Person {
String _name = 'hong';
// get 키워드를 사용하여 getter 명시
// 매개변수를 받지 않음.
String get name {
return this._name;
}
// set 키워드를 사용하여 setter 명시
// 매개변수로 하나의 변수를 받음.
set name(String name) {
this._name = name;
}
}
void main() {
Persion person = Person();
person.name = '홍길동'; // setter
print(person.name); // getter
}
상속
class Person {
final String name;
Person(this.name);
void sayName() {
print('My name is ${name}.');
}
}
// 상속
class Man extends Person {
// 상속 받은 생성자
// super 는 부모 클래스를 의미한다.
Man(String name) : super(name);
void sayMan() {
print('I am a man');
}
}
void main() {
Man man = Man('홍길동');
print(man.sayName()); // 부모한테 물려받은 메소드
print(man.sayMan()); // 자식이 추가한 메소드
}
오버라이드
class Person {
final String name;
Person(this.name);
void sayName() {
print('My name is ${name}.');
}
}
class Girl extends Person {
// super 키워드를 직접 사용할 수 있음.
Girl(super.name);
@override
void sayName() {
print('My name is ${name} and I am a girl');
}
}
void main() {
Girl girl = Girl('영희');
print(girl.sayName()); // override 된 메소드
}
인터페이스
// implements 키워드를 사용하면 클래스를 인터페이스로 사용할 수 있다.
class Girl implements Person {
final String name;
Girl(super.name);
// 인터페이스로 사용할 때는 모든 메소드를 재정의 해야한다.
void sayName() {
print('My name is ${name}');
}
}
void main() {
Girl girl = Girl('영희');
print(girl.sayName());
}
믹스인???
- 이건 잘 모르겠다... --;;;
추상 클래스
- 인스턴스화 할 필요가 없을 경우에 사용
// abstract 키워드를 사용하여 추상 클래스 정의.
abstract class Person {
final String name;
// 생성자 선언
Person(this.name);
// 추상 메소드 선언.
void sayName();
}
// implements 키워드를 사용하여 추상 클래스를 구현.
// 생성자를 비롯하여 모든 메소드를 정의해야 한다.
class Girl implements Person {
final String name;
// super 키워드를 직접 사용할 수 있음.
Girl(this.name);
void sayName() {
print('My name is ${name} and I am a girl');
}
}
void main() {
Girl girl = Girl('영희');
print(girl.sayName());
}
제너릭
// 제너릭
// 특정 변수의 타입을 제한하고 싶지 않을 때 사용.
// 인스턴스화 할 때 입력받을 타입을 T 로 지정.
class Generate<T> {
// 데이터 타입을 인스턴스화 할 때 지정한 타입으로 사용.
final T data;
Generate({
required this.data,
});
}
void main() {
// 제너릭에 입력된 값을 통해 data 변수의 타입이 자동으로 유추.
final gen = Generate<List<int>> (data : [1, 2, 3]);
print(gen.data.reduce((v,e) => v + e); // 6
}
static
class Count {
// static 은 클래스 자체에 귀속
static int i = 0;
// 생성자가 호출될때 마다 i 증가.
Count() {
i++;
print(i);
}
}
void main() {
Count c1 = Count(); // 1
Count c2 = Count(); // 2
Count c3 = Count(); // 3
}
Cascade
class Person {
final String name;
final int age;
Person(this.name, this.age);
void sayName() {
print('My name is ${this.name}');
}
void sayAge() {
print('My age is ${this.age}');
}
}
void main() {
// cascade 연산자 ..
// 인스턴스의 속성이나 메소드를 연속해서 사용하는 것.
Person person = Person('홍길동', 20)
..sayName()
..sayAge();
}
File -> New -> New Flutter Project ... 메뉴를 클릭하여 플러터 프로젝트를 생성한다.
lib/main.dart 파일에 코드를 작성 후 안드로이드 스튜디오 하단의 터미널 탭에서 dart lib/main.dart 명령어를 실행한다.
기초 문법
메인 함수
void main() {
// 한줄 주석
/* 시작기호, 끝 기호 */
/* 여러 줄 주석
*
*
* */
/// 슬래시 세 개를 사용하면 문서 주석을 작성할 수 있습니다.
}
변수 선언
// 변수 타입
String(문자열), int(정수형), double(실수형), bool(불리언 true/false)
String s = '';
int i = 1;
double d = 1.0;
bool b = true;
// 자동으로 타입을 추론. 한번 추론된 타입은 고정됨.
var name = '이름';
// 추후에 타입이 바뀌면 에러
name = 1; // 에러
// dynamic 키워드를 사용하면 변수 타입을 고정하지 않고 사용 가능.
dynamic d_name = '다이내믹';
d_name = 1;
// final, const 는 처음 선언 후 값 변경이 불가하다.
final String f_str = 'final string';
f_str = 'new string'; // 에러 발생
const String c_str = 'const string';
c_str = 'new const'; // 에러 발생
// final 은 런타임, const 는 빌드타임 상수이다.
// DateTime.now() 는 실행되는 순간(런타임 시) 값이 정해진다.
final DateTime f_now = DateTime.now();
const DateTime c_now = DateTime.now(); // 에러 발생
// 컬렉션
// List
List<String> list = ['s1', 's2', 's3'];
print(list[3]); // s3
list.add('s4');
final list_where = list.where((str) => str == 's1' || str == 's4'); // Iterable (s1, s4)
print(list_where.toList()); // [s1, s4]
final list_map = list.map((str) => 'new $str'); // Iterable (new s1, new s2, new s3, new s4);
final list_reduce = list.reduce((v, e) => v + ', ' + e); // (반환 타입이 String) s1, s2, s3, s4
final list_fold = list.fold(0, (v, e) => v + e.length); // (반환 타입 아무거나 가능) 8
// Map
Map<String, String> map = {
'a' : 'a1',
'b' : 'b2',
'c' : 'c3',
};
print(map['b']); // b2
print(map.keys); // Iterable (a, b, c)
print(map.values); // Iterable (a1, b2, c3)
// Set
Set set = {'s1', 's2', 's3'};
set.contain('s2'); // true
set.toList(); // Set to List
Set.from(list); // Set from List
// enum
enum Status {
ready,
play,
done,
}
Status status = Status.ready;
print(status); // Status.ready
연산자
// 사칙연산
+
-
*
/ // 몫
% // 나머지
++
--
+=
-=
*=
/=
// null 관련 연산자
// nallable
double? number1 = 1;
// not null
double number2 = null; // 에러 발생
// null 로 초기화
double? number;
// null 이면 할당.
number ??= 3; // 3
// not null 이면 할당하지 않음.
number ??= 4; // 3
// 값 비교
>
<
>=
<=
==
!=
// 타입 비교
print(number is int); // false
print(number is double); // true
print(number is! int); // true
print(number is! double); // false
// 논리 연산자
&&
||
제어문
if
else if
else
switch() {
case 1 :
print();
break;
default:
print();
}
for(int i=0; i < 10; i++) {
print(i);
}
List<int> list = [1,2,3];
for(int n in list) {
print(n);
}
while() {
}
do {
} while()
함수와 typedef
void main() {
// 고정된 매개변수(포지셔널 파라미터)
print(addTwoNumbers1(2, 3));
// 이름이 있는 매개변수(네임드 파라미터)
print(addTwoNumbers2(a:2, b:3));
// 고정된 매개변수 기본값 지정
print(addTwoNumbers3(2));
print(addTwoNumbers3(2, 1));
// 이름이 있는 매개변수 기본값 지정
print(addTwoNumbers4(a:2));
print(addTwoNumbers4(a:2, b:1));
// typedef 는 일반적인 변수의 type 처럼 사용.
Operation oper = add;
oper(1, 2);
oper = subtract;
subtract(5, 2);
calculate(3, 4, add);
}
// 함수의 시그니처 정의.
typedef Operation = void Function(int x, int y);
void add(int x, int y) {
print('Add Result = ${x + y}');
}
void subtract(int x, int y) {
print('Subtract Result = ${x - y}');
}
// dart 에서 함수는 일급 객체(일급 시민)이므로 함수를 값처럼 사용 가능.
// typedef 으로 선언한 함수를 매개변수로 사용 가능.
void calculate(int x, int y, Operation oper) {
oper(x, y);
}
int addTwoNumbers1(int a, int b) {
return a + b;
}
int addTwoNumbers2({required int a, required int b}) {
return a + b;
}
int addTwoNumbers3(int a, [int b=3]) {
return a + b;
}
int addTwoNumbers4({required int a, int b=3}) {
return a + b;
}