Container 레이아웃 치트시트

https://medium.com/jlouage/container-de5b0d3ad184


Flutter Layouts Walkthrough: Row, Column, Stack, Expanded, Padding

https://medium.com/coding-with-flutter/flutter-layouts-walkthrough-row-column-stack-expanded-padding-5ed64e8f9584


Flutter Layout Cheat Sheet

https://proandroiddev.com/flutter-layout-cheat-sheet-5363348d037e


레이아웃 Flutter 영상 #1

https://codingwithflutter.com/

Flutter App Tutorial #1

이 글의 모든 내용은 udemy 강좌를 공부하고 따라해본 내용입니다.

https://www.udemy.com/dart-and-flutter-the-complete-developers-guide

완성 된 형태이다.

첫 화면에서 오른쪽 아래 플로팅 버튼 클릭시 오른쪽 화면처럼 이미지와 글자를 가진 뷰가 하나씩 추가되는 형태이다.

소스는 http json 을 통해서 가져왔다.

소스는 https://github.com/bear2u/flutter-sample-pic.git 에서 확인가능하다. 


main.dart 시작

flutter는 첫진입은 일반적으로 lib/main.dart 로 시작된다.

  • 매트리얼 테마를 사용하기 위해서 import를 한다.
  • runApp 은 메테리얼 라이버러리를 통해 실행된다.
  • App 은 따로 빼서 진행한다.
# lib/main.dart

import 'package:flutter/material.dart';
import 'src/app.dart';

void main() {  
  runApp(App());
}

lib에 src 폴더를 만들고 app.dart 를 생성한다.

  • StatefulWidget을 통해서 서버로 들어오는 아이템들을 리스트로 보내주고 있다.
  • Scaffold 는 Floating Action Button 과 타이틀바 및 바텀바등을 기본적으로 가지는 위젯이다.
  • await 는 비동기방식을 동기형태로 바꿔주고 네트워크 결과값을 가져온다.
  • 그리고 미리 정의된 ImageModel.FromJson 을 통해 디코딩을 거친다.
  • 마지막으로 setState()을 통해 images 라는 리스트에 아이템을 추가를 해서 state 변경을 한다.
# lib/src/app.dart

import 'package:flutter/material.dart';
import 'package:http/http.dart' show get;
import 'models/image_model.dart';
import 'dart:convert';
import 'widgets/image_list.dart';

class App extends StatefulWidget {
  @override
    State<StatefulWidget> createState() {
      // TODO: implement createState
      return AppState();
    }
}

class AppState extends State<App> {
  int counter = 0;
  List<ImageModel> images = [];

  void fetchImage() async {
    counter++;
    var response = await get('https://jsonplaceholder.typicode.com/photos/$counter');
    var imageModel = ImageModel.fromJson(json.decode(response.body));

    setState(() {
      images.add(imageModel);
    });

  }

  Widget build(context) {
    return MaterialApp(
      home: Scaffold(
        body: ImageList(images),
        appBar: AppBar(
          title: Text("Lets see some images!"),
        ),
        floatingActionButton: FloatingActionButton(
          child: Icon(Icons.add), //Widget 추가
          onPressed: fetchImage,
        ),
      ),
    );
  }
}

// Must define a 'build' method that returns
// the widgets that *this* widget will show

모델 정의

서버로 부터 가져온 JSON 결과값을 dart 내 오브젝트 형태로 변경할수 있도록 할수 있다.

# lib/src/models/image_model.dart

/**
 * JSON Decoding Model
 */
class ImageModel {
  int id;
  String url;
  String title;

  ImageModel(this.id, this.url, this.title);

  ImageModel.fromJson(Map<String, dynamic> parsedJson) {
    id = parsedJson['id'];
    url = parsedJson['url'];
    title = parsedJson['title'];
  }

  // 이렇게도 정의할 수 있다.
  // ImageModel.fromJson(Map<String, dynamic> parsedJson)
  // : id = parsedJson['id'],
  //   url = parsedJson['url'],
  //   title = parsedJson['title'];

