seong

Flutter - flutter_callkit_incoming 라이브러리 사용 ( Fake_call ) 본문

Flutter/Flutter

Flutter - flutter_callkit_incoming 라이브러리 사용 ( Fake_call )

hyeonseong 2024. 9. 25. 10:03

전화 화면 기능 및 전화기능을 내가 만든 앱으로 변경 할 수 있다.

보통은 Native기능( Kotlin,Swift ) 로 개발하면 좋겠지만 Flutter에서 한번에 지원해주는 라이브러리가 있어서 우선 사용.

 

사용 라이브러리 : Flutter_callkit_incoming

- Stream_io 에서 만들어서 지원해주는 패키지이다.

어느정도 신뢰성이 있어서 일단 사용 해보기로 결정

https://pub.dev/packages/flutter_callkit_incoming

 

flutter_callkit_incoming | Flutter package

Flutter Callkit Incoming to show callkit screen in your Flutter app.

pub.dev

1. 필요 라이브러리 추가 

pubspec.yaml 

dependencies:
  flutter:
    sdk: flutter
    #CallKit 필요 라이브러리
  uuid: ^4.5.0
  flutter_callkit_incoming: ^2.0.4+1
  firebase_messaging: ^15.1.0
  firebase_core: ^3.4.0

 

2. AndroidManifest 추가

외부 서버와 통신을 할 필요가 있다면 아래 권한을 추가

ex) 전화 알림 데이터를 받는 등, 실제 전화 기능을 사용한다면 필수로 추가 해야한다.

<manifest...>
     ...
     <!--
         Using for load image from internet
     -->
     <uses-permission android:name="android.permission.INTERNET"/>

   
 </manifest>

 

3. iOS info.plist 추가

<key>UIBackgroundModes</key>
<array>
    <string>voip</string>
    <string>remote-notification</string>
    <string>processing</string> //you can add this if needed
</array>

 

4. Firebase 연동

- 사용자에게 알림을 주기 위해서 Firebase를 연동 해주어야한다. 

- 아래 들어가서 프로젝트 생성하고 Flutter앱을 추가 해주면 된다, 해당글은 callkit사용을 위해서이기 때문에Firebase 연동에 대해선 패스!

https://console.firebase.google.com/?hl=ko

 

Firebase콘솔에서 연동 완료 되었으면 아래 처럼 파일이 추가되어 있다.

firebase_options.dart

main함수에서 Firebase 초기화 

// 백그라운드 작업은 main함수 위에 작성
// entry-point 필요.
@pragma('vm:entry-point')
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  print("Handling a background message: ${message.messageId}");
  CallKitHandler().showCallkitIncoming(const Uuid().v4());
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(const MyApp());
}

 

5. callkit_handler 작성

import 'package:flutter_callkit_incoming/entities/android_params.dart';
import 'package:flutter_callkit_incoming/entities/call_kit_params.dart';
import 'package:flutter_callkit_incoming/entities/ios_params.dart';
import 'package:flutter_callkit_incoming/entities/notification_params.dart';
import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart';

