토이프로젝트

[토이프로젝트]대출이자계산기 만들어보기 - 4편 : Flutter로 모바일 서비스 개발하기

  • -
반응형

지난 글에 이어 오늘은 자바스크립트 채널을 이용해 웹 함수를 네이티브 앱으로 가져와 사용해보겠습니다.


토스트 팝업 적용

입력값 체크를 위해 토스트 팝업을 사용해보겠습니다.

flutter pub add fluttertoast

 

웹 뷰 위젯에 토스트 팝업을 추가합니다.

_openToast() 함수는 메세지(String message)와 팝업 출력 위치(ToastGravity gravity)를 인자값으로 받아 팝업을 출력합니다.

 

lib/widget/webview.dart

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:webview_flutter/webview_flutter.dart';

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

  @override
  State<MainWebView> createState() => _MainWebViewState();
}

class _MainWebViewState extends State<MainWebView> {
  late WebViewController _controller = WebViewController();
  late FToast fToast;

  void _openToast(
    String message,
    ToastGravity gravity,
  ) {
    Widget toast = Container(
      padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 12.0),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(25.0),
        color: const Color(0XFF0479f6),
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          const Icon(Icons.notifications_active, color: Colors.white),
          const SizedBox(width: 12.0),
          Text(
            message,
            style: const TextStyle(color: Colors.white),
          ),
        ],
      ),
    );

    fToast.showToast(
      child: toast,
      gravity: gravity,
      toastDuration: const Duration(seconds: 2),
    );
  }

  @override
  void initState() {
    fToast = FToast();
    fToast.init(context);

    _controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..addJavaScriptChannel('shareChannel',
          onMessageReceived: (JavaScriptMessage javaScriptMessage) async {
        var data = jsonDecode(javaScriptMessage.message);

        print(data);
      })
      ..addJavaScriptChannel('alertChannel',
          onMessageReceived: (JavaScriptMessage javaScriptMessage) {
        var data = jsonDecode(javaScriptMessage.message);

        _openToast(data["message"], ToastGravity.TOP);
      })
      ..setNavigationDelegate(
        NavigationDelegate(
          onProgress: (int progress) {},
          onPageStarted: (String url) {},
          onPageFinished: (String url) async {},
          onWebResourceError: (WebResourceError error) {},
          onNavigationRequest: (NavigationRequest request) {
            return NavigationDecision.navigate;
          },
        ),
      )
      ..loadRequest(Uri.parse('${dotenv.env['BASE_URL']}'));

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return WebViewWidget(controller: _controller);
  }
}

 

이제 다시 앱을 실행해 테스트해봅니다.

웹에서 사용된 alert 창이 아닌 토스트 팝업이 확인됩니다.

(좌) android (우) ios

 

 

네이티브 방식의 카카오 공유하기 추가

네이티브 방식으로 카카오 공유하기 구현시에는 몇가지 설정이 필요합니다.

  1. Android, iOS 플랫폼 추가
  2. Flutter kakao sdk 추가
  3. Android, iOS 커스텀 URL 스킴 추가

 

플랫폼을 등록할 때 앱 패키지명을 등록해주게 되는데 현재 패키지명은 com.example... 로 생성되어 있을 겁니다. 이후에 앱을 배포할 때 패키지명에 example이라는 값이 포함되면 안되기 때문에 미리 바꿔주도록 하겠습니다.

Android 먼저 수정해보겠습니다. 안드로이드 스튜디오를 실행하고 example 패키지 우클릭 후 Refactor - Rename을 통해 이름을 com.example.app_my_calculator에서 com.app.app_my_calculator로 바꾸겠습니다.

 

좌측 하단에 Do Refactor를 클릭합니다.

 

Command + Shift + R을 입력해 com.example이 포함된 파일을 모두 검색합니다. 파일을 클릭하고 변경된 패키지명을 입력합니다.

 

Xcode 실행 후 Runner - General 탭에서 Bundle Identifier를 클릭하고 패키지명을 바꿔줍니다.

 

 

1. Android, iOS 플랫폼 추가

카카오 개발자센터에 접속합니다.

플랫폼 - Android/iOS 플랫폼 등록을 클릭합니다. 각 플랫폼별 변경한 패키지명을 등록합니다.

 

