Flutter

Flutter Day 14: 블로그 만들기 - 프로젝트 기본 설정, 로그인 UI 구성

@leem 2025. 1. 31. 13:58
Flutter에서 로그인 기능을 구현하려면, 먼저 프로젝트를 설정하고 기본적인 UI를 만들어야 합니다.
이 글에서는 프로젝트 설정 → 기본 UI 구성 → 로그인 화면 구현 → 입력값 검증 → UI 디테일 보완까지
차근차근 정리해보겠습니다.

 

1. 프로젝트 기본 설정

Flutter 프로젝트에서 사용할 필수 패키지를 추가합니다.

pubspec.yaml 수정

 
dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.8
  flutter_svg: ^2.0.6
  intl: ^0.18.1
  dio: ^5.2.0 # 서버와 통신하는 HTTP 클라이언트
  flutter_riverpod: ^2.3.6 # 상태 관리를 위한 Riverpod
  logger: ^1.3.0 # 콘솔 로그 출력용 라이브러리
  flutter_secure_storage: ^8.0.0 # JWT 토큰을 저장하는 보안 라이브러리
  pull_to_refresh: ^2.0.0 # 화면 새로고침 기능
  • dio → 서버와 데이터를 주고받을 때 사용 (API 호출)
  • flutter_riverpod → 앱의 전역 상태를 관리하는 라이브러리
  • flutter_secure_storage → JWT 토큰 저장 등 보안이 필요한 데이터 저장
  • logger → 콘솔에 출력할 때 사용 (디버깅에 유용함)

 


2. 프로젝트 코드 스타일 유지 (Linter 설정)

Linter는 코드 스타일을 검사하고 오류를 사전에 방지하는 역할을 합니다.

linter:
  rules:
    prefer_const_constructors: false
    prefer_const_declarations: false
    prefer_const_literals_to_create_immutables: false

Linter는 코드 스타일을 체크하고 버그 가능성이 있는 코드를 경고해주는 도구입니다.


Dart에서는 linter 설정을 통해 프로젝트의 코딩 스타일을 통일할 수 있습니다.

✔ 코드 스타일을 일관되게 유지
✔ 잠재적인 버그를 사전에 방지
✔ 비효율적인 코드 패턴을 피할 수 있음

 


3. 앱 테마 및 공통 UI 설정

앱의 전반적인 테마(글꼴, 색상, 스타일)을 설정하고, 여백(Gap)도 전역적으로 관리하면 좋습니다.

theme.dart (앱 테마 설정)

import 'package:flutter/material.dart';

ThemeData theme() {
  return ThemeData(
    appBarTheme: const AppBarTheme(
      titleTextStyle: TextStyle(color: Colors.white, fontSize: 20),
      centerTitle: true,
      backgroundColor: Colors.black12,
      elevation: 0,
    ),
    useMaterial3: true, // 최신 Material3 스타일 적용
  );
}

size.dart (전역 여백 설정)

const double smallGap = 8.0;
const double mediumGap = 16.0;
const double largeGap = 32.0;

공통적인 여백(Gap)을 설정해 두면, 코드가 더 깔끔해집니다.


4. 프로젝트 기본 UI 구성

로그인, 회원가입, 게시글 화면을 만들고, 이를 Navigator로 관리할 수 있도록 설정합니다.

📌 주요 페이지 생성 (lib/ui/pages/auth/)

import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return const Placeholder(); // UI 구현 전, 자리 표시자
  }
}

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

  @override
  Widget build(BuildContext context) {
    return const Placeholder();
  }
}

추가로 PostListPage, PostWritePage도 같은 방식으로 생성하면 됩니다.


5. 화면 전환을 위한 Navigator 설정

Flutter에서는 Navigator를 사용하여 페이지 이동을 관리합니다.

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:class_f_story/ui/pages/auth/login_page.dart';
import 'package:class_f_story/ui/pages/auth/join_page.dart';

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

// 전역 Navigator 키 설정
GlobalKey<NavigatorState> navigatorkey = GlobalKey<NavigatorState>();

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorKey: navigatorkey,
      debugShowCheckedModeBanner: false,
      home: LoginPage(),
      routes: {
        "/login": (context) => LoginPage(),
        "/join": (context) => JoinPage(),
      },
      theme: theme(),
    );
  }
}

이제 Navigator.pushNamed(context, "/join") 같은 방식으로 화면 이동을 쉽게 할 수 있습니다.

 

GlobalKey를 사용하면 현재 화면(Context)에 접근 가능

✔ ProviderScope → Riverpod 상태 관리를 적용
navigatorKey → 전역적으로 Navigator 상태 관리
routes → 앱에서 이동할 수 있는 페이지 설정


6. 로그인 UI 구현

이제 로그인 화면을 직접 만들어 보겠습니다.

LoginPage 기본 구조

import 'package:class_f_story/ui/pages/auth/login_page/widgets/login_body.dart';
import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: LoginBody(),
    );
  }
}

별도의 LoginBody 위젯을 만들어 UI를 정리합니다.


로그인 입력 필드 및 버튼 추가 (LoginBody)

import 'package:flutter/material.dart';
import 'package:class_f_story/ui/widgets/custom_auth_text_form_field.dart';
import 'package:class_f_story/ui/widgets/custom_elevated_button.dart';
import 'package:class_f_story/ui/widgets/custom_logo.dart';
import 'package:class_f_story/ui/widgets/custom_text_button.dart';

class LoginBody extends StatelessWidget {
  final _usernameController = TextEditingController();
  final _passwordController = TextEditingController();

  LoginBody({super.key});

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: ListView(
        children: [
          CustomLogo('f-story'),
          CustomAuthTextFormField(
            text: 'Username',
            controller: _usernameController,
          ),
          const SizedBox(height: 20),
          CustomAuthTextFormField(
            text: 'Password',
            controller: _passwordController,
            obscureText: true,
          ),
          const SizedBox(height: 20),
          CustomElevatedButton(
            text: '로그인',
            click: () {
              String username = _usernameController.text.trim();
              String password = _passwordController.text.trim();

              if (username.isEmpty || password.isEmpty) {
                ScaffoldMessenger.of(context).showSnackBar(
                  const SnackBar(content: Text('아이디와 비밀번호를 입력해주세요.')),
                );
                return;
              }

              // TODO - 로그인 처리 로직 추가 예정
            },
          ),
          CustomTextButton(
            text: '회원가입 페이지로 이동',
            click: () {
              Navigator.pushNamed(context, '/join');
            },
          ),
        ],
      ),
    );
  }
}

 

입력값 검증 추가 → 아이디 또는 비밀번호를 입력하지 않으면 오류 메시지 표시
UI 개선 → 버튼 클릭 시 공백 제거 (trim())


마무리

프로젝트 설정 (패키지 추가, Linter 설정, 테마 설정)
기본 UI 구성 및 화면 이동 설정
로그인 화면 UI 디자인
입력값 검증 추가 (아이디/비밀번호 확인)

 

다음에는 Dio를 활용한 로그인 API 연동을 진행해보겠습니다.