코드로 우주평화

Flutter로 멋진 노트 앱을 만들어보자 ② - 고객 제안, 앱 정보, 개발자 이야기 추가하기 본문

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

Flutter로 멋진 노트 앱을 만들어보자 ② - 고객 제안, 앱 정보, 개발자 이야기 추가하기

daco2020 2024. 11. 15. 22:11
반응형

지난 글까지 노트 앱의 주요 기능들을 모두 구현하였습니다. 이제 실제 앱 출시 전에 마지막으로 설정 메뉴를 구현해 보겠습니다.

 

설정 메뉴에는 고객 제안과 앱 정보, 개발자 이야기 메뉴를 담아보겠습니다. 이 노트앱은 회원가입이 따로 없기 때문에 개인 정보와 계정 관리에 대한 메뉴는 추가하지 않겠습니다.

 

먼저 최종 결과를 영상으로 보겠습니다.

 

참고로, UI 컬러 변경 작업이 함께 진행되어 이전의 어두운 색이 아닌 밝은 색으로 변경되었습니다. 컬러 변경에 대해서는 다음 글에서 다루겠습니다.

 

 

목차

1. 설정 메뉴 추가하기

2. 고객 제안 설문 링크 연결하기

3. 앱 정보 페이지 추가 및 오픈소스 명시

4. 개발자 이야기 웹뷰 추가하기

 

 

1. 설정 메뉴 추가하기

기존 설정 메뉴는 아무것도 없는 화면이었습니다. 여기에 ListView ListTile을 사용해 설정 항목을 만듭니다. 각 항목은 아이콘과 텍스트를 포함하고 메뉴를 눌렀을 때 특정 동작을 실행하도록 구현합니다.

 

settings_view.dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; // 라우팅을 위한 패키지

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('띵노트 설정'),
      ),
      body: Padding(
        padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 20.0),
        child: ListView(
          children: [
            _buildSettingItem(
              icon: Icons.headset_mic,
              title: '고객 제안',
              onTap: () {}, // 고객 설문 링크 연결 (2번에서 구현)
            ),
            _buildSettingItem(
              icon: Icons.info_outline,
              title: '앱 정보',
              onTap: () => context.push('/appInfo'), // 앱 정보 페이지로 이동
            ),
            _buildSettingItem(
              icon: Icons.code,
              title: '개발자 이야기',
              onTap: () => context.push('/developerStory'), // 개발자 이야기 웹뷰로 이동
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildSettingItem({
    required IconData icon,
    required String title,
    required VoidCallback onTap,
  }) {
    return ListTile(
      leading: Icon(icon),
      title: Text(title),
      onTap: onTap,
    );
  }
}

 

앱 정보와 개발자 이야기 메뉴는 별도의 페이지를 구현할 예정이기 때문에 onTap에 미리 이동할 URI를 설정해 두었습니다.

 

앱 정보와 개발자 이야기 URI는 main.dart 에서 GoRouter로 추가해 주세요.

GoRoute(
  path: '/developerStory',
  pageBuilder: (context, state) => CustomTransitionPage(
    key: state.pageKey,
    child: DeveloperStoryView(),
    transitionDuration: const Duration(milliseconds: 200),
    transitionsBuilder: _fadeTransition,
  ),
),
GoRoute(
  path: '/appInfo',
  pageBuilder: (context, state) => CustomTransitionPage(
    key: state.pageKey,
    child: AppInfoView(),
    transitionDuration: const Duration(milliseconds: 200),
    transitionsBuilder: _fadeTransition,
  ),
),

 

참고로 이 둘은 네비게이션 탭 라우터가 아니므로 ShellRoute 가 아닌 바깥 routes에 추가해야 합니다.

 

 

 

2. 고객 제안 설문 링크 연결하기

사용자로부터 피드백을 받는 건 앱 개선에 필수적입니다. 저는 고객 설문 링크를 제공하여 회원 가입을 하지 않더라도 사용자와 소통할 수 있는 창구를 만들고 싶었습니다. 구글 설문 링크를 미리 만들어두고 외부 브라우저에서 설문 링크를 여는 기능을 구현하겠습니다.

 

외부 링크를 열기 위해 url_launcher 패키지를 사용하겠습니다. 이 패키지를 이용하면 외부 URL을 기본 브라우저에서 열 수 있습니다. 설문 링크는 환경변수를 활용해 설문 링크를 코드에 직접 노출하지 않도록 합니다.

 

먼저 필요한 패키지를 설치하겠습니다.

flutter pub add url_launcher
flutter pub add flutter_dotenv

 

flutter_dotenv는 환경변수 관리 패키지입니다.

 

루트 경로에 .env 파일을 생성하여 다음을 입력합니다.

GOOGLE_SURVEY_URL=https://example.com/feedback

 

여기서 'https://example.com/feedback' 에는 본인이 직접 생성한 구글 설문 링크를 넣어주세요.

 

.env 파일은 깃헙에 올라가면 안되니 .gitignore 파일에도 꼭 추가해 주세요.

 

 

main.dart 로 이동하여 아래 예시 코드처럼 dotenv 패키지를 설정합니다.

import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart'; // dotenv 패키지 import

void main() async {
  await dotenv.load(fileName: ".env"); // 환경변수 로드
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(title: Text('환경변수 테스트')),
        body: Center(
          child: Text(dotenv.env['GOOGLE_SURVEY_URL'] ?? '환경변수가 로드되지 않았습니다.'),
        ),
      ),
    );
  }
}

 

 

