728x90
✔︎ 이 포스팅은 3개의 글로 나누어져 있습니다. 또한 Mac 환경에서 Xcode 가 제공하는 iOS Simulator 로 빌드후 배포하는 것에 포커스가 맞추어져 있습니다. 참고하시어 시리즈 글을 읽으시면 건강에 좋습니다!

(1편) Flutter X Firebase, 환경 설정 하기 https://ondemand.tistory.com/266 
(2편) Flutter X Firebase, 패키지 임포트 및 UI 코드 만들기 https://ondemand.tistory.com/276
(3편, 이글) Flutter X Firebase, Firestore 객체로 CRUD 코드 만들기https://ondemand.tistory.com/277
 

 

지난 포스팅에서 우리는 Firestore 를 Flutter 프로젝트에서 사용하기 위하여 패키지를 추가하는 방법을 살펴보았습니다. Firestore 로부터 데이터를 받아오기 위하여 StreamBuilder 위젯을 통해 Firestore 의 스냅샷을 전달받는 코드를 추가했습니다. 전달된 데이터를 ListTile 위젯에 넣기 위해 DocumentSnapshot 타입을 이용했습니다.

Firestore 를 연동하여 UI 에 저장된 데이터를 표현하는 코드를 모두 추가했으니 이제 실제로 데이터를 넣고 빼는 과정에 필요한 코드를 만들어 보겠습니다. To-Do 앱이기 때문에 1) 할 일 추가, 2) 한 일 삭제, 3) 한 일로 기록 정도의 세가지 동작이 필요할 것 같습니다. 각각 _addTodo, _deleteTodo, _toggleTodo 메서드로 정의하여 _TodoListPageState 클래스에 넣어보겠습니다. (이 예제는 모두 <오준석의 플러터 생존코딩> 에 수록된 예제를 기반으로 합니다)


추가 메서드 만들기

먼저 UI 로 입력받은 할 일을 Firestore 에 등록하는 추가 메서드를 살펴보겠습니다. 삭제나 토글 메서드는 Firestore 에 이미 등록된 데이터를 다루는 메서드이고 추가 메서드는 새로운 데이터를 등록하는 메서드이기 때문에 메서드의 파라메터가 조금 다릅니다. (라고 적지만 그냥 나누어 봤습니다)

  void _addTodo(Todo todo) {
    // setState(() {
    //   _items.add(todo);
    //   _todoController.text = '';
    // });
    Firestore.instance
        .collection('todo')
        .add({'title': todo.title, 'isDone': todo.isDone});
    _todoController.text = '';
  }
  

Firestore 를 이용할 때와 이용하지 않고 State 만 이용할 때의 차이를 보기 위해 setState 를 호출하는 코드를 주석처리만 해보았습니다. 이미 선언한 Firestore 전역 객체가 있기 때문에 코드에서 보이는 것처럼 데이터를 쓸 콜렉션과 필드 값을 지정하는 것이 코드의 전부입니다. UI 의 위젯에 입력한 title 과 isDone 값을 할당하는 것으로 충분합니다. 

 

삭제, 토글 메서드 만들기

삭제와 토글 메서드는 추가 메서드와는 달리 메서드의 입력 파라메터로 앞서 이야기 되었던 DocumentSnapshot 타입을 받게 됩니다. 삭제하거나 토글해야 하는 데이터가 Firestore 의 데이터이기 때문입니다. 실제 삭제하거나 업데이트(토글) 할 데이터의 고유한 ID 를 알아야 합니다. Firestore 는 NoSQL DB 이기 때문에 데이터 Row 를 Document 라고 부릅니다. 흔히 NoSQL 을 도큐먼트 DB 라고도 부르는 것을 아마 들어보셨을 겁니다. 

  void _deleteTodo(DocumentSnapshot doc) {
    // setState(() {
    //   _items.remove(todo);
    // });
    Firestore.instance.collection('todo').document(doc.documentID).delete();
  }

  void _toggleTodo(DocumentSnapshot doc) {
    // Parameter : 'todo' variable of 'Todo' type
    // setState(() {
    //   todo.isDone = !todo.isDone;
    // });
    Firestore.instance
        .collection('todo')
        .document(doc.documentID)
        .updateData({'isDone': !doc['isDone']});
  }

