Flutter에서 서버 데이터로 동적 TextField 관리하기 : 개선된 사용자 입력 솔루션
2024. 4. 2. 21:17ㆍ프로그래밍/Flutter
문제 상황
- 서버로부터 랜덤한 갯수의 데이터를 받아서 ListView.Builder를 통해 만들어진 TextField의 값으로 넣어야 하는 상황으로
2가지 문제가 있다- TextField를 TextFormField로 바꾸고 initialValue로 사용시 처음 출력은 잘 되지만 해당 리스트를 삭제했을 때 문제가 발생한다 (데이터는 변경이 되지만 초기 값이라서 화면단에 적용이 안됨)
- 각 TextField를 TextEditingController로 관리하자니 랜덤한 갯수의 데이터를 받기 때문에 모두 준비할 수가 없다
해결 방안
- TextField를 Stack위젯으로 감싸고 유저가 상호작용할 수 있는 Container를 TextField위젯의 위에 배치한다
- String형태의 flag변수를 하나 선언하고 기본값으로 ‘default’를 할당한다
- 탭이벤트가 발생하변 해당 List의 index를 String형태로 flag변수에 할당한다
- TextField의 index가 flag변수와 동일하다면 화면에 보여주도록 하고 TextField의 autofocus를 true로 설정한다
- TextField의 값을 변경하고 onSubmitted 이벤트가 발생하면 해당 값을 저장한다
코드
//...
List<Map<String, dynamic>> exampleList = [...] // 임의의 리스트
String selectedInterestAreaTextFieldFlag = 'default'; // flag 변수
//...
ListView.builder(
itemCount: exampleList.length,
itemBuilder: (BuildContext context, int index) {
//...
Stack(
children: [
//* Container
GestureDetector(
onTap: () {
setState(() {
selectedInterestAreaTextFieldFlag = index.toString();
});
},
child: Container(
width: 128.w,
height: 44.w,
margin: EdgeInsets.only(left: 8.w),
padding: EdgeInsets.only(left: 12.w),
decoration: BoxDecoration(
borderRadius:BorderRadius.circular(25),
border: Border.all(
color: Colors.black,
width: 1
),
color: Colors.grey,
),
child: Align(
alignment: Alignment.centerLeft,
child: Text(examplelist[index]['name'],
style: TextStyle(
fontSize: 13,
color: // 조건에 따라 값이 있으면 검정색으로, 없다면 회색으로
),
),
)
),
),
//* Container의 탭이벤트가 발생하면 나오는 텍스트 필드
if(selectedInterestAreaTextFieldFlag==index.toString())
Container(
width: 128.w,
height: 44.w,
margin: EdgeInsets.only(left: 8.w),
decoration: BoxDecoration(
borderRadius:BorderRadius.circular(25),
border: Border.all(
color: Colors.black,
width: 1
),
color: Colors.grey,
),
child: TextField(
magnifierConfiguration: TextMagnifierConfiguration.disabled,
autocorrect: false,
autofocus: true,
maxLines: 1,
maxLength: 10,
style: TextStyle(
fontSize:13
),
decoration: InputDecoration(
hintText: // 힌트 텍스트
hintStyle: TextStyle(
fontSize: 13,
fontFamily: "NotoSans",
color: Colors.grey
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(22),
borderSide: BorderSide(color: Colors.grey)
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(22),
borderSide: BorderSide(color: Colors.grey)
),
filled: true,
fillColor: Colors.white,
contentPadding: EdgeInsets.fromLTRB(15,15,15,15),
counterText: ''
),
onSubmitted: (value) {
setState(() {
selectedInterestAreaTextFieldFlag = 'default'; // flag변수 초기화
});
examplelist[index]['name'] = value; // 값 변경
},
),
),
],
)
}
)
위의 코드는 다음과 같이 성능 및 사용성 개선할 수 있다
- Stack 내부에서 if문으로 Container가 조건부 렌더링하는데 별도의 함수나 위젯으로 분리하면 가독성이 좋아질 수 있다
- ListView.builder가 재빌드 될 때마다 모든 Stack이 재계산되므로 성능을 고려해 좀 더 효율적인 상태 관리 방법을 찾을 수 있다
- 사용자가 잘못된 입력을 한 경우나 네트워크 오류 등의 예외 상황에 대한 처리가 필요하다
- 이런 패턴이 여러곳에서 사용될 경우를 위해 재사용 가능한 위젯으로 수정할 수 있다
해당 개선내용을 적용한 코드는 아래에 작성한다
// 별도의 위젯으로 분리
class EditableTextField extends StatelessWidget {
final int index;
final bool isSelected;
final String text;
final Function(String) onSubmitted;
EditableTextField({
required this.index,
required this.isSelected,
required this.text,
required this.onSubmitted,
});
@override
Widget build(BuildContext context) {
return Stack(
children: [
GestureDetector(
onTap: () {
// 상태 변경 로직
setState(() {
selectedInterestAreaTextFieldFlag = index.toString();
});
},
child: Container(
// Container 설정
width: 128.w,
height: 44.w,
margin: EdgeInsets.only(left: 8.w),
padding: EdgeInsets.only(left: 12.w),
decoration: BoxDecoration(
borderRadius:BorderRadius.circular(25),
border: Border.all(
color: Colors.black,
width: 1
),
color: Colors.grey,
),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
text,
style: TextStyle(fontSize: 13, color: Colors.black),
),
),
),
),
Visibility(
visible: isSelected,
maintainState: true,
child: TextField(
// TextField 설정
magnifierConfiguration: TextMagnifierConfiguration.disabled,
autocorrect: false,
autofocus: true,
maxLines: 1,
maxLength: 10,
style: TextStyle(
fontSize:13
),
decoration: InputDecoration(
hintText: // 힌트 텍스트
hintStyle: TextStyle(
fontSize: 13,
fontFamily: "NotoSans",
color: Colors.grey
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(22),
borderSide: BorderSide(color: Colors.grey)
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(22),
borderSide: BorderSide(color: Colors.grey)
),
filled: true,
fillColor: Colors.white,
contentPadding: EdgeInsets.fromLTRB(15,15,15,15),
counterText: ''
),
onSubmitted: onSubmitted,
),
),
],
);
}
}
// ListView.builder에서 사용
ListView.builder(
itemCount: exampleList.length,
itemBuilder: (BuildContext context, int index) {
bool isSelected = selectedInterestAreaTextFieldFlag == index.toString();
String text = exampleList[index]['name'];
return EditableTextField(
index: index,
isSelected: isSelected,
text: text,
onSubmitted: (value) {
// 값 변경 처리
setState(() {
selectedInterestAreaTextFieldFlag = 'default';
exampleList[index]['name'] = value;
});
},
);
},
)
'프로그래밍 > Flutter' 카테고리의 다른 글
참조에 의한 할당 (Reference Assignment) 문제 (Flutter) (0) | 2024.04.01 |
---|