Flutter Project (리펙토링)

사용 프레임워크 및 라이브러리

  • Flutter
  • dart
  • Android-studio
  • Ios / Android
  • App Store 등록
  • google analytics

flutter 구조

  • main.dart => pages / components

  • main은 라우터 부분을 컨트롤 한다.

  • main.dart에서 라우터를 관리하고 모든걸 관리한다.
    그리고 전체적인 구성을 sidebar와 pages들로 UI를 나누어 구성한다.

  • 페이지는 총 3페이지로 구성되어있으며, url-parser / url-encoder / base64로 구성되어있다.
    그리고 계속 들어가는 부분인 sidebar도 컴포넌트로 구성되어있다.

  • main 위젯에서 전체적인 부분은 MaterialApp으로 감싸주고 routes / theme / home 3가지로 나눈 다음
    Home에서 컴포넌트들을 import하여 페이지를 구성해준다.

    Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      initialRoute: '/',
      routes: {
        '/url/parser': (context) => UrlParser(),
        '/url/encoder/decoder': (context) =>  UrlEncoder(),
        '/base64/encoder/decoder': (context) =>  Base64Encoder()
      },
      theme: ThemeData(
        fontFamily: 'Ubuntu',
        textTheme: const TextTheme(
          bodyText1: TextStyle(fontSize: 72.0, fontWeight: FontWeight.bold),
          button: TextStyle(fontSize: 12)
        )
      ),
      home: Container(
        color: const Color(0xFFF6F6F6),
        child: Row(
          children: [
            const SideBar(),
            const Expanded(
              child: MyIp(),
            ),
          ],
        ),
      )
    );
    }

CSS

  • 전체적인 전역 css를 관리해주는 파일을 하나 만들어서
    font.dart 파일을 만들고

      import 'package:flutter/material.dart';
    // set color
    const kPrimaryColor = Color(0xFF4F46E5);
    const kDefaultFontColor = Color(0xFF6B7280);
    
    // set font
    var kDefaultFontStyle = const TextStyle(
      fontFamily: 'Ubuntu',
      fontSize: 16,
      fontWeight: FontWeight.normal,
      decoration: TextDecoration.none,
      color: kDefaultFontColor
    );
    var kSubFontStyle = const TextStyle(
      fontFamily: 'Ubuntu',
      fontSize: 12,
      fontWeight: FontWeight.normal,
      decoration: TextDecoration.none,
      color: kDefaultFontColor
    );
    var kDefaultButtonFontStyle = const TextStyle(
        fontFamily: 'Noto Sans',
        fontSize: 12,
        fontWeight: FontWeight.w400,
        decoration: TextDecoration.none,
        color: kDefaultFontColor
    );

    이런식으로 font.dart에서 만들어준 css부분을 페이지 style부분에서 import하여 사용한다.

  • style: kDefaultTitleFontStyle,
    style: kSubFontStyle.copyWith(fontWeight: FontWeight.w400, color: Color(0xFF4F46E5))
    이런식으로 가져와서 사용해준다.

  • 위젯마다 문법이 다르기 때문에 위젯 문법에 맞게 css를 인라인으로 사용한다.

    focusedBorder: OutlineInputBorder(
         borderRadius: BorderRadius.circular(10),
         borderSide: BorderSide(width: 2, color: Color(0xFF4F46E5))),
     ),
    이런식으로 위젯마다 문법에 맞게 css를 인라인으로 사용한다.

