Flutter - 다른 Stateful 위젯의 함수 사용하기
자식 위젯이 부모 위젯의 함수 call
// 부모 위젯에서 위젯 생성 시 함수 넘겨 주기
SomeSfW(foo: foo));
// 자식 위젯에서 함수 사용
widget.foo();
함수를 그냥 그대로 넘겨주면 된다. 쉬우니까 패스
부모 위젯이 자식 위젯의 함수 call
부모 위젯이 자식 위젯의 함수를 부르는 것은 기본적으로 권장되지는 않는다고 합니다.
방법 1(X)
class SomeSfW extends StatefulWidget {
final SomeSfWState _someSfWState = SomeSfWState();
SomeSfW({
Key? key,
}) : super(key: key);
@override
SomeSfWState createState() => _someSfWState;
foo() => SomeSfWState.foo();
}
class SomeSfWState extends State<SomeSfW> {
void foo(){
/*
some jobs...
*/
}
@override
Widget build(BuildContext context) {
return Text(" !! ");
}
}
처음엔 이 방식으로 어느 정도는 잘 쓰다가, 문제가 생겼다.
예를 들어 List<SomeSfW> list
에 동적으로 SomeSfW
를 만들거나 지우는 등의 처리를 하자 에러가 생겼다.
에러
state._element == null
is not true
assertion failed
로 위와 같은 경고가 나왔다.
(flutter 설치 경로)/packages/flutter/lib/src/widgets/framework.dart
에서 문제가 생겼다 하여 찾아 봤는데,
/// An [Element] that uses a [StatefulWidget] as its configuration.
class StatefulElement extends ComponentElement {
/// Creates an element that uses the given widget as its configuration.
StatefulElement(StatefulWidget widget)
: _state = widget.createState(),
super(widget) {
assert(() {
if (!state._debugTypesAreRight(widget)) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('StatefulWidget.createState must return a subtype of State<${widget.runtimeType}>'),
ErrorDescription(
'The createState function for ${widget.runtimeType} returned a state '
'of type ${state.runtimeType}, which is not a subtype of '
'State<${widget.runtimeType}>, violating the contract for createState.',
),
]);
}
return true;
}());
assert(state._element == null);
state._element = this;
assert(
state._widget == null,
'The createState function for $widget returned an old or invalid state '
'instance: ${state._widget}, which is not null, violating the contract '
'for createState.',
);
state._widget = widget;
assert(state._debugLifecycleState == _StateLifecycle.created);
}
여기서 assert(state._element == null);
전에 state._element
를 출력해 보니 null
이 아닌 state
를 가진 상태의 위젯이 나오더라.
즉 위 방식처럼 위젯을 만들 때 final로 스테이트를 만들어 그걸 반환하는 식으로 하면,
스테이트를 동적으로 생성하거나 삭제하고 조작할 경우, 그 스테이트는 실제 플러터의 트리에 속하는 스테이트와는 다른 녀석일 수 있어 오동작을 하는 문제점이 있다.
위에서 설명한 에러는 아니지만, 에러가 나는 간단한 예제를 추가한다.
예제 코드 보기
부모 위젯에서 버튼을 눌러 자식인 카운터 위젯들의 카운터를 일괄적으로 늘리고, 큐 방식으로 뒤에 push하고 앞을 pop했을 때다.
부모: MyApp
자식: MyHomePage
Wrong
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
MyApp({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
List<MyHomePage> list = List.generate(5, (index) => MyHomePage(title: index.toString()));
int i = 5;
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text("test"),
),
body: ListView.builder(itemCount: list.length,itemBuilder: (constet, i) => list[i]),
floatingActionButton:
IconButton(onPressed: (){
for(MyHomePage h in list) h.incrementCounter();
list.add(MyHomePage(title: (i++).toString()));
list.removeAt(0);
setState(() {
});
}, icon: Icon(Icons.details)),
),
);
}
}
class MyHomePage extends StatefulWidget {
final MyHomePageState _someSfWState = MyHomePageState();
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _someSfWState;
void incrementCounter() => _someSfWState.incrementCounter();
}
class MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'${widget.title} You have pushed the button this many times: ',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
);
}
}
Right
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
MyApp({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
List<MyHomePage> list = [];
List<GlobalKey<MyHomePageState>> klist = [];
int i = 5;
@override
void initState(){
super.initState();
klist = List.generate(5, (index) => GlobalKey());
list = List.generate(5, (index) => MyHomePage(key: klist[index],title: index.toString()));
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text("test"),
),
body: ListView.builder(itemCount: list.length,itemBuilder: (constet, i) => list[i]),
floatingActionButton:
IconButton(onPressed: (){
for(GlobalKey<MyHomePageState> k in klist) k.currentState?.incrementCounter();
klist.add(GlobalKey());
list.add(MyHomePage(key: klist.last,title: (i++).toString()));
list.removeAt(0);
setState(() {
});
}, icon: Icon(Icons.details)),
),
);
}
}
class MyHomePage extends StatefulWidget {
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 Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'${widget.title} You have pushed the button this many times: ',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
);
}
}
방법 2(O)
class SomeSfW extends StatefulWidget {
SomeSfW({
Key? key,
}) : super(key: key);
@override
SomeSfWState createState() => SomeSfWState();
}
class SomeSfWState extends State<SomeSfW> {
void foo(){
/*
some jobs...
*/
}
@override
Widget build(BuildContext context) {
return Text(" !! ");
}
}
자식 위젯은 기본적인 형태와 같다.
// 부모 위젯에서 위젯 생성 시 키 넘겨 주기
GlobalKey<SomeSfWState> someSfWStateKey = GlobalKey();
SomeSfW(key: someSfWStateKey);
// 함수 사용
someSfWStateKey.currentState?.foo();
GlobalKey
를 사용해 자식 위젯에 직접 접근할 수 있다. 이 GlobalKey
는 스테이트마다 고유하므로 트리에서 꼬일 일도 없다.
참고
글로벌 키를 통해 someSfWStateKey.currentContext?.size
등 현재 스테이트의 컨텍스트도 접근할 수 있다.
굿
댓글남기기