Notice
Recent Posts
Recent Comments
Link
«   2024/12   »
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
Archives
Today
Total
관리 메뉴

nomad-programmer

[Programming/Flutter] Sample APP 분석 본문

Programming/Flutter

[Programming/Flutter] Sample APP 분석

scii 2020. 10. 6. 23:42

프로젝트를 새로 작성하면 샘플 앱이 표시된다. 프로젝트 창의 lib 폴더에서 main.dart 파일을 클릭하면 전체 코드를 볼 수 있다.

import 'package:flutter/material.dart';

// 앱 시작 부분
void main() {
  runApp(MyApp());
}

// 시작 클래스. 메터리얼 디자인 앱 생성
class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      // 표시할 화면의 인스턴스
      home: MyHomePage(title: '플러터 어플리케이션'),
    );
  }
}

// 시작 클래스가 실제로 표시하는 클래스. 카운터 앱 화면
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

// 위 MyHomePage 클래스의 상태를 나타내는 State클래스
class _MyHomePageState extends State<MyHomePage> {
  // 화면에 표시할 상탯값인 카운터 숫자
  int _counter = 0;

  // count 변수를 1 증가시키고 화면을 다시 그리는 메소드
  void _incrementCounter() {
    // 화면을 다시 그리도록 하는 함수. StatefulWidget만 가능
    setState(() {
      _counter++;
    });
  }

  // 화면에 UI를 그리는 메소드. 그려질 위젯을 반환
  @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 변수를 표시
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        // 클릭 시 _incrementCounter() 메소드 실행
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        // 상단 앱바
        child: Icon(Icons.add),
      ),
    );
  }
}

실행 화면

앱 구조

카운터 앱의 main.dart 파일은 여러 코드 덩어리로 구성되어 있다.

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {...}

class MyHomePage extends StatefulWidget {...}

class _MyHomePageState extends State<MyHomePage> {...}

위 세 덩어리는 사실상 거의 수정하지 않는 부분이다. 그 중 아래쪽 두 덩어리인 MyHomePage와 _MyHomePageState는 샘플 앱의 화면을 나타내는 코드이다. 모든 코드는 사실상 마지막 덩어리인 _MyHomePageState 에 작성한다.


앱 실행 부분

플러터에서는 화면을 그리는 모든 디자인 요소를 위젯(widget)이라고 한다. packge:fultter/material.dart 패키지에는 머터리얼 디자인 위젯들이 포함되어 있따. 머터리얼 디자인을 기본으로 하는 앱은 이 패키지를 임포트하여 머터리얼 디자인 위젯을 사용할 수 있다.

import 'package:flutter/material.dart';

main() 함수는 앱의 시작점이다. 여기서는 runApp() 함수에 MyApp() 인스턴스를 전달한다. 이 부분은 특별히 수정할 일이 없다.

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

StatelessWidget 클래스

StatelessWidget 클래스는 상태가 없는 위젯을 정의하는 데 사용된다. runApp() 함수에 전달된 MyApp 클래스는 다음과 같이 정의되어 있다.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(...);
  }
}

MyApp 클래스는 StatelessWidget 클래스의 서브 클래스이다. StatelessWidget 클래스는 상태를 가지지 않는 위젯을 구성하는 기본 클래스이다. 여기서 상태를 가지지 않는다는 것은 한 번 그려진 후 다시 그리지 않는 경우이며, 이러한 클래스는 프로퍼티로 변수를 가지지 않는다 (상수는 가질 수 있음).

StatelessWidget 클래스는 build() 메소드를 가지고 있다. build() 메소드는 위젯을 생성할 때 호출되는데, 실제로 화면에 그릴 위젯을 작성해 반환한다.

따라서 StatelessWidget 클래스를 상속받은 MyApp 클래스는 MaterialApp 클래스의 인스턴스를 작성해 반환한다.


MaterialApp 클래스

build() 메소드가 반환하는 MaterialApp 클래스는 다음과 같다. 여기까지는 거의 모든 프로젝트에서 동일하다.

return MaterialApp(
  title: 'Flutter Demo',
  theme: ThemeData(
    primarySwatch: Colors.blue,
  ),
  home: MyHomePage(title: 'Flutter Demo Home Page'),
);

