Dependency
Firebase Messaging
Firebase์ Messaging ๊ธฐ๋ฅ์ ์ฌ์ฉํ๊ธฐ ์ํ pub.
flutter pub add firebase_messaging
Dart
๋ณต์ฌ
iOS ํ๋ก์ ํธ ์ค์
โข
Required: Capability (xcode project setting)
โข
Required: ํธ์ ์ธ์ฆ์ ์์ฑ (developer apple center)
โข
Optional: Remote Notification, Background fetch
Apple Developer ์ค์
AppID ์์ฑ
์ฑ์ ์ฑ์คํ ์ด์ ์ถ์ํ๊ธฐ ์ํด์ AppID ๊ฐ ํ์ํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ AppID๋ฅผ ์ถ๊ฐํ๋ ๊ณผ์ ์์ ํด๋น ์ฑ์ Capability๋ฅผ ์ค์ ํฉ๋๋ค. iOS ํ๋ก์ ํธ ์ค์ ์์ XCode Project ์ Capability๋ฅผ ์ถ๊ฐํ๋ ๊ณผ์ ์ ์ง(?) ์ด๋ผ๊ณ ๋ณด๋ฉด ๋ฉ๋๋ค. ์ฌ๊ธฐ์ Notification ํ๋๋ง ์ถ๊ฐํด์ฃผ๋๋ก ํฉ๋๋ค.
APN Key ์์ฑ
FCM ์ผ๋ก Notification์ ์ฌ์ฉํ๋ ๋ฐฉ์์ 2๊ฐ๊ฐ ์์ต๋๋ค. ํธ์ ์ธ์ฆ์, APN Key.
ํธ์ ์ธ์ฆ์๋ ๊ณผ๊ฑฐ์ ์ฌ์ฉํ๋ ๋ฐฉ์์ผ๋ก 1๋
๋ง๋ค ๊ฐฑ์ ํด์ผํ๋ ๋ฒ๊ฑฐ๋ก์์ด ์์ต๋๋ค. ์ ๊ท ํ๋ก์ ํธ๋ฅผ ์์ฑํ๋ค๋ฉด APN Key ๋ฐฉ์์ ์ถ์ฒํฉ๋๋ค.
Firebase ์ค์
์์๋์ธ์
Flutter iOS Push Notification, FCM๋ผ๋ ํค์๋ ๋ฑ์ผ๋ก iOS์์ Push notification ์์
์ ํ๋ ๋ธ๋ก๊ทธ๋ค์ด ๋ง์๋ฐ, ์ ๋ถ Capability์์ background fetch์ Remote notification ์ต์
์ ์ค์ ํ๋ผ๊ณ ํฉ๋๋ค..
ํ์ง ๋ง์ธ์ Push Notification ๋ง ์ถ๊ฐํด์ฃผ์๋ฉด ๋ฉ๋๋ค.
background fetch์ Remote notification๋ ์ฉ๋ ์์ฒด๊ฐ ๋ค๋ฆ
๋๋ค...! ๋ค๋ฅธ ๋ธ๋ก๊ทธ์์๋ ์ ์ถ๊ฐํ๋ผ๋์ง ๋ชจ๋ฅด๊ฒ ์ด์..
fyi) background fetch ๋ ์ฑ์ด background ์ํ์ ์์๋ ์ฑ ๋ด ์ผ๋ถ ๊ธฐ๋ฅ๋ค์ ์คํํ๊ธฐ ์ํด ํ์ํ ๊ถํ์ ๋ฐ์ต๋๋ค. ์์
์ฑ, ์ ํ๋ธ, ๋๋ ์๊ฐ์ด ์ค๋๊ฑธ๋ฆฌ๋ ์์
์ด์ด์ background ์์๋ ์ด์ด์ ํด์ผํ๋ ์์
๋ค์ ํด์ผํ ๋ ํ์ํฉ๋๋ค.
remote notification์ push notification ์ค์์ content-available field ๊ฐ์ true ๋ก ํ๋ ๊ฒฝ์ฐ๋ฅผ ์๋ฏธํฉ๋๋ค. ์ฑ์ด ์ด push๋ฅผ ๋ฐ์ ๊ฒฝ์ฐ, ํ๋ฉด ์๋จ์ notification์ด ๋จ์ง ์์ต๋๋ค. ๋์ ์ฑ์ด background์์ ์คํ๋ฉ๋๋ค. ์ฌ์ฉ์ ๋ชจ๋ฅด๊ฒ์..! ์ฑ ๋ฐฑ๊ทธ๋ผ์ด๋์์ API ํธ์ถ ๋ฑ์ ํตํด ์ฑ ๋ด์ ๋ณด์ฌ์ง๋ ์ปจํ
์ธ ๋ค์ ์
๋ฐ์ดํธ ํฉ๋๋ค. ์ฌ์ฉ์๊ฐ ์ฑ์ ์ผ์ ์๋ก์ด ๋ด์ฉ์ ๋ฐ์์ค๊ธฐ ์ ์ ๋น ๋ฅด๊ฒ ๋จผ์ ์
๋ฐ์ดํธ ํ ๋ ์ฌ์ฉํฉ๋๋ค.
Example
main.dart
import 'dart:developer';
import 'package:chai_booster/Constants.dart';
import 'package:chai_booster/Interceptor/authorizationInterceptor.dart';
import 'package:chai_booster/Pages/Loader/Loader.dart';
import 'package:chai_booster/Pages/RootPage.dart';
import 'package:chai_booster/Providers/Auth/AuthProvider.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:http_interceptor/http_client_with_interceptor.dart';
import 'package:provider/provider.dart';
import 'main.mapper.g.dart' show initializeJsonMapper;
void main() {
Constants.setEnvironment(Environment.PROD);
initializeJsonMapper();
WidgetsFlutterBinding.ensureInitialized();
runApp(App());
}
class App extends StatefulWidget {
_AppState createState() => _AppState();
}
class _AppState extends State<App> {
bool _initialized = false;
bool _error = false;
final client = HttpClientWithInterceptor.build(
interceptors: [
AuthorizationInterceptor()
],
retryPolicy:
ExpiredTokenRetryPolicy()
);
void initializeFlutterFire() async {
try {
await Firebase.initializeApp();
if (Constants.IS_DEBUG) {
await FirebaseAuth.instance.useEmulator('http://localhost:9099');
}
setState(() {
_initialized = true;
});
} catch(e) {
setState(() {
log(e.toString());
_error = true;
});
}
}
void initializeNotification() async { // ๐๐๐๐๐๐๐๐ ์ฌ๊ธฐ๊ฐ ํต์ฌ
await Firebase.initializeApp();
FirebaseMessaging messaging = FirebaseMessaging.instance;
await messaging.setAutoInitEnabled(true);
_flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
await messaging.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
}
void initState() {
initializeFlutterFire();
initializeNotification();
super.initState();
}
Widget build(BuildContext context) {
if(_error || !_initialized) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Loader(),
),
)
);
}
return MaterialApp(
home: MultiProvider(
providers: [
ChangeNotifierProvider<AuthProvider>(create: (_) => AuthProvider()),
],
child: RootPage(),
)
);
}
}
Dart
๋ณต์ฌ
HomePage.dart // Notification Permission ์์ฒญํ๊ณ
mainPage.dart ์์๋ notification permission์ ์์ฒญํ ์ ์์ต๋๋ค. ๋์ , ์ฌ์ฉ์๋ ์ด ์ฑ์ด ์ด๋ค ๊ฒฝ์ฐ์ ์๋ฆผ์ ์ฃผ๋์ง๋ ๋ชจ๋ฅธ์ฑ ์๋ฆผ ๊ถํ ๊ฒฐ์ ์ ํด์ผํฉ๋๋ค. ์ข์ UX๊ฐ ์๋๋๋ค.
๊ทธ๋์ ์ฌ์ฉ์์๊ฒ ์๋ฏธ์๋ ํ๋ฉด์ธ HomePage.dart์์ ๊ถํ ์์ฒญ์ ๋ฐ๋๋ก ํ์ต๋๋ค.
import 'dart:convert';
import 'package:chai_booster/Constants.dart';
import 'package:chai_booster/Interceptor/authorizationInterceptor.dart';
import 'package:chai_booster/Pages/Tabs/BrandAlarmSubscriptions/BrandAlarmSettingPage.dart';
import 'package:chai_booster/Pages/Tabs/More/MorePage.dart';
import 'package:chai_booster/Pages/Tabs/Promotions/PromotionsPage.dart';
import 'package:chai_booster/Pages/Tabs/Today/TodayPage.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:http_interceptor/http_client_with_interceptor.dart';
class HomePage extends StatefulWidget {
HomePage({
Key key, this.title
}) : super(key: key);
final String title;
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int _currentIndex = 0;
final _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
void _requestPermission() async {
await FirebaseMessaging.instance.requestPermission(
alert: true,
announcement: false,
badge: true,
carPlay: false,
criticalAlert: false,
provisional: false,
sound: true,
);
final client = HttpClientWithInterceptor.build(
interceptors: [
AuthorizationInterceptor()
],
retryPolicy:
ExpiredTokenRetryPolicy()
);
FirebaseMessaging.instance.getToken().then((token){
// FCM ํ ํฐ์ ์๋ฒ์ ์ ์ฅ ๐๐๐๐๐๐๐๐๐๐๐
client.post(Uri.parse(Constants.API + 'booster/v1/fcm-token'), body: jsonEncode({ 'fcmToken': "$token" }));
});
FirebaseMessaging.instance.onTokenRefresh.listen((token) {
// FCM ํ ํฐ์ ์๋ฒ์ ์ ์ฅ ๐๐๐๐๐๐๐๐๐๐๐
client.post(Uri.parse(Constants.API + 'booster/v1/fcm-token'), body: jsonEncode({ 'fcmToken': "$token" }));
});
}
void didChangeDependencies() {
super.didChangeDependencies();
_requestPermission(); ๐๐๐๐๐๐๐๐๐
}
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: _currentIndex,
children: [
for (final tabItem in TabNavigationItem.items()) tabItem.page,
],
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (int index) => setState(() => _currentIndex = index),
items: [
for (final tabItem in TabNavigationItem.items())
BottomNavigationBarItem(
icon: tabItem.icon,
label: tabItem.title,
)
],
unselectedItemColor: Colors.grey,
selectedItemColor: Colors.red,
unselectedLabelStyle: TextStyle(color: Colors.grey, fontSize: 10),
selectedLabelStyle: TextStyle(color: Colors.blueAccent, fontSize: 10),
showSelectedLabels: true,
showUnselectedLabels: true,
),
);
}
}
class TabNavigationItem {
final Widget page;
final String title;
final Icon icon;
TabNavigationItem({
this.page,
this.title,
this.icon,
});
static List<TabNavigationItem> items() {
return [
TabNavigationItem(
page: TodayPage(),
icon: Icon(Icons.home),
title: "ํ",
),
TabNavigationItem(
page: PromotionPage(),
icon: Icon(Icons.money),
title: "ํํ",
),
TabNavigationItem(
page:BrandAlarmSettingPage(),
icon: Icon(Icons.alarm),
title: "์๋ฆผ",
),
TabNavigationItem(
page: MorePage(),
icon: Icon(Icons.more),
title: "๋๋ณด๊ธฐ",
),
];
}
Dart
๋ณต์ฌ
๋. FCM์ ์ด์ฉํด iOS์ฑ์ Push Notification ์ ์ ์กํ๋ ๋ฐฉ๋ฒ์ ๋๋ค.
๋ค๋ฅธ ๋ธ๋ก๊ทธ๋ฅผ ์ฐธ๊ณ ํด๋ณด๋ฉด FirebaseMessaging์์ getInitialMessage, onOpenAppMessaging... ๋ฑ ๋ถํ์ํ ๋ด์ฉ๋ค๊น์ง ๋ชจ๋ ๊ตฌํํด๋์๋๋ผ๊ตฌ์. ๋ฌผ๋ก ์๋น์ค ๊ณ ๋ํํ ๋ ํ์์ ๋ฐ๋ผ ๊ตฌํํด์ผํ๋ ๋ถ๋ถ๋ค์ด์ง๋ง ๋จ์ํ๊ฒ ์ฑ์ notification์ ์ ์กํ๋ ๊ฒ. ์ฌ๊ธฐ๊น์ง๊ฐ ๋์
๋๋ค..!