플러터 교제 코딩 따라하는데, null safety 오류 때문에 힘들면, 

안드로이드 스튜디오 경우, Run > Edit Configurations에 들어가, 

Additional run args 에 

-- no-sound-null-saftey 

를 추가합니다. 

안드로이드 스튜디오 Run > Edit Configuration
Additional run args 에 항목 추가

사실 의미도 잘 이해하지 못한채 null safety 를 맞추려고 ?, ! 를 무분별하게 남발하는 것보다는 소스를 깔끔하게 관리하면서, 코딩하는 것도 방법입니다. 특히, Do it 플러터 외에 다른 책들도 ?, ! 이 없던 예전 플러터 버전에 작성된 책들이 많아 그대로 따라 코딩하려면 이 방식이 제일 좋습니다.

이렇게 설정해도 실행과정에서 Null 값 오류가 발생되면 똑같이 Error가 발생합니다. 상황에 따라 소스를 수정하거나, ?, !를 추가해서 오류를 잡을 수 있습니다.

요약 
• Run > Edit Configurations > Additional run args 에 [ -- no-sound-null-saftey ] 를 추가하여 null safety 검증을 중지할 수 있다.
Posted by 목표를 가지고 달린다
,

ListView.builder를 이용해서 리스트뷰를 만들고, 이벤트(클릭, 더블클릭 등) 동작 처리

아래의 문법을 활용해서 Card안에 이미지, 텍스트 위젯을 코딩하면 됩니다. 
ListView.builder(itemBuilder : (context, position) {
      return Card(
       // 이 부분에 위젯을 이용한 데이터 표시
      );
     , itemCount : list!.length), // List 만큼의 스크롤을 생성해 줍니다. 
}

위는 교제에 있는 기본적인 소스입니다. 코딩을 따라하시다가 null safety 오류가 나오면 변수타입 선언 및 이용시에 ?, ! 을 적절히 이용하시면 오류가 잡히실 겁니다. 플러터는 2.0부터 강력한 null safety 정책 적용했습니다. 기본적으로 모든 변수는 'non nullable' 입니다. 하지만 개발 과정에 당장 초기값을 설정하지 않는다면 오류가 발생할 수 있습니다. 그래서 변수타입 뒤에 '?' 울 붙여서 nullable 이라고 선언해줍니다. (ex. int? age) 선언은 했지만, 실제 소스검증 과정에서 null값인 경우 오류가 발생할 것으로 예상되는 부분에 대해서는 '!' 을 붙여서 '이 부분은 로직상 무조건 값이 들어가 있다고 개발자가 표시'해야 합니다. 

null safety 정책에 따른 ?, ! 를 추가한 소스

// FirstPage.dart
import 'package:flutter/material.dart';
import '../animalItem.dart';

class FirstApp extends StatelessWidget {
  final List<Animal>? list;
  FirstApp({Key? key, this.list}) : super(key:key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        child: Center(
          child: ListView.builder(itemBuilder: (context, position) {
            return Card( // 이 부분에 위젯을 이용해 데이터 표시
              child: Row(
                children: <Widget>[
                  Image.asset(list![position].imagePath!,
                    height: 100,
                    width: 100,
                    fit: BoxFit.fitHeight),
                  Text(list![position].animalName!)
                ],
              ),
            );
          },
          itemCount: list!.length),
        ),
      ),
    );
  }

}

ListView 를 이용하려면 생명주기(initState(), dispose())에 따라 개발

initState()에서 TabController를 생성하고, 리스트를 만듭니다. dispose()에서 메모리 누수 방지를 위해 controller를 제거합니다. 그리고 build()에서 TabBarView, TabBar를 만듭니다. 앞에 2개에는 모두 controller가 들어가 있어야 합니다. 

class MyHomePage extends StatefulWidget {


  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {

  TabController? controller;
  List<Animal> animalList = [];

  @override
  void initState() {
    super.initState();
    controller = TabController(length: 2, vsync: this);

    animalList.add(Animal(animalName: "고슴도치", kind: "동물", imagePath: "repo/images/ani (1).jpg"));
    animalList.add(Animal(animalName: "고양이", kind: "동물", imagePath: "repo/images/ani (2).jpg"));
     ......................
    animalList.add(Animal(animalName: "양", kind: "동물", imagePath: "repo/images/ani (12).jpg"));

  }

  @override
  // 스테이이트풀 생명주기에서 위젯의 상태를 완전히 끝내도록 하여 메모리 누수를 막는다.
  void dispose() {
    controller!.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: Text('Listview example'),
      ),
      body: TabBarView(
        children: <Widget>[
          FirstApp(list: animalList),
          SecondApp()
        ],
        controller: controller),
      bottomNavigationBar: TabBar(
        tabs: <Tab>[
          Tab(
            icon: Icon(Icons.looks_one, color: Colors.blue),
          ),
          Tab(
            icon: Icon(Icons.looks_two, color: Colors.blue),
          )
        ],
        controller: controller,
      ),
    );
  }
}

ListView 터치 이벤트로 알림창 띄우기

ListView.Builer 안에 Card를 GestureDetector 위젯으로 감싼 후, 이벤트를 정의하면 됩니다. 이벤트는 터치(탭, 클릭), 두번 터치(더블 클릭), 길게 누르기 , 끌기 등이 있습니다. 