title, theme, home 세 가지 이름이 있는 인수를 설정한다. 이 프로퍼티들을 설정하여 위젯의 속성을 표현한다. 

title은 제목, theme는 테마 지정이다. 여기서는 파랑 계열의 색상 테마가 기본으로 설정되어 있다. home에 작성하는 위젯이 실제 이 앱이 표시하는 위젯이 된다.


StatefulWidget 클래스

상태가 있는 위젯을 정의할 때는 StatefulWidget 클래스를 사용한다. StatefulWidget 클래스는 StatefulWidget을 상속받은 MyHomePage 클래스와 State<MyHomePage> 클래스를 상속받은 _MyHomePageState 클래스로 구성된다.

샘플 앱은 다음과 같은 형태로 작성되어 있다.

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key); <1>
  
  final String title;
  
  @override
  _MyHomePageState createState() => _MyHomePageState(); <2>
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0; <3>
  ...
  
  @override
  Widget build(BuildContext context) { <4>
    return Scaffold(...);
  }
}
  1. MyHomePage 클래스의 생성자는 key와 title 프로퍼티를 옵션으로 받아 super 키워드로 부모 클래스의 생성자에 key를 전달한다.
  2. MyHomePage 클래스에는 상속받은 createState() 메소드를 재정의하여 _MyHomePageState 클래스의 인스턴스를 반환한다. 이 메소드는 StatefulWidget이 생성될 때 한 번만 실행되는 메소드다.
  3. State 클래스를 상속받은 클래스를 상태 클래스라고 부른다. 상태 클래스는 변경 가능한 상태를 프로퍼티 변수로 표현한다. 나중에 이 변수의 값을 변경하면서 화면을 다시 그리게 된다.
  4. _MyHomePageState 클래스의 상태에 따라 화면에 그려질 코드를 여기에 작성한다. 모양새는 StatelessWidget 클래스와 같다. build() 메소드를 가지고 있고 여기에 화면에 그려질 부분을 정의한다.

위젯에서 위젯으로 값 전달

위젯에서 위젯으로 값을 전달하는 과정을 살펴보자. MaterialApp 클래스에서 home 프로퍼티에 MyHomePage 인스턴스를 (생성하고) 인수로 전달하며, 그와 동시에 MyHomePage의 title인수값으로 '플러터 어플리케이션'을 전달했다.

return MaterialApp(
  title: 'Flutter Demo',
  theme: ThemeData(
    primarySwatch: Colors.blue,
  ),
  home: MyHomePage(title: '플러터 어플리케이션'),
);

이렇게 전달받은 title 인수값은 MyHomePage 클래스의 생성자의 this.title 매개변수로 전달되어 필드 상수인 String title에 대입된다.

class MyHomePage extends StatefulWidget {
  // 생성자
  MyHomePage({Key key, this.title}) : super(key: key);
  
  final String title;
  ...
}

// 상태 클래스
class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
    );
  }
}

이렇게 위젯 사이의 데이터 전달은 생성자를 활용한다. 상태 클래스에서 StatefulWidget 클래스에 접근하려면 widget 프로퍼티를 사용한다.


상태 변경

State 클래스에는 주로 상태를 저장할 변수들과 그 변수를 조작할 메소드를 작성한다. 카운트 앱의 State 클래스는 다음과 같이 정의되어 있다.

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  
  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(...);
  }
}

정수형 _counter는 0으로 초기화되어 있고, _incrementCounter() 메소드는 setState() 메소드를 실행한다. 여기서 setState() 메소드가 중요하다. 이 메소드의 인수로 입력 인수가 없고 반환값이 없는 익명 함수를 작성했다. 익명 함수의 내용은 _counter를 1만큼 증가 시키는 것이다.

setState() 메소드는 전달된 익명 함수를 실행한 후 화면을 다시 그리게 하는 역할을 한다. 화면은 build() 메소드가 실행되면서 그려진다고 말했다. 즉, setState() 메소드는 build() 메소드가 다시 실행되게 하는 역할을 한다. setState() 메소드는 State 클래스가 제공하는 메소드이다.

정리하면 MyHomePage 클래스는 StatefulWidget의 서브클래스이며 상태를 가질 수 있다. 그리고 그 상태는 State 클래스의 서브클래스로 정의한다. 여기서 변경 가능한 상태는 _counter 변수이다. 이 값이 변경될 때마다 화면을 다시 그리면 동적인 화면을 가진 앱이 되는 것이다.


