Flutter로 Counting App 만들기

간단한 개념 + 예제 코드

Posted by Minki on June 1, 2020

들어가기

아직 개발실력이 많이 부족합니다. 잘못된 내용은 언제든지 댓글로 알려주시면 감사드리겠습니다!

Flutter란?

Flutter는 아직까지 한국에서 핫한 언어는 아니지만, 외국에서는 이미 많이 사용하고 있는 언어입니다. Flutter에 대해 간략하게 설명하자면, dart 언어를 기반으로 한 프레임워크로, One code base로 iOS와 Android를 모두 개발할 수 있습니다.

플러터가 궁금하신 분들은 아래 링크에서 한번 둘러보시면 좋을것 같습니다.

flutter.dev

Flutter로 간단한 Counting App 만들기.

이번 포스팅에서는 Flutter를 처음 실행하면 나오는 Counting App 예제를 구현해보겠습니다.

1. 전체적인 앱 구조 흐름

우선 flutter 앱을 만들기 위해 필요한 class와 함수만을 나열해보면 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
import 'package:flutter/material.dart';

void main() => runApp(Myapp());

class Myapp extends StatelessWidget {
}

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

위의 코드의 흐름은 대략적으로 이렇습니다.

  1. MyApp을 material.dart로 만들 것이기 때문에 해당 라이브러리를 ìmport
  2. flutter가 실행되면 main함수를 호출 => Myapp 함수 호출 => build를 위한 MaterialApp 호출
  3. StatelessWidget이라는 바뀌지 않는 큰 도화지를 만들어 이를 Myapp에 상속
  4. StatefulWidget을 상속받은 MyHomePage 위젯을 Myapp 위에 퍼즐처럼 조립하겠다는 것.
  5. _MyHomePageState(); 위젯 추가

제가 부제목에서도 말슴 드렸지만 flutter는 모든 것이 다 위젯입니다. 그러니까 위의 과정을 간단히 말씀드리면 Myapp이라는 도화지를 만들어 그 위에 MyHomepage라는 위젯을 넣겠다는 말입니다.

2. 상단 bar 및 그 안에 텍스트 넣기

이제 메인화면에 텍스트를 출력해보겠습니다. 이 예제에서 텍스트는 크게 바뀔 필요가 없기 때문에 Stateless을 상속받은 MyappMyHomePage위젯을 만들어 그 내부에 build 하였습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Myapp extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
        return MaterialApp(
            title: 'Flutter Demo',
            theme: ThemeData(primarySwatch: Colors.blue),
            home: MyHomePage(title: 'Flutter Demo Home page')
        );
    }
}

class MyHomePage extends StatefulWidget {
    MyHomePage({Key key, this.title}) : super(key: key);

    // title은 절대 변하지 않으므로 final 변수로 설정
    final String title;

    @override
    _MyHomePageState createState() => _MyHomePageState();
}

위 코드를 입력하시면 상단바가 완성된것을 보실 수 있습니다. 이제 나머지 부분을 완성해보겠습니다.

2-1. Stateful MyHomePage

위에서 첫 문단의 코드만 작성하면 MyHomePage가 정의되지 않았다고 나옵니다. 그래서 MyHomePage를 내용을 바꿀 수 있게 Stateful한 위젯으로 만들기 위해서는 다음과 같은 명령어를 입력하시면 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
//!! stful을 치면 자동으로 생성됩니다. class와 extends 사이에 MyHomePage를 선언해주기만 하면 됩니다.
class  extends StatefulWidget {
  @override
  _State createState() => _State();
}

class _State extends State<> {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

3. Floating Action Button과 함수 설정

flutter에서는 버튼 역시 위젯입니다. 버튼을 형성하고 버튼을 눌렀을 때 아웃풋이 나올 수 있게 알맞은 함수를 짜보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class _MyHomePageState extends State<MyHomePage> {
    //private 변수 설정
    int _counter = 0;

    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(title: Text(widget.title))
            body: Center(
                child: Column(mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                    Text('You have pushed the button this many times:'),
                    Text('$_counter', style: Theme.of(context).textTheme.display1)
                ]
                )
            )
        )
        floatingActionButton:(
            children: <Widget>[
                FloatingActionButton(
                    onPressed: _incrementcounter,
                    tooltip: 'increment',
                    child: Icon(Icons.add),
                ),
            ]
        )
    }
}

