728x90

GetX의 의미

GetX는 매우 가볍고 강력한 상태 관리 솔루션이다.

 

GetX가 제공하는 기능

  • 고성능 상태 관리
  • 지능형 종속성 주입
  • 라우트 관리 기능 제공

 

GetX의 3가지의 기본 원칙

  • 성능
    • 성능과 리소스 소비의 최소화
    • Streams, ChangeNotifier를 사용하지 않는다.
  • 생산성
    • 쉽고 간결한 구문을 사용한다.
    • 사용하지 않는 리소스는 메모리에서 자동으로 제거해준다. 따라서 개발자가 메모리에서 컨트롤러를 제거하는 것을 신경쓰지 않아도 된다.
  • 조직화
    • 화면, 프레젠테이션 로직, 비즈니스 로직, 종속성 주입, 네비게이션을 완전히 분리할 수 있다.

 

1. GetX의 라우트 관리 기능


라우트 관리 기능은 GetX에서 가장 강력한 기능이다. 

 

GetX를 사용하지 않으면 페이지를 이동하거나 다이얼로그 창을 표시할 때 context를 필요로 한다. 하지만 이는 코딩할 때 고려하기 복잡한 개념이다.

 

GetX를 사용하면 context 없이 라우트를 관리할 수 있다. 따라서 코드가 더 간결해지고 쉬워진다.

 

// 변경 전
MaterialApp(
  title: 'Getx example',
  home: HomePage(),
)

// 변경 후
GetMaterialApp(
  title: 'Getx example',
  home: HomePage(),
)

 

1.1. Navigation

기본적인 화면 이동을 할 때 Navigation을 사용한다.

 

1.1.1. Get.to(page)

새로운 화면(page)으로 이동한다.

 

1.1.2. Get.toNamed(name)

미리 설정해둔 이름(name)을 통해 새로운 화면으로 이동한다.

GetMaterialApp(
  title: 'Getx example',
  home: HomePage(),
  getPages: [
    // HomePage()에서 Get.toNamed('/next')를 사용할 수 있게 된다.
    GetPage(name: '/next', page: () => NextPage()),
  ],
)

// NextPage() 페이지로 이동한다.
Get.toNamed('/next');

 

1.1.3. Get.back()

이전 화면으로 돌아간다.

 

1.1.4. Get.off()

다음 화면으로 이동하면서 이전 화면을 스택에서 없애버린다. 이전 화면으로 돌아갈 필요가 없을 때 사용한다.

Get.off(NextPage());

 

1.1.5. Get.offAll()

Get.off()가 이전 화면 하나만 없앴다면 Get.offAll()는 이전의 모든 화면을 없애고 다음 화면으로 이동한다.

Get.offAll(NextPage());

 

1.2. Snackbar


기본적으로 Snackbar는 하단에서만 나온다. 그런데 GetX를 사용하면 Snackbar를 상단에 보이게 할 수도 있다.

 

1.2.1. Get.snackbar()

제목과 메시지를 설정하면 해당 내용으로 Snackbar를 보여준다. 지속시간(duration), 방향(snackPosition), 배경색(backgroundColor) 등 여러 설정들을 추가할 수 있다.

Get.snackbar('Snackbar', 'Snackbar', snackPosition: SnackPosition.TOP);

snackPosition를 SnackPosition.TOP으로 설정했기 때문에 위에서 나오는 것을 볼 수 있다.

 

1.2.1. Get.showSnackbar()

Get.snackbar()와 거의 동일하지만 디자인이 아무 설정도 되어 있지 않은 상태다.

Get.showSnackbar(
  GetBar(
    title: 'Snackbar',
    message: 'Snackbar',
    duration: Duration(seconds: 2),
		snackPosition: SnackPosition.BOTTOM,
  ),
);

똑같이 제목과 메시지만 설정하고 실행했을 때, Get.snackbar()는 디자인과 설정이 기본적으로 되어있는 것에 비해 Get.showSnackbar()는 아무 설정도 안 되어 있는 것처럼 보인다. 이러한 점 때문에 Get.showSnackbar()보다 Get.snackbar()를 사용하게 되는 것 같다.

 

1.3. Dialog


1.3.1. Get.defaultDialog()

Dialog를 화면에 띄어준다. 확인/취소 시에 실행할 함수(onConfirm, onCancel), 확인/취소 텍스트(textConfirm, textCancel), 배경색(backgroundColor) 등 여러 설정들을 추가할 수 있다.

Get.defaultDialog(title: 'Dialog', middleText: 'Dialog');