Method / Library

  • TextEditingController()

    final _idTextEditController = TextEditingController();
    controller: _idTextEditController,
    _idTextEditController.text = value;

    => textfield에 컨트롤러를 추가하여 안에있던 속성들을 컨트롤 해준다.

  • clipboard.paste()

    FlutterClipboard.paste().then((value) {
         setState(() {
           _idTextEditController.text = value;
       });
       

    =>컨트롤러에 해당하는 부분의 텍스트에 값을 붙여넣는 라이브러리 함수이다.

  • child: UrlResult(urlData:_idTextEditController.text)

      class UrlResult extends StatefulWidget {
        const UrlResult({
          Key? key,
          required this.urlData
        }) : super(key: key);
        final urlData;
        페이지 안에서는 widget.urlData로 사용한다.

    => 부모컴포넌트에서 자식컴포넌트를 호출하여 활용한다.

  • Uri uri = Uri.parse(widget.urlData)

      uri.scheme,
      uri.userInfo,
      uri.host,
      uri.port.toString() = 혼자 int타입이기 때문에 string으로 바꿔준다.
      uri.path
      uri.port등등

    =>유저들이 붙여넣는 url들을 라이브러리를 이용하여 데이터를 parsing해준다.

  • encoded = Uri.encodeComponent(_idTextEditController.text)
    => url을 encode해줄때 사용한다. encoded라는 변수에 컨트롤러의 텍스트값을 인코딩한다.

  • decoded = Uri.decodeComponent(_idTextEditController.text)
    => url을 decode해줄때 사용한다. decoded라는 변수에 컨트롤러의 텍스트값을 디코딩한다.

  • Clipboard.setData(ClipboardData(text: encoded))
    =>인코딩 한 값을 담은 변수 encoded를 clipboard에 Copy한다.

  • Toast알림

      import 'package:flutter_toastr/flutter_toastr.dart';
    
      _showToast(String msg, {int? duration, int? position}) {
          FlutterToastr.show(
              msg, context,
              duration: FlutterToastr.lengthShort,
              position: FlutterToastr.bottom
          );
        }
       _showToast("Copy Successful.",)

    =>toast라이브러리를 사용하여 알림창을 띄운다.

  • ModalRoute.of(context)?.settings.name == '/' ? kPrimaryColor : kDefaultFontColor

    style: ModalRoute.of(context)?.settings.name == '/url/encoder/decoder' ?

    =>라우터로 세팅이된 이름을 구분하여 css를 주는 방식이다.
    !)꿀팁으로 웬만한 css는 이걸로 처리해주면 좋다.

  • Navigator.pushNamed(context, "/")

    Navigator.pushNamed(context, '/url/encoder/decoder')

    =>onTap이나 onPressed함수에 활용되어 라우터가 context 이름에 따라 이동된다.

Widget

  • StatelessWidget / StatefulWidget

    StatelessWidget : 상태가 없는 위젯. 변화가 거의 없는 위젯은 이것으로 선언한다

    StatefulWidget : state라는 데이터 변화를 감지하고, state가 변할시 위젯을 rebuild 하는 위젯. setState라는 함수를 통해 state변화를 감지하여야 한다

    class UrlResult extends StatefulWidget {
    //필수
      const UrlResult({
        Key? key,
        required this.urlData

      }) : super(key: key);
      final urlData;
      //부모 컴포넌트로부터 data를 받아왔을 경우에 위에 처럼 선언해준다.

      @override
      UrlResultState createState() => UrlResultState();
      //필수
    }

    class UrlResultState extends State<UrlResult> {
    //필수
    //이제 여기서 본문에서 사용할 변수와 함수들을 선언해준다.

      bool protocolView = false;

      _showToast(String msg, {int? duration, int? position}) {
        FlutterToastr.show(
            msg, context,
            duration: duration,
            position: FlutterToastr.bottom
        );
      }

      @override
      Widget build(BuildContext context) {
        Uri uri = Uri.parse(widget.urlData);
        return
          Container(
          );}
  • initState / dispose

    initState: StatefulWidget 생성시 초기에 딱 한번 호출. 이니셜라이징 할 곳은 이곳에 모아두자
    dispose : StatefulWidget에서 state object가 필요없을시 불리는 함수. uninit 할곳은 이곳에 모아두자.

    @override
      void initState() {
        super.initState();
        _findInternetConnection();
        _getMyIp();
      }
  • GestureDetector / MouseRegion

    GestureDetector : 많은 위젯들은 GestureDetector를 통해 다른위젯에 콜백을 전달합니다. 예를들어 IconButton, RaisedButton, FloatingActionButton 위젯은
    onPressed() 라는, 유저가 위젯을 탭 했을때 호출되는 콜백을 가지고 있습니다. tap 제스쳐 외에 drag, scale등의 제스처를 감지할 수 있습니다.

    MouseRegion : 마우스에 대한 속성들을 가지고 있는 위젯이다. 호버와 탭 커서등 여러가지 마우스에 속성을 부여할수있다.

       onHover: (s){
              setState(() {
                protocolView = true;
              });
            },
  • Material / Scaffold(appBar ,Body ,BottomNavigationBar)

    Material : Navigator라는 위젯을 제공합니다. Navigator는 위젯의 스택을 관리하며 각 스택은 "routes"라는 string 변수로 구분됩니다.

    Scaffold : 전체적인 시멘틱 태크와 같이 Scaffold위젯 안에서 사용된다.
    3부분으로 헤드 바디 바텀으로 나뉘어 위젯의 위치를 분류하여 꾸밀수있다.
    여러 다른 위젯을 named arguments로 받는것을 보실수 있다.

  • Stack / SingleChildScrollView / PageView

    Stack : children에 나열한 여러 위젯을 순서대로 겹치게해준다. 사진 위에 글자를 표현하거나 화면 위로 로딩 표시를 하는 상황에 사용한다.

    SingleChildScrollView: 하나의 자식을 포함하는 스크롤을 가능하게 하는 위젯이다. 자식으로는 Column보다는 ListBody위젯을 사용해주면 더 간편해진다.

    PageView : 여러 페이지를 좌우로 슬라이드 하여 넘길수 있도록 하는 위젯이다.
    children 프로퍼티에 각 화면을 표현할 위젯을 여러개 준비하여 지정하면 Tap과 연동되어 활용된다.

  • Container / SizedBox

    Container: 아무것도 없는 기본 위젯으로 div와 같은 형태를 띈다.
    다양한 프로퍼티를 가지고 있기때문에 사용하기에 따라서 다양한 응용이 가능하다.
    또다른 위젯을 가질수 잇으며, margin,padding등 여러가지 CSS를 적용할수있다.

    SizedBox ; 위젯을 특정한 크기로 만들고 싶은경우 사용한다. 하지만 Container가 더 많이 쓰이며, Container보단 가볍다.

  • Column / Row(MainAxisSize / MainAxisAlignment / CrossAxisAlignment)

    전체적으로 가로 / 세로로 나열할수있게 만들어주는 기본 방향 위젯이다.
    이 위젯 안에서는 중요한 부분을 정의할수있는데 MainAxisSize / MainAxisAlignment / CrossAxisAlignment등을 적용할수있다. 이것들을 flex와 같이 사용할수있기 때문에 편리하다.

  • Expanded / Card / Flexible

    Expanded : 자식위젯의 크기를 최대한으로 확장시켜주는 위젯으로 여러 위젯에 동시에 적용하면 flex프로퍼티에 정수 값을 지정하여 비율을 정할수 있으며 기본 값은 1이다.

    Card : 카드 형태의 모양을 제공하는 위젯으로 기본적으로 크기가 0이므로 자식 위젯의 크기에 따라 크기가 결정된다.

      Card(
       shape : RoundedRectangleBorder(
           borderRadius: BorderRadius.circular(16.0),
       ),
       elevation: [실수값], // 그림자 깊이
       child: [위젯],
       ),
       

    Flexible: Expanded와 비슷하지만 최대로 확장하는것이 아닌 flexible한 유연하게 크기를 조절할수가있다. Text부분이 overflow가 되거나 하는 부분들을 flexible로 감싸주면 효과가 적용된다.

        Flexible(
           child: Container(
             height: 25,
              child: Text(uri.scheme,
                	overflow: TextOverflow.ellipsis,
                      style: kDefaultFontStyle.copyWith(fontWeight: FontWeight.w300),
             ),
            ),
          ),
  • Text / Icon / Image / Progress / CircleAvatar / Button

    Text : 글자를 표시하는 위젯이다.

    Text( ' hello',
        style: TextStyle(
            fontSize: 40,
            fontStyle, FontStyle.italic,
            color: Colors.red
            ),
           ),

Icon : 아이콘은 그냥 아이콘 단독으로 사용가능하다.그리고 이미지를 부여할수도있다.

   icon: parseHover ? SvgPicture.asset(
          "assets/icons/edit.svg",
        color: kDefaultFontColor,
       )

Image : Image.asset("asset/sample.jpg")

Progress : 로딩중이거나 오래걸리는 작업을 할때 진행중임을 보여주는 위젯이다.

      CircularProgressIndicator()
       LinearProgressIndicator()
       

CircleAvatar : 프로필 화면 등에 많이 사용하는 원형 위젯으로 child 프로퍼티에 정의한 위젯을 원형으로 만들어준다.

   CircleAvatar(
        child: Icon(Icon.person),
   ),

   CircleAvatar(
        backgroundImage: NetworkImage([이미지 URL]),
    ),

Button :

1)RaisedButton : 입체감을 가지는 일반적인 버튼으로 onPress에 실행될 코드를 적용시켜야 비활성화가 풀린다.

2)FlatButton : 평평한 형태의 버튼이다.

3)IconButton : 아이콘을 표시하는 버튼이다. 아이콘 크기 색상등을 지정할수있다.

