routes 프로퍼티를 이용하면 일반적인 <a link>
처럼 매핑을 할 수 있다.
MaterialApp(
initialRoute: "/login",
routes:
"/login": (context) => LoginPage(),
"/home": (context) => HomePage()
},);
위처럼 라우팅을 하면 home: LoginPage(),
같은 고정된 페이지를 매핑시키지 않고 동적으로 매핑시킬수 있다.
코드 테스트할때 initialRoute: "/home",
로 바꾼다면 부분 수정이 아니므로 핫리로드 대신 에뮬레이터를 다시 실행해야 한다.
로그인 페이지를 Scaffold
로 만들었다고 하자
먼저 로고는 벡터이미지를 이용해서 그렸다.
벡터이미지를 사용하기 위해서는 의존성을 추가해야 한다.
Vector이미지를 이용하기 위해서 svg라이브러리를 이용한다.
https://pub.dev/ 으로 이동
svg 이동
dependencies:
flutter:
sdk: flutter
flutter_svg: ^2.0.4 # 이 녀석 추가
컴포넌트로 로고를 만든다.
class Logo extends StatelessWidget {
final title; // 변하지 않는 글자이기 때문에 String -> final로 변경
// Logo({required this.title ,Key? key}) : super(key: key); 선택적 매개변수를 사용한다면
Logo(this.title,{Key? key}) : super(key: key); // 하나만 변하게 하고 싶을때 시그니처 사용
Widget build(BuildContext context) {
return Column(
children: [
SvgPicture.asset("assets/logo.svg", height: 70, width: 70),
Text(title,
style: TextStyle(fontSize: 40, fontWeight: FontWeight.bold),)
],
);
}
}
<참고> 리스트의 위치나 순서를 바꾸기 위해서는 매개변수에 key
가 필요한데 아래코드를 참고하면 key
를 이용해서 순서를 제어한다.
ListView(
children: [
Text('Item 1', key: Key('item_1')),
Text('Item 2', key: Key('item_2')),
Text('Item 3', key: Key('item_3')),
],
onReorder: (oldIndex, newIndex) {
setState(() {
final item = _items.removeAt(oldIndex);
_items.insert(newIndex, item);
});
},
);
로그인 페이지를 간단하게 만든다.
build(BuildContext context) {
return Scaffold(
body: Container(
margin: EdgeInsets.all(30),
child: ListView(
children: [
Logo( 'Login'), // 컬럼은 기본 가운데
Form(
child: Column(
children: [
SizedBox(height: 10),
CustomTextFormField("email"),
SizedBox(height: 10),
CustomTextFormField("password", isObscure: true),// 밑에서 올라오는 키보드는 인셋이라고 한다.
SizedBox(height: 20),
TextButton(onPressed: (){},
child: Text("Login",
style: TextStyle(fontSize: 20),
)
),
],
),
),
],
),
),
);
}
Widget
TextFormField
를 이용해서 입력을 받는다면 자동적으로 아래에서 입력할수 있게 키보드가 올라오게 되는데 이때 올라오는 키보드의 영역을 keyboard overflow
라고 한다.
위에서는 ListView
를 이용해서 동적으로 화면을 움직이게 만들었지만 Column
으로 화면을 고정시켜서 영역을 계산하지 않고 만든다면 overflow
가 올라올때 아래처럼 오버플로우에러가 발생하게 된다.
CustomTextFormField
를 만들어서 데이터를 입력받게 만들었다
class CustomTextFormField extends StatelessWidget {
final title;
// final bool? isObscure; -> bool타입은 `?` 안붙이면 오류발생한다.
final isObscure; // 선택적 매개변수의 디폴트값 false로 지정해 `?` 제거함
const CustomTextFormField(this.title, {this.isObscure = false, Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title),
TextFormField(
validator: (value) => // validator 가 입력된 모든 데이터를 가져옴
value!.isEmpty ? "Please enter some text" : null, // 유효성 통과하면 null 리턴
obscureText: isObscure,
decoration: MyInputDecoration(
"Enter $title" // 변수 하나만 바인딩 한다면 중괄호가 굳이 필요없다.
),
),
],
);
}
}
class MyInputDecoration extends InputDecoration{
MyInputDecoration(String hint): super(
hintText: hint,
// 첫번째 보더 옵션
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(20)))
// 위 옵션을 사용하거나 아래 옵션을 사용
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(20)
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
),
);
}
InputDecoration
는 주로 TextFormField
를 스타일링하는데 사용한다.
obscureText: true,
프로퍼티는 비밀번호를 마스킹해서 *
로 표시해준다.
validator
는 입력된 필드의 유효성을 검사해주는 함수다.
해당 폼 위젯이 submit 되거나 포커스를 잃게 되면 발동한다.
validator
는 String?
을 리턴해야 하는데 만약 null을 리턴한다면 유효성 검사 통과를 의미한다.
삼항연산자를 사용해서 필드가 비었다면 문구를 표현하게 만들었다.
데이터가 유효 하다면 TextFormField
의 onSaved
함수를 호출해서 데이터를 저장하고 서버로 날릴 준비를 하면 된다.
간단하게 버튼을 눌렀을때 유효성 검사를 하려면 GlobalKey
를 로그인 필드에 추가한다.
GlobalKey
는 위젯트리내에서 전역적으로 고유한 키를 생성해주고 GlobalKey<FormState>()
는 폼 위젯의 키를 준다.
final _formKey = GlobalKey<FormState>(); // 폼의 상태를 확인 -> const 제거
키를 이용해서 Form
을 약간 수정한다.
Form(
key: _formKey, // 생성한 final 글로벌 키를 연결한다.
child: Column(
children: [
// 생략
TextButton(onPressed: (){
if(_formKey.currentState!.validate()) {
Navigator.pushNamed(context, "/home"); }
// 생략
validate()
를 호출할때 currentState
가 null 이라면 NullThrownError
가 발생할수 있기 때문에 컴파일러가 경고를 한다.
!
를 붙여서 null이 아님을 컴파일러에게 알려준다.
글로벌키를 이용해서 각각의 폼의 유효성결과가 모두 유효할때 _formKey.currentState!.validate()
가 true가 되어서 다음 로직을 수행하게 된다.
Navigator.pushNamed(context, "/home");
을 사용해서 홈으로 넘어가게 만들었다.
로그인을 성공하면 이전화면으로 돌아가는 경우는 없기 때문에 이전 화면은 날려서 메모리를 관리하자.
입력을 하지 않으면 텍스트필드의 validator
가 String을 리턴하게 되고 폼필드 아래에 해당 메세지가 나오게 된다.
유효성 검사가 모두 통과 한다면