제목과 텍스트만 설정했지만, 디자인 및 설정들이 기본적으로 되어있는 것을 볼 수 있다.

 

1.3.2. Get.dialog()

Get.defaultDialog()와 달리 원래 사용하던 Dialog 위젯을 가져와서 사용할 수 있다. 따라서 새로 시작하는 프로젝트에서 GetX를 적용할 때는 Get.defaultDialog()를 사용하는 것이 좋지만, 원래 존재하는 프로젝트에 GetX를 적용할 때는 Get.dialog()를 통해 기존 Dialog 위젯을 복사하여 빠르게 작업하는 편이 좋을 것 같다.

Get.dialog(
  Dialog(
    child: Container(
      height: 100,
      child: Center(
        child: Text('Dialog'),
      ),
    ),
  ),
);

 

1.4. BottomSheet


1.4.1. Get.bottomSheet()

내부에 들어갈 위젯만 넣어주면 해당 위젯을 포함하는 BottomSheet를 보여준다.

Get.bottomSheet(
  Container(
    height: 100,
    color: Colors.white,
    child: Center(
      child: Text('BottomSheet'),
    ),
  ),
)

 

2. 상태(State) 관리


GetX에는 크게 두가지의 상태 관리법이 존재한다. 이 두가지의 상태 관리법을 아주 간단한 예시인 Counter( 버튼 클릭 시에 숫자가 1씩 증가) 예시를 통해 설명해보겠다.

 

2.1. simple 방식

첫번째 방식은 비교적 간단한(simple) 방식이다. 이 방식은 이후에 설명할 reactive 방식보다 메모리를 적게 사용한다는 장점이 있다.

 

2.1.1. 변수 선언

GetxController를 extend하는 Controller 클래스를 선언하고, 초기값을 0으로 설정한 count1 변수를 선언한다.

class Controller extends GetxController {
  var count1 = 0;
}

 

2.1.2. GetBuilder()

GetBuilder을 통해 화면에 count1 변수를 보여준다. 이때 init을 설정하지 않으면 에러가 발생하는 것을 유의하자.

// GetBuilder 안에 GetXController를 상속한 Controller 클래스가 존재하는 것을 알 수 있다.
GetBuilder<Controller>(
  init: Controller(),
  builder: (_) => Text(
    'clicks: ${_.count1}',
   ),
)

 

2.1.3. update()

이제 count1 변수를 증가시켜야 한다. 또한 증가할 때 이를 화면에 알려줘야한다. 이를 위해 update() 함수를 사용한다.

class Controller extends GetxController {
  var count1 = 0;

  void increment1() {
    count1++;
    update();
  }
}

 

2.1.4. Get.find()

Get.find(), Get.put()는 순서상 여기에 들어가는 것이 매끄러워서 넣은 것이지, simple 방식에 해당하는 것은 아니다. 두 방식(simple, reactive) 모두에서 사용된다.

Get.find()을 사용하여 increment1()을 호출하는 버튼을 만들어 텍스트 아래에 배치한다.

TextButton(onPressed: Get.find<Controller>().increment1, child: Text('increment1'))

하지만 리빌드해보면 Get.find<Controller>()에서 에러가 발생할 것이다. 이는 Get.find<Controller>()가 Controller를 찾는 시점이 GetBuilder()의 init에서 Controller를 등록하기 이전이라서 그렇다.

 

2.1.5. Get.put()

이 문제를 해결하기 위해서 Get.put()을 사용한다.
우선 build() 메소드 내부에 controller 변수를 선언하며, 이때 Get.put()를 통해 Controller를 등록한다.

@override
Widget build(BuildContext context) {
  final controller = Get.put(Controller());
  // ...
}

controller 변수를 선언하면서 Controller를 등록했기 때문에 GetBuilder에서 또 등록할 필요가 없다. 따라서 init 부분을 지운다.

GetBuilder<Controller>(
  // init 부분 삭제.
  builder: (_) => Text(
    'clicks: ${_.count1}',
  ),
)

버튼에서 increment1()를 호출할 때, Get.find() 대신 controller 변수를 사용한다.

TextButton(onPressed: controller.increment1, child: Text('increment1')),

리빌드 해보면 에러가 발생하지 않을 것이다. 또한 버튼을 클릭해보면 화면의 숫자가 증가하는 것을 확인할 수 있다.

 

2.2. reactive 방식


simple 방식은 메모리를 적게 사용한다는 장점이 있었다. 그렇다면 reactive 방식은 무슨 장점이 있고 어느 경우에 사용할까?
reactive 방식에는 simple 방식에는 없는 특별한 기능이 있다. 이에 대해선 reactive 방식에 대한 기본 설명 이후에 정리해보도록 하겠다.

 