4)FloatingActionButton : 입체감있는 둥근 버튼으로 해당부분에 action을 줄수가있다.

ElevatedButton.icon : 아이콘과 텍스트를 같이 사용할수있으며 웬만한 기능들이 다 들어있는 최고의 버튼이다.

    child: ElevatedButton.icon(
      icon: parseHover ? SvgPicture.asset(
                "assets/icons/edit.svg",
                 color: kDefaultFontColor,
                  ) :
                  SvgPicture.asset(
                 "assets/icons/edit-over.svg",
                 color: kDefaultFontColor,
                  ),
       onPressed: _show,
       label: Text(
              "Parse",
               style: parseHover ?
               kSubFontStyle.copyWith(fontWeight: FontWeight.w400, color: Color(0xFF4F46E5)) 
               : kSubFontStyle.copyWith(fontWeight: FontWeight.w400),
                            ),
       style: ElevatedButton.styleFrom(
                primary: Colors.white,
                fixedSize: const Size(80, 25),
                side: parseHover ? BorderSide(width: 1.0, color: Color(0xFF4F46E5)) 
                : BorderSide(width: 1.0, color: Colors.transparent)
                            ),
                          ),



     

LifeCycle

Project Component

  • 유저가 브라우저를 열면 html파일을 제일 먼저 보는데
    여기 구조에서도 public의 index.html파일이 있고
    그안에 body태그안에 라우터를 통하여 컴포넌트와 페이지들이 들어간다.
    html파일이 먼저 보여지고 그다음 App.vue가 켜진다.

  • main>index>App>Home>…components

  • main은 app 과router를 실행시켜준다.

  • 라우터폴더 안에 index.js는 말 그대로 라우터를 관리한다.

  • App에서는 페이지 관련된 각각의 views 페이지로된 컴포넌트들을 관리하고 바인딩해준다.

  • Home에서는 일단 바인딩해주는 템플릿태그 안에서 Home페이지에 뿌려줄 컴포넌트들을 import해서
    써주고 버튼같은 부분들도 구현하여 이벤트를 걸어준다.

  • script태그 안에 export default부터는

    name:Home
    //사용할 이름을 써주고
    Components:{
    DataTitle,
    //여기도 사용할 컴포넌트를 써주고
    },
    data(){
     Return{
     //여기에는 관리할 state 들을 초기화 시켜둔다.
     }
    },
    Methods:{
     Async fetchCovidData(){
        const res = await fetch(``)
     },
    async created(){
        const data = await this.fetchCovidData()
    
        this.dataDate = data.Date
        this.stats = data.Global
        this.countries = data.Countries
        this.loading = false
    
    }

    : 여기에선 여러가지 fetch함수나 메소드등등 다양하게 쓴다.

Views Pages / Components

  • 중요

    this.$emit('get-country', country)
    (‘함수이름’,넘겨줄인자)
    Emit은 이벤트버스 이벤트 통신방법이다.
    부모로 값을 넘겨주어 부모 이벤트를 실행시킬때
    주로 사용한다.

  • 일단 header는 App.vue에서 컴포넌트를 따로 만들어 전역으로 사용될수있게 분리하여 App.vue에서 Import해주었다.

  • 1)Home.vue
    Header
    DataTitle
    DataBoxes
    CountrySelect