  @override
    String toString() {
      // TODO: implement toString
      return '$id,$url,$title';
    }
}

리스트 뷰 정의

  • ListView 의 경우 React상에 바보뷰라고 하는 상태가 전혀 없고 값만 가지고 핸들링을 한다.
  • final 사용을 통해 값이 immutable 한지 체크를 할 수 있다. (바뀌면 안된다)
  • 그리고 위젯을 함수로 빼서 사용가능하다. (buildImage)
  • border의 경우 decoration 으로 가능하다.
  • 생성자로 값을 받아서 그 값을 뷰에 세팅해주고 있다.
import 'package:flutter/material.dart';
import '../models/image_model.dart';

class ImageList extends StatelessWidget {
  final List<ImageModel> images;

  ImageList(this.images);

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return ListView.builder(
        itemCount: images.length,
        itemBuilder: (context, int index) {
          return buildImage(images[index]);
        });
  }

  Widget buildImage(ImageModel image) {
    return Container(
      decoration: BoxDecoration(
        border: Border.all(color: Colors.grey),
      ),
      padding: EdgeInsets.all(20.0),
      margin: EdgeInsets.all(20.0),
      child: Column(
        children: <Widget>[
          Padding(
            child: Image.network(image.url),
            padding: EdgeInsets.only(bottom: 10.0),
          ),        
          Text(image.title)
        ]),
    );
  }
}

이로써 아주 간단한 앱을 만들어보았다.

큰 흐름을 이해 할수 있는 소스였다.


Await / Future 비동기 함수 호출

Http 로 통신할때 일반적으로 비동기로 호출을 하는 편이다. 비동기란 함수 호출시 블럭이 되지 않고 한번 다 훝고 콜백식으로 완료가 됐을 경우 다시 결과를 받는 경우를 말한다.

만약 아래와 같은 코드에서 실행시 결과값은 마지막에 'I got the data' 로 떨어지게 된다.

그리고 자바스크립트에서 Promise 형태와 비슷한 걸 볼수 있다.

import 'dart:async';

void main () {
  print('1. start to fetch data');
  
  get('http://weasds.com')
    .then((data) {
      print(data);
    });
  
  print('3. finish call to fetch the data');
}

Future<String> get(String url) {
  return new Future.delayed(new Duration(seconds: 3), () {
    return '2. I got the data!!';
  });
}
...........
1. start to fetch data
3. finish call to fetch the data
2. I got the data!!
// 순서가 1-3-2 로 되는 걸 볼수 있다.

그리고 Promise말고도 await로 비동기를 기다렸다가 가져오는 방법도 지원한다.

import 'dart:async';

void main () async {
  print('1. start to fetch data');
  
  print(await get('http://weasds.com'));
  
  print('3. finish call to fetch the data');
}

Future<String> get(String url) {
  return new Future.delayed(new Duration(seconds: 3), () {
    return '2. I got the data!!';
  });
}

.........

1. start to fetch data
2. I got the data!! //약 3초뒤 나온다.
3. finish call to fetch the data

참고



JSON 핸들러

서버와 통신시 제일 자주 사용되는 형태가 JSON 이다. 그럼 다트에서는 어떻게 다룰수 있는 지 살펴보겠다.

테스트 환경은 https://dartpad.dartlang.org/ 에서 진행한다.

import 'dart:convert';

void main() {
  var rawJson = '{"url": "http://blah.jpg","id":1}';

  var parsedJson = json.decode(rawJson);
  print(parsedJson);
  print(parsedJson['url']);

}