2.2.1. Rx(observable 변수) 선언

reactive 방식에서는 observable 변수라는 특별한 변수를 사용한다. observable 변수를 Rx라고도 부른다. 이런 Rx를 선언하는 방법에는 아래와 같이 3가지가 있다.

  • Value.obs
  • Rx<Type>(Value)
  • RxType(Value)

이 중 가장 간단한 1번 방법을 주로 사용한다. 우리도 1번 방법을 사용하여 count2 변수를 정의해보자.

  var count2 = 0.obs; // 1번
  var count2 = Rx<int>(0); // 2번
  var count2 = RxInt(0); // 3번

 

2.2.2. Rx(observable 변수)의 값 접근 .value

Rx의 값을 접근할 때는 일반적인 변수의 값의 경우와 다르게 .value를 통해 접근할 수 있다. 여기서 주의해야할 점이 있다. String과 int 같은 primitive type에는 .value를 사용해야하지만, List에서는 .value가 필요없다. dart api가 리스트에서만 .value 없이도 값에 접근할 수 있게 해주기 때문이다.

이 점은 좀 불편하다고 생각했다. .value를 붙이는 것은 더 길게 써야하는 것이고, 그 방법 마저도 List라는 예외가 존재하기 때문이다. 이 이유에 대해서 찾아보니, code generator와 decoration을 사용하면 이 불편함을 해결할 수 있지만, 외부 종속성을 없애고자 그대로 두었다고 한다.

void increment2() => count2.value++;

.value를 사용해서 count2의 값을 1 증가시키는 increment2() 함수를 정의했다. reactive 방식에선 update() 함수가 필요하지 않다.

 

2.2.3. GetX()

simple 방식의 GetBuilder과 같은 역할을 하는 것이 GetX이다. 그럼 GetX를 사용해서 count2의 값을 보여주는 텍스트를 만들어보자.

GetX<Controller>(
  builder: (_) => Text(
    'clicks: ${_.count2.value}',
  ),
),

이전 simple 방식 예시(count1) 예시에 count2 코드를 추가하고 있기 때문에 이미 Get.put()을 통해 Controller를 등록한 상태이다. 따라서 GetX에 init 부분을 넣지 않았다. 필요한 경우에는 GetBuilder에서처럼 init을 통해 Controller를 등록할 수 있다.

 

count2의 초기값인 0이 보이는 것을 확인할 수 있다.

 

2.2.4. Obx()

GetX보다 더 간단한 방법이 있다. 바로 Obx()를 사용하는 것이다. Obx()의 경우 사용할 컨트롤러의 종류를 따로 명시할 필요가 없고, 보여줄 위젯만 리턴하면 된다. 하지만 이 방법은 무조건 Get.put()을 필요로 한다.

Obx를 사용해서 count2의 값을 보여주는 텍스트를 만들어서 GetX를 통해 만든 텍스트 하단에 배치하자.

Obx(() {
  return Text(
    'clicks: ${controller.count2.value}',
  );
}),

이미 앞선 예시에서 Get.put()을 통해 선언한 controller가 존재하기 때문에 이를 바로 사용했다.

TextButton(onPressed: controller.increment2, child: Text('increment2'))

버튼을 클릭하면 GetX()를 통해 만든 텍스트와 Obx()를 통해 만든 텍스트의 숫자가 모두 증가하는 것을 확인할 수 있다.

 

2.2.5. Workers

이전에 말했던 reactive 방식에서만 사용할 수 있는 특별한 기능들이 바로 Workers이다. Workers를 사용하면 Rx들의 변화를 감지하고 다양한 상황 별로 적절한 대응을 하도록 구현할 수 있다.

Workers에는 아래와 같이 총 4가지가 있다.

// count2가 처음으로 변경되었을 때만 호출된다.
once(count2, (_) {
  print('$_이 처음으로 변경되었습니다.');
});
// count2가 변경될 때마다 호출된다.
ever(count2, (_) {
  print('$_이 변경되었습니다.');
});
// count2가 변경되다가 마지막 변경 후, 1초간 변경이 없을 때 호출된다.
debounce(
  count2,
  (_) {
    print('$_가 마지막으로 변경된 이후, 1초간 변경이 없습니다.');
  },
  time: Duration(seconds: 1),
);
// count2가 변경되고 있는 동안, 1초마다 호출된다.
interval(
  count2,
  (_) {
    print('$_가 변경되는 중입니다.(1초마다 호출)');
  },
  time: Duration(seconds: 1),
);