이번에도 Firestore 객체가 제공하는 delete(), updateData() 메서드를 이용했습니다. 코드 자체는 무척 간단하죠? 주의해야 할 것은 Firestore 는 이러한 트랜잭션 하나하나를 카운트하고 무료 구간을 넘어가면 과금이 시작됩니다. 큰 규모의 데이터를 가지고 코드를 만들어 보고 있다면 트랜잭션 수가 얼마나 카운트 되었는지 중간중간 체크해 보시는 것이 좋습니다. 

한 것도 없는데 카운트가 쑥쑥...

 

시뮬레이터를 이용하여
실제 앱으로 확인해보기

이제 코드가 준비되었습니다. Xcode 가 제공하는 시뮬레이터를 이용하여 지금까지 만든 코드를 실행해보고 실제로 Firestore 에 데이터가 잘 적재되는지를 살펴보겠습니다. VS Code 에서 F5 를 누르고 <Start iOS Simulator> 를 선택하여 코드를 실행하면 됩니다. 참 쉽죠? 

시뮬레이터의 구동은 늘 시간이 걸리죠. 하지만 무사히 구동이 되었고 미리 등록해 둔 4개의 도큐먼트를 잘 가지고 와서 화면에 리스트로 출력을 해주었습니다. 가만히 보니 버튼 이름에 오타가 들어갔군요? 오타를 코드에서 수정하고 저장하면 Hot Reload 되어 수정된 내용이 바로 반영이 됩니다. Vue 로 Frontend 개발하는 느낌이라 무척 편리하네요!

 

이번에는 할일을 추가하고 Firestore 에 업데이트가 잘 되는지 확인을 해볼까요? 

 

UI 에서 입력한 새로운 데이터가 문제 없이 Firestore 에도 업데이트 된 것을 확인할 수 있었습니다. 이렇게 세번의 포스팅을 통해 Mac 환경에서 VS Code 를 이용하여 iOS 시뮬레이터에 배포하여 시험해보는 과정까지를 살펴보았습니다. 이유는 잘 모르겠지만 Flutter 와 관련된 책들이 대부분 안드로이드 환경에 대한 이야기가 중심이 되어 있어 iOS 로 배포해보고싶다는 생각에서 작업을 진행해 보았습니다.

이제 Flutter 를 이용해서 실제로 써먹을만한 앱을 기획해서 마켓에 배포해 볼 시간입니다. 100세 시대, 지속적으로 벌이를 할 수 있는 방법이 필요한 시대입니다! 강력 추천해 드리는 <오준석의 플러터 생존 코딩>은 아래의 링크를 참고하시기 바랍니다! 그 사이에 에이콘에서 번역서가 한 권 더 나온거 같으니 한 번 읽어봐야겠습니다!

 

오준석의 플러터 생존 코딩

소문난 명강사 ‘오준석’이 안드로이드·iOS 앱 개발자에게 보내는 선물 같은 책앱을 만드는 ‘완벽한 준비’에 시간을 낭비하지 말자. 이 책은 기본을 빠르게 익히고 앱을 직접 만들며 필요한

www.yes24.com

본 포스팅은 제휴마케팅을 통해 소정의 수수료를 지급받을 수 있습니다

728x90
728x90
✔︎ 이 포스팅은 3개의 글로 나누어져 있습니다. 또한 Mac 환경에서 Xcode 가 제공하는 iOS Simulator 로 빌드후 배포하는 것에 포커스가 맞추어져 있습니다. 참고하시어 시리즈 글을 읽으시면 건강에 좋습니다!