return GestureDetector(
                child : Card( // 이 부분에 위젯을 이용해 데이터 표시
                  child: Row(
                    children: <Widget>[
                      Image.asset(list![position].imagePath!,
                        height: 100,
                        width: 100,
                        fit: BoxFit.fitHeight),
                      Text(list![position].animalName!)
                    ],
                  ),
                ),
                onTap: () {
                  AlertDialog dialog = AlertDialog(
                    content: Text('이 동물은 ${list![position].kind}', style: TextStyle(fontSize: 30),
                                ),
                  );
                  showDialog(context: context, builder: (BuildContext context)=> dialog);
                },
            );
요약
• ListView.Builer(itemBuilder:(context, position)을 이용해서 리스트뷰를 생성
• itemCount : list!.length 를 이용해서 스크롤 생성
• initState(), dispose() 에서 컨트롤 생성 및 파기 
• GestureDetector 위젯으로 터치 등 이벤트 처리

 

Posted by 목표를 가지고 달린다
,

플러터 TabBarView, Tabbar 만들기

여러 페이지를 만들고 이동하기 위해 Navigator로 이동하는 것도 있지만, 우선 많이 이용하는 탭바를 먼저 알아보겠습니다. 

1. 먼저 Tabcontroller를 선언하고, 탭의 갯수를 정해줍니다. 

추가로, 클래스 선언할 때, with SingleTickerProviderStateMixin를 추가해야 탭을 눌렀을 때, 애니메이션 동작을 처리할 수있게 합니다. 필수입니다.

class _MyHomePageState extends State<MyHomePage>
  with SingleTickerProviderStateMixin {

  TabController? tabController;

  @override
  void initState() {
    tabController = TabController(length: 3, vsync: this);
    super.initState();
  }

2. dispose() 함수에서 메모리 누수를 막아야 합니다.

class _MyHomePageState extends State<MyHomePage>
  with SingleTickerProviderStateMixin {

@override
void dispose() {
	controller.dispose();
    super.dispose();
}

3. Scafold(appBar, body, bottomNavigationBar) 구조에서

  • 1안) body안에 TabBarView를 선언하고, bottomNavigationBar안에 TabBar를 선언
  • 2안) appBar>AppBar>bottom>TabBar에 icond을 정의하고, body>TabBarView를 정의
1안)
  @override
  Widget build(BuildContext context) {
    // Scaffold로 appBar, body, bottomNavigation를 선언한다.
    return Scaffold(
      appBar: AppBar(
        title: Text('TabBar Example'),
      ),
      // body에 TabBarView을 bottomNavigationBar에 TabBar 위젯을 넣는다.
      body: TabBarView(
        children: <Widget>[
          FirstApp(),
          SecondApp(),
        ],
        controller: controller,
      ),
      bottomNavigationBar: TabBar(
        tabs: <Tab>[
          Tab(
            icon: Icon(Icons.looks_one, color: Colors.blue),
          ),
          Tab(
            icon: Icon(Icons.looks_two, color: Colors.blue),
          )
        ],
        controller: controller,
      ),
    );
  }
2안)
@override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        bottom: TabBar(
          controller: tabController,
          tabs: [
            Tab(
              text: 'First',
            ),
            Tab(
              text: 'Second',
            ),
            Tab(
              text: 'Third',
            ),
          ],
        ),
      ),
      body: TabBarView(
        controller: tabController,
        children: [
          Container(
            color: Colors.red,
          ),
          WillPopScope(
            onWillPop: () async {
              tabController?.animateTo(0, duration: Duration(milliseconds: 500),);
              return false;
            },
            child: Container(
              color: Colors.yellow,
            ),
          ),
          WillPopScope(
            onWillPop: ()async {
              tabController?.animateTo(1, duration: Duration(milliseconds: 500),);
              return false;
            },
            child: Container(
              color: Colors.green,
            ),
          ),
        ],
      ),
    );
  }

탭바 구현 화면

요약
• 탭바로 다양한 내용을 화면 전환으로 보여줄 수 있다.
• 인터넷 예제 소스를 그대로 사용할 경우 오류가 날 때, 원인을 보고 null safety 문제라면 변수에 '?' 를 추가하면 해결
Posted by 목표를 가지고 달린다
,

플러터 핵심 기능(위젯과 이미지, 폰트 같은 Asset 이용)

  • Scafold 를 이용한 머티리얼 디자인 
  • 이미지와 폰트 추가
  • 사용자와 상호작용하는 앱 만들기

안드로이드 앱을 개발하려면 구글에서 지정한 디자인 지침이 있습니다. 머티리얼 디자인(material Design). 개발하는 앱의 특별함, 독특한 디자인도 좋지만, 사용자 입장에서 직관적이고 일관된 UI를 제공해야 거부감없이 자연스럽게 이용 가능하기에 플러터는 아래와 같이 머티리얼 디자인 라이브러리를 제공합니다.

import 'package:flutter/material.dart';

플러터 코드의 골격은 아래와 같습니다.

  1. main() 에서 runApp() 으로 StatelessWidget 호출
  2. MyApp(StatelessWidget) 에서 StatefulWidget 호출
  3. MaterialFlutterApp(StatefulWidget) 에서, createState() 호출
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MaterialFlutterApp(),
    );
  }
}