이 4가지의 Workers를 Controller에 적용해보자.

void onInit() {
  super.onInit();

  once(count2, (_) {
    print('$_이 처음으로 변경되었습니다.');
  });
  ever(count2, (_) {
    print('$_이 변경되었습니다.');
  });
  debounce(
    count2,
    (_) {
      print('$_가 마지막으로 변경된 이후, 1초간 변경이 없습니다.');
    },
    time: Duration(seconds: 1),
  );
  interval(
    count2,
    (_) {
      print('$_가 변경되는 중입니다.(1초마다 호출)');
    },
    time: Duration(seconds: 1),
  );
}

Controller에 onInit() override하고, super.onInit()을 제일 먼저 호출한다. 그 다음 사용하고자 하는 Worker를 등록해주면 된다.

 

 

728x90

Flutter는 2017년 5월에 구글이 출시한 모바일/웹/데스크톱 크로스 플랫폼 GUI SDK이다. 하나의 코드로 안드로이드, 아이폰, 맥, 리눅스, 윈도우즈, 및 웹 브라우저까지 모두 동작되는 앱이라는 것에 매력을 느껴 공부를 시작했다.

 

https://www.youtube.com/watch?v=BLcDObFKylE&ab_channel=DynamicCoding 

기본적인 동작만 하는 토이 프로젝트를 만들었었는데 보다 고도화된 앱을 개발하고 싶었다. 동계 모각코 계획은 다음과 같다.

 

모각코 계획

- Flutter + Firebase 알람 공유 어플리케이션 개발

- Google play에 배포

- 유지보수

 

[1회차]

Flutter 최신 개념 이해 및 정리 (Flutter Devtools, null-safety 추가 이유, GetX, StreamBuilder vs FutureBuilder)

 

[2회차]

기획, 스토리보드 작성 및 User flow 작성

페이지 수 산정 후 Figma로 대략적인 느낌 디자인하기 (색 선택, Material UI vs Cupertino UI)

Bottom navigation, Stack navigation 등 기본 UI 틀 구현

 

[3회차]

복잡한 로직의 세부 기능 구현 (알람 공유, 반복 알람)

 

[4회차]

 

Firebase firestore CRUD 구현 및 테스트

 

[5회차]

 

알람 기능 개선하기

 

[6회차]

 

Google play에 어플리케이션 출시

728x90

Flutter :: 핵심개념

1. StatelessWidget(SLW)과 StatefulWidget(SFW)의 차이

=> 각 위젯의 상태변경이 반영되어야 할 필요가 없으면 SLW, 아니면 SFW

 

SLW

StatelessWidget은 이름 그대로 상태(State)를 가지지 않는 위젯 클래스다. 그래서 SLW 내부의 모든 UI 위젯들은 상태를 가질 수 없으며 상태가 없으니 상태의 변화를 인지할 필요도 없고 할 수도 없는 것이다. 그래서 화면이 생성될 때 한 번만 build 메서드를 호출해서 화면을 구성한 후에는 build 함수가 다시 호출되지 않는다. 버튼을 클릭하여 _count의 값을 변경시키더라도 build 메서드는 호출되지 않으므로 화면 내 Text 위젯의 값도 변경되지 않는 것이다. SLW은 변화가 필요없는 화면을 구성할 때 사용하는 위젯 클래스이며, 그렇기 때문에 build 메서드는 한 번만 호출된다.

 

SFW
화면의 구성이 상태 변화에 따라 재구성되어야 할 때 사용된다.
상태 변경은 setState 메서드를 이용해서 변경해야 한다.
setState 메서드가 호출될 때마다 build 메서드를 재호출하여 화면을 다시 그린다.

 

2. Scaffold

=> AppBar / Body / BottomNavigationBar / FloatingActionButton / FloatingActionButton / FloatingActionButtonLocation 지원

 

AppBar
주로 창 이름을 담당하고 있습니다. Android 에서 TitleBar, iOS에서는 NavigationBar로 불리던 영역 입니다.

 

Body
가운데 영역을 의미합니다.

 

BottomNavigationBar
다른 창 으로 이동할 수 있는 버튼들이 있는 하단 바 입니다. Android에선 Bottom Navigation Bar라는 명칭을 그대로 쓰고 있고 iOS에선 Tab Bar로 불리던 영역 입니다.

 

FloatingActionButton
창 위에 떠 있는 효과를 주는 버튼을 말합니다. Android에선 support library를 통해서 FloatingActionButton을 지원해주고 있습니다. iOS 에선 직접 버튼을 배치해서 사용했습니다.

 

