오늘의 할 일
Foreground In-App Notifications 적용할 수 있는지 확인
APNs 등록하기
Flutter Flow에서 push nitification 활성화, 테스트 해보기
HTTP v1 API 확인하기
jazz up = make better
[Foreground In-App Notifications - Custom Code Modification Guide] 문서 내용 정리
3가지 시나리오가 있음
- App in the Foreground (when it's open and actively being used) - applicable to both web and mobile platforms.
- App in the Background or Closed - for web.
- App in the Background or Closed - for mobile.
그 중 App in the Foreground 방법
- Flutter Flow와 Github 연결
- Flutter Flow 코드 수정 custom code 만들기
- git checkout develop
- code 적용하기 (웹/앱 알림 설정)
lib/backend/push_notifications/push_notifications_util.dart
위의 경로의 코드 수정하기
[사용자 토큰 얻고 가져오기]
final fcmTokenUserStream = authenticatedUserStream
.where((user) => user != null)
.map((user) => user!.reference.path)
.distinct()
.switchMap(getFcmTokenStream)
.map(
(userTokenInfo) => makeCloudCall(
'addFcmToken',
{
'userDocPath': userTokenInfo.userPath,
'fcmToken': userTokenInfo.fcmToken,
'deviceType': kIsWeb
? 'Web'
: Platform.isIOS
? 'iOS'
: 'Android',
},
),
);
Stream<UserTokenInfo> getFcmTokenStream(String userPath) {
if (kIsWeb) {
return FirebaseMessaging.instance
.getToken()
.asStream()
.where((fcmToken) => fcmToken != null && fcmToken.isNotEmpty)
.map((token) => UserTokenInfo(userPath, token!));
} else {
return Stream.value(!kIsWeb && (Platform.isIOS || Platform.isAndroid))
.where((shouldGetToken) => shouldGetToken)
.asyncMap<String?>(
(_) => FirebaseMessaging.instance.requestPermission().then(
(settings) => settings.authorizationStatus ==
AuthorizationStatus.authorized
? FirebaseMessaging.instance.getToken()
: null,
))
.switchMap((fcmToken) => Stream.value(fcmToken)
.merge(FirebaseMessaging.instance.onTokenRefresh))
.where((fcmToken) => fcmToken != null && fcmToken.isNotEmpty)
.map((token) => UserTokenInfo(userPath, token!));
}
}
- 코드 해석
- fcmTokenUserStream
- 로그인된 사용자의 정보를 바탕으로,
- Firebase Cloud Messaging (FCM) 토큰을 얻고
- 해당 토큰과 유저 정보를 서버(Cloud Function)로 보내는 것
- getFcmTokenStream(String userPath)
- kIsWeb으로 분기 처리하여 true면 웹에서 실행중, false면 앱에서 실행중
- if kIsWeb == true
- FirebaseMessaging.instance.getToken()만 호출해서 FCM 토큰을 가져옴
- if kIsWeb == false
- requestPermission()을 호출해 사용자에게 알림 권한을 요청
- 권한이 승인되면 getToken()으로 토큰을 가져오기
- onTokenRefresh까지 합쳐서 토큰 갱신에도 대응
- if kIsWeb == true
- kIsWeb으로 분기 처리하여 true면 웹에서 실행중, false면 앱에서 실행중
- fcmTokenUserStream
이렇게 하면 웹/앱 알림이 설정됨
5.
lib/backend/push_notifications/push_notifications_handler.dart
해당 위치 파일의 코드 수정하기
수정할 사항 :
Custom Notification Dialog Import
Message Listener Function Definition
[앱이 포그라운드(foreground) 상태일 때 들어오는 Firebase 푸시 알림을 처리]
import '../../components/customNotificationDialog.dart';
/////////////// defining the custom function for handling FOREGROUND notifications
void messageListener(BuildContext context) {
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
// Handle multiple notifications for the same message
if (_handledMessageIds.contains('${message.messageId}0000foreground')) {
return;
}
_handledMessageIds.add('${message.messageId}0000foreground');
print('Got a new message whilst in the foreground!');
// ... Extract notification data ...
String? title = message.notification?.title;
print('Notification Title: $title');
String? body = message.notification?.body;
print('Notification Body: $body');
String? image = isWeb
? message.notification?.web?.image
: message.notification?.android?.imageUrl ??
message.notification?.apple?.imageUrl;
print('Notification Image: $image');
print('Message also contained data: ${message.data}');
// Show a custom dialog and handle after closure
showCustomNotificationDialog(context, image, title, body)
.then((_) => _handlePushNotification(message));
});
}
///////////////
- 코드 해석
- if (_handledMessageIds.contains('${message.messageId}0000foreground')) {
return;
}- 동일한 메시지가 여러 번 들어오는 경우(중복 처리 방지)를 위한 로직
- showCustomNotificationDialog(context, image, title, body).then((_) => _handlePushNotification(message));
- 추출한 정보를 기반으로 커스텀 다이얼로그(showCustomNotificationDialog)를 보여주기
- if (_handledMessageIds.contains('${message.messageId}0000foreground')) {
[새로 생긴 기능을 호출하기 위해 위해 initState로 돌아가기]
@override
void initState() {
super.initState();
handleOpenedPushNotification();
/////////////// calling the function for FOREGROUND notifications
messageListener(context);
///////////////
}
- 코드 해석
- 화면(위젯)이 처음 생성될 때
- 백그라운드 또는 종료 상태에서 푸시 알림을 눌러 앱이 열린 경우 처리
- 앱이 실행 중일 때(포그라운드) 수신된 푸시 알림 처리
- 화면(위젯)이 처음 생성될 때
[customized notification dialogs]
새로 파일을 만들거나 flutter flow에서 적용
import 'package:flutter/material.dart';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:just_audio/just_audio.dart';
import '../flutter_flow/flutter_flow_theme.dart';
import '../flutter_flow/flutter_flow_widgets.dart';
class CustomNotificationDialog extends StatelessWidget {
final String? image;
final String? title;
final String? body;
CustomNotificationDialog({
this.image,
this.title,
this.body,
});
@override
Widget build(BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24.0)),
elevation: 0,
backgroundColor: FlutterFlowTheme.of(context).secondaryBackground,
child: Container(
height: 400,
width: 400,
decoration: BoxDecoration(
color: FlutterFlowTheme.of(context).primaryBackground,
borderRadius: BorderRadius.circular(24.0),
border: Border.all(
color: FlutterFlowTheme.of(context).primaryBackground,
width: 1.0,
),
),
child: Column(
children: <Widget>[
Expanded(
child: ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(24.0),
topRight: Radius.circular(24.0),
),
child: Image.network(
image ?? 'your_default_image_url',
height: 200,
width: double.infinity,
fit: BoxFit.cover,
),
),
),
Padding(
padding: EdgeInsets.all(24.0),
child: Column(
children: <Widget>[
Text(
title ?? 'Notification',
style: FlutterFlowTheme.of(context).headlineLarge,
textAlign: TextAlign.center,
),
SizedBox(height: 10.0),
AutoSizeText(
body ?? 'No message body provided',
maxLines: 2,
textAlign: TextAlign.center,
style: FlutterFlowTheme.of(context).labelMedium.override(
fontFamily: 'Arial',
fontWeight: FontWeight.w500,
lineHeight: 1.4,
),
),
],
),
),
Padding(
padding: EdgeInsets.all(24.0),
child: FFButtonWidget(
onPressed: () async {
Navigator.of(context).pop();
// code for navigation or launching URL
},
text: 'Confirm',
options: FFButtonOptions(
height: 50.0,
padding: EdgeInsetsDirectional.fromSTEB(20.0, 0.0, 20.0, 0.0),
color: FlutterFlowTheme.of(context).primary,
textStyle: FlutterFlowTheme.of(context).titleSmall.override(
fontFamily: 'Arial',
color: Colors.black,
fontSize: 18.0,
fontWeight: FontWeight.w600,
),
elevation: 3.0,
borderSide: BorderSide(
color: Colors.transparent,
width: 2.0,
),
borderRadius: BorderRadius.circular(40.0),
hoverColor: FlutterFlowTheme.of(context).secondary,
hoverBorderSide: BorderSide(
color: FlutterFlowTheme.of(context).primary,
width: 2.0,
),
hoverTextColor: Colors.black,
hoverElevation: 6.0,
),
),
),
],
),
),
);
}
}
Future<void> showCustomNotificationDialog(
BuildContext context, String? image, String? title, String? body) async {
final AudioPlayer player = AudioPlayer();
await player.setAsset('assets/audios/sound_file_name.wav');
await player.play();
await showDialog(
context: context,
builder: (context) => CustomNotificationDialog(
image: image,
title: title,
body: body,
),
);
}
//// use this to show the dialog: showCustomNotificationDialog(context, image, title, body);
https://community.flutterflow.io/c/community-custom-widgets/post/foreground-in-app-notifications---custom-code-modification-guide-NlAIi4eAtDK9O1c
Foreground In-App Notifications - Custom Code Modification Guide
Greetings, fellow developers! Today, let's dive into enhancing the live feedback and notification systems of your projects. This is especially handy when dealing with scenarios that require real-time ...
community.flutterflow.io