안드로이드 플랫폼은 키 해시를 따로 등록해주어야 합니다. 키 해시는 디버그 키 해시와 릴리즈 키 해시 두가지가 있으며, 배포시에는 릴리즈 키 해시까지 등록해주어야 합니다. 릴리즈 키 해시는 이후 앱 배포를 진행할 때 등록해주겠습니다. 디버그 키 해시 생성 및 등록 방법은 아래 더보기를 참고해주세요.

더보기

1. 터미널 실행

2. 키 해시 생성 명령어 입력

keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore -storepass android -keypass android | openssl sha1 -binary | openssl base64

 

 3. 생성된 키 해시 등록

 

 

2. Flutter kakao sdk 추가

먼저 네이티브 앱 키를 복사해 env 파일에 추가합니다.

 

.env

BASE_URL=https://calculator.codedream.co.kr
KAKAO_API_KEY=b52af223c1ef1ee4c839add4abb6320e

 

flutter에서 사용할 카카오 sdk를 추가합니다.

pubspec.yaml

  kakao_flutter_sdk_share: ^1.9.1+2

 

카카오 sdk는 안드로이드의 최소 sdk 21버전을 만족해야 합니다.

 

안드로이드 스튜디오를 실행해 19버전으로 맞췄던 minSdkVersion을 21로 수정합니다. 수정 후에는 Sync Now로 리빌딩해줍니다.

build.gradle(Module: app)

 

 

3. Android, iOS 커스텀 URL 스킴 추가

커스텀 URL 스킴(Custom URL Scheme)은 사용자가 Android와 iOS 환경에서 카카오톡 메세지 버튼이나 링크를 통해 서비스 앱을 실행하는데 사용됩니다. 공유하기 버튼 클릭시 카카오톡 앱이 실행될 수 있도록 추가합니다.

 

Andorid

app - manifests - AndroidManifest.xml에 아래 코드를 추가합니다. android:scheme에는 kakao + 네이티브앱키 형태로 값을 입력합니다.

<intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />

    <!-- 카카오톡 공유, 카카오톡 메시지 -->
    <data android:host="kakaolink"
        android:scheme="kakaob52af223c1ef1ee4c839add4abb6320e" />
</intent-filter>

 

 

iOS

Runner - Info(Source Code)에 아래 내용을 추가합니다.

<key>LSApplicationQueriesSchemes</key>
<array>
	 <!-- 카카오 네이티브 앱키 -->
	 <string>kakaob52af223c1ef1ee4c839add4abb6320e</string>
     <!-- 카카오톡으로 로그인 -->
     <string>kakaokompassauth</string>
     <!-- 카카오톡 공유 -->
     <string>kakaolink</string>
</array>

 

Runner - TARGETS(Runner) - Info - URL Types를 클릭합니다.

추가 버튼을 클릭하고 URL Schemes에 kakako + 네이티브앱키 형태로 값을 입력합니다.

 

이제 설정은 마무리되었습니다.

main.dart에 카카오 sdk 초기화 함수를 추가합니다.

 

lib/main.dart

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // 구성 파일 로드
  await dotenv.load(fileName: ".env");

  // 카카오 sdk 초기화
  KakaoSdk.init(nativeAppKey: '${dotenv.env['KAKAO_API_KEY']}');

  runApp(const MyApp());
}

 

웹에서 사용된 카카오 공유하기 템플릿을 동일하게 만들어줍니다. 이 템플릿에는 shareChannel로 전달된 파라미터들이 사용됩니다.

 

lib/widget/webview.dart

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:kakao_flutter_sdk_share/kakao_flutter_sdk_share.dart';
import 'package:webview_flutter/webview_flutter.dart';

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

  @override
  State<MainWebView> createState() => _MainWebViewState();
}

class _MainWebViewState extends State<MainWebView> {
  late WebViewController _controller = WebViewController();
  late FToast fToast;

