새로운 화면으로 이동
💬 화면 전환을 하는 네비게이션 앱을 만들기 위해 기반이 되는 소스 코드를 작성하자.
import 'package:flutter/material.dart';
// 앱 시작 부분
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: FirstPage(),
);
}
}
// 첫 페이지
class FirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First'),
),
body: ElevatedButton(
child: Text('다음 페이지로'),
onPressed: () { },
)
);
}
}
// 두 번째 페이지
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Second'),
),
body: ElevatedButton(
child: Text('이전 페이지로'),
onPressed: () { },
),
);
}
}
파일 분할 및 임포트 방법
💬 main.dart 안에 여러 페이지를 작성할 수도 있지만, 유지보수를 위해서는 코드를 여러 파일에 분리하는 것이 좋다.
💬 파일을 분리할 경우에는 임포트하여 다른 파일에 있는 클래스를 사용할 수 있다.
💬 예를 들어 first_project 프로젝트의 sub.dart 파일을 import 하면 아래와 같다.
import 'package:first_project/sub.dart';
push로 새로운 화면 호출
Navigator.push(
context,
MaterialPageRoute(builder: (context) => [이동할 페이지]),
);
💬 머티리얼 디자인으로 작성된 페이지 사이에 화면 전환을 할 때 사용된다.
💬 builder 프로퍼티에 이동할 페이지를 나타내는 함수를 작성하며, BuildContext 타입은 타입 추론에 의해 생략이 가능하다.
💬 FirstPage의 버튼을 눌렀을 때 SecondPage로 이동하는 코드
class FirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First'),
),
body: ElevatedButton(
child: Text('다음 페이지로'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondPage()),
);
},
)
);
}
}
💬 화면을 이동하면 자동으로 AppBar의 leading 영역에 뒤로 가기 아이콘이 생성되는 것을 볼 수 있다.
pop으로 이전 화면으로 이동
💬 Navigator.pop() 메서드로 현재 화면을 종료하고 이전 화면으로 돌아갈 수 있다.
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Second'),
),
body: ElevatedButton(
child: Text('이전 페이지로'),
onPressed: () {
Navigator.pop(context); // 현재 화면을 종료하고 이전 화면으로 돌아가기
},
),
);
}
}
새로운 화면에 값 전달하기
💬 새로운 화면을 표시하면서 데이터도 함께 전달해보자.
💬 이름과 나이 프로퍼티를 갖는 Person 클래스를 정의한다.
class Person {
String name;
int age;
Person(this.name, this.age);
}
class FirstPage extends StatelessWidget {...}
class SecondPage extends StatelessWidget {...}
💬 FirstPage 클래스의 버튼 클릭 이벤트 부분을 아래와 같이 수정한다.
onPressed: () {
final person = Person('홍길동', 20);
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondPage(person: person)),
);
},
💬 SecondPage 클래스에서 Person 객체를 받을 수 있도록 아래와 같이 수정한다.
class SecondPage extends StatelessWidget {
final Person person;
SecondPage({required this.person});
@override
Widget build(BuildContext context) {
return ...
}
}
◽ required를 붙이면 필수 입력 인수를 나타낸다. SecondPage 클래스의 생성자는 Person 객체를 반드시 받아야 한다.
이전 화면으로 데이터 돌려주기
💬 Navigator.push() 메서드와 Navigator.pop() 메서드를 조금 수정하면 SecondPage 클래스에서 FirstPage 클래스로 데이터를 돌려줄 수 있다.
💬 우선, SecondPage 클래스에서 FirstPage 클래스로 데이터를 돌려주는 코드이다.
Navigator.pop(context, 'ok');
💬 다음으로, FirstPage 클래스에서 데이터를 돌려받는 부분에 대한 코드이다.
onPressed: () async {
final person = Person('홍길동', 20);
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondPage(person: person)),
);
print(result);
}
◽ push() 메서드는 Future 타입의 반환 타입을 갖는다.
◽ Future는 미래에 값이 들어올 것을 나타내는 클래스이다.
◽ Future 값을 반환받으려면 아래의 두 가지 조치를 해야 한다.
◽ await 키워드를 메서드 실행 앞에 추가한다.
◽ await 키워드를 사용하는 메서드의 인수와 함수 본문 사이에 async 키워드를 추가한다.
◽ 위의 코드는 push() 메서드가 어떤 값을 반환할 때까지 기다리게 하며, 반환값을 기다리는 동안 앱이 멈추지 않는다.
◽ 나중에 값이 들어오면 그 값이 result에 담긴 후 바로 print 문이 실행된다.
◽ 이렇게 어떤 일이 끝날 때까지 기다리면서 앱이 멈추지 않도록 하는 방식을 비동기 방식이라고 한다.
routes를 활용한 네비게이션
routes 정의
💬 페이지를 이동할 때마다 직접 이동할 페이지를 클래스명으로 작성했었지만 이는 긴 코드 작성이 필요하다.
💬 routes를 활용한 네비게이션을 사용하면 간결하고 체계적인 구성이 가능하다.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: FirstPage(), // 첫 페이지를 시작 페이지로 지정
routes: {
'/first': (context) => FirstPage(),
'/second': (context) => SecondPage(),
},
);
}
}
◽ routes 프로퍼티에 Map 형태로 문자열과 목적지 인스턴스를 작성하면 된다.
◽ '/first'는 FirstPage 클래스로, '/second'는 SecondPage 클래스로 연결되도록 정의했다.
◽ 문자열에서 '/'을 사용한 이유는 페이지가 많아졌을 때 구조화하기 용이하기 때문이다.
routes에 의한 화면 이동
💬 기존의 push() 메서드 대신 pushNamed() 메서드를 사용하면 화면 네비게이션을 실행시킬 수 있다.
💬 FirstPage 클래스의 화면 이동 부분을 아래와 같이 수정할 수 있다.
onPressed(): () async {
final result = await Navigator.pushNamed(context, '/second');
print(result);
}
네비게이션 동작 방식의 이해
💬 push()와 pop()으로 전환되는 화면들은 스택 구조로 메모리에 쌓이게 된다.
💬 스택에서 모든 화면이 제거되면 앱이 종료된다.
💬 StatelessWidget 클래스와 StatefulWidget 클래스의 동작 방법 차이를 이해해보자.
StatelessWidget 클래스 동작
💬 build() 메서드가 언제 호출되는지 확인하기 위하여 기존 코드에 print() 함수를 사용하여 로그를 작성해보자.
class FirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('FirstPage build()');
return Scaffold(...);
}
}
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('SecondPage build()');
return Scaffold(...);
}
}
💬 [ 앱 실행 ] > [ push로 두 번째 페이지 표시 ] > [ pop으로 ok 값을 가지고 돌아가기 ]를 실행한 로그는 아래와 같다.
I/flutter ( 7931): FirstPage build() // 앱 실행
I/flutter ( 7931): SecondPage build() // push로 두 번째 페이지 표시
I/flutter ( 7931): ok // pop으로 ok 값을 가지고 돌아가기
StatefulWidget 클래스 동작
💬 같은 방식으로 build() 메서드가 언제 호출되는지 클래스를 작성하고 print() 로그를 작성해보자.
class FirstStatefulPage extends StatefulWidget {
@override
_FirstStatefulPageState createState() => _FirstStatefulPageState();
}
class _FirstStatefulPageState extends State<FirstStatefulPage> {
@override
Widget build(BuildContext context) {
print('FirstPage build()');
return Scaffold(
appBar: AppBar(
title: Text('First'),
),
body: ElevatedButton(
child: Text('다음 페이지로'),
onPressed: () {
final person = Person('홍길동', 20);
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondStatefulPage()),
);
},
),
);
}
}
class SecondStatefulPage extends StatefulWidget {
@override
_SecondStatefulPageState createState() => _SecondStatefulPageState();
}
class _SecondStatefulPageState extends State<SecondStatefulPage> {
@override
Widget build(BuildContext context) {
print('SecondPage build()');
return Scaffold(
appBar: AppBar(
title: Text('Second'),
),
body: ElevatedButton(
child: Text('이전 페이지로'),
onPressed: () {
Navigator.pop(context);
},
),
);
}
}
💬 [ 앱 실행 ] > [ push로 두 번째 페이지 표시 ] > [ pop으로 이전 페이지로 돌아가기 ]를 실행한 로그는 아래와 같다.
// FirstPage 표시
I/flutter ( 1629): FirstPage build()
// SecondPage 표시
I/flutter ( 1629): SecondPage build()
// FirstPage로 돌아가기
// 앱 종료
💬 StatefulWidget 클래스의 build() 메서드에서는 앱 성능에 지장에 줄만한 코드를 작성하면 안 된다.
initState, dispose
💬 StatefulWidget 클래스에는 build() 메서드 외에도 특정 타이밍에 실행되는 여러 메서드가 있으며, 이를 생명주기 메서드라고 한다.
◽ initState() 메서드는 위젯이 생성될 때 호출된다.
◽ dispose() 메서드는 위젯이 완전히 종료될 때 (pop될 때) 호출된다.
class _FirstStatefulPageState extends State<FirstStatefulPage> {
@override
void initState() {
super.initState();
print('FirstPage initState()');
}
@override
void dispose() {
super.dispose();
print('FirstPage dispose()');
}
@override
Widget build(BuildContext context) {
print('FirstPage build()');
return ...
}
}
class _SecondStatefulPageState extends State<SecondStatefulPage> {
@override
void initState() {
super.initState();
print('SecondPage initState()');
}
@override
void dispose() {
super.dispose();
print('SecondPage dispose()');
}
@override
Widget build(BuildContext context) {
print('SecondPage build()');
return ...
}
}
💬 [ FirstPage ] > [ SecondPage ] > [ 뒤로 (pop) ] > [ 뒤로 (pop) ]를 실행한 로그는 아래와 같다.
// FirstPage 표시
I/flutter ( 1629): FirstPage initState()
I/flutter ( 1629): FirstPage build()
// SecondPage 표시
I/flutter ( 1629): SecondPage initState()
I/flutter ( 1629): SecondPage build()
// Firstpage로 돌아가기
I/flutter ( 1629): SecondPage dispose()
// 앱 종료
I/flutter ( 1629): FirstPage dispose()
💬 로그대로라면 build() 메서드에서 복잡한 처리나 네트워크 요청 등을 하면 안 되는 것을 쉽게 알 수 있으며, 이들은 initState() 메서드에서 수행해야 한다.
'Flutter > Concept' 카테고리의 다른 글
[ Flutter ] HTTP 통신 (0) | 2022.01.28 |
---|---|
[ Flutter ] Web 배포 using GitHub (0) | 2022.01.07 |
[ Flutter ] 쿠퍼티노 디자인 (0) | 2021.12.29 |
[ Flutter ] 애니메이션 (0) | 2021.12.29 |
[ Flutter ] 다이얼로그 (0) | 2021.12.29 |