나는 이렇게 논다/Flutter 로 간단한 노트 앱을 만들어보자

Flutter로 간단한 노트 앱을 만들어보자 ② - 하단 네비게이션 바 구현

daco2020 2024. 10. 30. 15:03
반응형

지난 글에서 Provider 패키지를 이용해 노트를 쓰고 저장하고, 목록을 조회하는 간단한 노트 앱을 만들었습니다. 하지만 노트를 쓰고 '노트 조회' 화면으로 이동하면 다시 '노트 쓰기' 화면으로 돌아갈 수 없었죠.

 

이번 글에서는 하단 네비게이션 바를 추가하여, '노트 쓰기', '노트 조회', '설정' 화면을 언제든지 이동할 수 있도록 구현해 보겠습니다. 아래 이미지는 네비게이션 바를 추가한 결과 화면입니다. 

 

 

 

이런 네비게이션 바를 어떻게 만들 수 있는지, 구현 과정을 하나씩 살펴보겠습니다. 

 

 

1. 하단 네비게이션 바 컴포넌트 구성

네비게이션 바는 앱의 공통 UI 요소이므로 어느 하나의 view 에 속하지 않고. lib/components/bottom_navbar.dart 파일에 별도의 컴포넌트로 정의했습니다. 

// lib/components/bottom_navbar.dart
import 'package:flutter/material.dart';

class BottomNavBar extends StatelessWidget {
  final int currentIndex;
  final Function(int) onTabTapped;

  const BottomNavBar({
    Key? key,
    required this.currentIndex,
    required this.onTabTapped,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BottomNavigationBar(
      backgroundColor: Colors.black,
      selectedItemColor: Colors.white,
      unselectedItemColor: Colors.grey,
      currentIndex: currentIndex,
      onTap: onTabTapped,
      items: [
        BottomNavigationBarItem(
          icon: Icon(Icons.create_outlined),
          label: '노트 쓰기',
        ),
        BottomNavigationBarItem(
          icon: Icon(Icons.list_alt_outlined),
          label: '노트 조회',
        ),
        BottomNavigationBarItem(
          icon: Icon(Icons.settings_outlined),
          label: '설정',
        ),
      ],
    );
  }
}

 

BottomNavBar 컴포넌트는 현재 활성화된 탭의 인덱스를 currentIndex 매개변수로 받고, 탭이 선택될 때 onTabTapped 콜백 함수로 해당 탭의 인덱스를 전달하여 업데이트할 수 있도록 구성했습니다. 

items 배열에 있는 BottomNavigationBarItem 은 네비게이션에 표시되는 아이콘과 라벨을 정의합니다.

 

 

 

2. HomeScreen에서 네비게이션과 화면 전환 구현

이제 lib/main.dart에서 각 탭의 화면 전환을 처리하는 HomeScreen을 추가해보겠습니다. 

// lib/main.dart
import 'package:flutter/material.dart';
import 'components/bottom_navbar.dart';
import 'views/note_detail_view.dart';
import 'views/note_list_view.dart';
import 'views/settings_view.dart';

final GlobalKey<_HomeScreenState> homeScreenKey = GlobalKey<_HomeScreenState>();

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomeScreen(key: homeScreenKey),
      theme: ThemeData.dark(),
    );
  }
}

class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  int _currentIndex = 0;

  final List<Widget> _screens = [
    NoteDetailView(),
    NoteListView(),
    SettingsView(),
  ];

  void onTabTapped(int index) {
    setState(() {
      _currentIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _screens[_currentIndex],
      bottomNavigationBar: BottomNavBar(
        currentIndex: _currentIndex,
        onTabTapped: onTabTapped,
      ),
    );
  }
}
  • 화면 배열(_screens) 구성: _screens 배열에 '노트 쓰기', '노트 조회', '설정' 화면을 순서대로 넣었습니다. _currentIndex 값에 따라 현재 화면이 결정되고 인덱스에 맞는 화면을 보여줍니다.
  • 탭 변경 함수(onTabTapped): onTabTapped 함수에서는 setState를 통해 _currentIndex를 업데이트하면서 화면을 전환할 수 있습니다. 이 함수가 실행되면, _screens 배열에서 새로운 _currentIndex 값에 해당하는 화면이 렌더링되는거죠.
  • 네비게이션 바 컴포넌트 적용: 하단 네비게이션 바는 앞서 구현한 BottomNavBar 컴포넌트를 넣어줍니다. BottomNavBar는 currentIndexonTabTapped 함수를 받아서 처리합니다.