  void _openToast(
    String message,
    ToastGravity gravity,
  ) {
    Widget toast = Container(
      padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 12.0),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(25.0),
        color: const Color(0XFF0479f6),
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          const Icon(Icons.notifications_active, color: Colors.white),
          const SizedBox(width: 12.0),
          Text(
            message,
            style: const TextStyle(color: Colors.white),
          ),
        ],
      ),
    );

    fToast.showToast(
      child: toast,
      gravity: gravity,
      toastDuration: const Duration(seconds: 2),
    );
  }

  @override
  void initState() {
    fToast = FToast();
    fToast.init(context);

    _controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..addJavaScriptChannel('shareChannel',
          onMessageReceived: (JavaScriptMessage javaScriptMessage) async {
        var data = jsonDecode(javaScriptMessage.message);
        String paymentMethod = data['paymentMethod'];
        String amount = data['amount'];
        String period = data['period'];
        String interest = data['interest'];
        String redirectUrl =
            '${dotenv.env['BASE_URL']}/share?paymentMethod=$paymentMethod&amount=$amount&period=$period&interest=$interest';

        try {
          bool isKakaoTalkSharingAvailable =
              await ShareClient.instance.isKakaoTalkSharingAvailable();
          if (isKakaoTalkSharingAvailable) {
            FeedTemplate defaultFeed = FeedTemplate(
              content: Content(
                title: "my-calculator",
                description: "계산 결과를 확인해 보세요!",
                imageUrl: Uri.parse('${dotenv.env['BASE_URL']}/favicon.png'),
                link: Link(
                    webUrl: Uri.parse(redirectUrl),
                    mobileWebUrl: Uri.parse(redirectUrl)),
              ),
              buttons: [
                Button(
                  title: '계산 결과 확인하기',
                  link: Link(
                    webUrl: Uri.parse(redirectUrl),
                    mobileWebUrl: Uri.parse(redirectUrl),
                  ),
                ),
              ],
            );

            Uri uri =
                await ShareClient.instance.shareDefault(template: defaultFeed);
            await ShareClient.instance.launchKakaoTalk(uri);
          } else {
            _openToast("카카오톡을 설치해 주세요.", ToastGravity.BOTTOM);
          }
        } catch (error) {
          _openToast("공유하기에 실패했습니다.", ToastGravity.TOP);
        }
      })
      ..addJavaScriptChannel('alertChannel',
          onMessageReceived: (JavaScriptMessage javaScriptMessage) {
        var data = jsonDecode(javaScriptMessage.message);

        _openToast(data["message"], ToastGravity.TOP);
      })
      ..setNavigationDelegate(
        NavigationDelegate(
          onProgress: (int progress) {},
          onPageStarted: (String url) {},
          onPageFinished: (String url) async {},
          onWebResourceError: (WebResourceError error) {},
          onNavigationRequest: (NavigationRequest request) {
            return NavigationDecision.navigate;
          },
        ),
      )
      ..loadRequest(Uri.parse('${dotenv.env['BASE_URL']}'));

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return WebViewWidget(controller: _controller);
  }
}

 

카카오 공유하기 테스트를 위해서는 애뮬레이터와 시뮬레이터가 아닌 실제 기기가 필요합니다.

케이블 연결 후에 안드로이드, 아이폰을 연결합니다. 안드로이드 폰이 없는 관계로 아이폰으로 테스트를 진행해보겠습니다.

맥북과 아이폰 연결 후 Xcode를 실행합니다. Runner에 본인 아이폰을 선택 후에 Product - Run을 실행합니다.

 

실행 후에는 아래와 같이 앱이 생성됩니다.

앱 접속 후 공유하기를 누르면 카카오톡으로 연결되며 공유되는 것을 확인할 수 있습니다.

 

여기까지 웹 뷰를 이용해서 간단한 모바일앱을 만들어봤습니다. 아직은 수정할 곳이 많아 보입니다.

다음 글에서 앱 이름 설정, 아이콘 생성 등과 같은 작업들을 통해 앱 스타일을 다듬어보고 마켓에 앱을 등록하는 과정을 알아보겠습니다.

전체 코드는 GitHub에서 확인하실 수 있습니다.

 

참고문서
 

webview_flutter | Flutter package

A Flutter plugin that provides a WebView widget on Android and iOS.

pub.dev

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com

 

반응형
Contents

포스팅 주소를 복사했습니다.

이 글이 도움이 되었다면 공감 부탁드립니다.