(1편) Flutter X Firebase, 환경 설정 하기 https://ondemand.tistory.com/266 
(2편, 이글) Flutter X Firebase, 패키지 임포트 및 UI 코드 만들기 https://ondemand.tistory.com/276
(3편) Flutter X Firebase, Firestore 객체로 CRUD 코드 만들기https://ondemand.tistory.com/277
 

먹고 살기 바쁘다 보니 첫 포스팅을 올리고 두달여만에 후속 포스팅을 올립니다. 그 사이에 생성해 둔 Firebase 의 Firestore 가 보안 정책 만료가 되었네요. 스스로 적어둔 지난번 포스팅을 다시 참고해 가면서 Firebase 에 Firestore NoSQL DB 를 사용 가능하도록 변경하고 글을 시작하도록 하겠습니다. 지난번 글은 아래의 링크입니다! (블로그가 역시 이렇게 좋습니다 ㅎㅎ)

 

Flutter X Firebase, 플러터에서 Firestore NoSQL DB 를 사용해보자 - iOS 와 VS Code 를 중심으로!

Flutter 를 이용하면서 Firebase 를 이용하는 방법을 살펴보겠습니다. 둘 다 구글에서 만든 것이라 그런지 연동하는 것이 어렵지 않습니다. 다만 손이 좀 가게되고 처음 다루는 경우에 조금 헤멜 수 �

ondemand.tistory.com


Firestore 정책 업데이트 하기 

Firebase 의 Firestore 는 최초 생성시 30일간 인증 없이 접근할 수 있는 Rule을 제공합니다. Firestore 콘솔의 <Rules> 탭에서 확인할 수 있습니다. 아마도 8월 14일에 DB 를 생성했고 그 시점으로부터 +1m 시점이 만료 시점으로 잡힌 것 같네요. 불필요한 비용 발생을 막기 위해서 들어가는 코드라고 생각하면 괜찮다는 생각이 듭니다.

물론 실제로 상용 서비스에서 Firestore 를 사용하는 경우에는 날짜 단위의 정책이 아니라 인증을 받은 경우 액세스가 가능하도록 변경하는 것이 좋을 것 같습니다. 자세한 내용은 Firestore 의 Rule 관련한 가이드를 살펴보시기 바랍니다! (이 글이 Firestore 를 위한 글은 아니라서라고 변명해 봅니다)

 

Firestore 사용을 위한 패키지 Import

지난 포스팅을 잘 따라오셨다면 Firestore 사용을 위한 준비가 끝났습니다. main.dart 파일을 열어서 추가한 Firestore 패키지를 코드에서 사용할 수 있도록 해보겠습니다. dart_todo 앱의 전체 코드 구조는 아래와 같습니다. 

2행에 추가된 것처럼 cloud_firestore.dart 를 import 해주셔야 합니다. 간단한 프로젝트이기 때문에 코드의 구조도 간단합니다. Firestore 를 다루는 코드는 당연히 가장 마지막에 있는 _TodoListPageState 클래스에 기술되어 있습니다. 8행에서 시작되는 Todo 클래스는 Firestore 에 저장할 객체를 들고 있을 코드상의 객체를 위한 클래스 입니다. Firestore 에 생성한 컬럼과 동일한 구조, 자료타입으로 만들어 두었습니다. 

StatelessWidget 으로 선언된 MyApp 은 기본 스캐폴딩을 통해 생성된 코드로 타이틀의 설정, 진입점을 TodoListPage 클래스로 지정해 준 정도의 변경사항만 가지고 있습니다. 코드는 아래와 같습니다

class Todo {
  bool isDone = false;
  String title;

  Todo(this.title, {this.isDone = false});
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '할 일 관리',
      theme: ThemeData(
        primarySwatch: Colors.red,
      ),
      home: TodoListPage(),
    );
  }
}

 

_TodoListPageState 클래스 살펴보기

