조금 매운맛 18 플러터 키 이해하기
What ar the keys?
1. 위젯이 위젯트리 상에서 이동을 하는 과정에서 위젯의 State를 보존하기 위해서
2. 위젯이나 요소들을 유니크하게 식별해주는 역할
위젯 수준의 state는 UI가 변경되도록 영향을 미치는 데이터
위젯이 위젯트리상에서 이동한다는 의미
> 투두리스트 앱을 예시로 List의 제일 첫번재 인덱스 0번을 가진 Item 3이 5,6 사이로 이동했다고 해서 Item 3번 인덱스가 변하거나 하지 않음
> 이 때 같은 타입의 위젯들에게 Key 값을 부여해서 플러터가 식별하도록 만들어줌
* 기본적으로 플러터 프레임워크에서 제공해주는 위젯들은 빌드 타임 시 키를 부여받게 됨.
> statefulwidget 생성시 자동으로 key가 생성된 것을 볼 수 있음
(강의에서 색깔이 바뀌는 앱이 예시로 나오는데 나중에 다시 봐야할듯)
//colorapp.dart
import 'package:flutter/material.dart';
import 'dart:math';
import 'package:fluttertoast/fluttertoast.dart';
void main() => runApp(
new MaterialApp(
home: PositionedTile(),
),
);
class PositionedTile extends StatefulWidget {
@override
State<StatefulWidget> createState() => PositionedTileState();
}
class PositionedTileState extends State<PositionedTile> {
List<Widget> tiles = [
StatefulColorfulTile(key: UniqueKey(),),
StatefulColorfulTile(key: UniqueKey(),),
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: tiles,
),
],
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.double_arrow_rounded),
onPressed: (){
swapTiles();
flutterToast();
}
),
);
}
void swapTiles() {
setState(() {
tiles.insert(1, tiles.removeAt(0));
});
}
}
class StatefulColorfulTile extends StatefulWidget {
const StatefulColorfulTile({Key key}) : super(key: key);
@override
_StatefulColorfulTileState createState() => _StatefulColorfulTileState();
}
class _StatefulColorfulTileState extends State<StatefulColorfulTile> {
Color myColor = getRandomColor();
@override
Widget build(BuildContext context) {
return Container(
color: myColor,
child: Padding(
padding: EdgeInsets.all(70.0),
),
);
}
}
getRandomColor() {
var r = Random();
return Color.fromARGB(255, r.nextInt(255), r.nextInt(255), r.nextInt(255));
}
void flutterToast() {
Fluttertoast.showToast(
msg: 'Color has been changed',
gravity: ToastGravity.BOTTOM,
backgroundColor: Colors.blue,
textColor: Colors.white,
toastLength: Toast.LENGTH_SHORT
);
}
element tree : 앱의 뼈대. 위젯트리에 근거해 플러터 프레임워크가 자동으로 생성해줌
> stateless 1과 2의 위치가 바뀌게 되면 element tree의 구조가 그대로인지 먼저 확인
> 위젯의 타입이 똑같다면 즉시 element tree의 구조를 새로운 위젯으로 업데이트
Statelss Tile 위젯을 Stateful 위젯으로 바꾸고 컬러에 대한 state를 StatefulTileState 객체에 저장하게 된다면?
> 색상이 변하지 않음
> 아까와 다른 점은 State 객체가 추가 됨
> State 객체에 컬러에 대한 정보가 저장됨. but 위젯 자체에는 저장이 되지 않음
> 타일의 위치가 바뀌게 되면 element tree의 위젯 타입과 이에 상응하는 state만을 확인해 위젯정보를 새롭게 업데이트
But 우리가 원하는 결과 값이 아님
그래서 이를 해결하기 위해서 Tile 위젯에 키 속성 추가
> 플러터는 위젯트리에 근거해 element tree의 구조를 확인하면서 key값도 함께 확인
> 키값이 맞는 위젯들을 찾아내서 element tree의 참조 요소를 업데이트 함
Key의 종류
1. Global key
2. Value key
3. Unique key
4. Object key
5. Page storage key
- Value Key
: Value값을 State로 갖는 Stateful Widget의 State를 보존하기 원할 때 Value Key를 사용
주사위 게임으로 예시
Flutter 17 로그인과 주사위 게임 플러트앱 만들기
조금 매운맛 3 로그인과 주사위 게임 앱 pt 1, 2 전체코드 // main.dart import 'package:flutter/material.dart'; import 'dice.dart'; void main() => runApp(MyApp()); class MyApp extends StatefulWidget { @override State createState() => _MyAppS
kbagsa.tistory.com
1) controller 주석처리
2) emailField boolean형 변수 생성
> emailField 값이 true이면 TextField를 보여줌
3) if문 추가
4) 버튼 onPressed 수정
결과
> 의도한대로 버튼을 누르면 emailField가 false로 바뀌면서 Enter dice(아이디) TextField가 사라짐
하지만 위젯트리 상에서 Enter dice(아이디) TextField가 제거되는 과정에서
Enter dice(아이디) TextField의 value값인 'dice'가 password TextField가 그대로 보여짐
왜냐면!
2개의 같은 타입의 state 위젯 중 Enter dice(아이디) TextField가 제거되는 과정에서 플러터는 위젯의 타입만을 유일하게 확인하게 됨
그래서 state가 뒤섞이게 됨(어떤 state위젯 보존할지 혼동함)
특정 위젯을 식별할 수 없기 때문에 일어나는 에러
> 그래서 Key로 구분하게 해줌
1. flutter는 기본적으로 위젯의 타입으로 식별(위젯의 타입이 다르면 아무런 문제 없음)
2. Stateful위젯의 식별을 위해서 Key가 필요
3. Value key는 valu값을 가지는 stateful위젯에 사용
- Global Key
(전역변수와 비슷. 전역변수 : 메소드 외부에 선언된 변수로서 모든 메소드에서 접근 가능)
: 한 위젯 내부에 있는 변수나 리소스를 외부 위젯에서 접근 할 수 있게 만드는 키
(0) 키는 전체 앱에서 중복되지 않은 유니크한 키이다.)
1) 글로버 키도 어떤 요소들을 고유하게 식별해주는 역할
2) buildcontext와 같은 요소들과 연관된 다른 외부의 요소들이 접근할 수 있도록 함
3) Stateful widget의 state에 접근할 수 있도록 함.
> 제네릭스를 사용해서 특정 stateful 위젯에 state를 bind(묶다, 묶어서 넘기는 역할 in 앱 전체)하게 되어 있음
> Global Key의 속성 중 한개
> Global Key가 현재 가지고 있는 트리상의 위젯의 state
= Global Key 사용 시, 해당 속성을 가지고 다른 stateful 위젯의 state에 접근할 수 있다.
예제1 기본 코드
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(primarySwatch: Colors.blue),
home: MyKey(),
);
}
}
class MyKey extends StatelessWidget {
const MyKey({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Global key'),
),
body: Center(
child: Counter(),
),
floatingActionButton: FloatingActionButton(
child: Icon(
Icons.add,
color: Colors.white,
),
onPressed: (){
},
),
);
}
}
class Counter extends StatefulWidget {
const Counter({Key? key}) : super(key: key);
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int count=0;
void increment(){
setState(() {
count++;
});
}
@override
Widget build(BuildContext context) {
return Center(
child: Text('Count number : $count'),
);
}
}
예제 활용 목표!: counter 객체 글로벌 키를 이용해서 counter 위젯 밖인 MyKey위젯에서 counter 변수와 increment 메소드에 접근하게 해보자!
1) MyKey 위젯의 기존의 자동생성 키 없애고 변수 생성
final counterKey = GlobalKey<_CounterState>();
> 제네릭스를 사용해서 bind 해줌
2) counter 위젯에 key 설정
child: Counter(
key: counterKey,
> 외부에서 마음대로counter state 객체 접근 가능해짐
3) floatingActionButton의 onPressed에 버튼 누르면 숫자 증가하도록 counter 이용
onPressed: (){
counterKey.currentState?.increment();
print(counterKey.currentState?.count);
},
(null safety 처리 안 해주면 빨간줄 뜸)
전체코드
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(primarySwatch: Colors.blue),
home: MyKey(),
);
}
}
class MyKey extends StatelessWidget {
final counterKey = GlobalKey<_CounterState>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Global key'),
),
body: Center(
child: Counter(
key: counterKey,
),
),
floatingActionButton: FloatingActionButton(
child: Icon(
Icons.add,
color: Colors.white,
),
onPressed: (){
counterKey.currentState?.increment();
print(counterKey.currentState?.count);
},
),
);
}
}
class Counter extends StatefulWidget {
const Counter({Key? key}) : super(key: key);
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int count = 0;
void increment() {
setState(() {
count++;
});
}
@override
Widget build(BuildContext context) {
return Center(
child: Text('Count number : $count',
style: TextStyle(
fontSize: 20.0
),),
);
}
}
예제2 기본코드 (scaffoldMessenger 참조)
Flutter 11 플러터 2.0 Snack bar와 ScaffoldMessenger, 버튼들(Elevated button, Text button, Outlined button)
순한맛 26 패치강좌 1 플러터 2.0 Snack bar와 ScaffoldMessenger - Why ScaffoldMessenger? 기존 Scaffold 위젯 ▲ Center 위젯 ▲ FlatButtond 위젯 ▲ Scaffold.of(Context) > Scaffold. 메소드가 의미하는 건 현재의 BuildContext에
kbagsa.tistory.com
scaffoldMessenger
- stateful 위젯으로 구성됨
- scaffoldMessengerKey라는 argument 있음
> scaffoldMessenger state가 존재한단 의미. 글로벌 키 이용해서 외부에서 scaffoldMessenger에 접근 가능하다는 의미
1) 제일 상단에 변수 생성
final rooScaffoldKey = GlobalKey<ScaffoldMessengerState>();
2) MaterialApp이 제공하는 rooScaffoldMessenger를 사용할 것이기 때문에 MaterialApp의 ScaffoldMessengerkey에 rooScaffoldKey를 전달해줌
scaffoldMessengerKey: rooScaffoldKey,
3) floatingActionButton의 onPressed 함수에 생성한 rooScaffoldKey를 불러오고 ScaffoldMessenger state에 currentState를 불러옴.
또한 ScaffoldMessenger state는 showSnackBar를 가지고 있어서 사용함
onPressed: () {
rooScaffoldKey.currentState?.showSnackBar(
SnackBar(content: Text('I like a new Snack Bar!'),
duration: Duration(seconds: 5),
action: SnackBarAction(
label: 'undo',
onPressed: (){
},
),
));
},
전체코드
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
final rooScaffoldKey = GlobalKey<ScaffoldMessengerState>();
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
scaffoldMessengerKey: rooScaffoldKey,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyPage(),
);
}
}
class MyPage extends StatelessWidget {
const MyPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ScaffoldMessenger'),
),
body: HomeBody(),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.thumb_up),
onPressed: () {
rooScaffoldKey.currentState?.showSnackBar(
SnackBar(content: Text('I like a new Snack Bar!'),
duration: Duration(seconds: 5),
action: SnackBarAction(
label: 'undo',
onPressed: (){
},
),
));
},
),
);
}
}
class HomeBody extends StatelessWidget {
const HomeBody({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: ElevatedButton(
child: Text('Go to the second page'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondPage()),
);
},
),
);
}
}
class SecondPage extends StatelessWidget {
const SecondPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Second Bar'),
),
body: Center(
child: Text('좋아요가 추가 되었습니다.',
style: TextStyle(
fontSize: 20.0,
color: Colors.redAccent,
),),
),
);
}
}
** 키에 대한 개념과 사용방법은 알겠는데 어디에 어떻게 넣고 어디 키에 생성한 키를 넣고 이런게 너무 헷갈려서 꼭 다시 공부하거나 영상 반복 시청할 것
'공부 > Flutter' 카테고리의 다른 글
Flutter 스터디 26 Firebase 연동 및 SignUp/SignIn 기능 구현 (0) | 2023.02.23 |
---|---|
Flutter 스터디 25 채팅 앱 UI 디자인, TextFormField validation 구현 (0) | 2023.02.20 |
Flutter 오류 Entrypoint isn't within the current project (0) | 2023.02.16 |
Flutter 스터디 23 날씨 앱 만들기 json 데이터 전달하기, UI 디자인, 데이터 연동 (0) | 2023.02.16 |
Flutter 스터디 22 날씨 앱 만들기 (widget lifecycle, API, Exception handling, Http pacage, Json parsing) (0) | 2023.02.16 |