.............
{url: http://blah.jpg, id: 1}
http://blah.jpg

그리고 모델 클래스를 만들어서 매핑하고자 할때에는 값을 키값을 줘서 값을 가져온다음 set 을 통해서 모델을 만들수 있다.

import 'dart:convert';

void main() {
  var rawJson = '{"url": "http://blah.jpg","id":1}';

  var parsedJson = json.decode(rawJson);
  var imageModel = new ImageModel(
            parsedJson['id'], 
            parsedJson['url']
  );

  print(imageModel);

}

class ImageModel {
  int id;
  String url;  

  ImageModel(this.id, this.url);

  String toString() {
    return '$id,$url';
  }
}

하지만 만약 여러개 속성을 파싱할때 이 방식은 좀 불편하다.

그래서 fromJson 함수를 제공해준다. 이 함수는 JSON 을 미리 정의된 값을 매핑해주는 역할을 한다.

그럼 코드를 보면 이해가 빠를거라 본다.

관련 링크

import 'dart:convert';

void main() {
  var rawJson = '{"url": "http://blah.jpg","id":1}';

  var parsedJson = json.decode(rawJson);
  var imageModel = new ImageModel.fromJson(parsedJson);

  print(imageModel);

}

class ImageModel {
  int id;
  String url;  

  ImageModel(this.id, this.url);

  ImageModel.fromJson(Map<String, dynamic> parsedJson) 
    : id = parsedJson['id'],
      url = parsedJson['url'];


  String toString() {
    return '$id,$url';
  }
}

............
1,http://blah.jpg


Flutter 카카오톡 오픈 채팅방 바로가기 : https://open.kakao.com/o/gsshoXJ


StatefulWidget

StatefulWidget은 위젯에 변경되는 state 를 담고 있는 커스텀 위젯을 뜻한다.

그럼 어떤식으로 만들어지는 지 살펴보자.

우선 StatefulWidget 을 extends 한다.

class App extends StatefulWidget {
    
}

State를 상속받은 클래스를 만든다.

class AppState extends State<App> {
  int counter = 0;

  Widget build(context) {
    return MaterialApp(
      home: Scaffold(
        body: Text('$counter'),
        appBar: AppBar(
          title: Text("Lets see some images!"),
        ),
        floatingActionButton: FloatingActionButton(
          child: Icon(Icons.add), //Widget 추가
          onPressed: () {
            // 이벤트 콜백 함수                        
          },
        ),
      ),
    );
  }
}

createState 함수를 StatefullState 클래스에 정의를 한다.

@override
State<StatefulWidget> createState() {
  // TODO: implement createState
  return AppState(); // 전 단계에서 만든 클래스
}

마지막으로 변경될 값을 setState() 함수내에서 정의한다.

여기에선 floating action button 을 클릭시 counter 를 1씩 올리는 작업이 이루어진다.

....
onPressed: () {
  // 이벤트 콜백 함수            
  setState(() {
    counter += 1; // 1씩 올리고 있다.
  });
},
....          

Flutter 카카오톡 오픈 채팅방 바로가기 : https://open.kakao.com/o/gsshoXJ

커스텀 위젯 추가

하나의 파일에 많은 코드를 추가시 복잡해지고 길어지는 단점이 있다. 이럴때 파일로 빼서 분리를 할수 있다.

임포트 방법

내부에 src 폴더를 만들고 그 안에 app.dart 파일을 만들자.

클래스 구조를 만들수 있다.

  1. import 매트리얼
  2. Stateless(StatefulWidget)Widget class 생성
  3. Build 함수 구현
  4. main.dart 에서 src/app.dart 호출
# src/app.dart

import 'package:flutter/material.dart';

class App extends StatelessWidget {
  Widget build(context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("Lets see some images!"),
        ),
        floatingActionButton: FloatingActionButton(
          child: Icon(Icons.add), //Widget 추가
          onPressed: () {
            // 이벤트 콜백 함수            
          },
        ),
      ),
    );
  }
}
# main.dart

import 'package:flutter/material.dart';
import 'src/app.dart';

void main() {  
  runApp(App());
}

그런데 여기에서 StatelessWidget 과 StatefulWidget 이 나오는데 차이점은 무엇일까?

StatelessWidget의 경우 상태관리가 필요없는 위젯을 뜻한다. 즉 다시말해 내부에서 변하는 값을 가지는 게 없는 경우이다.

StatefulWidget은 반대로 state 관리를 함으로써 값이 변할때 다시 랜더링을 해주는 차이이다.

StatefulWidget은 어떻게 만들어지는 다음 시간에 알아보도록 하겠다.



