Flutter Widget 간 State 전달 방법과 업데이트 방법을 정리합니다. Flutter에서는 React Props와 State 관리 방식과 유사한 방식을 사용하고 있습니다. Flutter에서는 크게 2가지 방법으로 분류할 수 있습니다.
- 부모 Widget에서 자식 Widget으로 State 전달
- 자식 Widget에서 부모 Widget의 callback함수를 통해서 State update
부모 Widget에서 자식 Widget으로 State 전달
부모 Widget에서 자식 Widget으로 state 전달하는 방법은 다음과 같은 3단계로 진행합니다.
- 부모 Widget에서 자식 Widget으로 State 보내기
- 자식 Widget에서 전달받을 state를 class member 변수로 등록하기
- 자식 Widget에서는 전달받은 state의class 변수를 사용하기
Flutter Application 생성 시 만든 코드를 예를 들면 다음과 같습니다. MyApp (=부모 Widget)에서 MyHomePage(=자식 Widget)으로 title 이름의 state에 'Flutter Demo Home Page'값으로 parameter를 전달합니다.
MyHomePage(title: 'Flutter Demo Home Page'),
MyHomePage(=자식 Widget)에서는 class member에 title을 등록합니다. 일반적으로 부모로 전달받은 state는 자식 Widget에서 값을 변경하지 않기 때문에 final 변수를 사용합니다.
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
...
}
MyHomePage의 State를 관리하는 _MyHomePageState에서는 MyHomePage의 class member를 사용하기 위해서 widget.title 값으로 접근해서 사용합니다. widget이란 keyword를 통해서 MyHomePage class member을 접근 가능 주의해주세요.
class _MyHomePageState extends State<MyHomePage> {
...
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
....
}
}
자식 Widget에서 부모 Widget으로 State 전달
자식 Widget에서 부모의 State를 직접 접근은 불 가능하며, 대신 부모 Widget에서 State처리 함수를 자식 Widget으로 전달하고 이를 호출하는 방식을 사용해야 합니다. 이에 대한 설명은 링크를 참조하시고, 방법을 요약하면 다음과 같습니다.
- Parent Widget: State 처리 함수 생성, State 처리 함수는 내부 변수를 직접 접근 가능하면 Rendering을 다시 하기 위해서는 setState() 함수를 반드시 호출해야 합니다.
- Parent Widget: State 처리 함수를 Child Widget으로 function parameter로 전달
- Chid Widget: Parent Widget에서 전달한 function parameter를 class member로 등록합니다.
- Child Widget: Parent Widget에서 전달받은 function을 실행합니다.
Demo 예제를 사용하면 Child Widget으로 Elevation Button을 Widget을 만들고 클릭 시 counter 값을 업데이트하는 예제로 설명하겠습니다.
Parent Widget에서 _counter 값을 선언하고 setState() 함수를 호출하는 _incrementCounter() 함수를 생성합니다.
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
Parent Widget에서 Child Widget인 SampelChildWidget으로 notifyParent 이름으로 _incrementCounter() 함수를 전달합니다.
SampleChildWidget(notifyParent: _incrementCounter)
ChildWigdet에서는 ParentWidget인 MyHomePage에서 전달한 notifyParent 인자 값을 선언하고 받습니다.
class SampleChildWidget extends StatefulWidget {
const SampleChildWidget({Key? key, required this.notifyParent}) : super(key: key);
final Function() notifyParent;
@override
_SampleChildWidgetState createState() => _SampleChildWidgetState();
}
ChildWidget에서 전달받은 notifyParent 함수를 onPress Event를 받아서 호출합니다.
class _SampleChildWidgetState extends State<SampleChildWidget> {
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: widget.notifyParent,
child: const Text('Child Widget'),
);
}
}
findAncestorStateOfType 함수를 사용하여 Child Widget에서 부모의 State 변경
Child Widget에서 context의 findAncestorStateOfType()로 parent를 찾고 parent의 state 변경도 가능합니다. (참고 링크) 개인적으로는 부모의 State 값을 자식에서 변경할 수 있기 때문에 지양하는 방법입니다.
본 포스팅에서 설명한 코드는 다음과 같고, GitHub https://github.com/kibua20/devDocs/tree/master/flutter/a01Basic (lib/main01.dart)에도 반영하였습니다.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
SampleChildWidget(notifyParent: _incrementCounter)
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class SampleChildWidget extends StatefulWidget {
const SampleChildWidget({Key? key, required this.notifyParent}) : super(key: key);
final Function() notifyParent;
@override
_SampleChildWidgetState createState() => _SampleChildWidgetState();
}
class _SampleChildWidgetState extends State<SampleChildWidget> {
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: widget.notifyParent,
child: const Text('Child Widget'),
);
}
}
관련 글:
[SW 개발/FrontEnd(Flutter)] - Flutter 특징과 개발환경 설정 방법
[SW 개발/FrontEnd(Flutter)] - Android Studio와 Visual Code로 Flutter Project 프로젝트 생성
[SW 개발/Python] - Selenium 4.0 개선 사항 정리 - WebDriver 자동 로딩 가능
[SW 개발/Android] - 안드로이드 adb 설치 및 설정 방법
[SW 개발/Data 분석 (RDB, NoSQL, Dataframe)] - Jupyter Notebook의 업그레이드: Jupyter Lab 설치 및 extension 사용법
[SW 개발/Python] - Python 가상환경(Virtual Environment) 만들기 위한 virtualenv 명령어 및 실행 예제
댓글