Flutter

Flutter Day 6-3: Flutter profile app (3)

@leem 2025. 1. 13. 17:35

프로필 앱 미리보기

이번 포스팅은 Flutter Day 5-3와 이어지는 글로 프로필 앱을 완성해보겠습니다.
ProfileHeader, ProfileCountInfo, ProfileButtons, ProfileTab와 같이 컴포넌트를 분리하고,
반복되는 UI 요소를 공통 함수로 만들어 활용하는 방법에 대해 다룹니다.

공통 함수 분리

Flutter에서는 반복되는 코드를 함수로 분리하면 유지보수성과 재사용성이 높아집니다.

이번 프로필 앱에서는 _buildInfo, _buildCountInfo와 같은 함수들을 재사용 가능한 형태리팩토링할 수 있습니다.

ProfileCountInfo

게시글 수, 좋아요 수, 공유 수를 보여주는 컴포넌트입니다.

Row와 Column을 조합해서 간단히 구성할 수 있지만, 각 정보를 빌드하는 로직을 함수로 분리해 반복을 줄였습니다.

 

리팩토링된 코드:

 
import 'package:flutter/material.dart';

class ProfileCountInfo extends StatelessWidget {
  const ProfileCountInfo({super.key});

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceAround,
      children: [
        _buildInfo('50', 'Posts'),
        _buildDivider(),
        _buildInfo('10', 'Likes'),
        _buildDivider(),
        _buildInfo('3', 'Share'),
      ],
    );
  }

  // 1. 구분선 Divider를 생성하는 함수
  Widget _buildDivider({double width = 2, double height = 60, Color color = Colors.blue}) {
    return Container(
      width: width,
      height: height,
      color: color,
    );
  }

  // 2. 정보 텍스트를 생성하는 함수
  Widget _buildInfo(String count, String title) {
    return Column(
      children: [
        Text(count, style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold)),
        const SizedBox(height: 5),
        Text(title, style: TextStyle(fontSize: 15)),
      ],
    );
  }
}

ProfileHeader

프로필의 핵심 정보(사진, 이름, 직업 등)를 보여주는 컴포넌트입니다.

여기서도 아바타 이미지 생성 함수를 공통으로 만들어 재사용 가능하게 수정했습니다.

 

리팩토링된 코드:

import 'package:flutter/material.dart';

class ProfileHeader extends StatelessWidget {
  const ProfileHeader({super.key});

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        const SizedBox(width: 20),
        _buildHeaderAvatar(imagePath: 'assets/avatar.png', size: 100),
        const SizedBox(width: 20),
        Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              '홍길동',
              style: TextStyle(
                fontSize: 25,
                fontWeight: FontWeight.w700,
              ),
            ),
            Text(
              '프로그래머/작가',
              style: TextStyle(
                fontSize: 20,
                fontWeight: FontWeight.w700,
              ),
            ),
            Text(
              '데어 프로그래밍',
              style: TextStyle(
                fontSize: 15,
                fontWeight: FontWeight.w700,
              ),
            ),
          ],
        )
      ],
    );
  }

  // 공통 아바타 생성 함수
  Widget _buildHeaderAvatar({required String imagePath, double size = 100}) {
    return SizedBox(
      width: size,
      height: size,
      child: CircleAvatar(
        backgroundImage: AssetImage(imagePath),
      ),
    );
  }
}

ProfilePage

ProfileHeader와 ProfileCountInfo를 포함해 버튼, 탭바 등을 조합해 만든 프로필 페이지입니다.

페이지의 레이아웃은 Column을 사용해 수직으로 배치하고, 필요한 경우 Expanded로 공간을 유연하게 사용했습니다.

리팩토링된 코드:

import 'package:flutter/material.dart';
import '../components/profile_tab.dart';
import '../components/profile_buttons.dart';
import '../components/profile_count_info.dart';
import '../components/profile_header.dart';

class ProfilePage extends StatelessWidget {
  const ProfilePage({super.key});

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        appBar: AppBar(
          centerTitle: true,
          title: Text('Profile'),
        ),
        endDrawer: Container(
          width: 200,
          color: Colors.blue,
        ),
        body: Column(
          children: [
            const SizedBox(height: 20),
            ProfileHeader(),
            const SizedBox(height: 20),
            ProfileCountInfo(),
            const SizedBox(height: 20),
            ProfileButtons(),
            Expanded(child: ProfileTab()),
          ],
        ),
      ),
    );
  }
}

중요 개념

1. StatelessWidget

  • 변하지 않는 UI를 정의할 때 사용하는 위젯입니다.
  • 모든 컴포넌트(ProfileHeader, ProfileCountInfo 등)는 StatelessWidget을 상속받아 선언되었습니다.

2. Column과 Row

  • Column: 수직으로 위젯을 나열합니다.
  • Row: 수평으로 위젯을 나열합니다.
  • mainAxisAlignment와 crossAxisAlignment로 정렬 방식을 지정할 수 있습니다.

3. CircleAvatar

  • 원형 이미지를 만들 때 사용하는 위젯입니다.
  • backgroundImage를 사용해 프로필 사진을 표시했습니다.

4. SizedBox

  • 간격을 추가하거나 위젯의 크기를 제한할 때 사용합니다.
  • const SizedBox(height: 20)로 컴포넌트 간 간격을 조정했습니다.

5. SafeArea

  • 기기의 노치 영역이나 시스템 UI 영역을 피해 안전한 공간에 UI를 배치합니다.

 

이 코드를 실행하면 아래와 같은 구조로 프로필 화면이 표시됩니다

  1. 프로필 헤더: 프로필 사진, 이름, 직업 정보.
  2. 정보 섹션: 게시물 수, 좋아요 수, 공유 수 등.
  3. 버튼 섹션: 팔로우 버튼이나 편집 버튼.
  4. 탭 섹션: 상세 정보 탭.