Scaffold 위젯 추가


Flutter 카카오톡 오픈 채팅방 바로가기 : https://open.kakao.com/o/gsshoXJ


기본적으로 모바일 구조가 상단에 Appbar가 있으며 하단에 바텀시트가 있으며 floating action button이 있다.

그 구조를 기본적으로 지원하는 하나의 위젯이 Scaffold라고 보면 된다.

그럼 하나씩 적용해보자.

import 'package:flutter/material.dart';

void main() {
  var app = MaterialApp(
    home: Scaffold(
      appBar: AppBar(),    // 추가  
    ),
  );

  runApp(app);
}

짜잔~ 이런 타이틀바가 생긴걸 볼수 있다. 이걸 AppBar 라고 한다.

그리고 아래 floating button 을 추가도 아주 쉽게 할수 있다.

import 'package:flutter/material.dart';

void main() {
  var app = MaterialApp(
    home: Scaffold(
      appBar: AppBar(
        title: Text("Lets see some images!"),        
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add), //Widget 추가
        onPressed: () { // 이벤트 콜백 함수
          print('Hi there!');
        },
      ),      
    ),
  );

  runApp(app);
}

추가적으로 메테리얼 아이콘을 변경시

https://material.io/tools/icons 에서 검색해서 Icons.*** 변경하면 된다.


Flutter 시작하기.


Flutter 카카오톡 오픈 채팅방 바로가기 : https://open.kakao.com/o/gsshoXJ


Flutter는 구글에서 나온 모바일 프레임워크입니다. Reactive Native 와 비슷하다고 보면 될것 같습니다. 네이티브 성격을 가진 하이브리드 앱인 셈이죠.

무엇보다 강력한 점은 각 플랫폼(Android, IOS)에 있는 대표 디자인 즉 메테리얼 디자인을 손쉽게 짤수가 있습니다.

하지만 플랫폼별로 다르게 나올수가 있는데 그 이유는 메테리얼도 플랫폼에 맞게 바뀌어서 나올 수 있기 때문입니다.

만약 IOS 에서 특유의 시스템을 이용하고 싶으면 쿠퍼티노 위젯을 사용하면 됩니다.

그 외에도 안드로이드 스튜디어, 비쥬얼 스튜디어 코드등 통합 IDE 도 사용 가능한 점이 큰 메리트입니다.

더 자세한 내용을 원하시면 공식홈페이지를 방문하시길 바랍니다.

구조

헬로우 플러터를 우선 보는게 중요하죠.

각 플랫폼별로 플러터 프로젝트를 생성합니다.

(vscode 에선 view -> 명령 팔렛트 -> flutter new project 선택)

그리고 실행을 해봅니다. ( 터미널에서 flutter run )

중요한건 화면까지 뛰우는 구조인데.

중요한건 4가지를 보시면 됩니다.

  1. import
  2. main 함수 생성
  3. main에 widget 추가
  4. 그 위젯을 화면에 추가

코드로 살펴보겠습니다.

import 'package:flutter/material.dart';

void main() {
  var app = MaterialApp(
    home: Text('Hi there!'),

  );

  runApp(app);
}

그럼 다음과 같이 투박한 화면이 나옵니다. ^^;;

그럼 다음시간에는 좀 더 위젯을 추가하도록 해보겠습니다.


GDG 부산 카페 바로가기

상하이로 출국

올해 처음 GDG 부산에 합류를 하게되었다. 부산에 마땅한 개발행사가 없고 특히나 수도권인 서울에 다 몰려있는 현실이 너무 안타까워서 나라도 나서자라는 마음에 시작하게 된 GDG 부산... 이리저리 착오끝에 구글 IO extended를 작게나마 진행했으며 다음 행사를 앞두고 있다. 

그런 와중에 상하이에서 GDG Asia를 진행하니 신청하라고 공지를 봤다. 혹시나 하는 마음에 신청해보고 얼떨결에 뽑히게 되었다. 