Scaffold 클래스와 AppBar 클래스

이제 _MyHomePageState 클래스의 build() 메소드가 호출될 때 불리는 Scaffold 클래스를 알아보자.

Scaffold 클래스는 머터리얼 디자인 앱을 만들 때 뼈대가 되는 위젯이다. 즉, 머터리얼 디자인 앱을 만든다면 MaterialApp -> Scaffold가 기본 형태이다.

머터리얼 앱의 기본 형태

만약 Scaffold 를 작성하지 않는다면 상단 앱바가 없고 머터리얼 디자인이 적용 안 된 화면이 그려지므로 이 구조를 유지하는 것이 디자인 통일에 유익하다.

카운터 앱의 Scaffold 클래스는 다음과 같이 appBar, body, floatingActionButton을 정의한다.

Scaffold(
  appBar: AppBar( <1>
    title: Text(widget.title), <2>
  ),
  body: ...
  floatingActionButton: ...
);
  1. appBar에 AppBar 클래스의 인스턴스를 전달한다. AppBAr는 머터리얼 디자인 앱에서 상단의 제목과 메뉴를 표시하는 영역을 나타낸다.
  2. AppBar 클래스는 title 프로퍼티에 Text위젯을 정의했다. Text 위젯은 글자를 나타내는 위젯이며 인수로 widget.title 값을 넘겨받아 화면에 표시한다. widget은 StatefulWidget 클래스(MyHomePage)의 프로퍼티를 참조할 때 사용한다.

body 코드를 살펴보자.

int _count = 0; <1>
...
body: Center(
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      Text(
        'You have pushed the button this many times:',
      ),
      Text(
        '$_count', <2>
        style: Theme.of(context).textTheme.display1,
      ),
    ],
  ),
),
  1. 0으로 초기화했다.
  2. _counter는 정수형 변수다. 이것을 Text 위젯에 표시하려면 문자열로 변경해야 한다. 변수값을 문자열 형태로 변경하고자 할 때는 변수 앞에 $ 기호를 붙인다.

이후 사용자가 (+) 버튼을 누르면 다음과 같은 _incrementCounter() 메소드가 호출된다.

void _incrementCounter() {
  setState(() { <1>
    _counter++; <2>
  });
}

 

  1. setState() 메소드에 의해 <2> _counter 변수가 1 증가한 후 build() 메소드가 다시 호출되고 화면이 다시 그려져 숫자가 갱신된다.

FloatingActionButton 클래스

Scaffold 클래스는 머터리얼 디자인에 자주 사용되는 FloatingActionButton 클래스를 정의하는 프로퍼티를 제공한다.

floatingActionButton: FloatingActionButton (
  onPressed: _incrementCounter,
  tooltip: 'Increment',
  child: Icon(Icons.add),
),

 

onPressed 프로퍼티는 버튼이 눌러지면 실행되는 부분이다. 여기서 동작시킬 코드를 함수 형태로 작성한다. 다트에서는 함수도 값으로 사용될 수 있기 때문에 _incrementCounter() 메소드의 이름을 직접 값으로 작성했다.

다트 문법에서 함수를 인수로 전달하는 방법은 몇 가지가 있는데 앞에서처럼 메소드명을 직접 지정하거나, 다음과 같이 람다식이나 익명 함수를 애용해 지정할 수 있다.

// 람다식
onPressed: () => _incrementCounter();

// 무명 함수
onPressed: () {
  return _incrementCounter();
}

세 방법 모두 같은 결과를 나타낸다. 가장 쉽게 느껴지는 것을 선택하여 사용하면 된다.

tooltip 프로퍼티에는 사용자가 FloatingActionButton을 길게 터치할 때 표시할 글자를 지정한다.

child 프로퍼티에는 아이콘 인스턴스를 정의하여 (+) 모양 버튼을 표현한다.


정리

  • 머테리얼 디자인 앱은 MaterialApp.Scaffold 클래스를 기본적으로 사용한다.
  • 앱 시작 부분의 모양은 거의 같다.
  • 상태가 없는 정적인 화면은 StatelessWidget 클래스로 만든다.
  • 상태가 있는 동적인 화면은 StatefulWidget 클래스로 만든다.
Comments