8일차 과제 - rlatkddbs99/Flutter GitHub Wiki

1.어떤 동작원리를 가지고 있는지, 그리고 Stateful이 사용되어야 하는 이유를 정리하세요.

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  // This widget is the home page of your application. It is stateful, meaning
  // that it has a State object (defined below) that contains fields that affect
  // how it looks.

  // This class is the configuration for the state. It holds the values (in this
  // case the title) provided by the parent (in this case the App widget) and
  // used by the build method of the State. Fields in a Widget subclass are
  // always marked "final".

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      // This call to setState tells the Flutter framework that something has
      // changed in this State, which causes it to rerun the build method below
      // so that the display can reflect the updated values. If we changed
      // _counter without calling setState(), then the build method would not be
      // called again, and so nothing would appear to happen.
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      body: Center(
        // Center is a layout widget. It takes a single child and positions it
        // in the middle of the parent.
        child: Column(
          // Column is also a layout widget. It takes a list of children and
          // arranges them vertically. By default, it sizes itself to fit its
          // children horizontally, and tries to be as tall as its parent.
          //
          // Invoke "debug painting" (press "p" in the console, choose the
          // "Toggle Debug Paint" action from the Flutter Inspector in Android
          // Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
          // to see the wireframe for each widget.
          //
          // Column has various properties to control how it sizes itself and
          // how it positions its children. Here we use mainAxisAlignment to
          // center the children vertically; the main axis here is the vertical
          // axis because Columns are vertical (the cross axis would be
          // horizontal).
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

기본적인 코드를 살펴보면 한 번 빌드를 한 후 ui를 바꿀 필요가 없는 타이틀과 테마에는 stl위젯으로 감싸져있다. 하지만 fab를 눌렀을 때 숫자가 증가하는 ui를 만들기 위해서 그 부분은 stf 위젯으로 만들어져 있는 점을 볼 수 있다. _incrementCounter 함수를 이용하여 fab를 눌렀을 때 center에 있는 _counter변수를 증가시키고 ui를 계속해서 바꿔주는 역할을 하고 있다. 만약 stl위젯으로 되어있다면 적용이 안되고 숫자가 증가하지 않고 0으로 화면 ui의 변경은 없을 것이다.

Stateful 위젯의 라이프사이클을 공부하고, initState함수가 무엇인지, 언제 작동되는지 소스코드로 직접 테스트해보세요. 그리고 버튼의 이벤트를 통해 setState((){}); 실행했을 때, 왜 initState 함수는 실행이 안되는지 정리하세요.

캡처2

createState() method는 statefulWidget을 구축하자마자 호출되며 state object를 생성한다. initState() : object가 트리에 주입되면(mounted 속성은 true로 설정), initState()가 class constructor 다음으로 자동으로 실행된다. initState()는 state obejct가 처음 생성될 때, 한 번만 호출된다. build() : 위젯을 반환(return)한다. 반환된 위젯이 렌더링되어 화면에 표시된다. build method는 필수 setState() : 상태가 변경되었을 때 프레임워크에 상태가 변경됨을 알린다. dispose() : state object가 영구적으로 삭제될때 호출된다. 이 함수는 주로 Stream이나 애니메이션 해제 시 사용된다.

class _CounterContainerState extends State<CounterContainer> {
  late int count;

  @override
  void initState() {
    super.initState();
    count = 0;
  }
  ...
}

initstate()는 state를 초기화하는 함수이다. initState는 statefulWidget이 실행하면 단 한 번만 작동을 한다. 그 이후 작동시키기 위해서는 해당 statefulWidget을 종료한 후에 가능하다. 이 함수 안을 수정하고 hot reload 또는 rebuild를 아무리 하더라도 바뀌지 않으니 반드시 수정한 statefulWidget을 종료(dispose) 후 다시 실행시켜야 한다

  1. 계산기 만들기 캡처1 캡처2
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyCustomWidget(),
    );
  }
}

class MyCustomWidget extends StatefulWidget {
  const MyCustomWidget({super.key});

  @override
  State<MyCustomWidget> createState() => _MyCustomWidgetState();
}

showResultDialog(BuildContext context, var result) {
  showDialog(
    context: context,
    builder: (context) {
      return Dialog(
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
        child: SizedBox(
          width: MediaQuery.of(context).size.width / 2,
          height: 150,
          child: Center(
              child: Text(
            "$result",
            style: const TextStyle(fontWeight: FontWeight.bold),
          )),
        ),
      );
    },
  );
}

class _MyCustomWidgetState extends State<MyCustomWidget> {
  int x = 0;
  int y = 0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        title: Text("사칙연산"),
      ),
      body: Center(
          child: Padding(
        padding: const EdgeInsets.all(32.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Row(
              children: [
                Text("x의 값은?"),
                SizedBox(
                  width: 80,
                ),
                SizedBox(
                  width: 200,
                  child: TextField(
                    onChanged: (value) {
                      x = int.parse(value);
                    },
                    decoration: InputDecoration(
                        hintText: "x의 값을 입력하세요.", border: OutlineInputBorder()),
                  ),
                ),
              ],
            ),
            Row(
              children: [
                Text("Y의 값은?"),
                SizedBox(
                  width: 80,
                ),
                SizedBox(
                  width: 200,
                  child: TextField(
                    onChanged: (value) {
                      y = int.parse(value);
                    },
                    decoration: InputDecoration(
                      hintText: "y의 값을 입력하세요",
                      border: OutlineInputBorder(),
                    ),
                  ),
                )
              ],
            ),
            ElevatedButton(
              onPressed: () {
                showResultDialog(context, x + y);
              },
              child: Text("더하기 결과"),
            ),
            ElevatedButton(
              onPressed: () {
                showResultDialog(context, x - y);
              },
              child: Text("빼기 결과"),
            ),
            ElevatedButton(
              onPressed: () {
                showResultDialog(context, x * y);
              },
              child: Text("곱하기 결과"),
            ),
            ElevatedButton(
              onPressed: () {
                showResultDialog(context, x / y);
              },
              child: Text("나누기 결과"),
            )
          ],
        ),
      )),
    );
  }
}
⚠️ **GitHub.com Fallback** ⚠️