Todo 앱은 ListTile 위젯으로 할일 하나하나를 표현하고 ListTile 은 ListView 의 Child 가 됩니다. ListView 는 다시 Expanded 위젯으로 감싸여 화면에 보여지게 됩니다. Expanded 위젯은 다시 StreamBuilder 위젯으로 감싸 스트림으로 Firestore 의 변경사항과 UI 의 변경이 이루어지도록 코드를 만들었습니다 (네, 제가 한건 아니고 <오준석의 플러터 생존코딩> 책의 내용입니다!)

위젯을 빌드하는 코드를 살펴보겠습니다. 위젯을 또 위젯이 감싸고 또 감싸고... 하는 연속이다 보니 복잡해 보이네요. UI 코드 보다는 StreamBuilder 위젯의 내용을 중점적으로 보시면 됩니다. 

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('남은 할 일'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Column(
          children: <Widget>[
            Row(
              children: <Widget>[
                Expanded(
                  child: TextField(controller: _todoController),
                ),
                RaisedButton(
                  child: Text('추 가'),
                  onPressed: () => _addTodo(Todo(_todoController.text)),
                ),
              ],
            ),
            StreamBuilder<QuerySnapshot>(
              stream: Firestore.instance.collection('todo').snapshots(),
              builder: (context, snapshot) {
                if (!snapshot.hasData) {
                  return CircularProgressIndicator();
                }

                final documents = snapshot.data.documents;
                return Expanded(
                  child: ListView(
                    children: documents
                        .map((todo) => _buildItemWidget(todo))
                        .toList(),
                  ),
                );
              },
            ),
          ],
        ),
      ),
    );
  }

StreamBuilder 위젯의 가장 안쪽 코드에 _buildItemWidget 위젯은 할 일 하나하나를 ListTile 위젯으로 리턴하는게 목표인 위젯입니다. 유의할 점은 Firestore 에서 가져온 NoSQL Document 는 DocumentSnapshot 타입을 이용해야 한다는 점입니다. 코드 한번 보시고 가겠습니다. 

  Widget _buildItemWidget(DocumentSnapshot doc) {
    final todo = Todo(doc['title'], isDone: doc['isDone']);
    return ListTile(
      onTap: () => _toggleTodo(doc),
      title: Text(
        todo.title,
        style: todo.isDone
            ? TextStyle(
                decoration: TextDecoration.lineThrough,
                fontStyle: FontStyle.italic,
              )
            : null,
      ),
      trailing: IconButton(
          icon: Icon(Icons.delete_forever), onPressed: () => _deleteTodo(doc)),
    );
  }

네, 이렇게 UI 에서 Firestore 에 있는 정보를 연동할 준비가 되었습니다. 중간 중간에 보이는 _addTodo(doc), _deleteTodo(doc) 그리고 _toggleTodo(doc) 가 실제 비즈니스로직(?)이 들어간 메서드들이 되겠습니다. 절단 신공을 이용하여 이 세가지 메서드는 다음 포스팅에서 확인해 보도록 하겠습니다!

 

오준석의 플러터 생존 코딩

소문난 명강사 ‘오준석’이 안드로이드·iOS 앱 개발자에게 보내는 선물 같은 책앱을 만드는 ‘완벽한 준비’에 시간을 낭비하지 말자. 이 책은 기본을 빠르게 익히고 앱을 직접 만들며 필요한

www.yes24.com

본 포스팅은 제휴마케팅을 통해 소정의 수수료를 지급 받을 수 있습니다

728x90
728x90

업무에 따라 다르겠지만 개인적으로는 DNS 에 대한 핸들링이 무척 많이 필요합니다. 커맨드라인에서 Shell 스크립트를 이용해서 여러가지를 만들어 사용하고 있긴 하지만, 가능하면 Python 이나 node.js 로 필요한 기능을 만들어 사용하는 것이 좋지 않을까 생각하고 있었습니다.

Python 쪽에서 사용할 수 있는 DNS 패키지가 어떤것이 있는지 살펴보다보니 `dnspython` 이라는 걸출한 물건이 있는 것을 발견했습니다. 패키지 공식 페이지에서 이야기 하는 것처럼 dnspython 은 DNS Toolkit 을 표방하고 있습니다. 정말 많은 기능들을 제공하고 있기 때문에 DNS 관련한 도구 개발에 무척 도움이 될 것 같습니다. 

