Design-loving front-end engineer
Ryong
Design-loving front-end engineer
전체 방문자
오늘
어제
    • Framework
    • React
      • Concept
      • Library
      • Hook
      • Component
      • Test
    • NodeJS
    • Android
      • Concept
      • Code
      • Sunflower
      • Etc
    • Flutter
      • Concept
      • Package
    • Web
    • Web
    • CSS
    • Language
    • JavaScript
    • TypeScript
    • Kotlin
    • Dart
    • Algorithm
    • Data Structure
    • Programmers
    • Management
    • Git
    • Editor
    • VSCode
    • Knowledge
    • Voice
Design-loving front-end engineer

Ryong

[ Flutter ] 화면 이동
Flutter/Concept

[ Flutter ] 화면 이동

2021. 12. 29. 22:17

새로운 화면으로 이동

💬  화면 전환을 하는 네비게이션 앱을 만들기 위해 기반이 되는 소스 코드를 작성하자.

더보기
더보기
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
    'Flutter/Concept' 카테고리의 다른 글
    • [ Flutter ] HTTP 통신
    • [ Flutter ] Web 배포 using GitHub
    • [ Flutter ] 쿠퍼티노 디자인
    • [ Flutter ] 애니메이션
    Design-loving front-end engineer
    Design-loving front-end engineer
    디자인에 관심이 많은 모바일 앱 엔지니어 Ryong입니다.

    티스토리툴바