FloatingActionButtonLocation
버튼의 위치를 설정합니다. 버튼을 하단 영역에 반쯤 걸치려면 보통 귀찮은게 아닌데, flutter에선 너무나도 가볍게 설정 되버립니다.

 


 

 

 

728x90

Flutter :: 플러터/앱개발/스키아엔진/개요/성능/HotReload/인기/디자인예시/Dart문법

 

1. 개요

구글에서 2017년 5월 크로스 플랫폼 모바일/웹/데스크톱 GUI 애플리케이션 소프트웨어 디자인 프레임워크로서 출시되었다. 언어도 구글에 의해 만들어진 Dart를 사용한다. 컴파일된 프로그램은 안드로이드와 iOS상에서 실행을 지원하며, 웹 브라우저에서 플러터 앱 실행을 위한 transpilation, 리눅스/윈도우즈/맥 데스크톱 상에서 플러터 앱 실행 지원도 공식 발표되었으며 구글 안드로이드 OS의 차기 운영체제인 퓨시아의 유저 인터페이스 및 퓨시아 애플리케이션들이 플러터로 작성된다.

네이티브 코드로 직접 변환 및 UI를 자체 렌더엔진(Skia Engine)으로 직접 렌더링하기 때문에 성능이 뛰어나며 크로스플랫폼 솔루션으로 iOS에서 구글의 Material 테마 디자인과 Ripple 애니메이션을 볼 수 있고 Android에서 애플의 Cupertino 테마 패키지를 사용가능하다. 마치 화면 전체를 그래픽 API로 fillRect 하고 drawText drawImage 해서 앱을 만드는 것처럼 Flutter는 Skia 엔진을 사용하여 렌더링해준다. 각 OS 플랫폼의 네이티브 widget/ui 구성요소로 변환하지 않고 플러터의 그래픽 렌더 엔진인 스키아로 직접 플랫폼 Canvas상에 드로잉되기 때문에 성능이 높고 플랫폼에 관계없이 픽셀 퍼팩트한 디자인을 구현가능하다.

 

2. 성능

크로스플랫폼 환경에서 네이티브 이상의 성능과 미려한 UI와 확장 기능을 제공하는 프레임워크이다. Flutter Gallery 샘플 앱을 설치하여 성능을 확인할 수 있다. 아이폰에서는 소스 코드를 다운받아 Xcode에서 실행하면 잘 작동한다. 안드로이드 스튜디오나 IntelliJ IDEA, 비주얼 스튜디오 코드 등을 이용하여 소프트웨어 디자인을 할 수 있다.

 

3. Hot Reload 기능

Hot Reload 기능을 사용하면 디버깅을 중지하지 않고 소스 수정 후 저장만 하면 에뮬레이터나 기기에 바로 반영되어 UI와 로직이 모두 업데이트된다. 프레임워크의 구조 설계 자체가 이 기능을 위해 특화되어있다. Hot Restart까지 사용하면 프로젝트를 닫기 전까지 디버깅을 중지할 필요가 없다.

 

4. 인기

Flutter UI로 작성한 코드를 Web 에서 실행할 수 있는 Hummingbird가 발표되었다. 개발자들의 호응을 알 수 있는 GitHub 스타 순위는 2020년 4월 기준 7위까지 올라왔다.


5. 디자인 예시

 

 

 

 

6. Dart문법

문장끝

세미콜론을 붙인다.

 

자료형

int / double / String / bool / num

 

* num : int와 double 포함

 

타입추론

var

 

상수

final : 문자열 상수 / final String str = "hello";

const : 상수 / const int count = 0;

 

연산자

+

-

*

% : 나머지

/ : 나누기

~/ : 몫

 

증감 연산자

++i / --i / i++ / i--

 

비교 연산자

== / != / < / > / <= / >=

 

논리 연산자

&& / || / ! / == / !=

 

주석

// statement : 한줄 주석

/* statement */ : 여러줄 주석

 

데이터 타입 검사

is / is! : 같은 타입인지 검사 ( true, false 반환 )

ex)

int a = 10;
print(a is int) // true

 

ex)

var a = 10; // 타입추론(int)
print(a is int) // true

타입 변환

class Parent {

    String name;

}

class Child extends Parent {

    String school;

}

void main() {

    // EX 1

    Parent parent = Parent();

    parent.name = "강호동";

    

    Child child = parent as Child // 다운캐스팅



    // EX 2

    Child child = Child();

    child.name = "강호동";

    

    Parent parent = child as Parent // 업캐스팅, 불필요한 행위, 이미 부모의 속성을 가지고 있기 때문에



}

 

 

 

 

 

+ Recent posts