Home.vue 안에
    순서대로 컴포넌트가 col형태로 분리되어 있으며,
    일단 템플릿태그안에 UI 바인딩되는 부분을 메인태그로 감싸서 여러 컴포넌트로 채워놨고
    그 밑에 버튼을 두었다.
    그리고 그 메인 태그에 v-if조건을 두어 로딩이되었을경우 보여지게끔 해두었고 로딩전에는
    밑에 따로 메인 태그에 v-else로 fetching data.. 하면서 로딩화면을 띄우게 해두었다.
    그리고 메소드로 가서 fetch함수를 이용하여 데이터를 불러오고 리턴해주어 다른 컴포넌트들에 props로 뿌려준다.

  • 2)DataTitle.vue
    : 여기에서는 국가이름과 데이터를 가져온 시간을 알려주는데 {{timestamp}}를 사용한다.
    그러려면 momet를 사용하는데 일단 moment를 import 해주고 computed를 통해서 timestamp를 써준다.

    Computed:{
        timestamp: function(){
            return moment(this.dataDate)
           .format(‘MMM Do YYYY, h:mm:ss a’)
     }
    }

    이런식으로 사용해준다.

  • 3)DataBoxes
    2개의 데이터박스를 만들어서 확진자와 사망자를 분리하고 stats이라는 props데이터를 가져오는데
    가져온 데이터를 뿌려줄때 숫자에 컴마를 추가하고싶은 경우에는

    numberWithCommas(x){
                 return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
            }
    이 메소드를 사용해준다.
    예)12345,133,34235,24234,3200
    
    {{numberWithCommas(stats.NewConfirmed)}}
    ㄴ>템플릿엔 이렇게 적어준다.
  • 4).CountrySelect.vue
    셀렉트박스를 컨트롤해준다.
    일단 home에서 국가에대한 데이터를 props로 받아와서
    템플릿태그안에 select 태그에 v-model=“selected” 를 사용하여 셀렉트 박스를 만든후
    option태그를 사용하고 그 안에 v-for를 이용하여 국가에대한 데이터를 받아와서
    다 뿌려준다.
    그리고 value=“country.ID”를 이용하여 ID에 해당하는 셀렉트된 국가를 보여준다.

    {{country.Country}}

    그리고 data()부분에서는 selected: 0으로 초기화해두고
    메소드부분에서 onchange이벤트를 이용하여 국가가 변경됐을때 호출되면서 셀렉한 국가를 fnid() 함수를 이용하여 찾아낸 후 그 아이템에 ID를 부여하고 셀렉된 국가를
    this.$emit('get-country', country)
    Emit 이벤트 버스를 이용하여 ID부여된 국가를 부모에게 준다.
    그러면 Home에서 해당하는 Props를받고 함수를 실행시켜 데이터를 변경해주고 다시 셀렉된 국가에 대한 정보를 뿌려준다.

  • 5.) clear버튼
    버튼의 함수에서는 다시 fetch함수를 불러와서 클리어할 부분의 state값을 다시 set해준다.
    그리고 v-if조건을 주어 국가를 지정했을때 버튼이 보이게끔 해두었다.

좋은 웹페이지 즐겨찾기