class CallKitHandler {
  Future<void> showCallkitIncoming(String uuid) async {
    final params = CallKitParams(
      id: uuid, // Callkit에서 사용 하는 uuid 
      nameCaller: 'Fake Call Test', // 발신자 이름
      appName: 'Fake Call Test App', // 앱의 이름 
      handle: '0123456789', // 발신 전화 번호 설정
      type: 0, // 통화 유형 0은 전화, 1은 영상통화 
      duration: 30000, // 30초 동안 안받을 시 끊김 (millisecond단위)
      textAccept: 'Accept', // 수락 버튼 Text
      textDecline: 'Decline', // 거절 버튼 Text
      missedCallNotification: const NotificationParams(
        showNotification: true, // 알림 표시 여부 
        isShowCallback: true, // 알림 재 발신 여부
        subtitle: 'Missed call', // 부재중 통화 알림의 부제목
        callbackText: 'Call back', // 재발신 버튼 제목
      ),
      extra: <String, dynamic>{'userId': '1a2b3c4d'}, // 추가 정보 Map 
      headers: <String, dynamic>{'apiKey': 'Abc@123!', 'platform': 'flutter'}, // HTTP 통신에 사용할 헤더 정보
      android: const AndroidParams( 
        isCustomNotification: false, // UI를 커스텀 한다면 true, 기본 UI는 false
        isShowLogo: false,
        ringtonePath: 'system_ringtone_default', // 알람 소리 재생 파일 경로, 현재는 기본 벨소리 사용
      ),
      ios: const IOSParams(
        iconName: 'Fake Call Test', 
        handleType: '', 
        supportsVideo: false, // 비디오 통화 지원 여부
        maximumCallGroups: 1, // 한번에 허용되는 통화 그룹 수 
        maximumCallsPerCallGroup: 1, // 한 통화 그룹에서 허용되는 최대 통화 수 
        audioSessionMode: 'default',// 오디오 세션 모드 , default = 기본 설정 
        audioSessionActive: true, // 통화 시작시 오디오 세션 활성화 여부 
        audioSessionPreferredSampleRate: 44100.0, 
        audioSessionPreferredIOBufferDuration: 0.005,
        supportsDTMF: false, // 통화 중 키패드 입력음 여부 
        supportsHolding: false, // 통화 보류 가능 여부 
        supportsGrouping: false, // 그룹 통화 여부
        supportsUngrouping: false, // 그룹 통화 개별로 분리 가능한지 여부 
        ringtonePath: 'system_ringtone_default',// 기본 통화 벨소리 
      ),
    );
    await FlutterCallkitIncoming.showCallkitIncoming(params);
  }
}

테스트 UI 제작

1. HomeScreen 

버튼을 클릭하면 Fake 전화가 오도록

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

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
  late final Uuid _uuid;
  String? _currentUuid;

  late final FirebaseMessaging _firebaseMessaging;

  @override
  void initState() {
    _uuid = const Uuid(); // 랜덤 uuid 생성
    initFirebase();
    WidgetsBinding.instance.addObserver(this);
    //Check call when open app from terminated
    checkAndNavigationCallingPage();

    super.initState();
  }

//  현재 통화중 상태인지 확인 하는 함수
  Future<dynamic> getCurrentCall() async {
    //check current call from pushkit if possible
    var calls = await FlutterCallkitIncoming.activeCalls();
    if (calls is List) {
      if (calls.isNotEmpty) {
        print('DATA: $calls');
        _currentUuid = calls[0]['id'];
        return calls[0];
      } else {
        _currentUuid = "";
        return null;
      }
    }
  }

//통화중이 아니라면 내가 의도한 페이지로 이동 
  Future<void> checkAndNavigationCallingPage() async {
    var currentCall = await getCurrentCall();
    if (currentCall != null) {
      if (mounted) {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => const SecondPage(),
          ),
        );
      }
    }
  }

// Firebase 초기화 
  Future<void> initFirebase() async {
    _firebaseMessaging = FirebaseMessaging.instance;
    FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
    FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
      print(
          'Message title: ${message.notification?.title}, body: ${message.notification?.body}, data: ${message.data}');
      _currentUuid = _uuid.v4();
      CallKitHandler().showCallkitIncoming(_currentUuid!);
    });
    _firebaseMessaging.getToken().then((token) {
      print('Device Token FCM: $token');
    });
  }

  @override
  Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
    print(state);
    if (state == AppLifecycleState.resumed) {
      //Check call when open app from background
      checkAndNavigationCallingPage();
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: ElevatedButton(
          onPressed: () async {
          // 전화 걸기 
            await CallKitHandler().showCallkitIncoming(_currentUuid!);
          },
          child: Text("Fake 전화 걸기"),
        ),
      ),
    );
  }
}