class MaterialFlutterApp extends StatefulWidget {

  @override
  State<MaterialFlutterApp> createState() => _MaterialFlutterApp();
}

class _MaterialFlutterApp extends State<MaterialFlutterApp> {
  @override
  Widget build(BuildContext context) {

    return Scaffold(      
    );
  }
}

Scafold 를 이용한 머티리얼 디자인

위의 위젯을 build 할때, Scafold() 내용의 구조는 아래와 같습니다.

class _MaterialFlutterApp extends State<MaterialFlutterApp> {
  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: Text('Materail Design App'),
      ),
      floatingActionButton: FloatingActionButton(child: Icon(Icons.add),
        onPressed: () {
        },
      ),
      body: Container(
        // Center is a layout widget. It takes a single child and positions it
        // in the middle of the parent.
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Icon(Icons.android),
            Text('android')
          ],
        ),
      ),
    );
  }
}

추가로, 아이콘을 직접 제작하기 보다는 기본적으로 제공하는 이미지를 활용하는 것이 좋습니다. 아래의 주소에 들어가시면, 다양한 이미지를 한번에 볼 수 있으니 참고하시기 바랍니다.

https://www.fluttericon.com/

 

FlutterIcon - Flutter custom icons generator

This site will not work if cookies are completely disabled. {"assets_hash":"e63afe94764170521b88e195c1026df9","page_data":{},"locale":"en-US","layout":"fontello.layout"}

www.fluttericon.com

머테리얼 디자인 아이콘 화면

이미지와 폰트 추가

이미지를 화면에 표시하려면 3가지 절차가 있습니다.

  1. 이미지 파일을 프로젝트 바로 아래 폴더를 만들어 이동하기
  2. pubspec.yaml 파일에 리소스 경로 추가하기 
  3. Image.asset(경로) 로 코딩하기

pubspec.yaml 등록시 파일단위로 할 수도 있고, 폴더째로 할 수도 있습니다. 또한 asset/images, images 등 폴더명에 대한 제약사항은 없고 프로젝트 바로 아래에만 생성해 주시면 됩니다. 등록 후, "Pub get", "Pub update" 한번 클릭해주세요.

프로젝트내 image 폴더를 만들어 이미지 추가 및 pubspec.yaml 수정

이미지를 추가하는 방식은 2가지가 있습니다. 프로젝트 내에 있는 asset 항목은 Image.asset으로 외부에 있는 것은 Image.network로 코딩하시면 됩니다. 

body: Container(
        // Center is a layout widget. It takes a single child and positions it
        // in the middle of the parent.
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Image.asset('images/flutter.png'),
            Image.network('https://w7.pngwing.com/pngs/14/381/png-transparent-flag-fluttering-red-flag-miscellaneous-flag-flag-of-india-thumbnail.png')
          ],
        ),
      ),

플러터의 이미지가 보이지 않는다면, 

  1. 코딩할 때, 항목의 경로를 정확하게 오타없이 작성했는지 확인
  2. pubspec.yaml 파일의 오타, 경로 확인 후, "Pub get" 실행
  3. 가상디바이스(Emulator) 재실행

사실 저도 이 포스트를 하면서 이미지가 나오지 않아 1시간 동안 위의 1~2번을 몇번을 했습니다. 폴더가 무조건 asset/images 여야 하나? 파일 단위가 아니라 폴더 단위로 해야 하나? 결국엔 가상디바이스 재실행까지 3번정도 하나가 어느순간 짜자~~~안...ㅠㅠ. 뭐가 문제인지는 정확히 알려나 주지..;;;

플러터의 이미지 크기 조절하는 fit 옵션값들

이미지가 원하는 규격이 아닐 경우, fit 옵션에 따라 아래와 그림과 같이 다양하게 표현할 수 있습니다. 

fit:BoxFit 값에 따른 이미지 표시

fit 옵션값 설명
예제 : Image.asset('images/flutter.png', width:100, height:150, fit:BoxFit.fill); 
BoxFit.fill width, height를 가득 채워서 그림
BoxFit.contain 이미지가 잘리지 않고, 비율을 유지하는 범위에서 가능한 크게 그림
BoxFit.cover 이미지가 잘리더라도, 비율을 유지한채 지정한 범위를 모두 덮도록 그림
BoxFit.none 원본 이미지를 그대로 표시하며, 이미지가 잘릴 수 있음
BoxFit.scaleDown 전체 이미지가 나올 수 있게 이미지 크기를 조절해서 표시
BoxFit.fitWidth width를 꽉 채워서 그림. 이미지가 잘릴 수 있음
BoxFit.fitHeight height를 꽉 채워서 그림. 이미지가 잘릴 수 있음

플러터의 폰트 등록 및 사용하기

pubspec.yaml 에 폰트를 등록한 후 사용합니다. 등록 과정에서 yaml을 탭간격이 띄어쓰기 2칸인데 줄맞춤이 맞질 않거나, 리소스명을 변경했다면, hot-reload가 실패할 경우가 많아 AVD를 재실행해야 합니다. 처음에 한글명을 이용했는데, 스튜디오에서는 인식 과정에 문제는 없었으나, 폰트가 변경되지 않은 것 같아 영문으로 변경하고, AVD 재실행하니 폰트가 변경되었습니다. 