*코드 상단의 homeScreenKey는 다른 곳에서 HomeScreen 상태에 접근하거나 조작할 때 사용하기 위한 키로, 아래에서 더 자세히 설명하겠습니다.

 

이렇게 수정한 코드가 정상적으로 돌아가는지 보겠습니다.

 

 

 

영상을 보시면 하단 네비게이션 바의 탭을 누르면 각각의 화면으로 전환되는 것을 볼 수 있습니다.

 

 

 

3. 노트 저장 후 화면 전환하기

지난 글에서 우리는 노트를 쓴 다음 저장을 할 때 '노트 조회' 화면으로 이동시켰습니다. 이를 위해 위에서 언급한 homeScreenKey 를 활용하겠습니다. homeScreenKey 는 HomeScreen 의 상태에 접근해 어디서든 불러와 사용할 수 있도록 도와줍니다.

 

'노트 쓰기' 화면을 정의한lib/views/note_detail_view.dart에서 노트 저장 로직에 homeScreenKey를 적용해 보겠습니다.

// lib/views/note_detail_view.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:ttingnote/main.dart';
import '../services/note_service.dart';

class NoteDetailView extends StatefulWidget {
  const NoteDetailView({Key? key}) : super(key: key);

  @override
  _NoteDetailViewState createState() => _NoteDetailViewState();
}

class _NoteDetailViewState extends State<NoteDetailView> {
  final TextEditingController _controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('노트 쓰기')),
      body: Column(
        children: [
          TextField(controller: _controller),
          ElevatedButton(
            onPressed: () async {
              if (_controller.text.isNotEmpty) {
                await Provider.of<NoteService>(context, listen: false)
                    .addNote(_controller.text);
                // 저장 후 '노트 조회' 화면으로 이동
                homeScreenKey.currentState?.onTabTapped(1);
              }
            },
            child: Text('저장'),
          ),
        ],
      ),
    );
  }
}

 

기존 화면 전환 로직은 Navigator 를 사용했었습니다.

Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => NoteListView()));

 

이번에 수정한 코드는 다음처럼 homeScreenKey 를 사용합니다. 

homeScreenKey.currentState?.onTabTapped(1);

 

바뀐 코드에서는 onTabTapped(1) 처럼 인덱스 1을 넣으므로 배열의 두 번째 요소인 '노트 조회' 화면으로 이동하게 됩니다.

 

 

마무리

이번 글에서는 하단 네비게이션 바를 만들고, 인덱스(미리 정의된 배열의 순서에 따라 화면을 선택)를 사용한 화면 전환 방식으로 탭을 구현했습니다. 하지만 이런 방식은 화면이 많아지고 앱이 복잡해지만 식별이 어려워지고 유지보수에 한계가 있습니다.

 

다음 글에서는 EnumGoRouter를 활용한 탭 전환 방식으로 리팩터링을 해보겠습니다. Enum을 사용하면 각 화면에 의미 있는 이름을 부여해 코드 가독성을 높일 수 있고, GoRouter를 통해 라우팅 관리를 쉽고 유연하게 관리할 수 있습니다. 이를 통해 새로운 화면을 추가해도 손쉽게 대응할 수 있도록 개선해보겠습니다.

 

[Flutter로 간단한 노트 앱을 만들어보자] 시리즈는 직접 독학으로 하나씩 만들어나가는 과정이므로 틀리거나 부족한 내용이 있을 수 있습니다. 조언과 피드백을 댓글로 남겨주시면 적극 반영하겠습니다. 감사합니다.

 

반응형