그렇게 나름 하나씩 준비를 해나가는 과정에서 같은달에 Google IO Extended 2018 Busan 을 개최를 하고 8월 마지막달에 드디어 GDG 아시아 분들이 모여있는 상해로 가게 되었다. 


조은님께서 알려주신 유심을 구매를 따로 하고(완소) 입국을 하고 떨리는 마음에 비자 심사대에 통과를 할려는 찰나에 뭔가 문제가 있다고 이미그레이션 사무실로 따라오라고 한다. 완전 긴장한 상태로 따라 갔는데 전상상의 착오로 오해가 있다고 말을 하고 풀려(?) 났다...


전철을 타보다. 

그리고 도착한 시간이 상해시간(시차 -1시간)은 2시쯤에 도착을 하게 되었다. 이미 도착하기전 슬랙 채널의 많은 분들이 경유를 상세히 알려주셨지만 그래도 한번 서민들 이동을 경험하고자 전철을 타고자 결심했다. 그래서 전철 표판매기에서 약 30분을 혼자서 끙끙거리며 구매를 하고 전철을 타보았다. 특이한 점은 전철에 들어갈때 공항에서 입국하는 것처럼 모든 짐을 엑스레이 검사를 한다는 점이다. 그리고 더 놀라운건 난 분명히 줄을 잘 서고 있는데 자꾸 새치기를 당해서 뒤로 밀려간다는 사실...;;;


호텔 도착

일단 우여곡절끝에 약 1시간 30분정도 전철을 두번 갈아타고 도착을 하게되었다. 푸동 에미뉴라는 스테이션이다. 내리자 마자 보이는 호텔의 웅장함에 다시 한번 고마움을 느끼며 걸어갔다. 입구에서 다행히 수호천사 조은님을 만나서 무사히 체크인을 하고 숙소에 짐을 풀수 있었다. 

엘리베이터를 타는 순간 하늘을 뚫고 나갈듯한 속도감에 당황도 했었다...;; 1층에서 36층까지 가는데 단 1초 걸린것 같은 느낌까지 받았다..




금요일 저녁 파티

도착한 그날이 금요일이었는데 저녁 6시부터 식사를 할 수 있었다. 그 자리에는 GDG 한국별로 각 챕터별 분들과 다른 나라의 챕터분들을 만날수 있었다. 개발자분도 계시고 디자이너분들도 계시고 회사 대표님도 계시는 아주 다양한 분야의 분들이 모인 자리이었다. 

무엇보다 적응이 필요한건 음식이었는데 전에도 상해에 왔을때도 음식때문에 좀 고생했는데 그 날도 덜하긴 했지만 여전히 중국 음식은 느끼하고 향기가 상당히 자극적이다. 그래도 맛이 있으며 준비해주신 음식에 고마움을 많이 느낄수 있었다. 



GDG 다른 분들과의 교류

금요일 저녁에 여러 챕터의 분들을 볼수 있었는데요. 그 중 인상깊은건 GDG 일본의 경우 분야별로 특수화 되어있다는 것이다. 예를 들어 GDG iot 라던가 GDG gcp 등과 같이 분류가 잘 되어 있었다. 한국도 이렇게 분류가 되어있는 것도 좋을 것 같다.

그리고 그 자리에서 만났던 분들은 대체적으로 영어를 잘 하는 편이었다. 많은 자극을 받은 자리였다고 말할수 있을것 같다. 

또 전에는 몰랐지만 맥주 맛이 맛있다는 느낌이 들었다. 아무래도 다른 분들과 행복한 시간을 보낸 것 같아서 그런것 같다. 


GDG 본격 행사

드디어 다음 날 토요일에 본행사가 열렸다. 오전부터 오후까지 진행하는 행사로 아주 유익했었다. 특히 안드로이드 관련해서 발표를 할땐 꽤 유심히 봤던 것 같다. 여러가지 스웩이랑 가방등이 있었는데 퀄리티도 좋았고 상당히 만족스러웠다. 




인사이트