이제 외부 링크를 연결하여 열어보겠습니다.

 

SettingsView 에 다음 프라이빗 메서드를 추가합니다.

Future<void> _launchFeedbackForm() async {
  final url = Uri.parse('${dotenv.env['GOOGLE_SURVEY_URL']}');
  if (!await launchUrl(url, mode: LaunchMode.externalApplication)) {
    throw 'Could not launch $url';
  }
  return;
}

 

해당 함수는 환경변수로 입력한 구글 설문 URL을 불러와 연결합니다.

 

그리고 고객 제안 메뉴 onTap에 연결시켜 줍니다.

_buildSettingItem(
  icon: Icons.headset_mic,
  title: '고객 제안',
  onTap: () async {
    try {
      await _launchFeedbackForm();
    } catch (e) {
      if (context.mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('링크를 여는데 실패했습니다. 잠시 후 다시 시도해주세요.'),
          ),
        );
      }
    }
  },
),

 

 

ScaffoldMessenger 를 이용해 만약 연결이 실패했을 때 안내 문구를 보여줍니다.

 

 

외부 URI를 연결할 때에는 플랫폼(안드로이드, IOS) 설정도 필요한데요. 다음처럼 설정해 주세요.

 

안드로이드 경로 : android/app/src/main/AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET"/>

 

이 설정은 안드로이드 앱에서 인터넷에 접근할 수 있도록 인터넷 사용 권한을 선언하는 코드입니다. 이 설정이 없으면 앱이 네트워크 요청을 처리하지 못하고 외부 링크나 API 요청 시 충돌이 발생할 수 있습니다.

 

iOS 경로 : ios/Runner/Info.plist

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>
<key>LSApplicationQueriesSchemes</key>
<array>
    <string>http</string>
    <string>https</string>
</array>

 

NSAppTransportSecurity 는 iOS에서 앱 보안을 강화하기 위해 기본적으로 HTTP(비보안) 요청을 차단하는 정책입니다. 이 옵션을 true로 설정하면 HTTP와 HTTPS 요청을 모두 허용할 수 있습니다.

LSApplicationQueriesSchemes 는 iOS에서 앱이 특정 URL 스킴으로 다른 앱이나 외부 리소스를 열기 위해 사전에 해당 스킴을 명시하는 것입니다. 여기서는 http와 https 스킴을 추가해 앱이 HTTP와 HTTPS 링크를 열 수 있게 허용했습니다.

 

 

이제 설정 화면에서 [고객 제안] 메뉴를 누르면 구글 설문 링크로 이동합니다.

 

 

 

 

3. 앱 정보 페이지 추가 및 오픈소스 명시

앱의 버전 정보와 오픈소스 라이선스를 명시하겠습니다. 앱의 버전은 현재 사용자가 사용하는 앱이 최신 버전인지 확인하는 데 사용하고, 라이선스는 법적 요구사항을 준수하기 위해 명시합니다

 

와 표시하고, 라이선스는 Flutter가 기본으로 제공하는 showLicensePage를 활용하겠습니다.

 