간단하게는 Resolver 역할을 수행하는 것에서 시작할 수 있고, 좀 멀리 나가면 Zone 에 대한 핸들링도 수행할 수 있는 것 같습니다. 개인적으로 사용해 본 내용이 Resolver 중심이고 도메인 이름에 대한 핸들링 정도여서, 실제 필요한 과제에 맞도록 Research 는 조금 더 해봐야 할 것 같습니다. PyPi 에 패키지가 등록되어 있기 때문에 pip 로 패키지를 설치하면 편리하게 사용해 보실 수 있습니다. 

 

dnspython | dnspython

17 July, 2020 at 06:30 PDT Dnspython 2.0.0 is now available on PyPI. See “What’s New” for all the features in this major release. Thanks to the many people who have contributed to this release! Python 2.x support ended with 1.16.0. Dnspython 2.0.0 re

www.dnspython.org

 

728x90
728x90

플러터 Flutter 는 크로스 플랫폼 어플리케이션 개발을 위한 UI 툴킷이다 보니 애플 iOS 와 안드로이드 운영체제를 위한 개발 환경을 각각 구성해야 합니다. 그 중, iOS 개발을 위해서는 아래의 조건이 충족되어야 합니다. 

  • Xcode 설치를 위한 MacOS 운영체제
  • Xcode 의 완전체 (Full Installation) 설치
  • 기타

 

플러터 의사 선생님의 진단 (Flutter Doctor)

저는 맥북 프로를 쓰고 있었고 Xcode 역시 설치가 되어 있어서 플러터 닥터 Flutter Doctor 가 점검하여 누락된 부분들을 제시된 명령을 이용해서 설치할 수 있었습니다. 이전에 올렸던 플러터 환경 구성 포스팅에서 보았던 플러터 의사 선생님의 진단서를 다시 한 번 인용해 보겠습니다. 

Xcode 의 완전체 설치는 문제가 없었는데 cocoapod 를 설치하는 과정에 문제가 발생했습니다. (참고로, cocoapod 는 Objective-C 나 Swift 를 이용한 개발시 외부 라이브러리에 대한 외부 라이브러리 개발을 위한 종속성 관리 도구 입니다.)

 

에러 메세지 분석

환경에 따라 나오는 에러의 경로 복잡도(?)는 달라질 수 있습니다. 이게 무슨 에러인가 하고 천천히 읽어보니... 로컬 환경에 설치되어 있는 openssl 을 찾지 못해서 발생하는 것으로 추정되었습니다. `NoMethodError` 라는 메세지 때문에 헷갈렸습니다만, `image not found` 와 `NilClass` 에서 단서를 얻어 "적당한 버전이 설치가 되지 않았거나, 경로가 잘못되었나 보군?" 하는 생각에 도달했습니다. 

$ sudo gem install cocoapods
Password:
ERROR:  Loading command: install (LoadError)
	dlopen(/usr/local/Cellar/ruby/2.4.2_1/lib/ruby/2.4.0/x86_64-darwin16/openssl.bundle, 9): Library not loaded: /usr/local/opt/openssl/lib/libssl.1.0.0.dylib
  Referenced from: /usr/local/Cellar/ruby/2.4.2_1/lib/ruby/2.4.0/x86_64-darwin16/openssl.bundle
  Reason: image not found - /usr/local/Cellar/ruby/2.4.2_1/lib/ruby/2.4.0/x86_64-darwin16/openssl.bundle