행사에서 특히 유익했던건 GDG 행사를 진행하는데 있어 어려운 점이나 극복해야 하는 점등을 발표주제로 둬서 서로 다른 나라의 챕터분들끼리 모여서 발표하는 자리가 인상깊었던 같다. 

나도 겨우 한번의 작은 행사를 가졌지만 노쇼라던가 GDG 오거나이저 비활동적인 걸 어떻게 극복해야 할지 다들 고민하고 있다는 것에 공감대가 형성된 것 같다. 

그리고 화상으로 발표하시는 첫번째 세션에서 부산이 가진 지리적 한계를 어떻게 극복하면 되는지를 알수 있을것 같다. 

부산에서는 행사개최를 함에 있어서 양질의 스피커 확보가 시급하다. 이 부분을 행아웃으로 가능하지 않을까 생각해본다. 

그러면 서울분들도 무리해서 안 내려오셔도 되고 부산에 계시는 많은 분들도 지식을 공유 받을 수 있지 않을까 한다. 

또 하나 작은 밋업이 큰 밋업으로 만들어 나갈 수 있다는 걸 이자리에서 다들 공감하는 것 같다. 특히 인천분들은 잦은 작은 행사를 통해서 인지도를 높여가며 큰 행사를 만들어 나가는 모습에 깊이 감명 받았다. 

다른 아시아 분들의 영어실력에 또 다른 놀라움을 가진 것도 분명하다. 특히 일본!! 영어 너무 잘한다..


출국?

하루동안 행사를 마무리 하고 다음날 출국을 해야하는 일정을 준비했다. 하지만 그 날 상하이 구글 오피스를 갔는데 엄청난 높이에 놀랐다. 안개낀 고층 건물이란...정말 엄청났다..

비록 출국 일정때문에 상하이 구글 오피스 건물에는 못 갔지만 사진을 보니 다들 즐거운 시간이었던 것 같다. 다음에 기회가 다시 되면 꼭 올라가보고 싶다. 

중국의 우버?

공항으로 이동시 위치가 애매해서 전철로 가기도 그렇고 악명높은 택시 타는 것도 좀 그래서 한번 유명한 디디추싱? 이라는 중국의 우버란 앱을 사용해보았다. 

이미 출국전에 신용카드를 연동해놓고 간 상태라 결제는 별로 문제가 안될 것 같다. 

그리고 앱을 실행해서 호출하니 5분내로 도착을 했는데 따로 정류소가 있었는데 그걸 몰라서 10초전에 겨우 도착을 해서 공항으로 갈 수 있었다. 


출국?

드디어 출국 수속을 받고 기다리는데 이게 왠걸...김해에서 돌풍이 많이 불어서 공함 도착이 안된다고는 소식이 들렸다. 정말 난감했는데 동방항공에서는 숙소 및 버스를 제공해주었다.. 

숙소가 조금 허름하지만 룸메이트 분이 체크인을 하고 다른 데로 가는 바람에 난 혼자서 편하게 잘 수 있었다. 그날은 정말 신경을 많이 써서 그런지 엄청 피곤했었다. 


특히 음식이 너무 먹기가 힘들어서 주위에 살펴보니 카르푸가 있고 KFC 도 마침 있어서 저녁을 먹을 수 있었다. 

이나라에서 특이한 점은 전부 QR 코드로 다 결제하고 주문받는 다는 것이다. 그리고 위챗을 이용해서 다 할 수 있다는 것이다. 정말 대단한 나라인것 같다. 한국도 배울점은 많다고 본다. 


드디어 출국

새벽 5시에 준비를 하고 공항에 가서 다시 출국 준비를 했다. 하지만 다시 지연되고 약 1시간 넘게 비행기에서 돌다가 드디어!! 김해로 갈수 있었다. 정말 기뻣다. 


결론

올해 처음 GDG가 되고 아직 친분이 없는 상태에서 비록 한국은 아니지만 상해에서 다른 챕터의 분들을 만날수 있어서 정말 유익했다. 그리고 구글러 분들의 노력으로 이러한 행사가 개최될 수 있어서 감사하다는 말씀을 드리고 싶다. 


GDG 화이팅




+ Recent posts