5. stateful

강재영's avatar
Dec 18, 2024
5. stateful
 
플러터는 상태를 가지고 그림을 그린다.
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: HomePage(), ); } } class HomePage extends StatefulWidget { @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { int num = 1; @override Widget build(BuildContext context) { print("빌드됨"); return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text("번호 : $num", style: TextStyle(fontSize: 30)), const MyContainer(), // new를 두번 하지 않는다. ], ), ), floatingActionButton: FloatingActionButton( onPressed: (){ num++; setState(() {}); print("num : $num"); }, child: Icon(Icons.add), ), ); } } class MyContainer extends StatelessWidget { const MyContainer(); @override Widget build(BuildContext context) { print("MyContainer 빌드됨"); return Container( height: 20, color: Colors.blue, ); } }
이렇게하면 new를 다시 안한다. new가 두번안된거 랜더링은 계속 다시된다.
성능적으로 new를 한번만 하니 좋다.
 
 

컨텍스트 분리가 안되어있는 예제

import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: HomePage(), ); } } class HomePage extends StatefulWidget { @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { int num = 1; @override Widget build(BuildContext context) { print("나그려짐"); return Container( color: Colors.yellow, child: Padding( padding: const EdgeInsets.all(20.0), child: Column( children: [ Expanded( child: Container( color: Colors.red, child: Align( child: Text( "${num}", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 100, decoration: TextDecoration.none), ), ), ), ), Expanded( child: Container( color: Colors.blue, child: Align( child: ElevatedButton( style: ElevatedButton.styleFrom(backgroundColor: Colors.red), onPressed: () { num++; setState(() {}); }, child: Text( "증가", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 100, ), ), ), ), ), ), ], ), ), ); } }
 
 
💡
문제1 .컨텍스트를 분리하지 않으면 화면전체가 그려진다.
문제2. 컨텍스트를 분리하면 해당컨텍스트만 분리는 가능하다 다만 이렇게해도 아직 전체가 stateful하다
 

수정한 예제

import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: HomePage(), ); } } class HomePage extends StatefulWidget { @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { // 1. 상태 int num = 1; // 2. 행위 void add(){ num++; setState(() {}); } @override Widget build(BuildContext context) { print("나그려짐"); return Container( color: Colors.yellow, child: Padding( padding: const EdgeInsets.all(20.0), child: Column( children: [ Header(num: num), Bottom(add: add), ], ), ), ); } } class Bottom extends StatelessWidget { final add; Bottom({required this.add}); @override Widget build(BuildContext context) { print("바텀다시그려짐"); return Expanded( child: Container( color: Colors.blue, child: Align( child: ElevatedButton( style: ElevatedButton.styleFrom(backgroundColor: Colors.red), onPressed: () { add(); }, child: Text( "증가", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 100, ), ), ), ), ), ); } } class Header extends StatelessWidget { const Header({ super.key, required this.num, }); final int num; @override Widget build(BuildContext context) { print("헤더다시그려짐"); return Expanded( child: Container( color: Colors.red, child: Align( child: Text( "${num}", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 100, decoration: TextDecoration.none), ), ), ), ); } }
 
부모가 stateful 이고 상태와 행위를 다 가지고 있는 예제
notion image
 
 

컨텍스트를 분리하여, 상태가 변경되는 부분만 sf로 만든다.

import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: HomePage(), ); } } class HomePage extends StatelessWidget { const HomePage({super.key}); @override Widget build(BuildContext context) { print("HomePage 다시 그려짐"); return Container( color: Colors.yellow, child: Padding( padding: const EdgeInsets.all(20.0), child: Column( children: [ Header(), Bottom(), ], ), ), ); } } class Bottom extends StatelessWidget { @override Widget build(BuildContext context) { print("Bottom 다시 그려짐"); return Expanded( child: Container( color: Colors.blue, ), ); } } class Header extends StatefulWidget { @override State<Header> createState() => _HeaderState(); } class _HeaderState extends State<Header> { int num = 1; void add() { num++; setState(() {}); } @override Widget build(BuildContext context) { print("Header 다시 그려짐"); return Expanded( child: Container( color: Colors.red, child: Align( child: Column( children: [ Text( "${num}", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 100, decoration: TextDecoration.none), ), ElevatedButton( onPressed: () { add(); }, child: Text("증가")) ], ), ), ), ); } }
 

공통부모를 둔 stateful 부분로딩가능

 
.
notion image
 
💡
그림을 그리다 보면, 상태의 화면과 행위의 화면이 다를때 가 있다.(가장 가까운 공통부모를 sf로 두고 상태와 행위를 전달한다.)
 
import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: HomePage(), ); } } class HomePage extends StatelessWidget { const HomePage({super.key}); @override Widget build(BuildContext context) { print("홈페이지"); return Container( color: Colors.yellow, child: Padding( padding: const EdgeInsets.all(20.0), child: Column( children: [ Header(), Expanded( child: Father(), ), ], ), ), ); } } class Header extends StatelessWidget { const Header({ super.key, }); @override Widget build(BuildContext context) { print("헤더 페이지"); return Container( color: Colors.green, height: 200, ); } } class Father extends StatefulWidget { @override State<StatefulWidget> createState() => _FatherState(); } class _FatherState extends State<Father>{ int num = 1; void add(){ num++; setState(() {}); } @override Widget build(BuildContext context) { // TODO: implement build return Column( children: [ Expanded(child: Top(num: num)), Expanded(child: Bottom(add: add)), ], ); } } class Bottom extends StatelessWidget { //행위 Function add; Bottom({required this.add}); @override Widget build(BuildContext context) { print("Bottom 페이지"); return Container( color: Colors.blue, child: Align( child: ElevatedButton( style: ElevatedButton.styleFrom(backgroundColor: Colors.red), onPressed: () { add(); }, child: Text( "증가", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 100, ), ), ), ), ); } } class Top extends StatelessWidget { const Top({ super.key, required this.num, }); final int num; @override Widget build(BuildContext context) { print("Top 페이지"); return Container( color: Colors.red, child: Align( child: Text( "${num}", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 100, decoration: TextDecoration.none), ), ), ); } }
 
 
이렇게하면
로그가
💡
홈페이지 헤더 페이지 Top 페이지 Bottom 페이지
Top 페이지 Bottom 페이지
Top 페이지 Bottom 페이지
이런식으로 처음에는 4개의 prinf가 호출되지만 클릭을 하여 추가버튼을 누르며 top과 bottom만 stateful하게 작동하는 걸 확인할 수있다.
 
 

옵저버 패턴

컨텍스트가 복잡하면 부모를 위치시키기 힘든경우가 생긴데 그래서 나온게
 
옵저버 패턴
 
위젯에서 분리시킨 상태 클래스를 두어서 만든다
Share article

강재영 블로그