무료 폰트는 인터넷에 검색하면 많이 있습니다. 저는 relation2 라는 사이트에서 무료라인센스로 되어 있는 것은 확인 후 다운 받았습니다. 

flutter:
  uses-material-design: true
  assets:
    - images/flutter.png     #파일 기준
    - images/profile/        #폴더 기준
  fonts:
    - family: strong_army
      fonts:
        - asset: assets/font/strong_army_medium.ttf
          weight: 200 #외곽선에 가중치를 100~900사이로 지정

    - family: kwedustrong
      fonts:
        - asset: assets/font/kwedustrong.ttf
          weight: 700

폰트 스타일을 지정한 코드와 결과 화면

사용자와 상호작용하는 앱만들기

TextEditingController()를 만들어, 공통된 입력값 항목 검증을 수행할 수 있습니다. 이 부분은 소스가 너무 많아, 책을 보고 코딩 연습은 했지만 블로그에는 기재하지 않겠습니다. 아래는 RaisedButton 폐기에 따른 수정 코딩입니다.

Padding(
                  padding: EdgeInsets.all(15),
                  child: ElevatedButton(
                    child: Row(
                      children: <Widget>[
                        Icon(Icons.add),
                        Text('더하기'),
                      ],
                    ),
                    style: ButtonStyle(
                          backgroundColor: MaterialStateProperty.all(Colors.amber)),
                    onPressed: () {

                    },
                  ),
                ),

 

요약
• 안드로이드의 머테리얼 디자인을 이용하기 위해 Scafold라는 큰 틀내에 위젯들을 호출
• pubspec.yaml 파일에 이미지, 폰트 등을 등록한 후, AVD 재실행
• TextEditingController를 이용해서 사용자값을 입력받고, 처리

 

Posted by 목표를 가지고 달린다
,

실전코드로 배우는 플러터 기본과 활용

Do it! 플러터 앱프로그래밍

어제는 Flutter의 등장배경과 Flutter에서 사용하는 언어인 Dart의 특징에 대해 간략히 배웠습니다. 

오늘은 플러터의 내부 구조(간단한 샘플코딩)와 위젯의 생명주기에 대해 공부하고자 합니다. 위젯의 생명주기는 안드로이드 액티비티의 생명주기와 비슷하다고 생각하시면 됩니다.

폴더 및 주요 파일 안내

Flutter 프로젝트 구조. 시작 파일은 lib>main.dart

폴더 내용 비고
android 안드로이드 프로젝트 관련 파일 안드로이드 스튜디오로 실행 가능
ios iOS 프로젝트 관련 파일 엑스코드로 실행 가능(맥 전용)
lib 플러터 앱 개발을 위한 다트 파일 플러터 SDK 설치 필요
test 플러터 앱 개발 중 테스트 파일 테스트 편의성 제공
파일 내용 비고
pubspec.yaml 패키지, 이미지, 폰트 등 asset 설정 직접 관리
README.md 프로젝트 소개
.gitignore git에 커밋, 푸시 등 소스 코드를 업로드할 때 필요 없는 파일 기록
서버 주요 정보, 운영/테스트 구분에 따른 배포시 배제해야 할 파일들 명시
.metadata 플러터 SDK 정보 자동 관리
.packages 플러터 SDK에 사용할 기본 패키지 경로
first_flutter_app.iml 파일이 자동으로 생성될때 만들어지는 폴더 위치
pubspec.lock pubspec.yaml 파일에 적용된 패키지 위치

* 플러터는 위젯 기반으로 개발하며, 위젯은 클래스로 정의하여 상속 개념이 존재합니다. 텍스트박스, 이미지, 버튼 모두가 위젯 클래스로 변경이 필요하면, 상속받아 필요한 속성만 수정하면 됩니다. 위젯은 크게 2가지 나눕니다. 상태 고정(statless, 화면에 보이기 위해 로딩한 순간 상태감시를 하지 않아 변화가 없는 위젯)과 상태변경(statefull, 동적 위젯으로 상태를 감시하다 알맞게 화면 변경) 입니다. 따라서 화면 변경이 필요한 경우, statefull 위젯에 코딩해야 합니다. 

교제에 나와 있는대로, 버튼 클릭시 글씨와 버튼색이 변하는 것을 statless위젯(클래스)에 개발하면 아무런 변화가 없지만, statefull위젯에 개발하면 화면이 변하는 것을 확인할 수 있습니다.

추가로 책의 예제를 따라 코딩해보면, 플러터 버전에 따라 폐기된 위젯들이 있어 아래와 같이 소스를 수정해서 코딩해야 합니다. 

// 교제 소스 //
child: RaisedButton (
            child: Text('$test'),
            color: _color,
            onPressed: () {
              if (test == 'hello') {
                setState(() {
                  test = 'Flutter';
                  _color = Colors.amber;
                });
              } else {
                setState(() {
                  test = 'hello';
                  _color = Colors.blue;
                });
              }
            },
          ),
// 수정 RaisedButton -> ElevatedButton
// 수정 style: ElevatedButton.styleFrom 이용
child: ElevatedButton (
            child: Text('$test'),
            style: ElevatedButton.styleFrom(
              primary: _color,
            ),
            onPressed: () {
              if (test == 'hello') {
                setState(() {
                  test = 'Flutter';
                  _color = Colors.amber;
                });
              } else {
                setState(() {
                  test = 'hello';
                  _color = Colors.blue;
                });
              }
            },
          ),