ERROR:  While executing gem ... (NoMethodError)
    undefined method `invoke_with_build_args' for nil:NilClass

에러 메세지에 나온참조 경로를 일단 찾아보기로 했습니다. "Libbrary not loaded" 메세지 뒤에 나온 경로의 libsll.1.0.0.dylib 파일이 존재하는지를 확인해 보았습니다. 네, 예상대로 파일이 존재하지 않았습니다. 

$ ls /usr/local/opt/openssl/lib/libssl.1.0.0.dylib
ls: /usr/local/opt/openssl/lib/libssl.1.0.0.dylib: No such file or directory

이 파일은 openssl 의 하위 구성 파일이니 MacOS 의 패키지 매니저인 brew 를 이용해서 설치된 openssl 의 정보를 확인해 보기로 했습니다. `brew list [패키지명]` 을 이용해서 아래와 같이 경로를 확인할 수 있었습니다. 

$ brew list openssl
/usr/local/Cellar/openssl/1.0.2s/.bottle/etc/ (8 files)
/usr/local/Cellar/openssl/1.0.2s/bin/c_rehash
/usr/local/Cellar/openssl/1.0.2s/bin/openssl
/usr/local/Cellar/openssl/1.0.2s/include/openssl/ (75 files)
/usr/local/Cellar/openssl/1.0.2s/lib/libcrypto.1.0.0.dylib
/usr/local/Cellar/openssl/1.0.2s/lib/libssl.1.0.0.dylib
/usr/local/Cellar/openssl/1.0.2s/lib/engines/ (12 files)
/usr/local/Cellar/openssl/1.0.2s/lib/pkgconfig/ (3 files)
/usr/local/Cellar/openssl/1.0.2s/lib/ (4 other files)
/usr/local/Cellar/openssl/1.0.2s/share/man/ (1683 files)

gem install 명령에서는 /openssl/lib/... 경로를 참고하고 있었지만 실제 설치된 openssl 라이브러리는 /openssl/1.0.2s/lib/... 경로였습니다. 아마도 개별적으로 HTTP2 지원 등을 위해서 별도로 설치했던 것이 Symlink 변경이나 특정한 환경변수? 에 업데이트가 되지 않은 것인가 하는 의심이 들었습니다.

 

잘못된 경로의 수정

결국은 brew 가 관리하는 패키지이니 일단 brew 에서 패키지에 대한 경로를 바꿔줄 수 있도록 `brew switch [패키지명] [버전]` 명령을 이용해서 경로를 재수정 해주었습니다. 

$ brew switch openssl 1.0.2s
Cleaning /usr/local/Cellar/openssl/1.0.2s
Opt link created for /usr/local/Cellar/openssl/1.0.2s

이제 잘 되었을까요? 플러터 의사 선생님이 알려준 명령을 이용하여 cocoapods 를 다시 설치해 보았습니다. 이번에는 문제 없이 잘 설치가 되는 것을 확인할 수 있었습니다. 

$ sudo gem install cocoapods
Password:
Fetching: concurrent-ruby-1.1.7.gem (100%)
Successfully installed concurrent-ruby-1.1.7
Fetching: i18n-0.9.5.gem (100%)
Successfully installed i18n-0.9.5
Fetching: thread_safe-0.3.6.gem (100%)
Successfully installed thread_safe-0.3.6
Fetching: tzinfo-1.2.7.gem (100%)
...
...
...
Parsing documentation for cocoapods-1.9.3
Installing ri documentation for cocoapods-1.9.3
Done installing documentation for concurrent-ruby, i18n, thread_safe, tzinfo, activesupport, nap, fuzzy_match, httpclient, algoliasearch, ffi, ethon, typhoeus, netrc, cocoapods-core, claide, cocoapods-deintegrate, cocoapods-downloader, cocoapods-plugins, cocoapods-search, cocoapods-stats, cocoapods-trunk, cocoapods-try, molinillo, atomos, CFPropertyList, colored2, nanaimo, xcodeproj, escape, fourflusher, gh_inspector, ruby-macho, cocoapods after 63 seconds
33 gems installed

 

플러터 환경 구성 뿐만 아니라 Xcode 개발 환경을 구성하는데 있어서 cocoapods 설치 문제가 발생한다면 위의 방법으로 경로 정보 수정을 해보시기 바랍니다. 

728x90

+ Recent posts