앱 버전 정보를 pubspec.yaml에서 읽어오기 위해 yaml 패키지를 설치합니다.

flutter pub add yaml

 

 

다음으로 버전 정보를 가져오는 유틸 클래스를 만들겠습니다.

 

version_util.dart

import 'package:flutter/services.dart';
import 'package:yaml/yaml.dart';

class VersionUtil {
  static Future<String> getVersionFromPubspec() async {
    final String pubspecContent = await rootBundle.loadString('pubspec.yaml');
    final yaml = loadYaml(pubspecContent);
    return yaml['version'] ?? '알 수 없음';
  }
}

 

 

 

유틸 함수를 만들었다면 앱 정보 화면을 구현하겠습니다.

 

app_info_view.dart

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('앱 정보'),
      ),
      body: ListView(
        children: [
          FutureBuilder<String>(
            future: VersionUtil.getVersionFromPubspec(),
            builder: (context, snapshot) {
              return ListTile(
                title: Text('버전 정보'),
                subtitle: Text(snapshot.data ?? '로딩 중...'),
              );
            },
          ),
          ListTile(
            title: Text('오픈소스 라이선스'),
            onTap: () => showLicensePage(context: context),
          ),
        ],
      ),
    );
  }
}

 

 

 

이제 [앱 정보] 메뉴를 클릭하면 이미지처럼 앱 정보 화면과 라이선스 목록이 표시됩니다. 

 

 

 

 

4. 개발자 이야기 웹뷰 추가하기

개발자 이야기는 제가 임의로 만든 메뉴로 저를 소개하고 앱을 개발하는 과정을 담아보려고 만든 메뉴입니다. 저는 노션을 이용할 생각이고 미리 웹으로 게시한 노션 페이지를 앱 내 웹뷰로 넣어보겠습니다.

 

웹뷰 기능을 사용하기 위해 webview_flutter 패키지를 설치하겠습니다.

flutter pub add webview_flutter

 

 

그리고 미리 게시한 노션 페이지 URL 을 .env 파일에 추가합니다.

DEVELOPER_STORY_URL=https://example.com/developer-story

 

 

앞서 url_launcher 은 앱을 떠나 외부 링크로 이동하는 것이라면 웹뷰는 해당 페이지를 앱 내에서 앱처럼 사용하는 기능입니다. 그렇기 때문에 웹뷰에 해당하는 화면(view)을 추가로 구현해야 합니다.

 

developer_story_view.dart:

import 'package:webview_flutter/webview_flutter.dart';

class DeveloperStoryView extends StatefulWidget {
  const DeveloperStoryView({super.key});

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

class _DeveloperStoryView

State extends State<DeveloperStoryView> {
  late WebViewController controller;

  @override
  void initState() {
    super.initState();
    controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..loadRequest(Uri.parse(dotenv.env['DEVELOPER_STORY_URL'] ?? ''));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('개발자 이야기'),
      ),
      body: WebViewWidget(controller: controller),
    );
  }
}

 

DeveloperStoryView Stateful 위젯을 통해 웹뷰 페이지를 생성하고 관리합니다.


initState 메서드에서 웹뷰 컨트롤러를 초기화하고 앞서 환경변수에 저장한 URL을 로드해 화면에 표시합니다

 

앱바에 제목을 표시하고 WebViewWidget 을 사용해 앱 내에서 웹 콘텐츠를 직접 볼 수 있도록 구성합니다.

 

 

이제 [개발자 이야기] 메뉴를 누르면 미리 게시해 둔 노션 페이지가 앱 내에 나타납니다.

 

 

마무리

이번 글에서는 노트 앱의 설정 메뉴를 구성하고, 고객 제안 설문 링크, 앱 정보 페이지 및 오픈소스 라이선스, 개발자 이야기 웹뷰를 추가하는 방법을 알아보았습니다.

 

사실 이걸로 충분한지는 현재로서는 감이 오지 않네요. 앱을 출시하는 과정에서 일부 추가되거나 빠지는 등의 보완 작업이 있을 수 있습니다.

 

다음 글에서는 기존의 컬러 팔레트를 보완하고 컬러를 변경하는 작업에 대해 다뤄보겠습니다.

 

 

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

 

반응형