* visualDensity 속성은 앱이 모바일, 웹, 데스크톱, 맥 등 어떤 플랫폼에서도 자연스럽게 보이도록 지원

위젯의 생명 주기 이해

class MyHomePage extends StatefulWidget {
	@override
    _MyHomePageState createState() => new _MyHomePageState();
}

State와 StatefulWidget 클래스를 나눈 이유는 성능 때문입니다. State 클래스가 무겁다 보니, StatefulWidget에서 감시하고 있다가 상태 변경 신호가 오면, State클래스로 화면을 갱신하도록 합니다. 만약 StatefulWidget에서 바로 갱신하면, 화면이 종료되더라도 할당받은 메모리를 바로 반환하지 않기 때문에 두개는 분리한 것입니다. 

호출순서 생명주기 내용
1 createState() 처음 스테이트풀을 시작할 때 호출
2 mounted == true createState() 함수가 호출되면 mounted 는 true
3 initState() State에서 제일 먼저 실행되는 함수. State 생성 후 한번만 호출
4 didChangeDependecies() initState() 호출후에 호출되는 함수
5 build() 위젯을 렌더링하는 함수. 위젯을 반환(=화면에 표시)
6 didUpdateWidget() 위젯을 변경해야 할 때 호출하는 함수
7 setState() 데이터가 변경되었음을 알리는 함수. 변경된 데이터를 UI에 적용하기 위해 필요
8 deactivate() State가 제거될 때 호출
9 dispose() State가 완전히 제거되었을 때 호출
10 mounted == false 모든 프로세스가 종료된 후 mounted가 falsed가 됨

 

요약(프로젝트 구조, 위젯 생애주기...)
• 프로젝트 주요폴더, 파일에 대해 확인
• createState(), initState(), build(), setState(), dispose() 등 생애주기 과정에 log, 데이터 처리 등 수행

 

Posted by 목표를 가지고 달린다
,

실전코드로 배우는 플러터 기본과 활용

Do it! 플러터 앱프로그래밍

오픈API활용, 파이어베이스, 구글맵까지 배울 수 있는 크로스 플랫폼. Flutter에 입문하기 위해 위 플러터 앱프로그래밍 책을 가지고 3주 이내로 공부하려고 합니다. 배우고 정리해서 최종적으로 간단하고 필요한 앱들이 3개 정도 만들 수 있는 수준까지 올라가는 것이 목표입니다. 코틀린이 발전하기 전, 안드로이드 스튜디오에서 Java로 만들때는 디자인이 너무 어려워서 포기도 했었는데, Flutter는 디자인 기반이라 개발자도 쉽게 괜찮은 수준으로 외관을 만들 수 있다고 해서 도전해 보려고 합니다. 

플러터 등장 배경

모바일OS는 안드로이드와 iOS가 대부분 점유하고 있습니다. 애플은 오브젝트-C에서 스위프트(Swift) 언어를를 추가 지원하고 있으며, 안드로이드는 자바에서 Kotlin을 밀고 있습니다.(오라클에서 Java 이용에 대한 라이선스 비용 이슈. 아직 Java기반의 앱들이 많아 Kotlin으로 모두 대체되는데는 시간이 상당 필요) 이런 각 모바일 운영체제에 맞는 언어로 개발한 앱을 네이티브 앱(native app)이라고 합니다.

최근 전세계 iOS와 안드로이드의 시장점유율. 출처 : statista

하지만, 두 운영체제의 언어로 개발하면 2source 1서비스라서 관리가 어려워, 웹앱이나 하이브리드앱이 등장하였습니다. 최근 Progressive 웹앱에서는 알림도 받을 수 있다고합니다. 하지만 이런 하이브리드 앱은 속도 측면에서 느리다는 단점이 있습니다. 

그래서 나왔던게 페이스북의 리액트, 구글의 플러터입니다.

구분 리액트 네이티브 플러터
개발주체 페이스북 구글
언어 자바스크립트 다트
성능 빠르지만, 네이티브 앱보단 느림 네이티브 앱에 근접한 속도
학습 곡선 높음(네이티브 앱 개발자 수준) 낮음(네이티브 앱 개발자 수준)
대표 앱 페이스북, 인스타그램, 핀터레스트, 토스 알리바바, 구글애드센스, 네이버 지식in
장점  웹 개발자의 접근성(코딩을 잘해야 함)
 많은 패키지 이용 가능
 다양한 위젯(디자인 구성이 편함)
 강력한  애니메이션 등
단점 • 기본 위젯이 부족해 커스텀해 사용
 안드로이드/iOS 네이티브 위젯을 이용하기에 OS 판올림에 따른 업데이트 필요
 블루투스 등 네이티브 커스텀해 통신하는 부분 개발이 어려움
 플로터 SDK로 앱 크기가 큼(네이티브 대비)
 업데이트 주기가 빠름(책, 인터넷의 예제에 deprecated(폐기) 된 항목들이 종종 있음)

개발해보면 아시겠지만, hot reload 기능으로 소스 수정하면 별도 빌드과정없이 바로 결과 화면(가상디바이스화면, 웹화면)에 바로 표시해 주어 개발 효율이 높고, 앱을 만들더라도 웹화면에서 개발 결과를 확인해도 됩니다.