이렇게 하면 화면에 텍스트와 버튼이 생긴것을 볼 수 있을 것입니다. 마지막으로 _incrementcounter 함수를 정의해줌으로써 버튼을 눌렀을 때 동작하게 하면 완성됩니다. setState()함수에 대해서는 다음 포스팅을 통해 알아보겠습니다.

1
2
3
4
5
void _incrementCounter() {
    setState(() {
        _counter++;
    });
}

4. list 추가와 버튼 하나더 추가하기.

Flutter에서 Body는 오직 하나의 위젯만을 가집니다. 하나의 Body는 하나의 텍스트 위젯만을 가진다는 말입니다. 그러나 이런 형태로는 앱을 복잡하게 만들 수 없습니다. 이를 해결하기 위해 위젯들에게 배열을 주어 하나의 바디가 여러개의 위젯을 가질 수 있도록 해야합니다. 이를 위해 위젯들의 배열을 내부에 가질 수 있는 타입의 위젯(Row/Column)을 body의 child로 설정합니다.

이렇게 짠 전체 코드는 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import 'package:flutter/material.dart';

// Myapp을 실행시키는 주된 함수
// 필수적으로 선언해주어야 함.
void main() => runApp(Myapp());

// stateless라는 앱의 형태를 Myapp에 상속
// flutter가 실행되면 main함수를 호출 -> Myapp함수 호출 -> build를 위한 MaterialApp호출
// stateless : 바뀌지 않은 정적인 위젯
class Myapp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    ); // materialApp으로 만들것이기 때문
  }
}

// MyHomePage를 StatefulWidget형태로 만듬
// stateful : 바뀌지 않은 Stateless위에 퍼즐 형태로 바뀔 수 있는 Stateful 위젯을 둔 것
class MyHomePage extends StatefulWidget {
  //Myape에서의 title을 받는 과
  MyHomePage({Key key, this.title}) : super(key: key);
  // final은 dart 변수 선언. 바꾸지 못함
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  var data = [ ];
  // _로 선언된 변수들은 private변수로 클래스 내에서만 쓸 수 있다.
  int _counter = 0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title),),
      body: Center(
        child: Column(mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text('What you saved'),
          ListView.builder(
            itemCount: data.length,
            padding: EdgeInsets.fromLTRB(50.0, 10.0, 50.0, 10.0),
            shrinkWrap: true,
            itemBuilder: (context, int index){
              return Container(
                color: Colors.white,
                width: 220,
                child: Text(
                  data[index],
                  textAlign: TextAlign.center,
                ),
              );
            },
          ),
          Text('You have pushed the button this many times:'),
          Text('$_counter', style: Theme.of(context).textTheme.display1,) //textTheme 중에 display1 테마를 적용해라
        ],
        ),
      ),
      floatingActionButton: Row(
        mainAxisSize: MainAxisSize.max,
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: <Widget>[
          FloatingActionButton(
            onPressed: _incrementCounter,
            tooltip: 'Increment',
            child: Icon(Icons.add),
          ),
          FloatingActionButton.extended(
            onPressed: _saveCounter,
            icon: Icon(Icons.save),
            label: Text("Save"),
            backgroundColor: Colors.pink,
          ),
          FloatingActionButton.extended(
            onPressed: _deleteCounter,
            icon: Icon(Icons.remove),
            label: Text("Delete"),
            backgroundColor: Colors.pink,
          ),
          FloatingActionButton.extended(
            onPressed: _resetCounter,
            icon: Icon(Icons.refresh),
            label: Text("Refresh"),
            backgroundColor: Colors.pink,
          ),
          FloatingActionButton(
            onPressed: _decreaseCounter,
            tooltip: 'Decrement',
            child: Icon(Icons.remove),
          ),
        ],
      ),
    );
  }

  // count +1
  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  // count -1
  void _decreaseCounter() {
    setState(() {
      _counter--;
    });
  }

  void _saveCounter() {
    setState(() {
      data.add(_counter.toString());
    });
  }

  void _deleteCounter() {
    setState(() {
      data.removeLast();
    });
  }

  void _resetCounter() {
    setState(() {
      _counter = 0;
    });
  }
}


reference