개발 환경 구축

안드로이드 스튜디오를 설치한 후, 플러터 SDK를 설치합니다. (https://docs.flutter.dev/get-started/install

정상적으로 설치가 되었다면, CMD창에서 flutter 폴더로 가셔서 flutter doctor 명령어로 개발 환경을 점검할 수있습니다.

flutter 개발환경 점검을 위해 flutter doctor 실행 결과 화면

그리고 안드로이드 스튜디오로 "Create New Flutter Project"를 생성하면서, "Flutter" 플로그인도 설치합니다.

Dart 핵심 요약 
  • 다트 언어의 6가지 특징
  • 비동기 처리 방식
  • JSON 데이터 처리 방식
  • 스트림 통신(순서 보장)

다트언어의 6가지 특징

  1. main()함수로 시작
  2. 변수는 어디에서나 선언 및 사용 가능
  3. 자료형이 엄격하여 선언한 타입외 다른 유형의 값 입력 불가. 선언하고 나중에 타입을 정하려면 var를 이용하고, 동일 변수에 여러 자료형을 허용하려면 dynamic 타입으로 이용 가능
  4. 제네릭 타입 이용 개발 가능 List<int>, List<dynamic> 등
  5. public, protected 같은 키워드 없음. 다만, 외부로 노출하고 싶지 안핟면, 변수나 함수 앞에 언드스코어( _ )를 이용해 표시. _hiddenValue, _hideValue
  6. 삼항 연산자를 자주 사용 (예 : var visibility = isPublic ? 'public' : 'private';)

비동기 처리 방식

다트는 async와 await 키워드를 이용해서 비동기 처리를 구현합니다.

  1. 함수 이름 뒤, 본문이 시작하는 중괄호 { 앞에 async 키워드를 붙여 비동기를 선언
  2. 비동기 함수 안에 언제 끝날지 모르는 작업 앞에 await 키워드를 붙임
  3. 2번 작업을 마친 결과를 받기 위해 비동기 함수 이름 앞에 Future(값이 여러개면 Stream) 클래스를 지정
void main() {
    checkVersion();
    print('end process');
}

Future checkVersion() async {
    var version = await lookUpVersion();
    print(version);
}

int lookUpVersion() {
    return 12;
}

#실행결과#
end process
12

비동기 함수가 반환하는 값을 처리하면, then() 함수를 이용

void main() async {
	await getVersionName().then( (value) => {
    	print(value)
    });
    print('end process');
}

다트는 단일 스레드로 동작하는 프로그래밍 언어

void printTwo() async {
	Future.delayed(Duration(seconds: 1), () {
    	print('Future');
    });
    print('Two');
}

JSON 데이터 주고 받기

Convert라는 라이브러리를 활용하여 쉽게 이용 가능하며, jsonDecode 반대는 jsonEncode() 함수입니다. 

import 'dart:convert';

void main () {
    var jsonString = '''
    [
    	{"lotto" : 5},
        {"lotto" : 15},
     ]
     ''';
     
	var lottos = jsonDecode(jsonString);
    print (lottos is List); // true 출력
    var lottoOne = lottos[0];
    print(lottoOne is Map);  // true 출력
    print(lottoOne['lotto'] == 5); //true
}

스트림 통신하기 

데이터를 주고 받을 때 순서 보장이 필요하다면, 스트림(stream)을 이용합니다. return은 한번 반환하면 함수가 종료되지만, yield 키워드는 반환 후에도 계속 함수를 유지합니다.(계속 반환) 그리고 async* 명령어는 yield를 이용해서 지속적으로 데이터를 전달하겠다는 의미입니다. 

import 'dart:async';

Future<int> sumStream(Stream<int> stream) async {
    var sum = 0;
    await for (var value in stream) {
    	print ('sumSteam : $value');
        sum += value;
    }
    return sum;
}

Stream<int> countStream(int to) async* {
    for (int i = 1; i <= to ; i++) {
    	print('countStream : $i');
        yield i;
    }
}

main() async {
    var stream = countStream(10);
    var sum = await sumStream(stream);
    print(sum);
}

다음처럼 then() 함수를 이용할 수있습니다. 다만, 스트림은 소모하면 사라지므로, first, last, length 등 한번만 실행해야 할 경우 이용합니다. 동일 스트림에 first, last를 같이 사용한다면 오류가 발생할 수 있습니다. 

main() {
    var stream = Stream.fromIterable([1,2,3,4,5]);
    
    //첫번째 값 출력
    stream.first.then((value)=> print('first: $value'));
    //마지막 값 출력
    stream.last.then((value)=> print('last: $value'));
    //스트림 비었는지 출력
    stream.isEmpty.then((value)=> print('isEmpty: $value'));
    //스트림 길이 출력
    stream.length.then((value)=> print('length: $value'));
요약(플러터 시작, 다트를 알면..)
• Flutter는 iOS, 안드로이드용 앱을 한번에 개발할 수 있는 개발 플랫폼
• Dart의 특징
 - proteced처럼 하려면 변수와 함수명 앞에 언드스코어( _ ) 추가
 - async-await-Future를 이용한 비동기 지원
 - 통신 과정에서 순서보장이 필요하면, async*-yield를 이용한 stream(스트림)  

 

Posted by 목표를 가지고 달린다
,

Django MVC 프로젝트가 Micro-frontend React 프로젝트로 탈바꿈하는 과정

Django MVC 프로젝트를 MSA 로 가는 과정

 

거대 프로젝트 구성도

* 인프라 패키지 : 동일한 빌드 툴링을 공유
* 라이브러리 패키지 : 공통 소스코드를 관리
* 서비스 패키지 : 페이지에서 독립적으로 작동

위의 방식을 통해, 소스와 빌드 설정을 완벽히 격리시키고, 서비스별 의존성 문제를 해결하여 빌드업 시간, 소스 동기화 시간을 해결할 수 있습니다. 

Monolithic에서 MSA전환 사례?

MSA(MicroService Architecture)는 Netflix에서 도입되어 IT에서 많이 화자가 되고 있습니다. 다양한 서비스들이 서로 의존성에 대한 걱정없이 배포하고 운영되는 것에 많은 흥미를 가졌습니다.

하지만, 이미 대규모의 웹, 앱을 안정적으로 운영하는 기업 입장에서 100에 +1, +2를 더하는 것은 쉬우나, 이것을 다시 1+1+1+1...(100번) 하여 재구축하는 것은 전면 재구축이라, 단순 MSA로 전환에 따른 이점이 크지 않아 도입하는게 좋은 선택이 아닐 수 있습니다. 또한, MSA에 대한 충분한 이해없이, 서비스간 의존성을 최소화하면서, 빌드, 분산 배포 계획까지 마련한 사례가 없어 참고자료가 마땅치 않습니다. 

MSA의 대표 사례인 Netflix경우, 서비스가 다양하지 않고, 일부 오류(제안, 검색 화면 등)가 나더라도 크리티컬하지 않지만, 금융거래 또는 개인정보 처리 및 전자서명하는 거래의 경우에는 거래의 무결성을 보장해야 하기에 오히려 부적합할 수도 있습니다. 

토스는 다른 전략을 갖고 개발되는 제품(서비스)들이 다르게 개발될 수 있는 문화가 이미 정착되어 있고, 그런 환경이기에 MSA가 적합해 보입니다. 그래서 제품 전략적으로 독립된 제품들이 기술적으로도 독립할 수 있는 것입니다. 따라, 각자 다른 엔지니어와의 커뮤니케이션을 최소화하여 개발에 집중할 수 있지만, 다른 측면으로는 추후 업무 이관 등 관리적면에서는 또다른 문제가 있을 수 있습니다. 기존 Monolithic 방식의 장점이 이런 관리 운영적인 측면에서 체계적이었기 때문입니다. 

빌드 시간 최소화

전체 소스빌드가 아닌 incremental build를 이용하여 변경된 소스만 빌드하고, 기존 소스는 빌드하지 않고, 그대로 이용하면 됩니다. Eclipse 기반에서는 build는 auto를 설정하면, 기본적으로 지금 수정된 파일만 incremental build를 수행하고, "clean"을 명령해야 전체 소스를 build합니다. 또한, 상용/무료 형상관리 프로그램 역시 두가지 버전(clean, incremental)을 모두 지원하고 있습니다. 

우리가 사용하는 서버의 Java 버전이 변경되는 경우는 매우 드믑니다. 없다고 생각하셔도 좋습니다.

  • 전체 소스코드를 rebuild해야 하며, 그과정에서 일부 소스 수정이 필요할 수도 있습니다. 따라 전체 메뉴에 대한 기능 테스트를 수행
  • 이용하고 있는 암호화 솔루션, 프린트 솔루션 등 자바 버전이 충돌되어 오작동하는 솔루션이 있는지 미리 체크
  • Java 버전 변경의 장점이 매우 적어 버전업을 하지 않지만, 너무 오래된 버전(1.3 등)의 경우에는 신기술 도입을 위해 전면 재개발을 할 때, 신규 서버에 Java 버전을 최신의 안정화 버전으로 Up

Java 버전이 변경되는 경우가 아니라면, 굳이 전체 소스를 build, 배포할 필요가 없으며, 배포할 필요가 없는 소스를 굳이 build할 필요 역시 없습니다. 

제로빌드 정책에 따른 소요시간. 불필요한 빌드 과정 단축

요약
• 수평적인 애자일 조직(각자 다른 서비스앱을 개발하는 것이 아니라, 여러팀이 각자 서비스를 단일 앱에 올리는 경우)에 MSA는 좋은 선택
• 변경한 소스만 build : Zero-Build
Posted by 목표를 가지고 달린다
,

DB 이중화, DR센터를 구축해야 하나요?

  • 금융회사라면 전자금융감독규정을 확인해보세요.
  • 인터넷 쇼핑몰이라면 4시간 장애로 인한 판매 중단시 발생하는 손실 금액을 확인해보세요.
  • 대기업이라면 회사 홈페이지 마비에 따른 업무 중단 또는 회사 이미지를 생각해보세요.

재해복구센터(DR) 구성도

시스템 HA구성을 얘기할 때, 3tier 구조(WEB Server - Application Server - DBMS 의 구성) 부터 시작합니다. 추가로 L3 또는 L4 같은 네트워크 장비와 WEB 방화벽 등 정보보호 시스템을 붙여 나갑니다. HA 구성은 이중화를 통해 로드 밸런싱외에도 장애 발생시 Fail-Over 기능을 수행합니다. 서버 장비가 2대 이상 구성함에 따라 많은 비용이 발생합니다. 물론 처음부터 100의 능력을 가진 서버가 아닌 50~70의 서버를 2대 이상 구성하는 것이 비용 측면에서는 좋을 선택일 수 있습니다. 

비용   VS  시스템 구성 복잡도, 장애 포인트 증가

DBMS(DataBase Management System)은 보통 유료버전의 경우 RAC, TAC 처럼 단일 Datafile을 이용하는 여러 instance를 이용하는 방법외에도 Master-Slave 방식의 방법도 제공하고 있습니다. 대부분의 웹서비스는 조회와 데이터처리(등록/정정/삭제) 거래량을 비교하면, 조회가 10배 이상이라 생각됩니다. 주식거래를 하다보면, 우리 자산을 하루에도 10번 이상 체크하지만 거래는 자주 하지 않는 것과 같습니다. 

그래서 데이터를 처리하는 Master가 있고, Master의 변동분을 읽어 동기화하는 N개의 Slave가 있습니다.그래서 이렇게 설계해야 데이터 동기화가 꼬이지 않고 Simple해지기 때문입니다. 동기화하는 전문 솔루션을 이용하거나 OS레벨에서 동기화 지원하는 Disk 제품도 있습니다. 이런 구성은 최소 비용으로 데이터의 성능을 올려 HA를 올리는 방법이고, 여기에 Active-Stanby 구성을 추가합니다. 그러면 Active 서버 장애 발생시 모니터링 서버가 인지하여, 자동(수동)으로 동기화된  Stanby서버로 거래가 들어오면서 서비스를 유지할 수 있습니다. 

재해복구센터(DR) 구성도

여기까지가 주센터에서 할 수있는 HA입니다. 

만약 주센터에서 해결할 수 없다고 판단된다면, DR 전환을 수행해야 합니다. 사실상 금융권은 전자금융감독규정에 따라 주기적으로 DR 전환훈련(재해복구 전환훈련) 을 수행하고 있습니다. 그래서 데이터 동기화도 실시간으로 진행되고 있습니다. 물론 이런 경우 추가로 고려해야 할 사항이 있습니다.

  • RTO(Recovery Time Objective, 목표 복구 시간) : 업무 중단 시점으로부터 복구되어 가동될 때까지의 소요시간, 장애 발생 후 4시간 이내 복구 가능
  • RPO(Recovery Point Objective, 목표 복구 시점) : 비상사태 또는 업무 중단 시점으로부터 데이터를 복구할 수 있는 기준점, 장애 발생 전인 지난 주 목요일에 백업시켜 둔 복원 시점으로 복구 가능 등

금융권외에 다른 권역에서는 백업을 주단위 또는 월단위로 할 수 있기에 복구 시점을 정확히 인지하고, 복구 이후 이후 변경에 대해 대책을 세워야 합니다. 또한, DR 전환이 성공할지라도 DR-주센터가 전환된게 아니기에 다시 안전하게 주센터로 전화해야 한다는 걸 고려해야 합니다. 따라, DR전환은 정말 주센터가 상당 기간 동안 정상화될 수 없다고 판단이 되면 시행해야 합니다. 

센터 장애시나리오 1, Master서버 다운
센터 장애시나리오 2, Fail-over 불가
DR 전환 시나리오

DB를 빨리 복구하는 방법

동기화하는 DR센터가 없는 상태에서 DB datafile이 깨진 경우를 대비하여, 거래량이 적어 Datafile의 변경이 적은 매일 새벽마다 Datafile을 복제하는 방법이 있습니다. 오늘이 6월 27일이면, 복제DB는 6월 27일 02시 기준의 자료입니다. 오늘이 6월 28일이면, 복제DB는 6월 28일 02시 기준의 자료가 됩니다. 복제DB는 관리 목적이 당일 02시 기준의 상태로 복구하는 것입니다. 따라 Full Copy할 테이블과 append하여 전일 변경분만 Copy할 테이블을 정리하여 복제하면 됩니다. DBMS에 따라 Datafile을 복제해도 가능합니다. 

물론, 데이터 복구 이후, 복구시점 이후 거래나 변동에 대해 조치를 취해야 합니다. Log파일이나, 다른 매체에 기록된 정보를 활용해서 재거래(Re-transaction)를 할 수 있는 프로그램(솔루션)을 개발해야 합니다. 재거래는 속도가 생명이지만(복구시간), 거래순서가 중요할 경우 적절히 거래를 Divide하여 병렬처리하면 됩니다. 

주기적인 당일 백업

요약
• 장애 발생에 대한 피해 정도, 법적 의무를 확인하여 비용내에 적절하게 HA 구성
• RTO가 길다면, 월1회에 직접 Datafile을 복제하여 별도 장소에 보관(복구 훈련 필요)
• DR전환은 성공하더라도 주센터로 전환까지 해야 하므로, 주센터 내에서 최대한 복구 대책 마련

https://www.youtube.com/watch?v=t96l6ry_qmw 

 

Posted by 목표를 가지고 달린다
,